예제 #1
0
    def apply(self, apply: Callable[[], None], rom: NintendoDSRom, config: Pmd2Data) -> None:
        START_ACCURACY = self.get_parameter("StartGraphicPos")
        START_POWER = START_ACCURACY + 12
        MAX_POWER = f"[M:B{START_POWER}]" + (f"[M:B{START_POWER + 10}]") * 9 + f"[M:B{START_POWER + 9}]"
        MAX_ACCU = f"[M:B{START_POWER}]" + (f"[M:B{START_ACCURACY + 11}]") * 10 + f"[M:B{START_POWER}]"
        DESC_CHANGES = {8: MAX_POWER,
                        60: MAX_POWER,
                        75: MAX_POWER,
                        100: MAX_POWER,
                        153: MAX_POWER,
                        156: MAX_POWER,
                        205: MAX_POWER,
                        206: MAX_POWER,
                        348: MAX_POWER,
                        477: MAX_POWER,
                        61: MAX_ACCU,
                        340: MAX_ACCU,
                        535: MAX_ACCU}
        bin_before = rom.getFileByName("FONT/markfont.dat")
        model = GraphicFontHandler.deserialize(bin_before)
        entries = []
        for x in range(model.get_nb_entries()):
            entries.append(model.get_entry(x))
        while len(entries) < max(START_ACCURACY + 12, START_POWER + 11):
            entries.append(None)

        for x in range(START_ACCURACY, START_ACCURACY + 12):
            img = Image.open(os.path.join(SRC_DIR, "accu_%02d.png" % (x - START_ACCURACY)), 'r')
            entries[x] = img
        for x in range(START_POWER, START_POWER + 11):
            img = Image.open(os.path.join(SRC_DIR, "pow_%02d.png" % (x - START_POWER)), 'r')
            entries[x] = img
        model.set_entries(entries)
        bin_after = GraphicFontHandler.serialize(model)
        rom.setFileByName("FONT/markfont.dat", bin_after)

        # Change some move descriptions
        for filename in get_files_from_rom_with_extension(rom, 'str'):
            bin_before = rom.getFileByName(filename)
            strings = StrHandler.deserialize(bin_before)
            block = config.string_index_data.string_blocks['Move Descriptions']
            for k, v in DESC_CHANGES.items():
                strings.strings[block.begin + k] = strings.strings[block.begin + k].replace(OLD_STAT, v)
            bin_after = StrHandler.serialize(strings)
            rom.setFileByName(filename, bin_after)

        try:
            apply()
        except RuntimeError as ex:
            raise ex
예제 #2
0
def draw_object(img: Image.Image, draw, obj: SsaObject, rom: NintendoDSRom):
    """Draws the sprite for an object"""
    if obj.object.name == 'NULL':
        if draw_invisible_actors_objects:
            # Draw invisible object hitboxes
            w = obj.hitbox_w * BPC_TILE_DIM
            h = obj.hitbox_h * BPC_TILE_DIM
            tlx = obj.pos.x_absolute - int(w / 2)
            tly = obj.pos.y_absolute - int(h / 2)
            draw.rectangle((
                tlx, tly, tlx + w, tly + h,
            ), COLOR_OBJECTS, (0, 0, 0))
        return

    try:
        sprite = FileType.WAN.deserialize(
            rom.getFileByName(f'GROUND/{obj.object.name}.wan')
        )
        ani_group = sprite.get_animations_for_group(sprite.anim_groups[0])
    except (ValueError, TypeError) as e:
        warnings.warn(f"Failed to render a sprite, replaced with placeholder ({obj}): {e}")
        if not draw_invisible_actors_objects:
            return
        return triangle(draw, obj.pos.x_absolute, obj.pos.y_absolute, COLOR_OBJECTS, obj.pos.direction.id)

    frame_id = obj.pos.direction.id - 1 if obj.pos.direction.id > 0 else 0
    if frame_id > len(ani_group) - 1:
        frame_id = 0
    mfg_id = ani_group[frame_id].frames[0].frame_id

    sprite_img, (cx, cy) = sprite.render_frame_group(sprite.frame_groups[mfg_id])
    render_x = obj.pos.x_absolute - cx
    render_y = obj.pos.y_absolute - cy
    img.paste(sprite_img, (render_x, render_y), sprite_img)
