Exemple #1
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()
    })
Exemple #2
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)
Exemple #3
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)
Exemple #4
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)
Exemple #5
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)
Exemple #6
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