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()
Exemple #3
0
 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
Exemple #6
0
    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
Exemple #7
0
    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