| 
									
										
										
										
											2024-06-17 22:48:15 -04:00
										 |  |  | import settings | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  | import worlds.Files | 
					
						
							|  |  |  | import hashlib | 
					
						
							|  |  |  | import Utils | 
					
						
							|  |  |  | import os | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  | import json | 
					
						
							|  |  |  | import pkgutil | 
					
						
							|  |  |  | import bsdiff4 | 
					
						
							|  |  |  | import binascii | 
					
						
							|  |  |  | import pickle | 
					
						
							|  |  |  | from typing import TYPE_CHECKING | 
					
						
							|  |  |  | from .Common import * | 
					
						
							|  |  |  | from .LADXR import generator | 
					
						
							|  |  |  | from .LADXR.main import get_parser | 
					
						
							|  |  |  | from .LADXR.hints import generate_hint_texts | 
					
						
							|  |  |  | from .LADXR.locations.keyLocation import KeyLocation | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  | LADX_HASH = "07c211479386825042efb4ad31bb525f" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  | if TYPE_CHECKING: | 
					
						
							|  |  |  |     from . import LinksAwakeningWorld | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class LADXPatchExtensions(worlds.Files.APPatchExtension): | 
					
						
							|  |  |  |     game = LINKS_AWAKENING | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @staticmethod | 
					
						
							|  |  |  |     def generate_rom(caller: worlds.Files.APProcedurePatch, rom: bytes, data_file: str) -> bytes: | 
					
						
							|  |  |  |         patch_data = json.loads(caller.get_file(data_file).decode("utf-8")) | 
					
						
							|  |  |  |         # TODO local option overrides | 
					
						
							|  |  |  |         rom_name = get_base_rom_path() | 
					
						
							|  |  |  |         out_name = f"{patch_data['out_base']}{caller.result_file_ending}" | 
					
						
							|  |  |  |         parser = get_parser() | 
					
						
							|  |  |  |         args = parser.parse_args([rom_name, "-o", out_name, "--dump"]) | 
					
						
							|  |  |  |         return generator.generateRom(rom, args, patch_data) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @staticmethod | 
					
						
							|  |  |  |     def patch_title_screen(caller: worlds.Files.APProcedurePatch, rom: bytes, data_file: str) -> bytes: | 
					
						
							|  |  |  |         patch_data = json.loads(caller.get_file(data_file).decode("utf-8")) | 
					
						
							|  |  |  |         if patch_data["options"]["ap_title_screen"]: | 
					
						
							|  |  |  |             return bsdiff4.patch(rom, pkgutil.get_data(__name__, "LADXR/patches/title_screen.bdiff4")) | 
					
						
							|  |  |  |         return rom | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class LADXProcedurePatch(worlds.Files.APProcedurePatch): | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |     hash = LADX_HASH | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  |     game = LINKS_AWAKENING | 
					
						
							|  |  |  |     patch_file_ending: str = ".apladx" | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |     result_file_ending: str = ".gbc" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  |     procedure = [ | 
					
						
							|  |  |  |         ("generate_rom", ["data.json"]), | 
					
						
							|  |  |  |         ("patch_title_screen", ["data.json"]) | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |     @classmethod | 
					
						
							|  |  |  |     def get_source_data(cls) -> bytes: | 
					
						
							|  |  |  |         return get_base_rom_bytes() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  | def write_patch_data(world: "LinksAwakeningWorld", patch: LADXProcedurePatch): | 
					
						
							|  |  |  |     item_list = pickle.dumps([item for item in world.ladxr_logic.iteminfo_list if not isinstance(item, KeyLocation)]) | 
					
						
							|  |  |  |     data_dict = { | 
					
						
							|  |  |  |         "out_base": world.multiworld.get_out_file_name_base(patch.player), | 
					
						
							|  |  |  |         "is_race": world.multiworld.is_race, | 
					
						
							|  |  |  |         "seed": world.multiworld.seed, | 
					
						
							|  |  |  |         "seed_name": world.multiworld.seed_name, | 
					
						
							|  |  |  |         "multi_key": binascii.hexlify(world.multi_key).decode(), | 
					
						
							|  |  |  |         "player": patch.player, | 
					
						
							|  |  |  |         "player_name": patch.player_name, | 
					
						
							|  |  |  |         "other_player_names": list(world.multiworld.player_name.values()), | 
					
						
							|  |  |  |         "item_list": binascii.hexlify(item_list).decode(), | 
					
						
							|  |  |  |         "hint_texts": generate_hint_texts(world), | 
					
						
							|  |  |  |         "world_setup": { | 
					
						
							|  |  |  |             "goal": world.ladxr_logic.world_setup.goal, | 
					
						
							|  |  |  |             "bingo_goals": world.ladxr_logic.world_setup.bingo_goals, | 
					
						
							|  |  |  |             "multichest": world.ladxr_logic.world_setup.multichest, | 
					
						
							|  |  |  |             "entrance_mapping": world.ladxr_logic.world_setup.entrance_mapping, | 
					
						
							|  |  |  |             "boss_mapping": world.ladxr_logic.world_setup.boss_mapping, | 
					
						
							|  |  |  |             "miniboss_mapping": world.ladxr_logic.world_setup.miniboss_mapping, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         "options": world.options.as_dict( | 
					
						
							|  |  |  |             "tradequest", | 
					
						
							|  |  |  |             "rooster", | 
					
						
							|  |  |  |             "experimental_dungeon_shuffle", | 
					
						
							|  |  |  |             "experimental_entrance_shuffle", | 
					
						
							|  |  |  |             "goal", | 
					
						
							|  |  |  |             "instrument_count", | 
					
						
							|  |  |  |             "link_palette", | 
					
						
							|  |  |  |             "warps", | 
					
						
							|  |  |  |             "trendy_game", | 
					
						
							|  |  |  |             "gfxmod", | 
					
						
							|  |  |  |             "palette", | 
					
						
							|  |  |  |             "text_shuffle", | 
					
						
							|  |  |  |             "shuffle_nightmare_keys", | 
					
						
							|  |  |  |             "shuffle_small_keys", | 
					
						
							|  |  |  |             "music", | 
					
						
							|  |  |  |             "music_change_condition", | 
					
						
							|  |  |  |             "nag_messages", | 
					
						
							|  |  |  |             "ap_title_screen", | 
					
						
							|  |  |  |             "boots_controls", | 
					
						
							|  |  |  |             # "stealing", | 
					
						
							|  |  |  |             "quickswap", | 
					
						
							|  |  |  |             "hard_mode", | 
					
						
							|  |  |  |             "low_hp_beep", | 
					
						
							|  |  |  |             "text_mode", | 
					
						
							|  |  |  |             "no_flash", | 
					
						
							|  |  |  |             "overworld", | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     patch.write_file("data.json", json.dumps(data_dict).encode('utf-8')) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  | def get_base_rom_bytes(file_name: str = "") -> bytes: | 
					
						
							|  |  |  |     base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) | 
					
						
							|  |  |  |     if not base_rom_bytes: | 
					
						
							|  |  |  |         file_name = get_base_rom_path(file_name) | 
					
						
							|  |  |  |         base_rom_bytes = bytes(open(file_name, "rb").read()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         basemd5 = hashlib.md5() | 
					
						
							|  |  |  |         basemd5.update(base_rom_bytes) | 
					
						
							|  |  |  |         if LADX_HASH != basemd5.hexdigest(): | 
					
						
							|  |  |  |             raise Exception('Supplied Base Rom does not match known MD5 for USA release. ' | 
					
						
							|  |  |  |                             'Get the correct game and version, then dump it') | 
					
						
							|  |  |  |         get_base_rom_bytes.base_rom_bytes = base_rom_bytes | 
					
						
							|  |  |  |     return base_rom_bytes | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_base_rom_path(file_name: str = "") -> str: | 
					
						
							| 
									
										
										
										
											2024-06-17 22:48:15 -04:00
										 |  |  |     options = settings.get_settings() | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |     if not file_name: | 
					
						
							|  |  |  |         file_name = options["ladx_options"]["rom_file"] | 
					
						
							|  |  |  |     if not os.path.exists(file_name): | 
					
						
							|  |  |  |         file_name = Utils.user_path(file_name) | 
					
						
							|  |  |  |     return file_name |