Example #1
0
def init_free_airship(rom: Rom) -> Rom:
    # Move the airship's start location to right outside of Coneria Castle.
    airship_start = OutputStream()
    airship_start.put_u32(0x918)
    airship_start.put_u32(0x998)

    return rom.apply_patch(0x65280, airship_start.get_buffer())
Example #2
0
    def write(self, rom: Rom) -> Rom:
        permissions_stream = OutputStream()
        for permission in self._permissions:
            permissions_stream.put_u16(permission)

        rom = rom.apply_patch(0x1A20C0, permissions_stream.get_buffer())
        rom = self._spells.write(rom)
        return self._shops.write(rom)
Example #3
0
def update_xp_requirements(rom: Rom, value) -> Rom:
    level_data = rom.open_bytestream(0x1BE3B4, 396)
    new_table = OutputStream()
    next_value = level_data.get_u32()
    while next_value is not None:
        new_table.put_u32(int(next_value * value))
        next_value = level_data.get_u32()
    rom = rom.apply_patch(0x1BE3B4, new_table.get_buffer())
    return rom
Example #4
0
 def test_overlap_patch(self):
     patch = OutputStream()
     patch.put_u32(0x12345678)
     patch.put_u32(0x12345678)
     with self.assertRaises(RuntimeError):
         patched = self.rom.apply_patches({
             0x0: patch.get_buffer(),
             0x4: patch.get_buffer()
         })
         self.assertNotEqual(patched, patched)
Example #5
0
    def write(self, rom: Rom) -> Rom:
        patches = {}
        for index, map in enumerate(self._maps):
            # TODO: Figure out what breaks the Caravan.
            if index == 0x73:
                continue

            data = OutputStream()
            map.write(data)

            patches[Rom.pointer_to_offset(
                self._map_lut[index])] = data.get_buffer()
        return rom.apply_patches(patches)
Example #6
0
    def write(self, rom: Rom) -> Rom:
        # Since there's a LUT and the data that it points to, create two output streams.
        # This should work because both are continuous.
        data_lut_stream = OutputStream()
        shop_inventory = OutputStream()

        next_shop_addr = self.shop_data_pointers[0].pointer
        start_size = 0
        for index in range(51):

            new_shop_length = self.shop_inventories[index].write(
                shop_inventory)
            sdp = self.shop_data_pointers[index]
            sdp.contents = (
                (sdp.shop_graphic << 4) & 0xf0) | (new_shop_length & 0x0f)
            sdp.pointer = next_shop_addr
            sdp.write(data_lut_stream)

            # Because the size of the data varies by shop, we have to keep track of how
            # large the output buffer was and move the pointer up by the difference.
            next_shop_addr += (shop_inventory.size() - start_size)
            start_size = shop_inventory.size()

        # Make a dictionary for the two parts so we only have to write the new Rom once.
        patches = {
            0x1E070C:
            data_lut_stream.get_buffer(),
            Rom.pointer_to_offset(self.shop_data_pointers[0].pointer):
            shop_inventory.get_buffer()
        }
        return rom.apply_patches(patches)
Example #7
0
    def write(self, stream: OutputStream):
        self.header.write(stream)

        for tile in self.tiles:
            tile.write(stream)
        for npc in self.npcs:
            npc.write(stream)
        for chest in self.chests:
            chest.write(stream)
        for sprite in self.sprites:
            sprite.write(stream)
        for shop in self.shops:
            shop.write(stream)

        # Close the map at the end. :)
        stream.put_u16(0xffff)
