cosarara.me

The blog

Today I Solved/Broke ROM-Hacking

GBA Pokémon game ROM-Hacking is messy. It's done using tools like map editors, hex editors or image inserters directly on the ROM file, and the only safety measure are plain file backups.

Normal game or software development, on the other hand, usually keeps all the source data in separate, easily editable files, then provides some means of building the final product automatically.

Wouldn't it be great if ROM Hacking could be done like normal software development? We'd only need some kind of build system, with a linker that could set all the pointers where they belong... Then we'd press a button and all our data would be written on top of a stripped down ROM file, producing the final game.

But we have that! The script compiler does exactly what we need. You give it your code with a bunch of @-prefixed labels, it looks for free space and it links it all together. What we don't have, though, is non-script things in script source form. To start with, we'd need:

  • Map headers and data
  • Graphics
  • Trainer data
  • Pokemon data

Graphics can probably be compressed by grit. I can make Blue Spider output source files. Trainer data, pokémon data, etc. are similar structures—some work, but possible. The only thing left is some way to nicely edit tileset block data, which my map editor doesn't do. Music and ASM modifications are done by the means of binaries already. So, let's give it a go...

I added a script to Blue Spider named dump_pks.py. It takes the map bank list, the list of maps for every bank, and most of the data for every map (including events, but not the tile map itself yet), and outputs it as pks scripts in a directory named map_dump when called as:

$ ./dump_pks.py FR.gba

It also writes a file include_list.pks which has an #include line for every other file, so that running:

$ asc-cli c FR.gba map_dump/include_list.pks

Builds the whole thing (and, if nothing has been changed on the source files, changes nothing on the ROM). But wait! I hear you say, that's full of raw addresses, nobody could work with that! And that's true, which is why dump_pks.py takes a --label option, which makes it spit out nice @labels instead of hex addresses:

$ ./dump_pks.py --label FR.gba

And so, map_dump/3/map_0.pks AKA Pallet Town will look more or less like this:

'map header
#org @map_3_0_map_header
#word @map_3_0_map_data_header 'map_data_ptr
#word @map_3_0_events_header 'event_data_ptr
#word @map_3_0_level_scripts 'level_script_ptr
#word 0x835276c 'connections_ptr
#hword 0x12c 'song_index
#hword 0x4e 'map_ptr_index
#byte 0x58 'label_index
#byte 0x0 'is_a_cave
#byte 0x2 'weather
#byte 0x1 'map_type
#hword 0x601 'null
#byte 0x0 'show_label
#byte 0x0 'battle_type

'map data header
#org @map_3_0_map_data_header
#word 0x18 'w
#word 0x14 'h
#word 0x82dd0f8 'border_ptr
#word 0x82dd100 'tilemap_ptr
#word @map_3_0_t1_header 'global_tileset_ptr
#word @map_3_0_t2_header 'local_tileset_ptr
#byte 0x2 'border_w
#byte 0x2 'border_h

't1 header
#org @map_3_0_t1_header
#byte 0x1 'is_compressed
#byte 0x0 'tileset_type
#hword 0x0 'null
#word 0x8ea1d68 'tileset_image_ptr
#word 0x8ea1b68 'palettes_ptr
#word 0x829f6c8 'block_data_ptr
[rest omitted]

Now, there's a couple things which must be done before this can be compiled. First, a suitable #dyn line must be added at the top of include_list.pks. And second, since Blue Spider doesn't know when to stop finding maps at the end of the last bank, all the bad maps must be removed from the definition of said bank. In the case of Fire Red, that means removing all maps but number 0 at map_dump/bank_42.pks.

And so, this is it for today. There is quite a bit of work ahead, but the future doesn't look bad.

—cosarara

Got any comments? Send me an email: blog@cosarara.me