Beispiel #1
0
    def __init__(self, rom: Rom):
        data_lut_stream = rom.open_bytestream(0x1DFB04, 0x198)
        self.shop_data_pointers = []
        self.shop_inventories = []
        for index in range(51):
            shop = ShopDataPointer(data_lut_stream)
            self.shop_data_pointers.append(shop)

            # This is overkill for vanilla, but is still small. Since code bytes don't count, the
            # value in shop.shop_data_length isn't quite as useful as it could be.
            inventory = ShopInventory(
                rom.open_bytestream(Rom.pointer_to_offset(shop.pointer), 0x20),
                shop.shop_data_length)
            self.shop_inventories.append(inventory)
Beispiel #2
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
Beispiel #3
0
    def __init__(self, rom: Rom, rng: Random):
        self._shops = ShopData(rom)
        self._spells = Spells(rom)

        self._permissions = []
        permissions_stream = rom.open_bytestream(0x1A20C0, 0x82)
        while not permissions_stream.is_eos():
            self._permissions.append(permissions_stream.get_u16())

        self._do_shuffle(rng)
Beispiel #4
0
    def __init__(self, rom: Rom):
        # 130 Because:
        # 8 levels of magic, 8 spells per level (white + black) = 64 spells.
        # Spell name + help text for each = 64 x 2 = 128
        # Slot 0 is skipped = 128 + 2 (blank name + empty help) = 130
        self._name_help = TextBlock(rom, 0x1A1650, 130)

        spell_data_stream = rom.open_bytestream(0x1A1980, 0x740)
        self._spell_data = []
        for index in range(65):
            self._spell_data.append(SpellData(spell_data_stream))
Beispiel #5
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())
Beispiel #6
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())
Beispiel #7
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())
Beispiel #8
0
class TestRom(unittest.TestCase):

    def setUp(self):
        self.rom = Rom(data=bytearray([
            # String (0x0)
            0x82, 0x66, 0x82, 0x8f, 0x82, 0x82, 0x82, 0x8c, 0x82, 0x89, 0x82, 0x8e, 0x00, 0x00, 0x00, 0x00,
            # LUT (0x10)
            0x34, 0x12, 0x00, 0x08, 0x78, 0x56, 0x00, 0x08, 0xbc, 0x9a, 0x00, 0x08, 0xf0, 0xde, 0x00, 0x08
        ]))

    def test_open_bytestream(self):
        stream = self.rom.open_bytestream(0x0, 0x4)
        self.assertEqual(stream.size(), 4)
        self.assertEqual(stream.get_u16(), 0x6682)

    def test_open_bytestream_out_of_bounds(self):
        with self.assertRaises(RuntimeError):
            stream = self.rom.open_bytestream(0x100, 0x4)

    def test_get_lut(self):
        lut = self.rom.get_lut(0x10, 4)
        self.assertEqual(len(lut), 4)

        addressses = [
            0x8001234,
            0x8005678,
            0x8009abc,
            0x800def0,
        ]
        for index, address in enumerate(lut):
            self.assertEqual(address, addressses[index])

    def test_get_lut_misaligned(self):
        with self.assertRaises(RuntimeError):
            lut = self.rom.get_lut(0x12, 2)

    def test_get_string(self):
        a_str = self.rom.get_string(0x0)
        self.assertEqual(len(a_str), 0xd)

    def test_patch(self):
        patch = OutputStream()
        patch.put_u32(0x12345678)
        patched = self.rom.apply_patch(0x0, patch.get_buffer())
        confirm = patched.open_bytestream(0x0, 0x4)
        self.assertEqual(confirm.get_u32(), 0x12345678)

    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)

    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)
Beispiel #9
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
Beispiel #10
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)