Example #8
0
def random_bucketed_treasures(rom: Rom,
                              rng: Random,
                              wealth_level: int = 0) -> Rom:
    """Randomly generates and shuffles treasured based on wealth_level"""
    bucket_data = TreasureBuckets()
    chest_stream = rom.open_bytestream(0x217FB4, 0x400)
    items = [(0, x + 1) for x in list(range(0x45))]
    items = items + [(1, x + 1) for x in list(range(0x3F))]
    items = items + [(2, x + 1) for x in list(range(0x2A))]
    itemCount = len(items)
    chests_to_shuffle = []
    original_list = []
    moneyCount = 0
    itemTotal = 0
    for index in range(256):
        chest = TreasureChest.read(chest_stream)
        original_list.append(chest)
        if isinstance(chest, ItemChest):
            if chest.item_type != 0:
                item_bucket = bucket_data.getBucket(chest.item_type,
                                                    chest.item_id)
                if wealth_level == 1:
                    item_bucket = bucket_data.up_one(item_bucket)
                if wealth_level == -1:
                    item_bucket = bucket_data.down_one(item_bucket)
                new_item = bucket_data.pullFromBucket(item_bucket, rng, 1)
                chest.item_id = new_item[0]
                itemTotal += 1
                chests_to_shuffle.append(chest)
        elif isinstance(chest, MoneyChest):
            chest.qty = rng.randint(1, 0xfff) * rng.randint(1, 6)
            chests_to_shuffle.append(chest)
            moneyCount += 1
        else:
            print("BAD CHEST")
    rng.shuffle(chests_to_shuffle)

    chest_data = OutputStream()
    for chest in original_list:
        if isinstance(chest, MoneyChest) or chest.item_type != 0:
            new_chest = chests_to_shuffle.pop()
            new_chest.write(chest_data)
        else:
            chest.write(chest_data)

    return rom.apply_patch(0x217FB4, chest_data.get_buffer())
Example #9
0
def random_treasures(rom: Rom, rng: Random) -> Rom:
    chest_stream = rom.open_bytestream(0x217FB4, 0x400)
    items = [(0, x + 1) for x in list(range(0x45))]
    items = items + [(1, x + 1) for x in list(range(0x3F))]
    items = items + [(2, x + 1) for x in list(range(0x2A))]
    itemCount = len(items)
    print(itemCount)
    chests_to_shuffle = []
    original_list = []
    moneyCount = 0
    itemTotal = 0
    for index in range(256):
        chest = TreasureChest.read(chest_stream)
        original_list.append(chest)
        if isinstance(chest, ItemChest):
            if chest.item_type != 0:
                new_item = items[rng.randint(0, itemCount - 1)]
                chest.item_type = new_item[0]
                chest.item_id = new_item[1]
                itemTotal += 1
                chests_to_shuffle.append(chest)
        elif isinstance(chest, MoneyChest):
            chest.qty = rng.randint(1, 0xfff) * rng.randint(1, 6)
            chests_to_shuffle.append(chest)
            print(chest.qty)
            moneyCount += 1
        else:
            print("BAD CHEST")
    return
    rng.shuffle(chests_to_shuffle)

    chest_data = OutputStream()
    for chest in original_list:
        if isinstance(chest, MoneyChest) or chest.item_type != 0:
            new_chest = chests_to_shuffle.pop()
            new_chest.write(chest_data)
        else:
            chest.write(chest_data)

    return rom.apply_patch(0x217FB4, chest_data.get_buffer())
Example #10
0
def treasure_shuffle(rom: Rom, rng: Random) -> Rom:
    chest_stream = rom.open_bytestream(0x217FB4, 0x400)

    chests_to_shuffle = []
    original_list = []
    for index in range(256):
        chest = TreasureChest.read(chest_stream)
        original_list.append(chest)
        if isinstance(chest, MoneyChest) or chest.item_type != 0:
            chests_to_shuffle.append(chest)

    rng.shuffle(chests_to_shuffle)

    chest_data = OutputStream()
    for chest in original_list:
        if isinstance(chest, MoneyChest) or chest.item_type != 0:
            new_chest = chests_to_shuffle.pop()
            new_chest.write(chest_data)
        else:
            chest.write(chest_data)

    return rom.apply_patch(0x217FB4, chest_data.get_buffer())
Example #11
0
    def test_patches(self):
        patch = OutputStream()
        patch.put_u32(0x12345678)
        patched = self.rom.apply_patches({
            0x0: patch.get_buffer(),
            0x10: patch.get_buffer()
        })
        confirm = patched.open_bytestream(0x0, 0x4)
        self.assertEqual(confirm.get_u32(), 0x12345678)

        confirm = patched.open_bytestream(0x10, 0x4)
        self.assertEqual(confirm.get_u32(), 0x12345678)