예제 #3
0
    def apply(self, apply: Callable[[], None], rom: NintendoDSRom,
              config: Pmd2Data) -> None:
        if config.game_version == GAME_VERSION_EOS:
            if config.game_region == GAME_REGION_US:
                string_id = STRING_ID_US
                overlay_size = OVERLAY13_INITAL_SIZE_US
            if config.game_region == GAME_REGION_EU:
                string_id = STRING_ID_EU
                overlay_size = OVERLAY13_INITAL_SIZE_EU
            if config.game_region == GAME_REGION_JP:
                string_id = STRING_ID_JP
                overlay_size = OVERLAY13_INITAL_SIZE_JP

        # Change dialogue
        for lang in config.string_index_data.languages:
            filename = 'MESSAGE/' + lang.filename
            bin_before = rom.getFileByName(filename)
            strings = StrHandler.deserialize(bin_before)
            strings.strings[string_id - 1] = get_locales().translate(
                MESSAGE, lang.locale.replace('-', '_'))
            bin_after = StrHandler.serialize(strings)
            rom.setFileByName(filename, bin_after)

        table = loadOverlayTable(rom.arm9OverlayTable, lambda x, y: bytes())
        ov = table[13]
        ov.ramSize = overlay_size + OVERLAY13_ADD_SIZE
        rom.arm9OverlayTable = saveOverlayTable(table)
        ov13 = rom.files[ov.fileID]
        rom.files[ov.fileID] = ov13[:overlay_size]
        try:
            apply()
        except RuntimeError as ex:
            raise ex
예제 #4
0
def draw_object(img: Image.Image, draw, obj: SsaObject, rom: NintendoDSRom):
    """Draws the sprite for an object"""

    if obj.object.name == 'NULL':
        return triangle(draw, obj.pos.x_absolute, obj.pos.y_absolute,
                        COLOR_ACTOR, obj.pos.direction.id)

    try:
        sprite = FileType.WAN.deserialize(
            rom.getFileByName(f'GROUND/{obj.object.name}.wan'))
    except ValueError as e:
        warnings.warn(
            f"Failed to render a sprite, replaced with placeholder ({obj}): {e}"
        )
        return triangle(draw, obj.pos.x_absolute, obj.pos.y_absolute,
                        COLOR_ACTOR, obj.pos.direction.id)

    ani_group = sprite.get_animations_for_group(sprite.anim_groups[0])
    frame_id = obj.pos.direction.id - 1 if obj.pos.direction.id > 0 else 0
    if frame_id > len(ani_group) - 1:
        frame_id = 0
    mfg_id = ani_group[frame_id].frames[0].frame_id

    sprite_img, (cx,
                 cy) = sprite.render_frame_group(sprite.frame_groups[mfg_id])
    render_x = obj.pos.x_absolute - cx
    render_y = obj.pos.y_absolute - cy
    img.paste(sprite_img, (render_x, render_y), sprite_img)
예제 #5
0
def draw_map_bgs(rom: NintendoDSRom, map_bg_dir):
    global map_bgs, map_bg_durations
    os.makedirs(map_bg_dir, exist_ok=True)

    bin = rom.getFileByName('MAP_BG/bg_list.dat')
    bg_list = FileType.BG_LIST_DAT.deserialize(bin)

    count = len(bg_list.level)
    for i, l in enumerate(bg_list.level):
        try:
            bma = l.get_bma(rom)
            print(f"{i + 1}/{count} - {l.bpl_name}")

            bpas = l.get_bpas(rom)
            non_none_bpas = [b for b in bpas if b is not None]
            bpc = l.get_bpc(rom)
            bpl = l.get_bpl(rom)

            # Saving animated map!
            bpa_duration = -1
            pal_ani_duration = -1
            if len(non_none_bpas) > 0:
                bpa_duration = round(
                    1000 / 60 *
                    non_none_bpas[0].frame_info[0].duration_per_frame)
            if bpl.has_palette_animation:
                pal_ani_duration = round(1000 / 60 *
                                         max(spec.duration_per_frame
                                             for spec in bpl.animation_specs))
            duration = max(bpa_duration, pal_ani_duration)
            if duration == -1:
                # Default for only one frame, doesn't really matter
                duration = 1000
            frames = bma.to_pil(bpc,
                                bpl,
                                bpas,
                                include_collision=False,
                                include_unknown_data_block=False)
            frames[0].save(os.path.join(map_bg_dir, l.bpl_name + '.gif'),
                           save_all=True,
                           append_images=frames[1:],
                           duration=duration,
                           loop=0,
                           optimize=False)
            frames[0].save(os.path.join(map_bg_dir, l.bpl_name + '.png'))
            map_bgs[l.bpl_name] = frames
            map_bg_durations[l.bpl_name] = duration
        except (NotImplementedError, SystemError) as ex:
            print(f"error for {l.bma_name}: {repr(ex)}", file=sys.stderr)
            print(''.join(
                traceback.format_exception(etype=type(ex),
                                           value=ex,
                                           tb=ex.__traceback__)),
                  file=sys.stderr)
