def get_ppmdu_config_for_rom(rom: NintendoDSRom) -> 'Pmd2Data': """ Returns the Pmd2Data for the given ROM. If the ROM is not a valid and supported PMD EoS ROM, raises ValueError. The configuration is loaded from the pmd2data.xml using the XML logic described in the README.rst of the ``skytemple_files.common.ppmdu_config`` package. Additionally supported data from the ROM is loaded and replaces the data loaded from the XML, if possible. See the README.rst for the package ``skytemple_files.common.ppmdu_config.rom_data`` for more information. """ from skytemple_files.common.ppmdu_config.xml_reader import Pmd2XmlReader data_general = Pmd2XmlReader.load_default() game_code = rom.idCode.decode('ascii') arm9off14 = read_uintle(rom.arm9[0xE:0x10], 0, 2) matched_edition = None for edition_name, edition in data_general.game_editions.items(): if edition.issupported and edition.gamecode == game_code and edition.arm9off14 == arm9off14: matched_edition = edition_name break if not matched_edition: raise ValueError("This ROM is not supported by SkyTemple.") # TODO: This is a bit silly. There should be a better check than to parse the XML twice. config = Pmd2XmlReader.load_default(matched_edition) # Patch the config with real data from the ROM RomDataLoader(rom).load_into(config) return config
def deserialize(cls, data: bytes, scriptdata=None, **kwargs: OptionalKwargs) -> Ssa: if scriptdata is None: scriptdata = Pmd2XmlReader.load_default().script_data return Ssa(scriptdata, data)
def serialize(cls, data: Ssb, static_data: Pmd2Data = None, **kwargs: OptionalKwargs) -> bytes: # type: ignore if static_data is None: static_data = Pmd2XmlReader.load_default() return SsbWriter(data, static_data).write()
async def main(executor): output_dir = os.path.join(os.path.dirname(__file__), 'dbg_output') base_dir = os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..') os.makedirs(output_dir, exist_ok=True) rom = NintendoDSRom.fromFile( os.path.join(base_dir, 'skyworkcopy_us_unpatched.nds')) script_info = load_script_files(get_rom_folder(rom, SCRIPT_DIR)) # total, opening. decompiling, parsing, compiling, serializing times: List[Tuple[float, float, float, float, float, float]] = [] static_data = Pmd2XmlReader.load_default(for_version='EoS_NA') awaitables = [] for i, file_name in enumerate(get_files_from_rom_with_extension( rom, 'ssb')): # TODO: Those scripts fail for JP. if file_name in [ 'SCRIPT/D42P21A/enter23.ssb', 'SCRIPT/D73P11A/us0303.ssb', 'SCRIPT/D73P11A/us0305.ssb', 'SCRIPT/D73P11A/us2003.ssb', 'SCRIPT/D73P11A/us2005.ssb', 'SCRIPT/D73P11A/us2103.ssb', 'SCRIPT/D73P11A/us2105.ssb', 'SCRIPT/D73P11A/us2203.ssb', 'SCRIPT/D73P11A/us2205.ssb', 'SCRIPT/D73P11A/us2303.ssb', 'SCRIPT/D73P11A/us2305.ssb' ]: continue # Run multiple in parallel with asyncio executors. awaitables.append( loop.run_in_executor(executor, process_single, file_name, times, static_data, output_dir, rom)) pending = awaitables while len(pending) > 0: done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED) # to raise exceptions of tasks back to main loop: for fut in done: try: fut.result() except Exception: loop.stop() with poison_lock: poison_container[0] = True raise times_structured = list(zip(*times)) print_table_row("", "TOTAL", "OPENING", "DECOMPILING", "PARSING", "COMPILING", "SERIALIZING") print_table_row(*(["==========="] * 7)) print_table_row("TOTAL:", *[round(sum(t), 2) for t in times_structured]) print_table_row("AVG:", *[round(sum(t) / len(t), 2) for t in times_structured]) print_table_row("MAX:", *[round(max(t), 2) for t in times_structured]) print_table_row("MIN:", *[round(min(t), 2) for t in times_structured])
def deserialize(cls, data: bytes, *, static_data: Pmd2Data = None, **kwargs) -> 'MappaBin': if static_data is None: static_data = Pmd2XmlReader.load_default() from skytemple_files.common.types.file_types import FileType return FileType.SIR0.unwrap_obj(FileType.SIR0.deserialize(data), MappaBin, static_data)
def create(cls, static_data: Pmd2Data = None) -> Ssb: """Create a new empty script""" if static_data is None: static_data = Pmd2XmlReader.load_default() if static_data.game_region == GAME_REGION_US: header_cls = SsbHeaderUs elif static_data.game_region == GAME_REGION_EU: header_cls = SsbHeaderEu else: raise ValueError( f"Unsupported game edition: {static_data.game_edition}") return Ssb.create_empty(static_data.script_data, header_cls.supported_langs())
def deserialize(cls, data: bytes, static_data: Pmd2Data = None, **kwargs) -> Ssb: if static_data is None: static_data = Pmd2XmlReader.load_default() if static_data.game_region == GAME_REGION_EU: ssb_header = SsbHeaderEu(data) elif static_data.game_region == GAME_REGION_US: ssb_header = SsbHeaderUs(data) elif static_data.game_region == GAME_REGION_JP: ssb_header = SsbHeaderJp(data) else: raise ValueError( f"Unsupported game edition: {static_data.game_edition}") return Ssb(data, ssb_header, ssb_header.data_offset, static_data.script_data, string_codec=static_data.string_encoding)
def __init__(self, builder: Gtk.Builder, window: Window): self.builder = builder Global.main_builder = builder self.window: Window = window accel = Gtk.AccelGroup() accel.connect(Gdk.KEY_space, Gdk.ModifierType.CONTROL_MASK, 0, self.on_show_debug) self.window.add_accel_group(accel) self.builder.get_object('progress').add_accel_group(accel) self.static_config = Pmd2XmlReader.load_default('EoS_EU') # version doesn't really matter for this self.chosen_file = None # Load default configuration self.ui_applier = ConfigUIApplier(self.builder, self.static_config.dungeon_data.dungeons) self.ui_reader = ConfigUIReader(self.builder) self.ui_applier.apply(ConfigFileLoader.load(os.path.join(data_dir(), 'default.json'))) ConfigDocApplier(self.window, self.builder).apply() self.builder.connect_signals(self)
def main(): output_dir = os.path.join(os.path.dirname(__file__), 'dbg_output') base_dir = os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..') os.makedirs(output_dir, exist_ok=True) rom = NintendoDSRom.fromFile(os.path.join(base_dir, 'sky_jp.nds')) with open(os.path.join(output_dir, "enter23.ssb"), 'wb') as f: f.write(rom.getFileByName('SCRIPT/D42P21A/enter23.ssb')) for file_name in get_files_from_rom_with_extension(rom, 'ssb'): print(file_name) out_file_name = os.path.join(output_dir, file_name.replace('/', '_') + '.txt') bin_before = rom.getFileByName(file_name) ssb = SsbHandler.deserialize(bin_before, Pmd2XmlReader.load_default('EoS_JP')) with open(out_file_name, 'w') as f: f.write(export_ssb_as_txt(ssb))
'..') rom = NintendoDSRom.fromFile( os.path.join(base_dir, 'skyworkcopy_us_unpatched.nds')) patcher = Patcher(rom, get_ppmdu_config_for_rom(rom)) assert not patcher.is_applied('ActorAndLevelLoader') patcher.apply('ActorAndLevelLoader') bin_before = rom.getFileByName('BALANCE/actor_list.bin') # noinspection PyTypeChecker # - Bug in PyCharm with bound TypeVars actor_list_before: ActorListBin = Sir0Handler.unwrap_obj( Sir0Handler.deserialize(bin_before), ActorListBin) # This only works with unmodified ROMs! assert actor_list_before.list == Pmd2XmlReader.load_default( 'EoS_NA').script_data.level_entities assert len( Sir0Handler.deserialize(bin_before).content_pointer_offsets) == len( Sir0Handler.wrap_obj(actor_list_before).content_pointer_offsets) bin_after = Sir0Handler.serialize(Sir0Handler.wrap_obj(actor_list_before)) # noinspection PyTypeChecker actor_list_after: ActorListBin = Sir0Handler.unwrap_obj( Sir0Handler.deserialize(bin_after), ActorListBin) with open('/tmp/before.bin', 'wb') as f: f.write(bin_before) with open('/tmp/after.bin', 'wb') as f: f.write(bin_after)
#print(f"hook__script_entry_point_determine: {address}") pass def hook_print_lr(emu, address, size): print(f"CALL {address}: LR 0x{emu.memory.register_arm9.lr:0x}") def hook__get_script_id_name(emu: 'DeSmuME', address, size): id = emu.memory.register_arm9.r0 print( f"GetScriptIDName({id}) -> {emu.memory.read_string(emu.memory.unsigned.read_long(table_script_files + (id * 12)))}" ) static_data = Pmd2XmlReader.load_default() # Below tests only works with EU PMD EoS: start_of_arm_ov11_eu = 0x22DCB80 start_of_arm_ov11_us = 0x22DD8E0 start_of_arm_ov11 = start_of_arm_ov11_eu # Fun_022DD164 [US] (Fun_22DDAA4 [EU]) start_of_loop_fn = start_of_arm_ov11 + 0xF24 assert static_data.binaries['overlay/overlay_0011.bin'].symbols[ 'FuncThatCallsCommandParsing'].begin_absolute == start_of_loop_fn start_of_loop_fn_loop = start_of_loop_fn + 0x2C start_of_switch_last_return_code = start_of_loop_fn + 0x34 start_of_call_to_opcode_parsing = start_of_loop_fn + 0x5C - 4 # For US: 0x200C240
def main(): output_dir = os.path.join(os.path.dirname(__file__), 'dbg_output') base_dir = os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..') os.makedirs(output_dir, exist_ok=True) rom = NintendoDSRom.fromFile(os.path.join(base_dir, 'skyworkcopy.nds')) script_info = load_script_files(get_rom_folder(rom, SCRIPT_DIR)) # total, opening. decompiling, parsing, compiling, serializing times: List[Tuple[float, float, float, float, float, float]] = [] for i, file_name in enumerate(get_files_from_rom_with_extension( rom, 'ssb')): print(file_name) out_file_name = os.path.join(output_dir, file_name.replace('/', '_') + '.ssbs') time_before = time.time() bin_before = rom.getFileByName(file_name) time_opening = time.time() ssb_before = SsbHandler.deserialize(bin_before) ssb_script, source_map_before = ssb_before.to_ssb_script() time_decompiling = time.time() for pos_mark in source_map_before.get_position_marks__direct(): print(pos_mark) with open(out_file_name, 'w') as f: f.write(ssb_script) # Test the compiling and writing, by compiling the model, writing it to binary, and then loading it again, # and checking the generated ssb script. compiler = ScriptCompiler(Pmd2XmlReader.load_default()) time_parsing = 0 def callback_after_parsing(): nonlocal time_parsing time_parsing = time.time() ssb_after, source_map_after = compiler.compile_ssbscript( ssb_script, callback_after_parsing) time_compiling = time.time() bin_after = SsbHandler.serialize(ssb_after) time_serializing = time.time() ssb_after_after = SsbHandler.deserialize(bin_after) ssb_script_after = ssb_after_after.to_ssb_script()[0] with open('/tmp/diff1.ssb', 'w') as f: f.write(ssb_script) with open('/tmp/diff2.ssb', 'w') as f: f.write(ssb_script_after) assert (ssb_script == ssb_script_after) assert (source_map_before == source_map_after) times.append(( time_serializing - time_before, # total time_opening - time_before, # opening. time_decompiling - time_opening, # decompiling, time_parsing - time_decompiling, # parsing, time_compiling - time_parsing, # compiling, time_serializing - time_compiling, # serializing )) times_structured = list(zip(*times)) print_table_row("", "TOTAL", "OPENING", "DECOMPILING", "PARSING", "COMPILING", "SERIALIZING") print_table_row(*(["==========="] * 7)) print_table_row("TOTAL:", *[round(sum(t), 2) for t in times_structured]) print_table_row("AVG:", *[round(sum(t) / len(t), 2) for t in times_structured]) print_table_row("MAX:", *[round(max(t), 2) for t in times_structured]) print_table_row("MIN:", *[round(min(t), 2) for t in times_structured])
def _gdefconf(): from skytemple_files.common.ppmdu_config.xml_reader import Pmd2XmlReader global _defconf if _defconf is None: _defconf = Pmd2XmlReader.load_default().dungeon_data.item_categories return _defconf
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()
# (at your option) any later version. # # SkyTemple is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with SkyTemple. If not, see <https://www.gnu.org/licenses/>. # mypy: ignore-errors from skytemple_files.common.ppmdu_config.xml_reader import Pmd2XmlReader from skytemple_files.common.types.file_types import FileType FAULTY_SSB_BYTES = b'\x01\x00\x03\x00<\x00\x0f\x00\x15\x00\x00\x00;\x00\x01\x00\x05\x00\x01\x00\x00\x00\xa4\x00\x00\x00\x00\x00\x05\x00\x9e\x00\x00\x00\xaa\x00\x01\x00\x02\x00d\x00\x01\x00\x16\x00d\x00\x02\x009\x00\x87\x009\x00\xa4\x00\x00\x00\x00\x00\x05\x00\xae\x00\x03\x00\x96\x00\x80\x00\x03\x00\x04\x00\x02\x00\xea\x00\x00\x00\x1e\x00\xdb\x00\x01\x00\x1e\x00)\x00\x11\x00\x07\x000\x00\x8d\x00\x03\x00\x00\x00\x87\x003\x00\x8d\x00\x13\x00\x00\x00\x9d\x006\x00\x8d\x00\xff\x7f\x00\x00\x82\x00\x88\x00\x00\x00\x08\x00(Do I need to prepare more?)\x00\x00&\x00,\x003\x00(No.)\x00(Yes.)\x00 ...I think we can go.\x00' ssb = FileType.SSB.deserialize(FAULTY_SSB_BYTES, static_data=Pmd2XmlReader.load_default('EoS_NA')) print(str(ssb._header)) print(f"number_of_routines: {len(ssb.routine_info)}") print(f"constants: {ssb.constants}") print(f"strings: {ssb.strings}") print(str(ssb.routine_info)) print("=== RAW ==") lines = [] for i, ops in enumerate(ssb.routine_ops): lines.append(f">>> Routine {i}:") op_cursor = 0 for op in ops: lines.append(f"{op.offset:10x}: ({op.op_code.id:3}) {op.op_code.name:45} - {op.params}") op_cursor += 2 + len(op.params) * 2 print('\n'.join(lines))
def p(string, expl=''): string = string.replace('"', '\\"') if expl != '': expl = '# TRANSLATORS: ' + expl print(f'_("{string}") {expl}') def dump(data: Pmd2Data): for lang in data.string_index_data.languages: p(lang.name) for string_block in data.string_index_data.string_blocks.values(): p(string_block.name) for category in data.dungeon_data.item_categories.values(): p(category.name) for patch in data.asm_patches_constants.patches.values(): for param in patch.parameters.values(): p(param.label, 'Used in the confirguation dialog for a patch') # We don't dump item or dungeon names, since the SkyTemple UI reads them from ROM, always. if __name__ == '__main__': dump( Pmd2XmlReader.load_default(GAME_VERSION_EOS + '_' + GAME_REGION_US, translate_strings=False)) dump( Pmd2XmlReader.load_default(GAME_VERSION_EOS + '_' + GAME_REGION_EU, translate_strings=False))
def get_dungeon_names(self): static_data = Pmd2XmlReader.load_default('EoS_EU') # version doesn't really matter for this return {dungeon.id: dungeon.name for dungeon in static_data.dungeon_data.dungeons}