Example #12
0
    def build(self):
        stream = OutputStream()
        written_length = 0
        if len(self._magic) > 0:
            for magic_id in self._magic:
                stream.put_u8(magic_id)
                written_length += 1

        if len(self._armor) > 0:
            stream.put_u8(0xfc)
            for item_id in self._armor:
                stream.put_u8(item_id)
                written_length += 1

        if len(self._weapons) > 0:
            stream.put_u8(0xfd)
            for item_id in self._weapons:
                stream.put_u8(item_id)
                written_length += 1

        if len(self._items) > 0:
            stream.put_u8(0xfe)
            for item_id in self._items:
                stream.put_u8(item_id)
                written_length += 1

        if written_length > 0xf:
            raise RuntimeError(
                f"Error: Too many items in shop: {written_length}")

        return ShopInventory(InputStream(stream.get_buffer()), written_length)
Example #13
0
    def pack(self, rom: Rom) -> Rom:
        text_block = OutputStream()
        text_lut = OutputStream()

        next_addr = self.lut[0]
        text_block_offset = Rom.pointer_to_offset(next_addr)

        for index, data in enumerate(self.strings):
            if data is not None:
                text_lut.put_u32(next_addr)
                text_block.put_bytes(data)
                next_addr += len(data)
            else:
                text_lut.put_u32(self.lut[0])

        patches = {
            self.lut_offset: text_lut.get_buffer(),
            text_block_offset: text_block.get_buffer()
        }
        return rom.apply_patches(patches)
Example #14
0
 def write(self, stream: OutputStream):
     stream.put_u16(self.base_hp)
     stream.put_u16(self.base_mp)
     stream.put_u8(self.starting_spell_level)
     stream.put_u8(self.base_strength)
     stream.put_u8(self.base_agility)
     stream.put_u8(self.base_intellect)
     stream.put_u8(self.base_stamina)
     stream.put_u8(self.base_luck)
     stream.put_u8(self.base_accuracy)
     stream.put_u8(self.base_evade)
     stream.put_u8(self.base_mdef)
     stream.put_u8(self.weapon_id)
     stream.put_u8(self.armor_id)
     stream.put_u8(self.unused)
Example #15
0
    def write(self, stream: OutputStream) -> int:
        written_length = 0
        if len(self.magic) > 0:
            for magic_id in self.magic:
                stream.put_u8(magic_id)
                written_length += 1

        if len(self.armor) > 0:
            stream.put_u8(0xfc)
            for item_id in self.armor:
                stream.put_u8(item_id)
                written_length += 1

        if len(self.weapons) > 0:
            stream.put_u8(0xfd)
            for item_id in self.weapons:
                stream.put_u8(item_id)
                written_length += 1

        if len(self.items) > 0:
            stream.put_u8(0xfe)
            for item_id in self.items:
                stream.put_u8(item_id)
                written_length += 1

        if written_length > 0xf:
            raise RuntimeError(
                f"Error: Too many items in shop: {written_length}")

        return written_length
Example #16
0
 def _save_chests(self):
     # Save the chests (without key items in them).
     chest_data = OutputStream()
     for chest in self.chests:
         chest.write(chest_data)
     self.rom = self.rom.apply_patch(0x217FB4, chest_data.get_buffer())
