示例#1
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)
示例#2
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()
    })
示例#3
0
 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
     ]))
示例#4
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
示例#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)
示例#6
0
    def __init__(self, rom: Rom):
        self._maps = []
        self.dummy_chests = []

        self._map_lut = rom.get_lut(0x1E4F40, 124)
        for map_id, map_addr in enumerate(self._map_lut):
            map_stream = rom.get_stream(Rom.pointer_to_offset(map_addr),
                                        bytearray.fromhex("ffff"))
            map = MapFeatures(map_id, map_stream)
            self._maps.append(map)

            # Collect the dummy chests together
            self.dummy_chests += map.dummy_chests
示例#7
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)
示例#8
0
 def __init__(self,
              rom: Rom,
              table_offset: int,
              table_size: int,
              base_event_id=0):
     self._base_event_id = base_event_id
     self._lut = list(rom.get_lut(table_offset, table_size))
示例#9
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())
示例#10
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())
示例#11
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)
示例#12
0
def main(argv):
    if len(argv) != 1:
        raise ValueError("Please pass ROM path and event ID parameters")

    rom = Rom(argv[0])
    event_ranges = [(0x0, 0xD4), (0xFA0, 0xFAB), (0x1388, 0x13CD),
                    (0x1F40, 0x2030), (0x2328, 0x2405)]

    find_jump_cmds(rom, event_ranges)
示例#13
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)
示例#14
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)
示例#15
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))
示例#16
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())
示例#17
0
    def _do_placement(self, key_item_locations: tuple):
        source_headers = self._prepare_header(key_item_locations)

        patches = {}
        for event_id, source in EVENT_SOURCE_MAP.items():
            event_source = pparse(f"{source_headers}\n\n{source}")

            event_addr = self.events.get_addr(event_id)
            event_space = self.rom.get_event(
                Rom.pointer_to_offset(event_addr)).size()

            # See if the event fits into it's vanilla location.
            event = easm.parse(event_source, event_addr)
            if len(event) > event_space:
                # Didn't fit. Move it to our space.
                event_addr = self.our_events.current_addr()
                self.events.set_addr(event_id, event_addr)

                # We'll write all of our events together at the end
                event = easm.parse(event_source, event_addr)
                self.our_events.put_bytes(event)
            else:
                # Add the event to the vanilla patches.
                patches[Rom.pointer_to_offset(event_addr)] = event

        self._update_npcs(key_item_locations)
        self._unite_mystic_key_doors()
        self._better_earth_plate()
        self._rewrite_give_texts()
        self._save_chests()

        # Append our new (relocated) events in the patch data.
        patches[0x223F4C] = self.our_events.get_buffer()

        # And then get all the patch data for the LUTs
        for offset, patch in self.events.get_patches().items():
            patches[offset] = patch
        self.rom = self.rom.apply_patches(patches)
        self.rom = self.maps.write(self.rom)
示例#18
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())
示例#19
0
def create_patch():
    vanilla_rom = Rom("ff-dos.gba")
    flags_string = request.form['flags']
    flags = Flags(flags_string)
    rom_seed = gen_seed(request.form['seed'])

    rom = randomize_rom(vanilla_rom, flags, rom_seed)

    gba_name = get_filename("ff-dos", flags, rom_seed)
    filename = parse.quote(gba_name[:len(gba_name) - 4] + ".ips")

    patch = Patch.create(vanilla_rom.rom_data, rom.rom_data)
    response = make_response(patch.encode())
    response.headers['Content-Type'] = "application/octet-stream"
    response.headers['Content-Disposition'] = f"inline; filename={filename}"
    return response
示例#20
0
def randomize():
    uploaded_rom = request.files['rom']
    rom = Rom(data=uploaded_rom.read())
    flags_string = request.form['flags']
    flags = Flags(flags_string)
    rom_seed = gen_seed(request.form['seed'])

    rom = randomize_rom(rom, flags, rom_seed)

    filename = uploaded_rom.filename
    index = filename.lower().rfind(".gba")
    if index < 0:
        return f"Bad filename: {filename}"
    filename = parse.quote(get_filename(filename, flags, rom_seed))

    response = make_response(rom.rom_data)
    response.headers['Content-Type'] = "application/octet-stream"
    response.headers['Content-Disposition'] = f"inline; filename={filename}"
    return response