예제 #6
0
    def apply(self, apply: Callable[[], None], rom: NintendoDSRom,
              config: Pmd2Data) -> None:
        if config.game_version == GAME_VERSION_EOS:
            if config.game_region == GAME_REGION_US:
                type_table = TYPE_TABLE_US
                gummi_iq_table = GUMMI_IQ_TABLE_US
                gummi_belly_table = GUMMI_BELLY_TABLE_US
            if config.game_region == GAME_REGION_EU:
                type_table = TYPE_TABLE_EU
                gummi_iq_table = GUMMI_IQ_TABLE_EU
                gummi_belly_table = GUMMI_BELLY_TABLE_EU
            if config.game_region == GAME_REGION_JP:
                type_table = TYPE_TABLE_JP
                gummi_iq_table = GUMMI_IQ_TABLE_JP
                gummi_belly_table = GUMMI_BELLY_TABLE_JP

        bincfg = config.binaries['overlay/overlay_0010.bin']
        data = bytearray(get_binary_from_rom_ppmdu(rom, bincfg))
        data[type_table:type_table + TABLE_LEN] = bytearray(NEW_TYPES)
        set_binary_in_rom_ppmdu(rom, bincfg, bytes(data))

        # Change Fairy's type name
        for filename in get_files_from_rom_with_extension(rom, 'str'):
            bin_before = rom.getFileByName(filename)
            strings = StrHandler.deserialize(bin_before)
            block = config.string_index_data.string_blocks['Type Names']
            strings.strings[block.begin + 18] = TYPE_LIST[filename]
            bin_after = StrHandler.serialize(strings)
            rom.setFileByName(filename, bin_after)

        bincfg = config.binaries['arm9.bin']
        data = bytearray(get_binary_from_rom_ppmdu(rom, bincfg))
        data[gummi_iq_table:gummi_iq_table +
             TABLE_LEN] = bytearray(NEW_IQ_GUMMI)
        data[gummi_belly_table:gummi_belly_table +
             TABLE_LEN] = bytearray(NEW_BELLY_GUMMI)
        set_binary_in_rom_ppmdu(rom, bincfg, bytes(data))

        try:
            apply()
        except RuntimeError as ex:
            raise ex
예제 #7
0
    def apply(self, apply: Callable[[], None], rom: NintendoDSRom, config: Pmd2Data) -> None:
        if config.game_version == GAME_VERSION_EOS:
            if config.game_region == GAME_REGION_US:
                string_id = STRING_ID_US
            if config.game_region == GAME_REGION_EU:
                string_id = STRING_ID_EU
            if config.game_region == GAME_REGION_JP:
                string_id = STRING_ID_JP

        # Change dialogue
        for lang in config.string_index_data.languages:
            filename = 'MESSAGE/' + lang.filename
            bin_before = rom.getFileByName(filename)
            strings = StrHandler.deserialize(bin_before)
            strings.strings[string_id - 1] = get_locales().translate(MESSAGE, lang.locale.replace('-', '_'))
            bin_after = StrHandler.serialize(strings)
            rom.setFileByName(filename, bin_after)
        try:
            apply()
        except RuntimeError as ex:
            raise ex
예제 #8
0
    def apply(self, apply: Callable[[], None], rom: NintendoDSRom,
              config: Pmd2Data) -> None:
        param = self.get_parameters()
        if self.is_applied_ms(rom, config):
            param["MoveShortcuts"] = 1
        else:
            param["MoveShortcuts"] = 0

        if self.is_applied_dv(rom, config):
            param["DisplayVal"] = 1
        else:
            param["DisplayVal"] = 0

        START_EXT = param["StartGraphicPos"]
        START_LVL = START_EXT + 11
        START_SUB = START_LVL + 9
        bin_before = rom.getFileByName("FONT/markfont.dat")
        model = GraphicFontHandler.deserialize(bin_before)
        entries = []
        for x in range(model.get_nb_entries()):
            entries.append(model.get_entry(x))
        while len(entries) < max(START_LVL + 9, START_SUB + 4, START_EXT + 11):
            entries.append(None)
        for x in range(START_EXT, START_EXT + 11):
            img = Image.open(
                os.path.join(SRC_DIR, "ext%d.png" % (x - START_EXT)), 'r')
            entries[x] = img
        for x in range(START_LVL, START_LVL + 9):
            img = Image.open(
                os.path.join(SRC_DIR, "lvl%d.png" % (x - START_LVL)), 'r')
            entries[x] = img
        for x in range(START_SUB, START_SUB + 4):
            img = Image.open(
                os.path.join(SRC_DIR, "sub%d.png" % (x - START_SUB)), 'r')
            entries[x] = img
        model.set_entries(entries)
        bin_after = GraphicFontHandler.serialize(model)
        rom.setFileByName("FONT/markfont.dat", bin_after)

        if MGROW_PATH not in rom.filenames:
            mgrow_data_stat = bytearray(0x96)
            mgrow_data_dama = bytearray(0x96)
            for x in range(25):
                exp_req = MGROW_TABLE[x * 4]
                pwr = MGROW_TABLE[x * 4 + 1]
                pp = MGROW_TABLE[x * 4 + 2]
                acc = MGROW_TABLE[x * 4 + 3]
                write_u16(mgrow_data_dama, u16(exp_req), x * 6)
                write_u16(mgrow_data_dama, u16(pwr), x * 6 + 2)
                write_u8(mgrow_data_dama, u8(pp), x * 6 + 4)
                write_u8(mgrow_data_dama, u8(acc), x * 6 + 5)
            bin_before = rom.getFileByName("BALANCE/waza_p.bin")
            model = WazaPHandler.deserialize(bin_before)
            total = bytearray(0)
            for m in model.moves:
                if m.category != WazaMoveCategory.STATUS and m.max_upgrade_level == 99:
                    total += mgrow_data_dama
                else:
                    total += mgrow_data_stat
            create_file_in_rom(rom, MGROW_PATH, bytes(total))
        try:
            apply()
        except RuntimeError as ex:
            raise ex