Example #17
0
def parse(source: str, base_addr: int, debug=False) -> bytearray:
    symbol_table = {}
    icode = []
    current_addr = base_addr

    for line_number, line in enumerate(source.splitlines()):
        tokens = TokenStream(line_number, line)

        token = tokens.next()
        if token is None:
            # Empty or comment only line
            continue

        if isinstance(token, RawCommandToken):
            parameters = []
            token = tokens.expect(GRAMMAR["$$value$$"])
            while token is not None:
                if isinstance(token, SymbolToken):
                    if token in symbol_table:
                        parameters.append(symbol_table[token])
                    else:
                        raise SymbolNotDefinedError(token, line, line_number)
                else:
                    parameters.append(token)
                token = tokens.expect(GRAMMAR["$$value$$"])

            icode.append(parameters)
            current_addr += parameters[1]
        else:
            # Save the op name
            op_name = token

            if type(token) not in GRAMMAR:
                raise ParserSyntaxError(token, line, line_number)
            match = GRAMMAR[type(token)]
            if isinstance(match, dict):
                rule_set = match["rule"]
            else:
                rule_set = match

            parameters = []

            if rule_set is not None:
                for rule in rule_set:
                    if isinstance(rule, str) and rule.startswith("$$"):
                        rule = GRAMMAR[rule]
                    token = tokens.expect(rule)

                    if token is None:
                        raise ParserSyntaxError(token, line, line_number)

                    if isinstance(op_name, SymbolToken):
                        parameters.append(token)
                    else:
                        if isinstance(token, SymbolToken):
                            if token in symbol_table:
                                parameters.append(symbol_table[token])
                            else:
                                raise SymbolNotDefinedError(
                                    token, line, line_number)
                        else:
                            parameters.append(token)

                verify_end = tokens.expect(CommentToken())
            else:
                verify_end = tokens.expect(CommentToken())

            if op_name == "def_symbol" or op_name == "def_label":
                name = parameters[0]
                value = parameters[1]
                if name in symbol_table:
                    raise DuplicateSymbolError(name, line, line_number)

                if isinstance(value, ColonToken):
                    value = current_addr
                symbol_table[name] = value
            else:
                if isinstance(op_name, list):
                    output = simple_gen(op_name, parameters)
                else:
                    method = getattr(codegen, op_name)
                    output = method(parameters)

                if output is not None:
                    icode.append(output)
                    current_addr += output[1]

    # At this point, all of the intermediate code is built and the only thing left is to resolve
    # the left over symbols, which will all bel labels.
    bytecode = OutputStream()
    for code in icode:
        txt = ""
        for bd in code:
            if isinstance(bd, LabelToken):
                label = bd
                if label not in symbol_table:
                    raise UndefinedLabel(label)
                bytecode.put_u32(symbol_table[label])
                txt += f"{label} ({hex(symbol_table[label])}) "
            else:
                txt += f"{hex(bd)} "
                bytecode.put_u8(bd)
        if debug:
            print(txt)

    # Done!
    return bytecode.get_buffer()
Example #18
0
class FormationRandomization(object):
    FLYING_MONSTER_IDS = [0x51, 0x52, 0x52, 0xA2, 0x3E, 0x3F, 0xBB]

    MINI_BOSS_IDS = [
        0xf,  # Pirate(s)
        0x69,  # Garland
        0x71,  # Astos
        0x76  # Death Machine
    ]

    def __init__(self, rom: Rom, rng: Random):
        size_stream = rom.open_bytestream(0x223644, 0xc3)
        sizes = []
        while not size_stream.is_eos():
            sizes.append(size_stream.get_u8())

        small_enemies = []
        large_enemies = []

        enemy_id_map = []
        for index, size in enumerate(sizes):
            enemy_id_map.append(index)

            if index >= 0x80:
                break

            if index not in FormationRandomization.MINI_BOSS_IDS:
                if size == 0:
                    small_enemies.append(index)
                elif size == 1:
                    large_enemies.append(index)

        shuffled_small = ShuffledList(small_enemies, rng)
        shuffled_large = ShuffledList(large_enemies, rng)

        enemy_data_stream = rom.open_bytestream(0x1DE044, 0x1860)
        enemies = []
        scaled_enemies = []
        while not enemy_data_stream.is_eos():
            enemy = EnemyStats(enemy_data_stream)
            enemies.append(enemy)
            scaled_enemies.append(enemy)

        for index in range(len(small_enemies)):
            original = shuffled_small.original(index)
            replacement = shuffled_small[index]
            scaled_enemy = FormationRandomization.scale_enemy(
                enemies[original], enemies[replacement])
            scaled_enemies[replacement] = scaled_enemy
            enemy_id_map[original] = replacement

        for index in range(len(large_enemies)):
            original = shuffled_large.original(index)
            replacement = shuffled_large[index]
            scaled_enemy = FormationRandomization.scale_enemy(
                enemies[original], enemies[replacement])
            scaled_enemies[replacement] = scaled_enemy
            enemy_id_map[original] = replacement

        self._scaled_enemies_out = OutputStream()
        for enemy in scaled_enemies:
            enemy.write(self._scaled_enemies_out)

        formations_stream = rom.open_bytestream(0x2288B4, 0x14 * 0x171)
        self._formations_out = OutputStream()
        while not formations_stream.is_eos():
            formation = Encounter(formations_stream)

            has_land = False
            has_flying = False
            for index, group in enumerate(formation.groups):
                if group.enemy_id < len(enemy_id_map):
                    group.enemy_id = enemy_id_map[group.enemy_id]

                if group.max_count == 0:
                    group.enemy_id = 0

                if group.enemy_id in FormationRandomization.FLYING_MONSTER_IDS:
                    has_flying = True
                else:
                    has_land = True

            if formation.config in [0x0, 0x05]:
                if has_flying and has_land:
                    # Both land and flying -> Mixed
                    formation.config = 0x5
                else:
                    # Even if they're all flying, they still get mapped to "1-9 small"
                    formation.config = 0x0

            # Save the new formations
            formation.write(self._formations_out)

    def patches(self) -> dict:
        return {
            0x1DE044: self._scaled_enemies_out.get_buffer(),
            0x2288B4: self._formations_out.get_buffer()
        }

    @staticmethod
    def scale_enemy(from_enemy: EnemyStats,
                    to_enemy: EnemyStats) -> EnemyStats:
        self_exp = from_enemy.exp_reward if from_enemy.exp_reward > 1 else 1620
        other_exp = to_enemy.exp_reward if to_enemy.exp_reward > 1 else 1620
        ratio = other_exp / self_exp

        estimate_damage = (
            (to_enemy.acc / 200) * to_enemy.atk) * to_enemy.hit_count
        target_attack = (estimate_damage /
                         (from_enemy.acc / 200)) / from_enemy.hit_count
        new_hit_count = from_enemy.hit_count
        if target_attack > 220:
            if new_hit_count == 1:
                new_hit_count = 2
                target_attack = min(target_attack / 2, 220)

        scaled_enemy = copy.deepcopy(from_enemy)
        scaled_enemy.exp_reward = to_enemy.exp_reward if from_enemy.exp_reward > 1 else from_enemy.exp_reward
        scaled_enemy.gil_reward = int(
            from_enemy.gil_reward * ratio) if from_enemy.gil_reward > 1 else 1
        scaled_enemy.hp = int(from_enemy.max_hp * ratio)
        scaled_enemy.intel = min(int(from_enemy.intel * ratio), 160)
        scaled_enemy.attack = min(int(target_attack), 220)
        scaled_enemy.hit_count = int(new_hit_count)
        return scaled_enemy
