| 
									
										
										
										
											2020-04-22 05:09:46 +02:00
										 |  |  | from __future__ import annotations | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-21 15:32:31 +02:00
										 |  |  | import typing | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def tuplize_version(version: str) -> typing.Tuple[int, ...]: | 
					
						
							| 
									
										
										
										
											2020-12-29 19:23:14 +01:00
										 |  |  |     return Version(*(int(piece, 10) for piece in version.split("."))) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-21 15:32:31 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-29 19:23:14 +01:00
										 |  |  | class Version(typing.NamedTuple): | 
					
						
							|  |  |  |     major: int | 
					
						
							|  |  |  |     minor: int | 
					
						
							| 
									
										
										
										
											2021-02-21 23:46:05 +01:00
										 |  |  |     build: int | 
					
						
							| 
									
										
										
										
											2020-04-22 05:09:46 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-18 22:15:54 +02:00
										 |  |  | __version__ = "0.1.3" | 
					
						
							|  |  |  | version_tuple = tuplize_version(__version__) | 
					
						
							| 
									
										
										
										
											2020-04-20 14:50:49 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-03 14:41:21 +01:00
										 |  |  | import builtins | 
					
						
							| 
									
										
										
										
											2017-11-28 09:36:32 -05:00
										 |  |  | import os | 
					
						
							| 
									
										
										
										
											2017-12-17 00:25:46 -05:00
										 |  |  | import subprocess | 
					
						
							| 
									
										
										
										
											2017-11-28 09:36:32 -05:00
										 |  |  | import sys | 
					
						
							| 
									
										
										
										
											2020-09-09 01:41:37 +02:00
										 |  |  | import pickle | 
					
						
							| 
									
										
										
										
											2020-02-16 15:32:40 +01:00
										 |  |  | import functools | 
					
						
							| 
									
										
										
										
											2021-01-03 14:32:32 +01:00
										 |  |  | import io | 
					
						
							| 
									
										
										
										
											2020-02-16 15:32:40 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-05 02:06:00 +02:00
										 |  |  | from yaml import load, dump, safe_load | 
					
						
							| 
									
										
										
										
											2020-02-16 15:32:40 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | try: | 
					
						
							|  |  |  |     from yaml import CLoader as Loader | 
					
						
							|  |  |  | except ImportError: | 
					
						
							|  |  |  |     from yaml import Loader | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-28 09:36:32 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  | def int16_as_bytes(value): | 
					
						
							|  |  |  |     value = value & 0xFFFF | 
					
						
							|  |  |  |     return [value & 0xFF, (value >> 8) & 0xFF] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-16 15:32:40 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-17 18:38:54 -05:00
										 |  |  | def int32_as_bytes(value): | 
					
						
							|  |  |  |     value = value & 0xFFFFFFFF | 
					
						
							|  |  |  |     return [value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-16 15:32:40 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-22 22:51:54 -04:00
										 |  |  | def pc_to_snes(value): | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  |     return ((value << 1) & 0x7F0000) | (value & 0x7FFF) | 0x8000 | 
					
						
							| 
									
										
										
										
											2018-09-22 22:51:54 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-21 23:15:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-22 22:51:54 -04:00
										 |  |  | def snes_to_pc(value): | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  |     return ((value & 0x7F0000) >> 1) | (value & 0x7FFF) | 
					
						
							| 
									
										
										
										
											2018-09-22 22:51:54 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-21 23:15:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  | def parse_player_names(names, players, teams): | 
					
						
							| 
									
										
										
										
											2020-03-02 23:27:16 +01:00
										 |  |  |     names = tuple(n for n in (n.strip() for n in names.split(",")) if n) | 
					
						
							| 
									
										
										
										
											2020-08-20 04:03:49 +02:00
										 |  |  |     if len(names) != len(set(names)): | 
					
						
							| 
									
										
										
										
											2021-03-07 20:11:36 +01:00
										 |  |  |         import collections | 
					
						
							|  |  |  |         name_counter = collections.Counter(names) | 
					
						
							|  |  |  |         raise ValueError(f"Duplicate Player names is not supported, " | 
					
						
							|  |  |  |                          f'found multiple "{name_counter.most_common(1)[0][0]}".') | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |     ret = [] | 
					
						
							|  |  |  |     while names or len(ret) < teams: | 
					
						
							|  |  |  |         team = [n[:16] for n in names[:players]] | 
					
						
							| 
									
										
										
										
											2020-08-20 04:03:49 +02:00
										 |  |  |         # 16 bytes in rom per player, which will map to more in unicode, but those characters later get filtered | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |         while len(team) != players: | 
					
						
							| 
									
										
										
										
											2020-04-18 21:46:57 +02:00
										 |  |  |             team.append(f"Player{len(team) + 1}") | 
					
						
							| 
									
										
										
										
											2020-01-14 10:42:27 +01:00
										 |  |  |         ret.append(team) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         names = names[players:] | 
					
						
							|  |  |  |     return ret | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-21 23:15:19 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | def is_bundled() -> bool: | 
					
						
							| 
									
										
										
										
											2017-11-28 09:36:32 -05:00
										 |  |  |     return getattr(sys, 'frozen', False) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-21 23:15:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-25 13:22:47 +02:00
										 |  |  | def local_path(*path): | 
					
						
							| 
									
										
										
										
											2020-03-15 19:32:00 +01:00
										 |  |  |     if local_path.cached_path: | 
					
						
							| 
									
										
										
										
											2020-08-25 13:22:47 +02:00
										 |  |  |         return os.path.join(local_path.cached_path, *path) | 
					
						
							| 
									
										
										
										
											2017-11-28 09:36:32 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-23 07:45:40 +01:00
										 |  |  |     elif is_bundled(): | 
					
						
							|  |  |  |         if hasattr(sys, "_MEIPASS"): | 
					
						
							|  |  |  |             # we are running in a PyInstaller bundle | 
					
						
							|  |  |  |             local_path.cached_path = sys._MEIPASS  # pylint: disable=protected-access,no-member | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             # cx_Freeze | 
					
						
							|  |  |  |             local_path.cached_path = os.path.dirname(os.path.abspath(sys.argv[0])) | 
					
						
							| 
									
										
										
										
											2017-11-28 09:36:32 -05:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2020-03-23 07:45:40 +01:00
										 |  |  |         import __main__ | 
					
						
							| 
									
										
										
										
											2021-04-04 03:18:19 +02:00
										 |  |  |         if hasattr(__main__, "__file__"): | 
					
						
							|  |  |  |             # we are running in a normal Python environment | 
					
						
							|  |  |  |             local_path.cached_path = os.path.dirname(os.path.abspath(__main__.__file__)) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             # pray | 
					
						
							|  |  |  |             local_path.cached_path = os.path.abspath(".") | 
					
						
							| 
									
										
										
										
											2020-03-15 19:32:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-25 13:22:47 +02:00
										 |  |  |     return os.path.join(local_path.cached_path, *path) | 
					
						
							| 
									
										
										
										
											2017-11-28 09:36:32 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-28 09:36:32 -05:00
										 |  |  | local_path.cached_path = None | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-25 13:22:47 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | def output_path(*path): | 
					
						
							| 
									
										
										
										
											2020-03-15 19:32:00 +01:00
										 |  |  |     if output_path.cached_path: | 
					
						
							| 
									
										
										
										
											2020-08-25 13:22:47 +02:00
										 |  |  |         return os.path.join(output_path.cached_path, *path) | 
					
						
							| 
									
										
										
										
											2020-08-20 15:43:22 +02:00
										 |  |  |     output_path.cached_path = local_path(get_options()["general_options"]["output_path"]) | 
					
						
							| 
									
										
										
										
											2020-08-25 13:22:47 +02:00
										 |  |  |     path = os.path.join(output_path.cached_path, *path) | 
					
						
							| 
									
										
										
										
											2020-08-01 16:52:11 +02:00
										 |  |  |     os.makedirs(os.path.dirname(path), exist_ok=True) | 
					
						
							|  |  |  |     return path | 
					
						
							| 
									
										
										
										
											2017-11-28 09:36:32 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-28 09:36:32 -05:00
										 |  |  | output_path.cached_path = None | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-28 09:36:32 -05:00
										 |  |  | def open_file(filename): | 
					
						
							|  |  |  |     if sys.platform == 'win32': | 
					
						
							|  |  |  |         os.startfile(filename) | 
					
						
							|  |  |  |     else: | 
					
						
							| 
									
										
										
										
											2017-12-17 00:25:46 -05:00
										 |  |  |         open_command = 'open' if sys.platform == 'darwin' else 'xdg-open' | 
					
						
							| 
									
										
										
										
											2017-11-28 09:36:32 -05:00
										 |  |  |         subprocess.call([open_command, filename]) | 
					
						
							| 
									
										
										
										
											2017-12-02 09:21:04 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-02 09:21:04 -05:00
										 |  |  | def close_console(): | 
					
						
							|  |  |  |     if sys.platform == 'win32': | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  |         # windows | 
					
						
							| 
									
										
										
										
											2017-12-02 09:21:04 -05:00
										 |  |  |         import ctypes.wintypes | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             ctypes.windll.kernel32.FreeConsole() | 
					
						
							| 
									
										
										
										
											2017-12-17 00:25:46 -05:00
										 |  |  |         except Exception: | 
					
						
							| 
									
										
										
										
											2017-12-02 09:21:04 -05:00
										 |  |  |             pass | 
					
						
							| 
									
										
										
										
											2018-01-01 14:42:23 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-09 05:28:48 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-05 02:06:00 +02:00
										 |  |  | parse_yaml = safe_load | 
					
						
							|  |  |  | unsafe_parse_yaml = functools.partial(load, Loader=Loader) | 
					
						
							| 
									
										
										
										
											2020-02-16 15:32:40 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-09 05:28:48 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-06 00:48:23 +01:00
										 |  |  | def get_public_ipv4() -> str: | 
					
						
							|  |  |  |     import socket | 
					
						
							|  |  |  |     import urllib.request | 
					
						
							|  |  |  |     import logging | 
					
						
							|  |  |  |     ip = socket.gethostbyname(socket.gethostname()) | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         ip = urllib.request.urlopen('https://checkip.amazonaws.com/').read().decode('utf8').strip() | 
					
						
							|  |  |  |     except Exception as e: | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             ip = urllib.request.urlopen('https://v4.ident.me').read().decode('utf8').strip() | 
					
						
							|  |  |  |         except: | 
					
						
							|  |  |  |             logging.exception(e) | 
					
						
							|  |  |  |             pass  # we could be offline, in a local game, so no point in erroring out | 
					
						
							|  |  |  |     return ip | 
					
						
							| 
									
										
										
										
											2020-03-15 19:32:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-14 09:06:37 +02:00
										 |  |  | def get_public_ipv6() -> str: | 
					
						
							|  |  |  |     import socket | 
					
						
							|  |  |  |     import urllib.request | 
					
						
							|  |  |  |     import logging | 
					
						
							|  |  |  |     ip = socket.gethostbyname(socket.gethostname()) | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         ip = urllib.request.urlopen('https://v6.ident.me').read().decode('utf8').strip() | 
					
						
							|  |  |  |     except Exception as e: | 
					
						
							|  |  |  |         logging.exception(e) | 
					
						
							| 
									
										
										
										
											2020-06-21 16:13:42 +02:00
										 |  |  |         pass  # we could be offline, in a local game, or ipv6 may not be available | 
					
						
							| 
									
										
										
										
											2020-06-14 09:06:37 +02:00
										 |  |  |     return ip | 
					
						
							| 
									
										
										
										
											2020-03-15 19:32:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-28 11:34:29 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | def get_default_options() -> dict: | 
					
						
							|  |  |  |     if not hasattr(get_default_options, "options"): | 
					
						
							|  |  |  |         # Refer to host.yaml for comments as to what all these options mean. | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  |         options = { | 
					
						
							|  |  |  |             "general_options": { | 
					
						
							| 
									
										
										
										
											2021-04-01 11:40:58 +02:00
										 |  |  |                 "output_path": "output", | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             "factorio_options": { | 
					
						
							|  |  |  |                 "executable": "factorio\\bin\\x64\\factorio", | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             "lttp_options": { | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  |                 "rom_file": "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc", | 
					
						
							|  |  |  |                 "qusb2snes": "QUsb2Snes\\QUsb2Snes.exe", | 
					
						
							|  |  |  |                 "rom_start": True, | 
					
						
							| 
									
										
										
										
											2021-04-01 11:40:58 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  |             }, | 
					
						
							|  |  |  |             "server_options": { | 
					
						
							|  |  |  |                 "host": None, | 
					
						
							|  |  |  |                 "port": 38281, | 
					
						
							|  |  |  |                 "password": None, | 
					
						
							|  |  |  |                 "multidata": None, | 
					
						
							|  |  |  |                 "savefile": None, | 
					
						
							|  |  |  |                 "disable_save": False, | 
					
						
							|  |  |  |                 "loglevel": "info", | 
					
						
							|  |  |  |                 "server_password": None, | 
					
						
							|  |  |  |                 "disable_item_cheat": False, | 
					
						
							|  |  |  |                 "location_check_points": 1, | 
					
						
							| 
									
										
										
										
											2021-06-05 21:15:54 -07:00
										 |  |  |                 "hint_cost": 10, | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  |                 "forfeit_mode": "goal", | 
					
						
							|  |  |  |                 "remaining_mode": "goal", | 
					
						
							|  |  |  |                 "auto_shutdown": 0, | 
					
						
							|  |  |  |                 "compatibility": 2, | 
					
						
							| 
									
										
										
										
											2021-04-07 02:37:21 +02:00
										 |  |  |                 "log_network": 0 | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  |             }, | 
					
						
							|  |  |  |             "multi_mystery_options": { | 
					
						
							|  |  |  |                 "teams": 1, | 
					
						
							|  |  |  |                 "enemizer_path": "EnemizerCLI/EnemizerCLI.Core.exe", | 
					
						
							|  |  |  |                 "player_files_path": "Players", | 
					
						
							|  |  |  |                 "players": 0, | 
					
						
							|  |  |  |                 "weights_file_path": "weights.yaml", | 
					
						
							|  |  |  |                 "meta_file_path": "meta.yaml", | 
					
						
							| 
									
										
										
										
											2021-03-03 02:20:37 -08:00
										 |  |  |                 "pre_roll": False, | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  |                 "create_spoiler": 1, | 
					
						
							|  |  |  |                 "zip_roms": 0, | 
					
						
							|  |  |  |                 "zip_diffs": 2, | 
					
						
							| 
									
										
										
										
											2021-06-05 21:15:54 -07:00
										 |  |  |                 "zip_apmcs": 1, | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  |                 "zip_spoiler": 0, | 
					
						
							|  |  |  |                 "zip_multidata": 1, | 
					
						
							|  |  |  |                 "zip_format": 1, | 
					
						
							| 
									
										
										
										
											2021-03-22 13:14:19 -07:00
										 |  |  |                 "glitch_triforce_room": 1, | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  |                 "race": 0, | 
					
						
							|  |  |  |                 "cpu_threads": 0, | 
					
						
							|  |  |  |                 "max_attempts": 0, | 
					
						
							|  |  |  |                 "take_first_working": False, | 
					
						
							|  |  |  |                 "keep_all_seeds": False, | 
					
						
							|  |  |  |                 "log_output_path": "Output Logs", | 
					
						
							|  |  |  |                 "log_level": None, | 
					
						
							|  |  |  |                 "plando_options": "bosses", | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-28 11:34:29 -08:00
										 |  |  |         get_default_options.options = options | 
					
						
							|  |  |  |     return get_default_options.options | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-30 07:43:13 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | blacklisted_options = {"multi_mystery_options.cpu_threads", | 
					
						
							|  |  |  |                        "multi_mystery_options.max_attempts", | 
					
						
							|  |  |  |                        "multi_mystery_options.take_first_working", | 
					
						
							|  |  |  |                        "multi_mystery_options.keep_all_seeds", | 
					
						
							|  |  |  |                        "multi_mystery_options.log_output_path", | 
					
						
							|  |  |  |                        "multi_mystery_options.log_level"} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-28 14:51:13 -08:00
										 |  |  | def update_options(src: dict, dest: dict, filename: str, keys: list) -> dict: | 
					
						
							|  |  |  |     import logging | 
					
						
							|  |  |  |     for key, value in src.items(): | 
					
						
							|  |  |  |         new_keys = keys.copy() | 
					
						
							|  |  |  |         new_keys.append(key) | 
					
						
							| 
									
										
										
										
											2020-11-30 07:43:13 -08:00
										 |  |  |         option_name = '.'.join(new_keys) | 
					
						
							| 
									
										
										
										
											2020-11-28 14:51:13 -08:00
										 |  |  |         if key not in dest: | 
					
						
							|  |  |  |             dest[key] = value | 
					
						
							| 
									
										
										
										
											2020-11-30 07:43:13 -08:00
										 |  |  |             if filename.endswith("options.yaml") and option_name not in blacklisted_options: | 
					
						
							|  |  |  |                 logging.info(f"Warning: {filename} is missing {option_name}") | 
					
						
							| 
									
										
										
										
											2020-11-28 14:51:13 -08:00
										 |  |  |         elif isinstance(value, dict): | 
					
						
							|  |  |  |             if not isinstance(dest.get(key, None), dict): | 
					
						
							| 
									
										
										
										
											2020-11-30 07:43:13 -08:00
										 |  |  |                 if filename.endswith("options.yaml") and option_name not in blacklisted_options: | 
					
						
							|  |  |  |                     logging.info(f"Warning: {filename} has {option_name}, but it is not a dictionary. overwriting.") | 
					
						
							| 
									
										
										
										
											2020-11-28 14:51:13 -08:00
										 |  |  |                 dest[key] = value | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 dest[key] = update_options(value, dest[key], filename, new_keys) | 
					
						
							|  |  |  |     return dest | 
					
						
							| 
									
										
										
										
											2020-11-28 11:34:29 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-15 19:32:00 +01:00
										 |  |  | def get_options() -> dict: | 
					
						
							| 
									
										
										
										
											2020-03-23 07:59:55 +01:00
										 |  |  |     if not hasattr(get_options, "options"): | 
					
						
							| 
									
										
										
										
											2020-03-15 19:32:00 +01:00
										 |  |  |         locations = ("options.yaml", "host.yaml", | 
					
						
							|  |  |  |                      local_path("options.yaml"), local_path("host.yaml")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for location in locations: | 
					
						
							|  |  |  |             if os.path.exists(location): | 
					
						
							|  |  |  |                 with open(location) as f: | 
					
						
							| 
									
										
										
										
											2020-11-28 11:34:29 -08:00
										 |  |  |                     options = parse_yaml(f.read()) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-28 14:51:13 -08:00
										 |  |  |                 get_options.options = update_options(get_default_options(), options, location, list()) | 
					
						
							| 
									
										
										
										
											2020-03-23 07:59:55 +01:00
										 |  |  |                 break | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise FileNotFoundError(f"Could not find {locations[1]} to load options.") | 
					
						
							|  |  |  |     return get_options.options | 
					
						
							| 
									
										
										
										
											2020-04-14 20:22:42 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-28 15:48:11 +02:00
										 |  |  | def get_item_name_from_id(code: int) -> str: | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |     from worlds import lookup_any_item_id_to_name | 
					
						
							|  |  |  |     return lookup_any_item_id_to_name.get(code, f'Unknown item (ID:{code})') | 
					
						
							| 
									
										
										
										
											2020-04-14 20:22:42 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-28 15:48:11 +02:00
										 |  |  | def get_location_name_from_id(code: int) -> str: | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |     from worlds import lookup_any_location_id_to_name | 
					
						
							| 
									
										
										
										
											2021-04-28 15:48:11 +02:00
										 |  |  |     return lookup_any_location_id_to_name.get(code, f'Unknown location (ID:{code})') | 
					
						
							| 
									
										
										
										
											2020-04-14 20:22:42 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-25 02:07:28 +01:00
										 |  |  | def persistent_store(category: str, key: typing.Any, value: typing.Any): | 
					
						
							| 
									
										
										
										
											2020-04-24 05:29:02 +02:00
										 |  |  |     path = local_path("_persistent_storage.yaml") | 
					
						
							|  |  |  |     storage: dict = persistent_load() | 
					
						
							|  |  |  |     category = storage.setdefault(category, {}) | 
					
						
							|  |  |  |     category[key] = value | 
					
						
							|  |  |  |     with open(path, "wt") as f: | 
					
						
							|  |  |  |         f.write(dump(storage)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-26 15:14:30 +02:00
										 |  |  | def persistent_load() -> typing.Dict[dict]: | 
					
						
							| 
									
										
										
										
											2020-06-04 21:27:29 +02:00
										 |  |  |     storage = getattr(persistent_load, "storage", None) | 
					
						
							|  |  |  |     if storage: | 
					
						
							|  |  |  |         return storage | 
					
						
							| 
									
										
										
										
											2020-04-24 05:29:02 +02:00
										 |  |  |     path = local_path("_persistent_storage.yaml") | 
					
						
							|  |  |  |     storage: dict = {} | 
					
						
							|  |  |  |     if os.path.exists(path): | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             with open(path, "r") as f: | 
					
						
							| 
									
										
										
										
											2020-07-05 02:06:00 +02:00
										 |  |  |                 storage = unsafe_parse_yaml(f.read()) | 
					
						
							| 
									
										
										
										
											2020-04-24 05:29:02 +02:00
										 |  |  |         except Exception as e: | 
					
						
							|  |  |  |             import logging | 
					
						
							|  |  |  |             logging.debug(f"Could not read store: {e}") | 
					
						
							| 
									
										
										
										
											2020-04-29 22:42:26 -07:00
										 |  |  |     if storage is None: | 
					
						
							|  |  |  |         storage = {} | 
					
						
							| 
									
										
										
										
											2020-06-04 21:27:29 +02:00
										 |  |  |     persistent_load.storage = storage | 
					
						
							| 
									
										
										
										
											2020-04-24 05:29:02 +02:00
										 |  |  |     return storage | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-09 18:02:15 +02:00
										 |  |  | def get_adjuster_settings(romfile: str) -> typing.Tuple[str, bool]: | 
					
						
							| 
									
										
										
										
											2020-06-07 12:04:33 -07:00
										 |  |  |     if hasattr(get_adjuster_settings, "adjuster_settings"): | 
					
						
							|  |  |  |         adjuster_settings = getattr(get_adjuster_settings, "adjuster_settings") | 
					
						
							|  |  |  |     else: | 
					
						
							| 
									
										
										
										
											2020-11-11 13:15:35 +01:00
										 |  |  |         adjuster_settings = persistent_load().get("adjuster", {}).get("last_settings_3", {}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-07 12:04:33 -07:00
										 |  |  |     if adjuster_settings: | 
					
						
							|  |  |  |         import pprint | 
					
						
							|  |  |  |         import Patch | 
					
						
							|  |  |  |         adjuster_settings.rom = romfile | 
					
						
							|  |  |  |         adjuster_settings.baserom = Patch.get_base_rom_path() | 
					
						
							| 
									
										
										
										
											2021-03-27 07:12:08 -07:00
										 |  |  |         adjuster_settings.world = None | 
					
						
							| 
									
										
										
										
											2020-06-07 12:04:33 -07:00
										 |  |  |         whitelist = {"disablemusic", "fastmenu", "heartbeep", "heartcolor", "ow_palettes", "quickswap", | 
					
						
							| 
									
										
										
										
											2020-11-11 13:15:35 +01:00
										 |  |  |                      "uw_palettes", "sprite"} | 
					
						
							| 
									
										
										
										
											2020-06-07 12:04:33 -07:00
										 |  |  |         printed_options = {name: value for name, value in vars(adjuster_settings).items() if name in whitelist} | 
					
						
							| 
									
										
										
										
											2021-03-30 09:28:28 -07:00
										 |  |  |         if hasattr(adjuster_settings, "sprite_pool"): | 
					
						
							|  |  |  |             sprite_pool = {} | 
					
						
							|  |  |  |             for sprite in getattr(adjuster_settings, "sprite_pool"): | 
					
						
							|  |  |  |                 if sprite in sprite_pool: | 
					
						
							|  |  |  |                     sprite_pool[sprite] += 1 | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     sprite_pool[sprite] = 1 | 
					
						
							|  |  |  |             if sprite_pool: | 
					
						
							|  |  |  |                 printed_options["sprite_pool"] = sprite_pool | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-11 13:15:35 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-07 12:04:33 -07:00
										 |  |  |         if hasattr(get_adjuster_settings, "adjust_wanted"): | 
					
						
							|  |  |  |             adjust_wanted = getattr(get_adjuster_settings, "adjust_wanted") | 
					
						
							| 
									
										
										
										
											2021-01-02 12:49:43 +01:00
										 |  |  |         elif persistent_load().get("adjuster", {}).get("never_adjust", False):  # never adjust, per user request | 
					
						
							| 
									
										
										
										
											2020-11-11 13:15:35 +01:00
										 |  |  |             return romfile, False | 
					
						
							| 
									
										
										
										
											2020-06-07 12:04:33 -07:00
										 |  |  |         else: | 
					
						
							|  |  |  |             adjust_wanted = input(f"Last used adjuster settings were found. Would you like to apply these? \n" | 
					
						
							|  |  |  |                                   f"{pprint.pformat(printed_options)}\n" | 
					
						
							| 
									
										
										
										
											2020-11-11 13:15:35 +01:00
										 |  |  |                                   f"Enter yes, no or never: ") | 
					
						
							| 
									
										
										
										
											2020-06-07 12:04:33 -07:00
										 |  |  |         if adjust_wanted and adjust_wanted.startswith("y"): | 
					
						
							| 
									
										
										
										
											2021-03-27 07:12:08 -07:00
										 |  |  |             if hasattr(adjuster_settings, "sprite_pool"): | 
					
						
							| 
									
										
										
										
											2021-06-05 13:58:59 -07:00
										 |  |  |                 from LttPAdjuster import AdjusterWorld | 
					
						
							| 
									
										
										
										
											2021-03-27 07:12:08 -07:00
										 |  |  |                 adjuster_settings.world = AdjusterWorld(getattr(adjuster_settings, "sprite_pool")) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-07 12:04:33 -07:00
										 |  |  |             adjusted = True | 
					
						
							| 
									
										
										
										
											2021-06-05 13:58:59 -07:00
										 |  |  |             import LttPAdjuster | 
					
						
							|  |  |  |             _, romfile = LttPAdjuster.adjust(adjuster_settings) | 
					
						
							| 
									
										
										
										
											2021-03-27 07:12:08 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |             if hasattr(adjuster_settings, "world"): | 
					
						
							|  |  |  |                 delattr(adjuster_settings, "world") | 
					
						
							| 
									
										
										
										
											2020-11-11 13:15:35 +01:00
										 |  |  |         elif adjust_wanted and "never" in adjust_wanted: | 
					
						
							|  |  |  |             persistent_store("adjuster", "never_adjust", True) | 
					
						
							|  |  |  |             return romfile, False | 
					
						
							| 
									
										
										
										
											2020-06-07 12:04:33 -07:00
										 |  |  |         else: | 
					
						
							|  |  |  |             adjusted = False | 
					
						
							|  |  |  |             import logging | 
					
						
							|  |  |  |             if not hasattr(get_adjuster_settings, "adjust_wanted"): | 
					
						
							|  |  |  |                 logging.info(f"Skipping post-patch adjustment") | 
					
						
							|  |  |  |         get_adjuster_settings.adjuster_settings = adjuster_settings | 
					
						
							|  |  |  |         get_adjuster_settings.adjust_wanted = adjust_wanted | 
					
						
							|  |  |  |         return romfile, adjusted | 
					
						
							| 
									
										
										
										
											2020-06-09 18:02:15 +02:00
										 |  |  |     return romfile, False | 
					
						
							| 
									
										
										
										
											2020-06-07 12:04:33 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 21:27:29 +02:00
										 |  |  | def get_unique_identifier(): | 
					
						
							|  |  |  |     uuid = persistent_load().get("client", {}).get("uuid", None) | 
					
						
							|  |  |  |     if uuid: | 
					
						
							|  |  |  |         return uuid | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     import uuid | 
					
						
							|  |  |  |     uuid = uuid.getnode() | 
					
						
							|  |  |  |     persistent_store("client", "uuid", uuid) | 
					
						
							|  |  |  |     return uuid | 
					
						
							| 
									
										
										
										
											2020-09-09 01:41:37 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | safe_builtins = { | 
					
						
							|  |  |  |     'set', | 
					
						
							|  |  |  |     'frozenset', | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class RestrictedUnpickler(pickle.Unpickler): | 
					
						
							|  |  |  |     def find_class(self, module, name): | 
					
						
							|  |  |  |         if module == "builtins" and name in safe_builtins: | 
					
						
							|  |  |  |             return getattr(builtins, name) | 
					
						
							| 
									
										
										
										
											2021-04-12 00:06:27 +02:00
										 |  |  |         if module == "NetUtils" and name in {"NetworkItem", "ClientStatus", "Hint"}: | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |             import NetUtils | 
					
						
							|  |  |  |             return getattr(NetUtils, name) | 
					
						
							| 
									
										
										
										
											2021-05-16 22:59:45 +02:00
										 |  |  |         if module == "Options": | 
					
						
							|  |  |  |             import Options | 
					
						
							|  |  |  |             obj = getattr(Options, name) | 
					
						
							|  |  |  |             if issubclass(obj, Options.Option): | 
					
						
							|  |  |  |                 return obj | 
					
						
							| 
									
										
										
										
											2020-09-09 01:41:37 +02:00
										 |  |  |         # Forbid everything else. | 
					
						
							|  |  |  |         raise pickle.UnpicklingError("global '%s.%s' is forbidden" % | 
					
						
							|  |  |  |                                      (module, name)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def restricted_loads(s): | 
					
						
							|  |  |  |     """Helper function analogous to pickle.loads().""" | 
					
						
							| 
									
										
										
										
											2021-01-03 14:32:32 +01:00
										 |  |  |     return RestrictedUnpickler(io.BytesIO(s)).load() |