예제 #9
0
    def apply(self, apply: Callable[[], None], rom: NintendoDSRom,
              config: Pmd2Data) -> None:
        if config.game_version == GAME_VERSION_EOS:
            if config.game_region == GAME_REGION_US:
                item_effect = ITEM_EFFECT_US
            if config.game_region == GAME_REGION_EU:
                item_effect = ITEM_EFFECT_EU
            if config.game_region == GAME_REGION_JP:
                item_effect = ITEM_EFFECT_JP

        # Apply Gummi item properties
        item_p_bin = rom.getFileByName('BALANCE/item_p.bin')
        item_p_model = ItemPHandler.deserialize(item_p_bin)
        gummi = item_p_model.item_list[GUMMI_ITEM_ID]
        gummi.buy_price = 800
        gummi.sell_price = 50
        gummi.category = 3
        gummi.sprite = 17
        gummi.item_id = GUMMI_ITEM_ID
        gummi.move_id = 0
        gummi.range_min = 0
        gummi.range_max = 0
        gummi.palette = 14
        gummi.action_name = 4
        gummi.is_valid = True
        gummi.is_in_td = False
        gummi.ai_flag_1 = False
        gummi.ai_flag_2 = True
        gummi.ai_flag_3 = False
        rom.setFileByName('BALANCE/item_p.bin',
                          ItemPHandler.serialize(item_p_model))

        # Apply Gummi item dungeon effect
        item_cd_bin = rom.getFileByName('BALANCE/item_cd.bin')
        item_cd_model = DataCDHandler.deserialize(item_cd_bin)
        effect_id = item_cd_model.nb_effects()
        item_cd_model.add_effect_code(item_effect)
        item_cd_model.set_item_effect_id(GUMMI_ITEM_ID, effect_id)
        rom.setFileByName('BALANCE/item_cd.bin',
                          DataCDHandler.serialize(item_cd_model))

        # Change item's text attributes
        for filename in get_files_from_rom_with_extension(rom, 'str'):
            bin_before = rom.getFileByName(filename)
            strings = StrHandler.deserialize(bin_before)
            block = config.string_index_data.string_blocks['Item Names']
            strings.strings[block.begin + GUMMI_ITEM_ID] = NAME_LIST[filename]
            block = config.string_index_data.string_blocks[
                'Item Short Descriptions']
            strings.strings[block.begin + GUMMI_ITEM_ID] = SDES_LIST[filename]
            block = config.string_index_data.string_blocks[
                'Item Long Descriptions']
            strings.strings[block.begin + GUMMI_ITEM_ID] = LDES_LIST[filename]
            bin_after = StrHandler.serialize(strings)
            rom.setFileByName(filename, bin_after)

        bar_bin = rom.getFileByName('BALANCE/itembar.bin')
        bar_model = DataSTHandler.deserialize(bar_bin)
        gummi_id = bar_model.get_item_struct_id(OTHER_GUMMI_ID)
        bar_model.set_item_struct_id(GUMMI_ITEM_ID, gummi_id)
        rom.setFileByName('BALANCE/itembar.bin',
                          DataSTHandler.serialize(bar_model))

        try:
            apply()
        except RuntimeError as ex:
            raise ex