Example #19
0
 def write(self, stream: OutputStream):
     stream.put_u16(self.identifier)
     stream.put_u16(self.event)
     stream.put_u16(self.x_pos)
     stream.put_u16(self.y_pos)
     stream.put_u16(self.sprite_id)
     stream.put_u16(self.move_speed)
     stream.put_u16(self.facing)
     stream.put_u16(self.in_room)
Example #20
0
 def write(self, stream: OutputStream):
     stream.put_u16(self.exp_reward)
     stream.put_u16(self.gil_reward)
     stream.put_u16(self.max_hp)
     stream.put_u8(self.morale)
     stream.put_u8(self.unused_ai)
     stream.put_u8(self.evasion)
     stream.put_u8(self.pdef)
     stream.put_u8(self.hit_count)
     stream.put_u8(self.acc)
     stream.put_u8(self.atk)
     stream.put_u8(self.agi)
     stream.put_u8(self.intel)
     stream.put_u8(self.crit_rate)
     stream.put_u16(self.status_atk_elem)
     stream.put_u8(self.status_atk_ailment)
     stream.put_u8(self.family)
     stream.put_u8(self.mdef)
     stream.put_u8(self.unused)
     stream.put_u16(self.elem_weakness)
     stream.put_u16(self.elem_resists)
     stream.put_u8(self.drop_type)
     stream.put_u8(self.drop_id)
     stream.put_u8(self.drop_chance)
     for data in self.padding:
         stream.put_u8(data)
