Beispiel #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())
Beispiel #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)
Beispiel #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
Beispiel #4
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 #5
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 #6
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 #7
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 #8
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 #9
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())