def run(self, status: Status): if not self.config['starters_npcs']['npcs']: return status.done() status.step("Apply 'ActorAndLevelLoader' patch...") patcher = Patcher(self.rom, self.static_data) if not patcher.is_applied('ActorAndLevelLoader'): patcher.apply('ActorAndLevelLoader') status.step("Updating special recruitment table...") actor_list: ActorListBin = FileType.SIR0.unwrap_obj( FileType.SIR0.deserialize( self.rom.getFileByName('BALANCE/actor_list.bin')), ActorListBin) binary = get_binary_from_rom_ppmdu( self.rom, self.static_data.binaries['overlay/overlay_0011.bin']) sp_list = HardcodedRecruitmentTables.get_monster_species_list( binary, self.static_data) for i, actor in enumerate(actor_list.list): if i in ACTOR_TO_RECRUIT_MAPPING: for bi in ACTOR_TO_RECRUIT_MAPPING[i]: sp_list[bi] = actor.entid HardcodedRecruitmentTables.set_monster_species_list( sp_list, binary, self.static_data) set_binary_in_rom_ppmdu( self.rom, self.static_data.binaries['overlay/overlay_0011.bin'], binary) status.done()
def run(self, status: Status): if not self.config['starters_npcs']['starters']: return status.done() status.step("Randomizing Partner Starters...") overlay13 = get_binary_from_rom_ppmdu( self.rom, self.static_data.binaries['overlay/overlay_0013.bin']) pokemon_string_data = self.static_data.string_index_data.string_blocks[ "Pokemon Names"] langs = list(get_all_string_files(self.rom, self.static_data)) orig_partner_ids = HardcodedPersonalityTestStarters.get_partner_md_ids( overlay13, self.static_data) new_partner_ids = [ self._random_gender(choice(get_allowed_md_ids(self.config))) for _ in range(0, len(orig_partner_ids)) ] HardcodedPersonalityTestStarters.set_partner_md_ids( new_partner_ids, overlay13, self.static_data) status.step("Randomizing Player Starters...") # The player options are put into two-pairs for each nature, first male then female. orig_player_ids = HardcodedPersonalityTestStarters.get_player_md_ids( overlay13, self.static_data) new_player_ids = [] k = 0 # Index of text for "Will be..." for i in range(0, len(orig_player_ids)): new_id = choice(get_allowed_md_ids(self.config)) if k % 3 == 0: k += 1 # todo: refactor, this isn't really efficient. for lang, string_file in langs: string_file.strings[0x67C + k] = replace_strings( string_file.strings[0x67C + k], { self._get_name(string_file, orig_player_ids[i], pokemon_string_data): self._get_name(string_file, new_id, pokemon_string_data) }) if i % 2 == 1 and new_id + NUM_ENTITIES <= 1154: new_id += NUM_ENTITIES new_player_ids.append(new_id) k += 1 HardcodedPersonalityTestStarters.set_player_md_ids( new_player_ids, overlay13, self.static_data) status.step("Cloning missing starter portraits...") kao = FileType.KAO.deserialize( self.rom.getFileByName('FONT/kaomado.kao')) for new in new_player_ids + new_partner_ids: new_base = new % 600 clone_missing_portraits(kao, new_base - 1) set_binary_in_rom_ppmdu( self.rom, self.static_data.binaries['overlay/overlay_0013.bin'], overlay13) for lang, string_file in langs: self.rom.setFileByName(f'MESSAGE/{lang.filename}', FileType.STR.serialize(string_file)) self.rom.setFileByName('FONT/kaomado.kao', FileType.KAO.serialize(kao)) status.done()
def modify_binary(self, binary: Union[Pmd2Binary, BinaryName, str], modify_cb: Callable[[bytearray], None]): """Modify one of the binaries (such as arm9 or overlay) and save it to the ROM""" if not isinstance(binary, Pmd2Binary): binary = self.get_rom_module().get_static_data().binaries[str(binary)] data = bytearray(self.get_binary(binary)) modify_cb(data) set_binary_in_rom_ppmdu(self._rom, binary, data) self.force_mark_as_modified()
def run(self, status: Status): if not self.config['starters_npcs']['npcs']: return status.done() status.step("Apply 'ActorAndLevelLoader' patch...") patcher = Patcher(self.rom, self.static_data) if not patcher.is_applied('ActorAndLevelLoader'): patcher.apply('ActorAndLevelLoader') status.step("Updating bosses...") actor_list: ActorListBin = FileType.SIR0.unwrap_obj( FileType.SIR0.deserialize( self.rom.getFileByName('BALANCE/actor_list.bin')), ActorListBin) binary = get_binary_from_rom_ppmdu( self.rom, self.static_data.binaries['overlay/overlay_0029.bin']) boss_list = HardcodedFixedFloorTables.get_monster_spawn_list( binary, self.static_data) for i, actor in enumerate(actor_list.list): if i in ACTOR_TO_BOSS_MAPPING: for bi in ACTOR_TO_BOSS_MAPPING[i]: boss_list[bi].md_idx = actor.entid for extra_id in EXTRA_FF_MONSTER_RANDOMIZE: boss_list[extra_id].md_idx = choice( get_allowed_md_ids(self.config, False)) HardcodedFixedFloorTables.set_monster_spawn_list( binary, boss_list, self.static_data) set_binary_in_rom_ppmdu( self.rom, self.static_data.binaries['overlay/overlay_0029.bin'], binary) status.done()
def apply(self, patch: Union[Pmd2Patch, Pmd2SimplePatch], binaries: Dict[str, Pmd2Binary], patch_file_dir: str, stub_path: str, game_id: str, parameter_values: Dict[str, Union[int, str]]): with tempfile.TemporaryDirectory() as tmp: try: shutil.copytree(patch_file_dir, tmp, symlinks=True, dirs_exist_ok=True) set_rw_permission_folder(tmp) # Build ASM file to run asm_entrypoint = '' # First read in stub with open(os.path.join(tmp, stub_path)) as fi: asm_entrypoint += fi.read() + '\n' if isinstance(patch, Pmd2SimplePatch): for replace in patch.string_replacements: fn = os.path.join(tmp, replace.filename) game = None for game_candidate in replace.games: if game_candidate.game_id == game_id: game = game_candidate if game is not None: with open_utf8(os.path.join(tmp, fn), 'r') as fi: new_content = replace.regexp.sub( game.replace, fi.read()) with open_utf8(os.path.join(tmp, fn), 'w') as fi: fi.write(new_content) # If it's a simple patch just output and re-import all binaries. for binary_name, binary in binaries.items(): binary_path = os.path.join(tmp, binary_name.split('/')[-1]) # Write binary to tmp dir with open(binary_path, 'wb') as fib: try: fib.write( get_binary_from_rom_ppmdu( self.rom, binary)) except ValueError as err: if binary_name.split( '/')[-1] == 'overlay_0036.bin': continue # We ignore if End's extra overlay is missing. raise err # For simple patches we also output the overlay table as y9.bin: binary_path = os.path.join(tmp, Y9_BIN) # Write binary to tmp dir with open(binary_path, 'wb') as fib: fib.write(self.rom.arm9OverlayTable) # Then include other includes for include in patch.includes: asm_entrypoint += f'.include "{os.path.join(tmp, include.filename)}"\n' # Build binary blocks if isinstance(patch, Pmd2Patch): for open_bin in patch.open_bins: binary = binaries[open_bin.filepath] binary_path = os.path.join( tmp, open_bin.filepath.split('/')[-1]) os.makedirs(os.path.dirname(binary_path), exist_ok=True) # Write binary to tmp dir with open(binary_path, 'wb') as fib: fib.write( get_binary_from_rom_ppmdu(self.rom, binary)) asm_entrypoint += f'.open "{binary_path}", 0x{binary.loadaddress:0x}\n' for include in open_bin.includes: asm_entrypoint += f'.include "{os.path.join(tmp, include.filename)}"\n' asm_entrypoint += '.close\n' # Write final asm file with open_utf8(os.path.join(tmp, ASM_ENTRYPOINT_FN), 'w') as fi: fi.write(asm_entrypoint) # Build parameters for equ parameters = [] for param_name, param_value in parameter_values.items(): parameters += [ '-equ', param_name, f'"{param_value}"' if isinstance( param_value, str) else str(param_value) ] # Run armips try: prefix = "" # Under Windows, try to load from SkyTemple _resources dir first. if sys.platform.startswith('win') and os.path.exists( os.path.join(get_resources_dir(), 'armips.exe')): prefix = os.path.join(get_resources_dir(), '') exec_name = os.getenv('SKYTEMPLE_ARMIPS_EXEC', f'{prefix}armips') cmd_line = [exec_name, ASM_ENTRYPOINT_FN] + parameters if os.getenv('SKYTEMPLE_DEBUG_ARMIPS_OUTPUT', False): print("ARMIPS CMDLINE:") print(cmd_line) result = subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tmp) retcode = result.wait() except FileNotFoundError as ex: raise make_user_err( ArmipsNotInstalledError, _("ARMIPS could not be found. Make sure, that " "'armips' is inside your system's PATH.")) from ex if os.getenv('SKYTEMPLE_DEBUG_ARMIPS_OUTPUT', False): print("ARMIPS OUTPUT:") if result is not None: print(str(result.stdout.read(), 'utf-8')) # type: ignore print( str(result.stderr.read(), 'utf-8') if result.stderr else '') # type: ignore if retcode != 0: raise make_user_err( PatchError, _("ARMIPS reported an error while applying the patch." ), str(result.stdout.read(), 'utf-8'), str(result.stderr.read(), 'utf-8') # type: ignore if result.stderr else '') # type: ignore # Load the binaries back into the ROM opened_binaries = {} if isinstance(patch, Pmd2SimplePatch): # Read in all binaries again opened_binaries = binaries # Also read in arm9OverlayTable binary_path = os.path.join(tmp, Y9_BIN) with open(binary_path, 'rb') as fib: self.rom.arm9OverlayTable = fib.read() else: # Read opened binaries again for open_bin in patch.open_bins: opened_binaries[open_bin.filepath] = binaries[ open_bin.filepath] for binary_name, binary in opened_binaries.items(): binary_path = os.path.join(tmp, binary_name.split('/')[-1]) with open(binary_path, 'rb') as fib: try: set_binary_in_rom_ppmdu(self.rom, binary, fib.read()) except ValueError as err: if binary_name.split( '/')[-1] == 'overlay_0036.bin': continue # We ignore if End's extra overlay is missing. raise err except (PatchError, ArmipsNotInstalledError): raise except BaseException as ex: raise RuntimeError(f( _("Error while applying the patch: {ex}"))) from ex
T(WH), T(WH), T(WH), T(WH), T(WH), T(WH), T(WH), T(WH), T(WH), T(FR), T(FR), T(FR), T(FR), T(FR), T(WZ), T(WH), T(WH), ITEM1, T(SR), MONS1, MONS2, T(SR), ITEM2, T(WH), T(WH), ITEM3, T(SR), T(FR), T(FR), T(SR), ITEM4, T(WH), T(WH), T(FR), T(FR), T(FR), T(FR), T(FR), T(FR), T(WH), T(WH), T(WH), T(WH), T(K1), T(FH), T(K2), T(WH), T(WH), T(WH), T(FH), T(FH), ITEM5, T(FH), T(FH), T(FH), T(WH), T(WH), T(LS), T(WH), T(WH), T(WH), T(WH), T(WH), T(WH) ] ov29_before = bytes(ov29) HardcodedFixedFloorTables.set_item_spawn_list(ov29, item_table, static_data) HardcodedFixedFloorTables.set_tile_spawn_list(ov29, tile_table, static_data) HardcodedFixedFloorTables.set_monster_spawn_list(ov29, monster_table, static_data) HardcodedFixedFloorTables.set_entity_spawn_table(ov29, entity_table, static_data) set_binary_in_rom_ppmdu(rom, static_data.binaries['overlay/overlay_0029.bin'], ov29) assert ov29_before != get_binary_from_rom_ppmdu(rom, static_data.binaries['overlay/overlay_0029.bin']) assert item_table == HardcodedFixedFloorTables.get_item_spawn_list(ov29, static_data) assert monster_table == HardcodedFixedFloorTables.get_monster_spawn_list(ov29, static_data) assert tile_table == HardcodedFixedFloorTables.get_tile_spawn_list(ov29, static_data) assert entity_table == HardcodedFixedFloorTables.get_entity_spawn_table(ov29, static_data) fixed_bin_after = FixedBinHandler.serialize(fixed) assert fixed_bin != fixed_bin_after rom.setFileByName('BALANCE/fixed.bin', fixed_bin_after) rom.saveToFile(os.path.join(output_dir, 'test_fixed_floor.nds')) assert get_binary_from_rom_ppmdu( NintendoDSRom.fromFile(os.path.join(output_dir, 'test_fixed_floor.nds')), static_data.binaries['overlay/overlay_0029.bin'] ) != ov29_before
def apply(self, patch: Union[Pmd2Patch, Pmd2SimplePatch], binaries: Dict[str, Pmd2Binary], patch_file_dir: str, stub_path: str, game_id: str): try: with tempfile.TemporaryDirectory() as tmp: shutil.copytree(patch_file_dir, tmp, dirs_exist_ok=True) # Build ASM file to run asm_entrypoint = '' # First read in stub with open(os.path.join(tmp, stub_path)) as f: asm_entrypoint += f.read() + '\n' if isinstance(patch, Pmd2SimplePatch): for replace in patch.string_replacements: fn = os.path.join(tmp, replace.filename) game = None for game_candidate in replace.games: if game_candidate.game_id == game_id: game = game_candidate if game is not None: with open(os.path.join(tmp, fn), 'r') as f: new_content = replace.regexp.sub( game.replace, f.read()) with open(os.path.join(tmp, fn), 'w') as f: f.write(new_content) # If it's a simple patch just output and re-import all binaries. for binary_name, binary in binaries.items(): binary_path = os.path.join(tmp, binary_name.split('/')[-1]) # Write binary to tmp dir with open(binary_path, 'wb') as f: try: f.write( get_binary_from_rom_ppmdu( self.rom, binary)) except ValueError as err: if binary_name.split( '/')[-1] == 'overlay_0036.bin': continue # We ignore if End's extra overlay is missing. raise err # For simple patches we also output the overlay table as y9.bin: binary_path = os.path.join(tmp, Y9_BIN) # Write binary to tmp dir with open(binary_path, 'wb') as f: f.write(self.rom.arm9OverlayTable) # Then include other includes for include in patch.includes: asm_entrypoint += f'.include "{os.path.join(tmp, include.filename)}"\n' # Build binary blocks if isinstance(patch, Pmd2Patch): for open_bin in patch.open_bins: binary = binaries[open_bin.filepath] binary_path = os.path.join( tmp, open_bin.filepath.split('/')[-1]) os.makedirs(os.path.dirname(binary_path), exist_ok=True) # Write binary to tmp dir with open(binary_path, 'wb') as f: f.write(get_binary_from_rom_ppmdu( self.rom, binary)) asm_entrypoint += f'.open "{binary_path}", 0x{binary.loadaddress:0x}\n' for include in open_bin.includes: asm_entrypoint += f'.include "{os.path.join(tmp, include.filename)}"\n' asm_entrypoint += '.close\n' # Write final asm file with open_utf8(os.path.join(tmp, ASM_ENTRYPOINT_FN), 'w') as f: f.write(asm_entrypoint) # Run armips original_cwd = os.getcwd() os.chdir(tmp) try: prefix = "" # Under Windows, try to load from SkyTemple _resources dir first. if sys.platform.startswith('win') and os.path.exists( os.path.join(get_resources_dir(), 'armips.exe')): prefix = os.path.join(get_resources_dir(), '') result = subprocess.Popen( [f'{prefix}armips', ASM_ENTRYPOINT_FN], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) retcode = result.wait() except FileNotFoundError as ex: raise ArmipsNotInstalledError( "ARMIPS could not be found. Make sure, that " "'armips' is inside your system's PATH.") from ex finally: # Restore cwd os.chdir(original_cwd) if retcode != 0: raise PatchError( "ARMIPS reported an error while applying the patch.", str(result.stdout.read(), 'utf-8'), str(result.stderr.read(), 'utf-8') if result.stderr else '') # Load the binaries back into the ROM opened_binaries = {} if isinstance(patch, Pmd2SimplePatch): # Read in all binaries again opened_binaries = binaries # Also read in arm9OverlayTable binary_path = os.path.join(tmp, Y9_BIN) with open(binary_path, 'rb') as f: self.rom.arm9OverlayTable = f.read() else: # Read opened binaries again for open_bin in patch.open_bins: opened_binaries[open_bin.filepath] = binaries[ open_bin.filepath] for binary_name, binary in opened_binaries.items(): binary_path = os.path.join(tmp, binary_name.split('/')[-1]) with open(binary_path, 'rb') as f: try: set_binary_in_rom_ppmdu(self.rom, binary, f.read()) except ValueError as err: if binary_name.split( '/')[-1] == 'overlay_0036.bin': continue # We ignore if End's extra overlay is missing. raise err except (PatchError, ArmipsNotInstalledError): raise except BaseException as ex: raise RuntimeError(f"Error while applying the patch: {ex}") from ex