Example #21
0
    def __init__(self, rom: Rom, rng: Random):
        size_stream = rom.open_bytestream(0x223644, 0xc3)
        sizes = []
        while not size_stream.is_eos():
            sizes.append(size_stream.get_u8())

        small_enemies = []
        large_enemies = []

        enemy_id_map = []
        for index, size in enumerate(sizes):
            enemy_id_map.append(index)

            if index >= 0x80:
                break

            if index not in FormationRandomization.MINI_BOSS_IDS:
                if size == 0:
                    small_enemies.append(index)
                elif size == 1:
                    large_enemies.append(index)

        shuffled_small = ShuffledList(small_enemies, rng)
        shuffled_large = ShuffledList(large_enemies, rng)

        enemy_data_stream = rom.open_bytestream(0x1DE044, 0x1860)
        enemies = []
        scaled_enemies = []
        while not enemy_data_stream.is_eos():
            enemy = EnemyStats(enemy_data_stream)
            enemies.append(enemy)
            scaled_enemies.append(enemy)

        for index in range(len(small_enemies)):
            original = shuffled_small.original(index)
            replacement = shuffled_small[index]
            scaled_enemy = FormationRandomization.scale_enemy(
                enemies[original], enemies[replacement])
            scaled_enemies[replacement] = scaled_enemy
            enemy_id_map[original] = replacement

        for index in range(len(large_enemies)):
            original = shuffled_large.original(index)
            replacement = shuffled_large[index]
            scaled_enemy = FormationRandomization.scale_enemy(
                enemies[original], enemies[replacement])
            scaled_enemies[replacement] = scaled_enemy
            enemy_id_map[original] = replacement

        self._scaled_enemies_out = OutputStream()
        for enemy in scaled_enemies:
            enemy.write(self._scaled_enemies_out)

        formations_stream = rom.open_bytestream(0x2288B4, 0x14 * 0x171)
        self._formations_out = OutputStream()
        while not formations_stream.is_eos():
            formation = Encounter(formations_stream)

            has_land = False
            has_flying = False
            for index, group in enumerate(formation.groups):
                if group.enemy_id < len(enemy_id_map):
                    group.enemy_id = enemy_id_map[group.enemy_id]

                if group.max_count == 0:
                    group.enemy_id = 0

                if group.enemy_id in FormationRandomization.FLYING_MONSTER_IDS:
                    has_flying = True
                else:
                    has_land = True

            if formation.config in [0x0, 0x05]:
                if has_flying and has_land:
                    # Both land and flying -> Mixed
                    formation.config = 0x5
                else:
                    # Even if they're all flying, they still get mapped to "1-9 small"
                    formation.config = 0x0

            # Save the new formations
            formation.write(self._formations_out)
Example #22
0
 def write(self, rom: Rom) -> Rom:
     spell_stream = OutputStream()
     for spell in self._spell_data:
         spell.write(spell_stream)
     return rom.apply_patch(0x1A1980, spell_stream.get_buffer())
Example #23
0
 def write(self, stream: OutputStream):
     stream.put_u16(self.identifier)
     stream.put_u16(self.low_x)
     stream.put_u16(self.low_y)
     stream.put_u16(self.high_x)
     stream.put_u16(self.high_y)
Example #24
0
 def get_lut(self) -> bytearray:
     stream = OutputStream()
     for addr in self._lut:
         stream.put_u32(addr)
     return stream.get_buffer()
Example #25
0
 def write(self, stream: OutputStream):
     stream.put_u8(self.contents)
     for data in self.unused:
         stream.put_u8(data)
     stream.put_u32(self.pointer)