예제 #10
0
    def apply(self, apply: Callable[[], None], rom: NintendoDSRom,
              config: Pmd2Data) -> None:
        if not self.is_applied(rom, config):
            if config.game_version == GAME_VERSION_EOS:
                if config.game_region == GAME_REGION_US:
                    evo_hp_bonus = EVO_HP_BONUS_US
                    evo_ph_bonus = EVO_PHYS_BONUS_US
                    evo_sp_bonus = EVO_SPEC_BONUS_US
                if config.game_region == GAME_REGION_EU:
                    evo_hp_bonus = EVO_HP_BONUS_EU
                    evo_ph_bonus = EVO_PHYS_BONUS_EU
                    evo_sp_bonus = EVO_SPEC_BONUS_EU
                if config.game_region == GAME_REGION_JP:
                    evo_hp_bonus = EVO_HP_BONUS_JP
                    evo_ph_bonus = EVO_PHYS_BONUS_JP
                    evo_sp_bonus = EVO_SPEC_BONUS_JP
            # Create Evo Table
            md_bin = rom.getFileByName('BALANCE/monster.md')
            md_model = MdHandler.deserialize(md_bin)

            mevo_data = bytearray(len(md_model) * MEVO_ENTRY_LENGTH + 4)
            write_u32(mevo_data, u32_checked(len(mevo_data)), 0)
            for i in range(len(md_model)):
                if i in SPECIAL_EVOS:
                    next_stage = SPECIAL_EVOS[i]
                else:
                    next_stage = []
                    for j, x in enumerate(md_model):
                        if (i < 600 and 1 <= j < 555) or (i >= 600
                                                          and 601 <= j < 1155):
                            if x.pre_evo_index == i and x.evo_method != EvolutionMethod.NONE and (
                                    x.evo_param2 != AdditionalRequirement.MALE
                                    or i < 600
                            ) and (x.evo_param2 != AdditionalRequirement.FEMALE
                                   or i >= 600):
                                next_stage.append(j)
                write_u16(mevo_data, u16_checked(len(next_stage)),
                          i * MEVO_ENTRY_LENGTH + 4)
                for j, x in enumerate(next_stage):
                    write_u16(mevo_data, x, i * MEVO_ENTRY_LENGTH + j * 2 + 6)
                if i in SPECIAL_EGGS:
                    next_stage = SPECIAL_EGGS[i]
                else:
                    next_stage = []
                    pre_evo = i
                    tries = 0
                    while md_model[pre_evo].pre_evo_index != 0:
                        current = md_model[pre_evo]
                        pre_evo = current.pre_evo_index
                        if current.evo_param2 == AdditionalRequirement.MALE and md_model[
                                pre_evo %
                                600].gender == Gender.MALE and md_model[
                                    pre_evo % 600 +
                                    600].gender == Gender.FEMALE:
                            pre_evo = pre_evo % 600
                        elif current.evo_param2 == AdditionalRequirement.FEMALE and md_model[
                                pre_evo %
                                600].gender == Gender.MALE and md_model[
                                    pre_evo % 600 +
                                    600].gender == Gender.FEMALE:
                            pre_evo = pre_evo % 600 + 600
                        tries += 1
                        if tries >= MAX_TRY:
                            raise Exception(
                                _("Infinite recursion detected in pre evolutions for md entry {i}. "
                                  ))
                    next_stage.append(pre_evo)
                if next_stage != [i]:
                    write_u16(mevo_data, u16_checked(len(next_stage)),
                              i * MEVO_ENTRY_LENGTH + 0x16)
                    for j, x in enumerate(next_stage):
                        write_u16(mevo_data, x,
                                  i * MEVO_ENTRY_LENGTH + j * 2 + 0x18)

            hp_bonus = read_u32(rom.arm9, evo_hp_bonus)
            atk_bonus = read_u16(rom.arm9, evo_ph_bonus)
            def_bonus = read_u16(rom.arm9, evo_ph_bonus + 2)
            spatk_bonus = read_u16(rom.arm9, evo_sp_bonus)
            spdef_bonus = read_u16(rom.arm9, evo_sp_bonus + 2)
            evo_add_stats = bytearray(MEVO_STATS_LENGTH)
            write_u16(evo_add_stats, u16_checked(hp_bonus), 0)
            write_u16(evo_add_stats, atk_bonus, 2)
            write_u16(evo_add_stats, spatk_bonus, 4)
            write_u16(evo_add_stats, def_bonus, 6)
            write_u16(evo_add_stats, spdef_bonus, 8)
            mevo_data += evo_add_stats * len(md_model)
            if MEVO_PATH not in rom.filenames:
                create_file_in_rom(rom, MEVO_PATH, mevo_data)
            else:
                rom.setFileByName(MEVO_PATH, mevo_data)
        try:
            apply()
        except RuntimeError as ex:
            raise ex
