def create_new_file(self, new_filename, model, file_handler_class: Type[DataHandler[T]], **kwargs): """Creates a new file in the ROM and fills it with the model content provided and writes the serialized model data there""" copy_bin = file_handler_class.serialize(model, **kwargs) create_file_in_rom(self._rom, new_filename, copy_bin) self._opened_files[new_filename] = file_handler_class.deserialize(copy_bin, **kwargs) self._file_handlers[new_filename] = file_handler_class self._file_handler_kwargs[new_filename] = kwargs return copy_bin
def extract(self, entry_len: int, string_offs_per_entry: List[int], write_subheader=True): """Performs the extraction. Raises a RuntimeError on error.""" try: binary = get_binary_from_rom_ppmdu(self._rom, self._binary) data = self._wrap_sir0(binary, binary[self._block.begin:self._block.end], entry_len, string_offs_per_entry, write_subheader) if self._out_path not in self._rom.filenames: create_file_in_rom(self._rom, self._out_path, data) else: self._rom.setFileByName(self._out_path, data) except BaseException as ex: raise RuntimeError("Error during extraction for patch.") from ex
def run(self, status: Status): status.step("Loading Seed Info...") langs = list(get_all_string_files(self.rom, self.static_data)) str_offset = STR_EU if self.static_data.game_region == GAME_REGION_US: str_offset = STR_US for lang, string_file in langs: string_file.strings[str_offset] = f"""Randomized with SkyTemple Randomizer. Version:[CS:Z]{version()}[CR] Seed: [CS:C]{self.seed}[CR] [CS:H]PLEASE NOTE:[CR] This seed will only produce the same output when used with the exact same version and configuration of the randomizer that was used. You can see the configuration of the last randomization applied by talking to the NPC on Crossroads.""" for lang, string_file in langs: self.rom.setFileByName(f'MESSAGE/{lang.filename}', FileType.STR.serialize(string_file)) status.step("Placing Info NPC...") # Place NPC in scene scene: Ssa = FileType.SSA.deserialize(self.rom.getFileByName(f'SCRIPT/{MAP}/{SCENE}')) layer = scene.layer_list[0] already_exists = any(a.script_id == TALK_SCRIPT for a in layer.actors) if not already_exists: layer.actors.append(SsaActor( scriptdata=self.static_data.script_data, actor_id=ACTOR_TO_USE, pos=SsaPosition( scriptdata=self.static_data.script_data, direction=self.static_data.script_data.directions__by_name['Down'].ssa_id, x_pos=NPC_X, y_pos=NPC_Y, x_offset=0, y_offset=0 ), script_id=TALK_SCRIPT, unkE=-1, )) already_exists = any(a.script_id == TWO_TALK_SCRIPT for a in layer.actors) if not already_exists: layer.actors.append(SsaActor( scriptdata=self.static_data.script_data, actor_id=TWO_ACTOR_TO_USE, pos=SsaPosition( scriptdata=self.static_data.script_data, direction=self.static_data.script_data.directions__by_name['Down'].ssa_id, x_pos=TWO_NPC_X, y_pos=TWO_NPC_Y, x_offset=0, y_offset=0 ), script_id=TWO_TALK_SCRIPT, unkE=-1, )) self.rom.setFileByName(f'SCRIPT/{MAP}/{SCENE}', FileType.SSA.serialize(scene)) # Fill talk script 1 exps = f""" def 0 {{ with (actor ACTOR_TALK_MAIN) {{ ExecuteCommon(CORO_LIVES_REPLY_NORMAL, 0); }} with (actor ACTOR_TALK_SUB) {{ ExecuteCommon(CORO_LIVES_REPLY_NORMAL, 0); }} with (actor ACTOR_ATTENDANT1) {{ SetAnimation(2); }} message_SetFace(ACTOR_NPC_TEST010, FACE_HAPPY, FACE_POS_TOP_L_FACEINW); message_Talk(" This ROM has been randomized\\nwith the SkyTemple Randomizer!"); message_ResetActor(); message_Notice("SkyTemple Randomizer by [CS:A]Parakoopa[CR].\\nVersion:[CS:Z]{escape(version())}[CR]\\nSeed: [CS:C]{escape(str(self.seed))}[CR]"); §l_menu; switch ( message_SwitchMenu(0, 1) ) {{ case menu("Show Settings"): ~settings(); jump @l_menu; case menu("Patch Credits"): ~patches(); jump @l_menu; case menu("Goodbye!"): default: break; }} JumpCommon(CORO_END_TALK); }} macro settings() {{ §l_settings; switch ( message_SwitchMenu(0, 1) ) {{ case menu("Starters & More"): message_Mail("Randomize Starters?: {self._bool(self.config['starters_npcs']['starters'])}\\nRandomize NPCs and Bosses?: {self._bool(self.config['starters_npcs']['npcs'])}\\nRandomize Shops?: {self._bool(self.config['starters_npcs']['global_items'])}\\nRandomize OW Music?: {self._bool(self.config['starters_npcs']['overworld_music'])}"); jump @l_settings; case menu("Dungeons: General"): message_Mail("Mode: {self._dungeon_mode(self.config['dungeons']['mode'])}\\nLayouts and Tilesets?: {self._bool(self.config['dungeons']['layouts'])}\\nRandomize Weather?: {self._weather(self.config['dungeons']['weather'])}\\nRandomize Items?: {self._bool(self.config['dungeons']['items'])}\\nRandomize Pokémon?: {self._bool(self.config['dungeons']['pokemon'])}\\nRandomize Traps?: {self._bool(self.config['dungeons']['traps'])}\\nRandomize Boss Rooms?: {self._bool(self.config['dungeons']['fixed_rooms'])}"); jump @l_settings; case menu("Improvements"): message_Mail("Download portraits?: {self._bool(self.config['improvements']['download_portraits'])}\\nApply 'MoveShortcuts'?: {self._bool(self.config['improvements']['patch_moveshortcuts'])}\\nApply 'UnusedDungeonChance'?: {self._bool(self.config['improvements']['patch_unuseddungeonchance'])}\\nApply 'CTC'?: {self._bool(self.config['improvements']['patch_totalteamcontrol'])}"); jump @l_settings; case menu("Pokémon: General"): message_Mail("Randomize IQ Groups?: {self._bool(self.config['pokemon']['iq_groups'])}\\nRandomize Abilities?: {self._bool(self.config['pokemon']['abilities'])}\\nRandomize Typings?: {self._bool(self.config['pokemon']['typings'])}\\nRandomize Movesets?: {self._movesets(self.config['pokemon']['movesets'])}\\nBan Unowns?: {self._bool(self.config['pokemon']['ban_unowns'])}"); jump @l_settings; case menu("Pokémon: Abilities"): {self._abilities(self.config['pokemon']['abilities_enabled'])} jump @l_settings; case menu("Locations (First)"): message_Mail("Randomize?: {self._bool(self.config['locations']['randomize'])}"); {self._locs_chaps(self.config['locations']['first'])} jump @l_settings; case menu("Locations (Second)"): message_Mail("Randomize?: {self._bool(self.config['locations']['randomize'])}"); {self._locs_chaps(self.config['locations']['second'])} jump @l_settings; case menu("Chapters"): message_Mail("Randomize?: {self._bool(self.config['chapters']['randomize'])}"); {self._locs_chaps(self.config['chapters']['text'])} jump @l_settings; case menu("Text"): message_Mail("Randomize Main Texts?: {self._bool(self.config['text']['main'])}\\nRandomize Story Dialogue: {self._bool(self.config['text']['story'])}"); jump @l_settings; {self._dungeon_cases()} case menu("Goodbye!"): default: break; }} }} macro patches() {{ §l_patches; switch ( message_SwitchMenu(0, 1) ) {{ {self._patch_credits()} case menu("Goodbye!"): default: break; }} }} """ script, _ = ScriptCompiler(self.static_data).compile_explorerscript( exps, 'script.exps', lookup_paths=[] ) script_fn = f'SCRIPT/{MAP}/{TALK_SCRIPT_NAME}' script_sera = FileType.SSB.serialize(script, static_data=self.static_data) try: create_file_in_rom(self.rom, script_fn, script_sera) except FileExistsError: self.rom.setFileByName(script_fn, script_sera) exps = f""" def 0 {{ with (actor ACTOR_TALK_MAIN) {{ ExecuteCommon(CORO_LIVES_REPLY_NORMAL, 0); }} with (actor ACTOR_TALK_SUB) {{ ExecuteCommon(CORO_LIVES_REPLY_NORMAL, 0); }} with (actor ACTOR_ATTENDANT1) {{ SetAnimation(2); }} message_SetFace(ACTOR_NPC_TEST009, FACE_HAPPY, FACE_POS_TOP_L_FACEINW); message_Talk(" This ROM has been randomized\\nwith the SkyTemple Randomizer!"); message_ResetActor(); message_Notice("SkyTemple Randomizer by [CS:A]Parakoopa[CR].\\nVersion:[CS:Z]{escape(version())}[CR]\\nSeed: [CS:C]{escape(str(self.seed))}[CR]"); §l_menu; switch ( message_SwitchMenu(0, 1) ) {{ case menu("Artist Credits"): ~artists(); jump @l_menu; case menu("Goodbye!"): default: break; }} JumpCommon(CORO_END_TALK); }} macro artists() {{ §l_artists; switch ( message_SwitchMenu(0, 1) ) {{ {self._artist_credits()} case menu("Goodbye!"): default: break; }} message_ResetActor(); }} """ script, _ = ScriptCompiler(self.static_data).compile_explorerscript( exps, 'script.exps', lookup_paths=[] ) script_fn = f'SCRIPT/{MAP}/{TWO_TALK_SCRIPT_NAME}' script_sera = FileType.SSB.serialize(script, static_data=self.static_data) try: create_file_in_rom(self.rom, script_fn, script_sera) except FileExistsError: self.rom.setFileByName(script_fn, script_sera) status.done()
def create_file_manually(self, filename: str, data: bytes): """ Manually create a file in the ROM. """ create_file_in_rom(self._rom, filename, data) self.force_mark_as_modified()
def main(): os.makedirs(output_dir, exist_ok=True) rom = NintendoDSRom.fromFile(os.path.join(base_dir, 'skyworkcopy_edit.nds')) bin_before = rom.getFileByName('SCRIPT/G01P01A/enter.sse') ssa_before = SsaHandler.deserialize(bin_before) data = Pmd2XmlReader.load_default() scriptdata = data.script_data ssa_before.layer_list[0].objects = [ # TODO: 5=Width, 1=Height! SsaObject( scriptdata, 6, 5, 1, SsaPosition(scriptdata, 44, 24, 0, 0, scriptdata.directions__by_name['Down'].id), 10, -1) ] # Write NPC types npc_table_start = data.binaries['arm9.bin'].blocks['Entities'].begin NPC_TABLE_ENTRY_LEN = 0x0c # uint16: type, uint16: entid, uint32: pointer to name, unk3, unk4 # Shaymin NPC_SHEIMI 534 / V02P06A ent_id__shaymin = scriptdata.level_entities__by_name['NPC_SHEIMI'].id print( read_uintle( rom.arm9, npc_table_start + ent_id__shaymin * NPC_TABLE_ENTRY_LEN + 0x02, 2)) write_uintle( rom.arm9, 534, npc_table_start + ent_id__shaymin * NPC_TABLE_ENTRY_LEN + 0x02, 2) # Elekid NPC_SHEIMI1 266 / V02P07A ent_id__elekid = scriptdata.level_entities__by_name['NPC_SHEIMI1'].id print( read_uintle( rom.arm9, npc_table_start + ent_id__elekid * NPC_TABLE_ENTRY_LEN + 0x02, 2)) write_uintle(rom.arm9, 266, npc_table_start + ent_id__elekid * NPC_TABLE_ENTRY_LEN + 0x02, 2) # Piplup NPC_SHEIMI2 428 / V03P01A ent_id__piplup = scriptdata.level_entities__by_name['NPC_SHEIMI2'].id print( read_uintle( rom.arm9, npc_table_start + ent_id__piplup * NPC_TABLE_ENTRY_LEN + 0x02, 2)) write_uintle(rom.arm9, 428, npc_table_start + ent_id__piplup * NPC_TABLE_ENTRY_LEN + 0x02, 2) # Meowth NPC_SHEIMI3 52 / V03P02A ent_id__meowth = scriptdata.level_entities__by_name['NPC_SHEIMI3'].id print( read_uintle( rom.arm9, npc_table_start + ent_id__meowth * NPC_TABLE_ENTRY_LEN + 0x02, 2)) write_uintle(rom.arm9, 52, npc_table_start + ent_id__meowth * NPC_TABLE_ENTRY_LEN + 0x02, 2) # Buneary NPC_SHEIMI4 469 / V03P03A ent_id__buneary = scriptdata.level_entities__by_name['NPC_SHEIMI4'].id print( read_uintle( rom.arm9, npc_table_start + ent_id__buneary * NPC_TABLE_ENTRY_LEN + 0x02, 2)) write_uintle( rom.arm9, 469, npc_table_start + ent_id__buneary * NPC_TABLE_ENTRY_LEN + 0x02, 2) ssa_before.layer_list[0].actors = [ SsaActor( scriptdata, ent_id__shaymin, SsaPosition(scriptdata, 14, 24, 2, 0, scriptdata.directions__by_name['Down'].id), 5, -1), SsaActor( scriptdata, ent_id__elekid, SsaPosition(scriptdata, 20, 24, 2, 0, scriptdata.directions__by_name['Down'].id), 6, -1), SsaActor( scriptdata, ent_id__piplup, SsaPosition(scriptdata, 26, 24, 2, 0, scriptdata.directions__by_name['Down'].id), 7, -1), SsaActor( scriptdata, ent_id__meowth, SsaPosition(scriptdata, 32, 24, 2, 0, scriptdata.directions__by_name['Down'].id), 8, -1), SsaActor( scriptdata, ent_id__buneary, SsaPosition(scriptdata, 38, 24, 2, 0, scriptdata.directions__by_name['Down'].id), 9, -1), # Mimikyu NPC_PUKURIN 40 / V03P04A # Litten NPC_ZUBATTO 41 / V04P02A # Zorua NPC_DIGUDA 50 / V03P13A ] ssa_before.layer_list[0].events = [ SsaEvent(6, 2, 1, 0, SsaPosition(scriptdata, 27, 0, 0, 0, None), 65535), SsaEvent(6, 2, 2, 0, SsaPosition(scriptdata, 27, 49, 0, 0, None), 65535), ] # Exit Guild ssa_before.layer_list[1].actors = [ SsaActor( scriptdata, 0, SsaPosition(scriptdata, 29, 7, 2, 0, scriptdata.directions__by_name['Down'].id), -1, -1), SsaActor( scriptdata, 10, SsaPosition(scriptdata, 29, 4, 2, 0, scriptdata.directions__by_name['Down'].id), -1, -1) ] # Exit Town ssa_before.layer_list[2].actors = [ SsaActor( scriptdata, 0, SsaPosition(scriptdata, 29, 44, 2, 0, scriptdata.directions__by_name['Up'].id), -1, -1), SsaActor( scriptdata, 10, SsaPosition(scriptdata, 29, 47, 2, 0, scriptdata.directions__by_name['Up'].id), -1, -1) ] # Create scripts, if don't exist tpl_ssb = rom.getFileByName('SCRIPT/G01P01A/enter01.ssb') try: rom.getFileByName('SCRIPT/G01P01A/enter05.ssb') except ValueError: create_file_in_rom(rom, 'SCRIPT/G01P01A/enter05.ssb', tpl_ssb) try: rom.getFileByName('SCRIPT/G01P01A/enter06.ssb') except ValueError: create_file_in_rom(rom, 'SCRIPT/G01P01A/enter06.ssb', tpl_ssb) try: rom.getFileByName('SCRIPT/G01P01A/enter07.ssb') except ValueError: create_file_in_rom(rom, 'SCRIPT/G01P01A/enter07.ssb', tpl_ssb) try: rom.getFileByName('SCRIPT/G01P01A/enter08.ssb') except ValueError: create_file_in_rom(rom, 'SCRIPT/G01P01A/enter08.ssb', tpl_ssb) try: rom.getFileByName('SCRIPT/G01P01A/enter09.ssb') except ValueError: create_file_in_rom(rom, 'SCRIPT/G01P01A/enter09.ssb', tpl_ssb) try: rom.getFileByName('SCRIPT/G01P01A/enter10.ssb') except ValueError: create_file_in_rom(rom, 'SCRIPT/G01P01A/enter10.ssb', tpl_ssb) bin_after = SsaHandler.serialize(ssa_before) rom.setFileByName('SCRIPT/G01P01A/enter.sse', bin_after) rom.saveToFile(os.path.join(base_dir, 'skyworkcopy_edit.nds')) draw_maps_main()