Example #26
0
def randomize_rom(rom: Rom, flags: Flags, rom_seed: str) -> Rom:
    rng = random.Random()
    rng.seed(rom_seed)

    print(f"Randomize ROM: {flags.text()}, seed='{rom_seed}'")
    patches_to_load = BASE_PATCHES
    if flags.encounters is not None:
        patches_to_load.append("data/FF1EncounterToggle.ips")
    if flags.default_party is not None:
        patches_to_load.append("data/RandomDefault.ips")

    patched_rom_data = rom.rom_data

    for patch_path in patches_to_load:
        patch = Patch.load(patch_path)
        patched_rom_data = patch.apply(patched_rom_data)
    rom = Rom(data=bytearray(patched_rom_data))

    rom = init_free_airship(rom)
    rom = add_credits(rom)

    event_text_block = EventTextBlock(rom)
    event_text_block.shrink()
    rom = event_text_block.pack(rom)

    rom = update_xp_requirements(rom, flags.exp_mult)

    if flags.key_item_shuffle is not None:
        placement = KeyItemPlacement(rom, rng.randint(0, 0xffffffff))
    else:
        placement = KeyItemPlacement(rom)
    rom = placement.rom

    if flags.magic is not None:
        shuffle_magic = SpellShuffle(rom, rng)
        rom = shuffle_magic.write(rom)

    if flags.treasures is not None:
        if flags.treasures == "shuffle":
            rom = treasure_shuffle(rom, rng)
        else:
            rom = random_bucketed_treasures(rom, rng, flags.wealth)

    if flags.debug is not None:
        class_stats_stream = rom.open_bytestream(0x1E1354, 96)
        class_stats = []
        while not class_stats_stream.is_eos():
            class_stats.append(JobClass(class_stats_stream))

        class_out_stream = OutputStream()
        for job_class in class_stats:
            # Set the starting weapon and armor for all classes to something
            # very fair and balanced: Masamune + Diamond Armlet. :)
            job_class.weapon_id = 0x28
            job_class.armor_id = 0x0e

            # Write the (very balanced) new data out
            job_class.write(class_out_stream)

        rom = rom.apply_patch(0x1E1354, class_out_stream.get_buffer())

    if flags.shuffle_formations:
        formation = FormationRandomization(rom, rng)
        rom = rom.apply_patches(formation.patches())

    if True:
        enemy_data_stream = rom.open_bytestream(0x1DE044, 0x1860)
        enemies = []
        while not enemy_data_stream.is_eos():
            enemies.append(EnemyStats(enemy_data_stream))

        # Rebalance (Revisited) Fiend HP
        enemies[0x78].max_hp = enemies[0x77].max_hp * 2
        enemies[0x7a].max_hp = enemies[0x79].max_hp * 2
        enemies[0x7c].max_hp = enemies[0x7b].max_hp * 2
        enemies[0x7e].max_hp = enemies[0x7d].max_hp * 2

        # And Chaos
        enemies[0x7f].max_hp = enemies[0x7e].max_hp * 2

        # Finally, Piscodemons can suck it
        enemies[0x67].atk = int(enemies[0x67].atk / 2)

        # We'll also lower everyone's INT just to see how that works
        for index in range(0x80):
            enemies[index].intel = int(.666 * enemies[index].intel)
            # print(f"{hex(index)} HP: {enemies[index].max_hp}, INT: {enemies[index].intel}")

        out = OutputStream()
        for enemy in enemies:
            enemy.write(out)
        rom = rom.apply_patch(0x1DE044, out.get_buffer())

    # Add the seed + flags to the party creation screen.
    seed_str = TextBlock.encode_text(f"Seed:\n{rom_seed}\nFlags:\n{flags}\x00")
    pointer = OutputStream()
    pointer.put_u32(0x8227054)
    rom = rom.apply_patches({
        0x227054: seed_str,
        0x4d8d4: pointer.get_buffer()
    })

    return rom
Example #27
0
 def write(self, stream: OutputStream):
     stream.put_u8(self.config)
     stream.put_u8(self.unrunnable)
     stream.put_u16(self.surprise_chance)
     for data in self.groups:
         data.write(stream)
Example #28
0
 def write(self, stream: OutputStream):
     stream.put_u16(self.identifier)
     stream.put_u16(self.event)
     stream.put_u16(self.x_pos)
     stream.put_u16(self.y_pos)
Example #29
0
def add_credits(rom: Rom) -> Rom:
    credits_lut = rom.get_lut(0x1D871C, 128)
    base_addr = credits_lut[0]

    new_lut = OutputStream()
    data_stream = OutputStream()

    for index, line in enumerate(CREDITS_TEXT.splitlines()[1:]):
        line = line.strip()
        if len(line) > 0:
            encoded = TextBlock.encode_text(line)

            new_lut.put_u32(base_addr + data_stream.size())
            data_stream.put_bytes(encoded)
        else:
            new_lut.put_u32(0x0)

    # And EOF marker
    new_lut.put_u32(0xffffffff)

    # Change the duration so it doesn't take so long to scroll
    duration = OutputStream()
    duration.put_u16(60 * 60)

    return rom.apply_patches({
        0x016848:
        duration.get_buffer(),
        0x1D871C:
        new_lut.get_buffer(),
        Rom.pointer_to_offset(base_addr):
        data_stream.get_buffer()
    })
Example #30
0
 def write(self, stream: OutputStream):
     stream.put_u8(self.enemy_id)
     stream.put_u8(self.min_count)
     stream.put_u8(self.max_count)
     stream.put_u8(self.unused)