示例#21
0
def main():
    parser = ArgumentParser(
        description="Final Fantasy: Dawn of Souls Event->Script")
    parser.add_argument("rom",
                        metavar="ROM file",
                        type=str,
                        help="ROM source file")
    parser.add_argument("--event",
                        dest="event",
                        type=str,
                        help="Event to disassemble")
    parsed = parser.parse_args()

    # Opening the ROM is simple.
    rom = Rom(parsed.rom)

    # The event id is a bit trickier. The parser won't recognize hex values, so we need to accept it as a
    # string and convert it ourselves.
    if parsed.event.startswith("0x"):
        event_id = int(parsed.event, 16)
    else:
        event_id = int(parsed.event)

    disassemble_event(rom, event_id)
示例#22
0
def main(argv):
    if len(argv) != 2:
        raise ValueError("Please pass ROM path and event ID parameters")

    global rom, event_text
    rom = Rom(argv[0])
    event_text = EventTextBlock(rom)

    if argv[1] == "--strings":
        for idx, estr in enumerate(event_text):
            print(f"({idx}) @ {hex(event_text.lut[idx])}: {estr}")
    elif argv[1] == "--all":
        with open("labels/locations.txt", "r") as f:
            locations = f.readlines()
            for loc in locations:
                loc = loc.rstrip().lstrip()
                data = loc.split()
                index = int(data[0], 0)

                print(f"Map init event {loc}")
                decompile_event(index)
                print(f"\n* * *\n")

        with open("labels/game_events.txt", "r") as f:
            events = f.readlines()
            for event in events:
                event = event.rstrip().lstrip()
                data = event.split(":")
                index = int(data[0], 0)

                print(f"Main game event {event}")
                decompile_event(index)
                print(f"\n* * *\n")
    elif argv[1] == "--max":
        event_desc = {}
        with open("labels/event_desc.txt", "r") as f:
            events = f.readlines()
            for event in events:
                event = event.rstrip().lstrip()
                data = event.split(":")
                event_desc[data[0].lower()] = data[1]

        event_ranges = [(0x0, 0xD4), (0xFA0, 0xFAB), (0x1388, 0x13CD),
                        (0x1F40, 0x2030), (0x2328, 0x2405)]
        for event_range in event_ranges:
            for event_id in range(event_range[0], event_range[1]):
                if hex(event_id) in event_desc:
                    title = f"Event: {hex(event_id)} - {event_desc[hex(event_id)]}"
                else:
                    title = f"Event: {hex(event_id)}"

                print(title)
                decompile_event(event_id)
                print(f"\n* * *\n")

    else:
        if argv[1].startswith("0x"):
            event_id = int(argv[1], 0)
        else:
            event_id = int(argv[1], 16)

        # Decompile the event in a function so it can recurse.
        decompile_event(event_id)
示例#23
0
 def __init__(self, rom: Rom, lut_offset: int, count: int):
     self.lut_offset = lut_offset
     self.lut = list(rom.get_lut(lut_offset, count))
     self.strings = []
     for addr in self.lut:
         self.strings.append(rom.get_string(Rom.pointer_to_offset(addr)))
示例#24
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)
示例#25
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)
示例#26
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())
示例#27
0
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

# Designed to parse the Map data in a human reable format.
# Takes in a FF DoS rom named 'FF1.gba' because lazy
import sys

from doslib.rom import Rom

if sys.argv[1] is not None:
    rom = Rom(sys.argv[1])
else:
    rom = Rom('./primary_code/FF1.gba')

locations = ''
temp_spirtes = ''
with open("labels/locations.txt", "r") as f:
    locations = f.readlines()
with open("labels/sprites.txt", "r") as f:
    temp_sprites = f.readlines()

sprites = dict()

for s in temp_sprites:
    index = int(s.split(' ')[0], 16)
    sprites[index] = s.split(' ')[1].strip() + ' (' + s.split(
示例#28
0
 def get_map_offset(self, map_id: int) -> int:
     return Rom.pointer_to_offset(self._map_lut[map_id])
示例#29
0
def randomize(rom_path: str, flags: Flags, rom_seed: str):
    rom_seed = gen_seed(rom_seed)
    vanilla_rom = Rom(rom_path)
    rom = randomize_rom(vanilla_rom, flags, rom_seed)

    rom.write(get_filename(rom_path, flags, rom_seed))
示例#30
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