One of my favourite games is Fire Emblem: Radiant Dawn, the tenth installment in the Fire Emblem series. A couple of months ago, after playing through the game more than half a dozen times, I decided to try modding the game. I was surprised to see the lack of such a community for FE10.
This blog post will be post 1 of a series of blog posts on Fire Emblem 10 Modding. In this post, we will delve into the structure of the ISO and locations of key files that we care about.
My primary focus for modding was to be able to modify arbitrary stats, and not so modifying the visuals. I wanted to develop a library that can easily change, insert, or remove any [numerical] value in the game. Want to halve all of Ike's stats? This would be doable in a few lines of code. Want to change the amount of bonus EXP on a particular level, or add a new item into the game? Again, easily modifiable.
Of course, this won't be accomplished in the current blog post. For the current work in progress library, we will focus on being able to replace arbitrary stats without having to insert/delete bytes.
Note: The basis for these posts was heavily inspired by Vincent's research in FE10: Radiant Dawn Hacking Notes. His posts give a general idea of how to start modding, such as how and where the files are laid out. Without his research, these blog posts would have been significantly harder to make.
Part 1: Extracting the relevant data
The first part of modding FE10 is to get your hands on a ROM. For this post, I'll be using a copy of a ROM with a SHA256 of
$ sha256sum "Fire Emblem - Radiant Dawn (USA) (Rev 1).iso" 742683756a1fd212f3f138935c61fa3a733e55e76be13390d37a93d58b3c1d4f Fire Emblem - Radiant Dawn (USA) (Rev 1).iso
Other copies may work, but I haven't tested so there's no guarantees.
Next, you'll need to extract the files.
wit is a great tool for this.
$ mkdir out/ $ wit EXTRACT "Fire Emblem - Radiant Dawn (USA) (Rev 1).iso" out/
This should dump everything into a directory named
out/. The key files here are:
sys/main.dol. This contains the main runtime for FE10. All logic is stored in here.
files/*.cms. These files are main data files (LZ77 compressed).
files/Shop/shopitem_*.bin. These files contain the data for the shop system (one for each difficulty, though all files are the same modulo metadata).
files/Scripts/*.cmb. These files contain the scripts for each chapter using a scripting language (one for each chapter and tutorial).
We'll give a general break down some of these files here, and go into more detail in future posts.
Part 2: Random notes
There's a few random pieces of information I wanted to point out before we start breaking down each file.
"Pointers" are used throughout many FE10 files to reference data. Usually, it'll be a pointer into the string pool to reference a string, but rarely it will also reference entire other sections of data. Most pointers are offset by some number of bytes. Keep this in mind when following pointers around, as there may be an offset that you must correct manually. Also, note that there can be many pointers pointing to the same piece of data, so be careful when making modifications to pointer data.
Internal names are used in the files, and a translation to in-game names is done when displaying. This means that many names may be different than what you're used to. This name chart is a great resource for converting between internal names and in-game names.
Part 3: Breaking down
FE10Data.cms contains most of the useful data that we want to modify.
.cms files are compressed with LZ77, we will first need to decompress it in order to modify its contents. There's a small Python script to do this that I wrote, which you can grab here.
$ python3 lz77.py decompress out/files/FE10Data.cms FE10Data.cms.decompressed
The file is broken down into a few sections, which we will describe here.
Part 3.1: The header
The header consists of 0x2c bytes, and should look similar to:
00000000 0004 582e 0003 4d64 0000 3f12 0000 00b8 00000010 0000 0000 0000 0000 0000 0000 0000 0000 00000020 0000 0000 0002 7feb 0003 41e6
- 4 bytes [unsigned int]: the file size.
- 4 bytes [pointer]: a pointer to the table of pointers (section 3.4).
- 4 bytes [unsigned int]: the number of pointers in the table of pointers (section 3.4).
- 4 bytes [unsigned int]: the number of sub-sections in section 3.2.
- 20 bytes: null bytes.
- 4 bytes [string pointer]: file date.
- 4 bytes [string pointer]: file author.
Part 3.2: Multiple sub-sections for each stat of the game.
Each sub-section contains a data set for one part of the game. For example, there's one sub-section for character stats, one for item stats, etc. Each sub-section's start location and the data stored there is referenced in section 3.5. Thus, we can simply parse section 3.5 to determine the sub-sections. For the version of the file I'm using:
|Start location||Number of objects||Object byte size||Data stored (Official label from section 3.5)|
|0x12810||142||0x2c bytes||Skill (SkillData, SID_*)|
|0x146d4||1||0x10 bytes||Support Gain Bonus (RelianceParam)|
|0x146e4||9||0xc bytes||Affinity (DivineData)|
|0x14754||1||0x40 bytes||Attack XP (GameData)|
|0x14794||1||0x48 bytes||Unknown (GameData)|
|0x147dc||43||0x2c bytes||Terrain (TerrainData)|
|0x15bb8||44||0x194 bytes||Battle Map (BattleTerrIndex)|
|0x1a12c||106||0x4 bytes||Battle Map Background (BattleTerrName)|
|0x1a2d4||71||0x240 bytes||Buddy Support (RelianceData)|
|0x24298||60||0xb4 bytes||Chapter (ChapterData)|
|0x26ccc||45||0xc bytes||Group (GroupData)|
|0x26eec||46||0xc bytes||Bond Support (KiznaData)|
|0x2711c||64||0xc bytes||Affinity Bonus (DivineParam)|
|0x27420||27||0xc bytes||Weapon Triangle Bonus (3SukumiData)|
|0x27568||10||0x10 bytes||Biorhythm Type (BioData)|
|0x27608||5||0xc bytes||Biorhythm Level Bonus (BioParam)|
In post 2, we'll go over in detail all the sub-sections, as there's too much to cover for here.
Part 3.3: A string pool containing all the strings used in 3.2.
This is exactly what it is — a list of a strings, each terminated with a null byte. All pointers to this pool are offset by 0x20 bytes.
Part 3.4: A table of pointers used in 3.1 and 3.2.
These pointers have an offset of 0x0, and point to every pointer used in 3.2. This table is likely used to correct the pointers with the appropriate offset when the file is initially loaded.
Part 3.5: A table of pointers to the beginning of each sub-section in 3.2.
A table of pairs, where the first value is a pointer (offset of 0x20) to the start of the sub-section, and the second value is a pointer into the second string pool for labeling the sub-section. The number of such pairs is given in the header.
Part 3.6: A second string pool to label the pointers in 3.5.
Similar to 3.4, except all pointers to this pool are offset by the start of this pool rather than 0x20 bytes.
Part 4: Breaking down the script files (
Each chapter, including the debug chapter
C0000.cmb and the tutorial chapters
T*.cmb have a script file associated with it.
These scripts contain all the information required to run each chapter from start to end, such as the sequence of events. Some other notable data they contain includes hidden treasure locations, base conversations (including items received from these conversations), bonus XP, win conditions, and lose conditions.
I haven't spent too much time analyzing these files, but I just wanted to mention some useful resources in case you wanted to modify them:
- FE9 and FE10 Event Script doc/notes: describes the file format and opcodes.
- StanHash's event script decompiler: decompiles the scripts into a semi-readable format, which is useful for checking that any modifications were correctly made. It crashes on some FE10 scripts though, so I've forked the repository and attempted to silence these crashes here.
Part 5: Recreating the ISO
Now, let's see how we would recreate the ISO.
If there are any decompressed
.cms files, you'll need to re-compress them back to their original locations. For
$ python3 lz77.py compress FE10Data.cms.decompressed out/files/FE10Data.cms
Just a disclaimer, this script doesn't actually do any compression — it just re-writes the file in a way that is understood by an LZ77 decompressor. This means that the output file will be slightly larger than the original file, but this should be fine.
wit to recreate the ISO.
$ wit COPY out/ new.iso
That's it! You should be able to run the new ISO, assuming any modifications that you made still results in valid files. We'll make some modifications very soon.
Part 6: Final thoughts
This first post was mainly to introduce FE10 modding. We didn't actually modify anything though. Unfortunately, since there's a lot to cover, it's necessary to give a general overview first. In future posts, we'll go into more detail and actually begin modding, starting with getting our hands dirty with
FE10Data.cms's sub-sections followed by introducing my FE10 library. Hope you stay tuned!