Example #1
0
 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
Example #2
0
 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
Example #3
0
    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()
Example #4
0
 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()
Example #5
0
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()