Speedrunning and Modding The Incredibles: Rise of the Underminer
In this post, I’ll talk about reverse engineering and modding rotu in order to optimize a speedrun. On the way, I’ll answer why cheat code books (remember those?) and websites have listed incomplete or inaccurate cheat codes for this game. I’ll also show some of the game’s debug features and a couple out of bounds glitches.
Background
My brother and I grew up playing the somewhat niche game The Incredibles: Rise of the Underminer on the GameCube. Since then, it’s become a tradition to play whenever we’re at home for the holidays.
Last year, for fun, we filmed our casual playthrough. At the time, the casual run was fast enough for the WR in the Co-Op Glitchless category at 1:04:58.
Someone beat us just a short while later. We haven’t been able to beat it since. It’s time to get serious, so let’s reverse engineer the game to look for optimizations.
The whole point of rotu is to beat up robots as you progress through 11 levels. The game ends with a boss fight against The Underminer, the villain introduced at the end of the first Incredibles film.
Since so much of the game is combat focused, if we can figure out how to optimize combat, we can shave time off our run.
Reverse Engineering
One way we can learn about the combat system is to statically reverse engineer the game. A Gamecube iso will include a .dol file, which is the executable file format for the GameCube. We can use Cuyler36’s Ghidra-GameCube-Loader to load the .dol file into Ghidra. This will allow us to see the PowerPC instructions along with Ghidra’s attempted C decompilation.
We’re very lucky that rotu is one of ~10% of GameCube games that shipped with debug symbols. This means that we’re able to read function names. Here’s an arbitrary example of the kind of decompilation we’re working with:
/* __thiscall zNPC::zNPCBomber::GetCombatData(void) */
undefined ** __thiscall zNPC::zNPCBomber::GetCombatData(zNPCBomber *this)
{
int iVar1;
undefined **ppuVar2;
iVar1 = (**(code **)(*(int *)(this + 0xdc) + 0x100))();
ppuVar2 = &@unnamed@zNPCBomber_cpp@::bomberEliteCombatInitData;
if (iVar1 == 0) {
ppuVar2 = &@unnamed@zNPCBomber_cpp@::bomberCombatInitData;
}
return ppuVar2;
}
There’s a lot of code defining the combat system, and it’s not obvious where we should start looking. It seems hard to find interesting or exploitable combat behavior by just reading the code, since there are a lot of interacting components and the emergent behavior is hard to reason about.
Modding
I realized that what I actually wanted was a mod that will draw the health of all the enemies on the screen. That way, we can experiment with ideas in-game and get immediate feedback on the effect on the enemy’s health.
The idea is simple in theory. First, we’ll look at Ghidra to find where enemies are stored in memory. We’ll also find useful functions we can call, like a text rendering function. Then, we’ll insert a hook into the main render function and draw text on the screen at the entities’ projected screen coordinates.
To do this in practice, we’ll first set up a C toolchain so we can write mods in C against the actual rotu game engine. The toolchain first compiles C to PowerPC via DevkitPPC. Then it parses the game’s .dol file to determine which patches to write where, and finally it outputs an Action Replay code that will write the compiled instructions of the mod to the right places.
All that’s left is to write the mod! It’s ergonomic to be able to leverage existing functions in the game engine, like libc functions, but more importantly, we can use functions that draw textboxes and convert world coordinates to screen coordinates.
Action Replay
As mentioned above, the final output of the toolchain is an Action Replay code. The Action Replay is a game cheating device that the Dolphin emulator also supports. I chose to use Action Replay codes instead of patching the game because they’re easy to share and it makes for a good story. The best docs for GameCube AR codes are on the Dolphin GitHub wiki.
In an Action Replay code, each line is an instruction. As I understand, these instructions get executed ~every frame. For our purposes, we’ll only make use of the “write memory” instructions to write our mod’s instructions where they need to go. This works fine in an emulator. No idea if it works on real hardware.
You can see the final AR code here. It’s hilarious long: 1.2k lines long at the time of this writing!
Results
Check out the mod in action!
An example subtle observation that is made possible with this method is that Frozone’s ice glide does different damage depending on how long he is in contact with the enemy. It’s as if it can “hit twice”:
Perhaps the most important observation is just that punching is overpowered. In particular, the punch damage doubles for sequential punches, up to 4 punches.
Mr. I Punch Damage
Upgrade Level | First punch | Second punch | Third punch | Fourth punch |
---|---|---|---|---|
Upgrade 1 | 5 | 10 | 15 | 20 |
Upgrade 2 | 7 | 15 | 22 | 30 |
Upgrade 3 | 10 | 20 | 30 | 40 |
Note: Fourth punch does insane damage on frozen enemies. It one-hits frozen enforcers (100 hp).
Frozone Punch Damage
Upgrade Level | First punch | Second punch | Third punch | Fourth punch |
---|---|---|---|---|
Upgrade 1 | 3 | 5 | 8 | 15 |
Upgrade 2 | 4 | 7 | 12 | 22 |
Upgrade 3 | 6 | 10 | 16 | 30 |
Mr. I Slam Damage
Upgrade Level | Damage |
---|---|
Upgrade 1 | 20 |
Upgrade 2 | 30 |
Upgrade 3 | 40 |
Note: Sometimes does double damage if you collide with bot while slamming.
Frozone Ice Glide Damage
Upgrade Level | Damage |
---|---|
Upgrade 1 | 15 |
Upgrade 2 | 22 |
Upgrade 3 | 30 |
Note: Often “hits twice” on large enemies.
Side Quest: Cheat Codes
The game has a cheat code entry screen accessible from the pause menu. While not useful for speedrunning, it has always been a curiousity to me.
Many years ago at a scholastic book fair at my elementary school, I bought a book of video game cheat codes. It listed off several codes for the game. Later, I learned that it was an incomplete set and I could find more online. However, some of the online codes didn’t work. For example, take a look at this website that lists some of the codes as “unknown”. One example of such a code is “NUKE”. When entered, the game reports “CHEAT INCORRECT”. Where did these incorrect codes come from?
My best guess is that the people hunting for cheat codes in the early days just
ran strings
on the iso. You’ll see that these cheat codes aren’t hard to find,
especially if you already know one:
$ strings rotu.iso | grep -i inahurry -C 10
FMV\Promo14
DoneA
DoneB
EgoProblem
FrozBoom
FrozMaster
FrozProf
Headroom
HUDBeGone
ICantHearYou
InAHurry
LevelLocksmith
Maximillion
MrIBoom
MrIMaster
MrIProf
Nuke
RollCall
Shameless
ShowMe
ThisIsTooEasy
You’ll see that “NUKE” is among the ones listed. So why isn’t it accepted? I did a bit of rev work to learn that it’s locked behind a developer flag. When dev mode is enabled, these codes work. When activated, “NUKE” will allow for unlimited super moves. I didn’t dig too far into what the other dev cheats, but it’s nice to get some closure :)
Here’s an Action Replay code to enable dev mode:
003988ed 00000001
Some others dev cheat codes haven’t been documented by any site that I know of
include “CA”, “CB”, “CC”, … to warp to checkpoints in a level, and “X” “XX”
“XXX” … to give different amounts of XP to the player. I would guess that
those looking at the output of strings
would miss this in the noise of the
other random strings they were seeing.
Perhaps more importantly, when dev mode is enabled, you can hit L+R+dpad down
to fly!
L+R+X
enables a debug overlay that includes information like player position
and FPS.
Something else caught my eye while reversing various cheat codes. I learned that if you hold L+R in the pause screen and type in a sequence of X+Y, then it will enable some cheats. Unfortunately it looks like the implementation of those cheats was removed from rotu entirely. I was informed by hoongoons that these kind of codes are present in other games using the same engine.
Misc Out of Bounds Glitches
We came across a couple out of bounds glitches while practicing (read: goofing around). Most of these involve weird logic around respawning a character while the alive character is not on the ground. It’s not obvious that any of these exact examples will save time, but the technique could be useful to others.
Conclusion
I had a lot of fun doing this rev work, but we still haven’t made much progress toward reclaiming our record. Maybe by publishing this research, others in the very tiny rotu speedrunning community can build off of it. Thanks for reading! All the code can be found here. 🐪