예제 #11
0
    def apply(self, apply: Callable[[], None], rom: NintendoDSRom,
              config: Pmd2Data) -> None:
        if config.game_version == GAME_VERSION_EOS:
            if config.game_region == GAME_REGION_US:
                new_pkmn_str_region = US_NEW_PKMN_STR_REGION
                new_cat_str_region = US_NEW_CAT_STR_REGION
                file_assoc = US_FILE_ASSOC
                table_sf = US_TABLE_SF
                table_mf = US_TABLE_MF
                table_sp = US_TABLE_SP
            if config.game_region == GAME_REGION_EU:
                new_pkmn_str_region = EU_NEW_PKMN_STR_REGION
                new_cat_str_region = EU_NEW_CAT_STR_REGION
                file_assoc = EU_FILE_ASSOC
                table_sf = EU_TABLE_SF
                table_mf = EU_TABLE_MF
                table_sp = EU_TABLE_SP
        if not self.is_applied(rom, config):
            bincfg = config.binaries['arm9.bin']
            binary = bytearray(get_binary_from_rom_ppmdu(rom, bincfg))

            # Apply the patch
            for filename in get_files_from_rom_with_extension(rom, 'str'):
                bin_before = rom.getFileByName(filename)
                strings = StrHandler.deserialize(bin_before)
                block = config.string_index_data.string_blocks['Pokemon Names']
                monsters = strings.strings[block.begin:block.end]
                strings.strings[block.begin:block.end] = [""] * (block.end -
                                                                 block.begin)
                block = config.string_index_data.string_blocks[
                    'Pokemon Categories']
                cats = strings.strings[block.begin:block.end]
                strings.strings[block.begin:block.end] = [""] * (block.end -
                                                                 block.begin)
                for x in range(NUM_NEW_ENTRIES):
                    if x < NUM_PREVIOUS_ENTRIES * 2:
                        str_pkmn = monsters[x % NUM_PREVIOUS_ENTRIES]
                    else:
                        str_pkmn = "DmyPk%04d" % x
                    if len(strings.strings) <= new_pkmn_str_region + x - 1:
                        strings.strings.append(str_pkmn)
                    else:
                        strings.strings[new_pkmn_str_region + x - 1] = str_pkmn
                for x in range(NUM_NEW_ENTRIES):
                    if x < NUM_PREVIOUS_ENTRIES * 2:
                        str_cat = cats[x % NUM_PREVIOUS_ENTRIES]
                    else:
                        str_cat = "DmyCa%04d" % x
                    if len(strings.strings) <= new_cat_str_region + x - 1:
                        strings.strings.append(str_cat)
                    else:
                        strings.strings[new_cat_str_region + x - 1] = str_cat
                bin_after = StrHandler.serialize(strings)
                rom.setFileByName(filename, bin_after)

                sorted_list = list(
                    enumerate(strings.strings[new_pkmn_str_region -
                                              1:new_pkmn_str_region - 1 +
                                              NUM_NEW_ENTRIES]))
                sorted_list.sort(key=lambda x: normalize_string(x[1]))
                sorted_list = [x[0] for x in sorted_list]
                inv_sorted_list = [
                    sorted_list.index(i) for i in range(NUM_NEW_ENTRIES)
                ]
                m2n_model = ValListHandler.deserialize(
                    rom.getFileByName(file_assoc[filename][0]))
                m2n_model.set_list(inv_sorted_list)
                rom.setFileByName(file_assoc[filename][0],
                                  ValListHandler.serialize(m2n_model))
                n2m_model = ValListHandler.deserialize(
                    rom.getFileByName(file_assoc[filename][1]))
                n2m_model.set_list(sorted_list)
                rom.setFileByName(file_assoc[filename][1],
                                  ValListHandler.serialize(n2m_model))

            # Expand kao file
            kao_bin = rom.getFileByName('FONT/kaomado.kao')
            kao_model = KaoHandler.deserialize(kao_bin)
            kao_model.expand(NUM_NEW_ENTRIES - 1)
            for i in range(NUM_PREVIOUS_ENTRIES - 1):
                for j in range(SUBENTRIES):
                    a = kao_model.get(i, j)
                    b = kao_model.get(i + NUM_PREVIOUS_ENTRIES, j)
                    if b == None and a != None:
                        kao_model.set(i + NUM_PREVIOUS_ENTRIES, j, a)
            rom.setFileByName('FONT/kaomado.kao',
                              KaoHandler.serialize(kao_model))

            # Expand tbl_talk
            tlk_bin = rom.getFileByName('MESSAGE/tbl_talk.tlk')
            tlk_model = TblTalkHandler.deserialize(tlk_bin)
            while tlk_model.get_nb_monsters() < NUM_NEW_ENTRIES:
                tlk_model.add_monster_personality(DUMMY_PERSONALITY)
            rom.setFileByName('MESSAGE/tbl_talk.tlk',
                              TblTalkHandler.serialize(tlk_model))

            # Add monsters
            md_bin = rom.getFileByName('BALANCE/monster.md')
            md_model = MdHandler.deserialize(md_bin)
            while len(md_model.entries) < NUM_NEW_ENTRIES:
                md_model.entries.append(
                    MdEntry.new_empty(u16_checked(len(md_model.entries))))
            for i in range(NUM_PREVIOUS_ENTRIES):
                md_model.entries[i].entid = i
                if md_model.entries[NUM_PREVIOUS_ENTRIES +
                                    i].gender == Gender.INVALID:
                    md_model.entries[NUM_PREVIOUS_ENTRIES +
                                     i].entid = NUM_PREVIOUS_ENTRIES + i
                else:
                    md_model.entries[NUM_PREVIOUS_ENTRIES + i].entid = i
            block = bincfg.symbols['MonsterSpriteData']
            data = binary[block.begin:block.end] + binary[block.begin:block.
                                                          end]
            data += b'\x00\x00' * (NUM_NEW_ENTRIES - (len(data) // 2))
            for i in range(0, len(data), 2):
                md_model.entries[i // 2].unk17 = data[i]
                md_model.entries[i // 2].unk18 = data[i + 1]
                md_model.entries[i // 2].bitfield1_0 = False
                md_model.entries[i // 2].bitfield1_1 = False
                md_model.entries[i // 2].bitfield1_2 = False
                md_model.entries[i // 2].bitfield1_3 = False

            x = table_sf
            while read_u16(rom.arm9, x) != 0:
                pkmn_id = read_u16(rom.arm9, x)
                md_model.entries[pkmn_id].bitfield1_3 = True  # pylint: disable=invalid-sequence-index
                if md_model.entries[NUM_PREVIOUS_ENTRIES +
                                    pkmn_id].gender != Gender.INVALID:
                    md_model.entries[NUM_PREVIOUS_ENTRIES +
                                     pkmn_id].bitfield1_3 = True
                x += 2
            x = table_mf
            while read_u16(rom.arm9, x) != 0:
                pkmn_id = read_u16(rom.arm9, x)
                md_model.entries[pkmn_id].bitfield1_2 = True  # pylint: disable=invalid-sequence-index
                if md_model.entries[NUM_PREVIOUS_ENTRIES +
                                    pkmn_id].gender != Gender.INVALID:
                    md_model.entries[NUM_PREVIOUS_ENTRIES +
                                     pkmn_id].bitfield1_2 = True
                x += 2
            ov19 = rom.loadArm9Overlays([19])[19].data
            for x in range(table_sp, table_sp + TABLE_SP_SIZE, 2):
                pkmn_id = read_u16(ov19, x)
                md_model.entries[pkmn_id].bitfield1_1 = True  # pylint: disable=invalid-sequence-index
                md_model.entries[pkmn_id].bitfield1_0 = True  # pylint: disable=invalid-sequence-index
                if md_model.entries[NUM_PREVIOUS_ENTRIES +
                                    pkmn_id].gender != Gender.INVALID:
                    md_model.entries[NUM_PREVIOUS_ENTRIES +
                                     pkmn_id].bitfield1_1 = True
                    md_model.entries[NUM_PREVIOUS_ENTRIES +
                                     pkmn_id].bitfield1_0 = True

            rom.setFileByName('BALANCE/monster.md',
                              MdHandler.serialize(md_model))

            # Edit Mappa bin
            mappa_bin = rom.getFileByName('BALANCE/mappa_s.bin')
            mappa_model = MappaBinHandler.deserialize(mappa_bin)
            dl = HardcodedDungeons.get_dungeon_list(bytes(rom.arm9), config)
            # Handle Dojos
            start_floor = 0
            for x in range(DOJO_DUNGEONS_FIRST, DOJO_DUNGEONS_LAST - 2):
                dl.append(
                    DungeonDefinition(u8(5), DOJO_MAPPA_ENTRY, u8(start_floor),
                                      u8(0)))
                start_floor += 5
            dl.append(
                DungeonDefinition(u8(1), DOJO_MAPPA_ENTRY, u8(start_floor),
                                  u8(0)))
            start_floor += 1
            dl.append(
                DungeonDefinition(u8(0x30), DOJO_MAPPA_ENTRY, u8(start_floor),
                                  u8(0)))
            start_floor += 0x30
            for dungeon in dl:
                for f in range(dungeon.start_after + 1,
                               dungeon.start_after + dungeon.number_floors, 2):
                    try:
                        for entry in mappa_model.floor_lists[
                                dungeon.mappa_index][f].monsters:
                            if entry.md_index != DUMMY_PKMN and entry.md_index < NUM_PREVIOUS_ENTRIES and \
                                    entry.md_index + NUM_PREVIOUS_ENTRIES < len(md_model.entries) and \
                                    md_model.entries[entry.md_index + NUM_PREVIOUS_ENTRIES].gender != Gender.INVALID:
                                entry.md_index += NUM_PREVIOUS_ENTRIES
                    except:
                        print(f"{dungeon.mappa_index}, {f} is not valid.")
            rom.setFileByName('BALANCE/mappa_s.bin',
                              MappaBinHandler.serialize(mappa_model))

            # Add moves
            waza_p_bin = rom.getFileByName('BALANCE/waza_p.bin')
            waza_p_model = WazaPHandler.deserialize(waza_p_bin)
            while len(waza_p_model.learnsets) < NUM_PREVIOUS_ENTRIES:
                waza_p_model.learnsets.append(
                    waza_p_model.learnsets[DUMMY_LS])  # Max Moveset
            waza_p_model.learnsets = waza_p_model.learnsets + waza_p_model.learnsets
            while len(waza_p_model.learnsets) < NUM_NEW_ENTRIES:
                waza_p_model.learnsets.append(
                    waza_p_model.learnsets[DUMMY_LS])  # Max Moveset
            rom.setFileByName('BALANCE/waza_p.bin',
                              WazaPHandler.serialize(waza_p_model))

            # Add moves 2
            waza_p_bin = rom.getFileByName('BALANCE/waza_p2.bin')
            waza_p_model = WazaPHandler.deserialize(waza_p_bin)
            while len(waza_p_model.learnsets) < NUM_PREVIOUS_ENTRIES:
                waza_p_model.learnsets.append(
                    waza_p_model.learnsets[DUMMY_LS])  # Max Moveset
            waza_p_model.learnsets = waza_p_model.learnsets + waza_p_model.learnsets
            while len(waza_p_model.learnsets) < NUM_NEW_ENTRIES:
                waza_p_model.learnsets.append(
                    waza_p_model.learnsets[DUMMY_LS])  # Max Moveset
            rom.setFileByName('BALANCE/waza_p2.bin',
                              WazaPHandler.serialize(waza_p_model))

            # Add levels
            level_bin = rom.getFileByName('BALANCE/m_level.bin')
            level_model = BinPackHandler.deserialize(level_bin)
            while len(level_model.get_files_bytes()) < NUM_PREVIOUS_ENTRIES:
                new_bytes_unpacked = bytes(LEVEL_BIN_ENTRY_LEVEL_LEN * 100)
                new_bytes_pkdpx = PkdpxHandler.serialize(
                    PkdpxHandler.compress(new_bytes_unpacked))
                new_bytes = Sir0Handler.serialize(
                    Sir0Handler.wrap(new_bytes_pkdpx, []))
                level_model.append(new_bytes)  # Empty Levelup data
            for i in range(NUM_PREVIOUS_ENTRIES):
                level_model.append(level_model[i])
            while len(level_model.get_files_bytes()) < NUM_NEW_ENTRIES:
                new_bytes_unpacked = bytes(LEVEL_BIN_ENTRY_LEVEL_LEN * 100)
                new_bytes_pkdpx = PkdpxHandler.serialize(
                    PkdpxHandler.compress(new_bytes_unpacked))
                new_bytes = Sir0Handler.serialize(
                    Sir0Handler.wrap(new_bytes_pkdpx, []))
                level_model.append(new_bytes)  # Empty Levelup data
            rom.setFileByName('BALANCE/m_level.bin',
                              BinPackHandler.serialize(level_model))

            # Add evolutions
            evo_bin = rom.getFileByName('BALANCE/md_evo.bin')
            evo_model = MdEvoHandler.deserialize(evo_bin)
            while len(evo_model.evo_entries) < NUM_NEW_ENTRIES:
                evo_model.evo_entries.append(
                    MdEvoEntry(bytearray(MEVO_ENTRY_LENGTH)))
            while len(evo_model.evo_stats) < NUM_NEW_ENTRIES:
                evo_model.evo_stats.append(
                    MdEvoStats(bytearray(MEVO_STATS_LENGTH)))
            rom.setFileByName('BALANCE/md_evo.bin',
                              MdEvoHandler.serialize(evo_model))

            # Fixed floors
            ov29 = config.binaries['overlay/overlay_0029.bin']
            ov29bin = bytearray(get_binary_from_rom_ppmdu(rom, ov29))
            monster_list = HardcodedFixedFloorTables.get_monster_spawn_list(
                ov29bin, config)
            for m in monster_list:
                if m.md_idx >= NUM_PREVIOUS_MD_MAX:
                    m.md_idx += NUM_NEW_ENTRIES - NUM_PREVIOUS_MD_MAX
            HardcodedFixedFloorTables.set_monster_spawn_list(
                ov29bin, monster_list, config)
            set_binary_in_rom_ppmdu(rom, ov29, bytes(ov29bin))
        try:
            apply()
        except RuntimeError as ex:
            raise ex