Reverse Engineering a Gameboy Advance game: Understanding the Game Physics — Part 3

Bruno Macabeus
10 min readSep 6, 2019

--

This post is part of a series entitled Reverse Engineering a Gameboy Advance Game. Read the introduction here. Read the previous post here.

Follow me on Twitter to more computer fun 🐦

At this point I didn’t really know what to do, since Klonoa falling through the bridge wasn’t something I expected; after all, the tiles were there, we could see them, but Klonoa wasn’t able to stand on them!

And so I did a small test: did the tiles I created remain when they go out of view?

GIF demonstrating what happens with our tiles when we scroll them off-screen.

Doing this simple test, we can see that the extension we added to the bridge disappears when it goes out of view… Interesting… That means, besides the Fast WRAM, there are more parts of memory which serve as the source of information… But at that moment, I was really confused, not knowing how to proceed. (Thinking about it as I write now, maybe I could have watched the DMA logs as I walked around… but I didn’t connect those dots, so I followed another, more difficult strategy.)

Without knowing much of what to do, I decided to dig deeper into reverse engineering something much more complex: better understanding the logic of the game physics, more specifically gravity and collision!
The idea starts with the following premise: when Klonoa is falling, his Y-position is updated until he reaches the floor. Therefore, somewhere in the code there must be a check to know where he should stop, and for some reason, that check isn’t being performed on the tiles I created, so Klonoa passes right through them.
If we understand better how this logic works, we’ll know how to apply this check to the tiles we added to the bridge, making them work! With that in mind, let’s understand how the physics work in the game, so we can apply them to our tiles.

To start, we’ll let Klonoa fall and watch for the moment his Y position is updated, okay?
But before this, I need to explain a very important concept from the GBA: the OAM (Object Attribute Memory).
Besides displaying tiles, we can display objects on the screen. Some examples of objects include Klonoa himself, the monsters, the diamonds, in short, everything which is “dynamic”. To offer this dynamism, we need a way to manage it, specifying the position, the current sprite… As this is a common resource in many games, and the GBA is hardware specialized for games, it already defines a built-in architecture for this management, called the OAM.
The OAM is defined in the region of memory 0700:1kb. If you want to learn more about how OAM is structured, you can look at this list.

Just like the tilemap viewer, No$GBA has a very useful tool for debugging the OAM, and we’ll use it now!

No$GBA OAM Debugging Panel

One curious thing is that Klonoa is always the first object in the OAM, including when he doesn’t appear, like in the title screen. Looking at the OAM specification, we know that the first bytes determine the sprite’s Y position in the OAM. Because of this, we know that Klonoa’s Y position will always be at byte 07000000, which facilitates our analysis. Therefore, we can let Klonoa fall and use the debugger to step through the assembly until we find the instruction which updates this byte.
Stepping through the assembly, I found that the value was updated by an instruction which made no sense: `swi 5h`
Later I’ll explain in detail what this instruction does, but we can leave it for now. What is important is this: it has nothing to do with changing values in memory. After thinking a bit more to understand how byte 07000000 was updated, I thought: it’s the mischievous DMA again!
That game is writing Klonoa’s new OAM values somewhere in memory, and afterwards overwriting the bytes in 0700.

Opening the TTY logs, I confirmed this hypothesis, as you can see on the last line:

DMA3: 03000900 0600E000 80000400
DMA3: 03001100 0600E800 80000400
DMA3: 03004DB0 0600F000 80000200
DMA3: 03004800 07000000 8400003E

So we need to monitor alterations to byte 03004800! Remember that this byte is in the region of memory called Fast WRAM. Going there, one of the most important tests I did to understand this byte was to change its value and afterwards run the game to view its impact.

GIF showing what happens after modifying byte 03004800

And so we get an interesting effect: after updating it and stepping one frame, we really moved Klonoa’s position, cool! However, the effect lasts only a single frame, because on the next he returns to falling from where he was before… Or rather, this byte really is used to determine Klonoa’s position, but for its source of information it uses some other byte which we haven’t discovered yet, which we need to find in order to analyze it and succeed in understanding the game’s physics.

So I started to dig through the flow of byte 03004800, to find which instructions read and write there.
From this point, I’ll start to talk a lot about values written to another part of memory: 0800:32mb. This refers to data in the cartridge, or in other words, the game itself, where all of its instructions (in ARMv4T format) are stored along with other assets.

Stepping through the assembly, something that caught my attention occurs in the function called by byte 08005D00, since it overwrites the byte 03004800 and everything around it with garbage!
And shortly thereafter, in the instruction at byte 0800696E, the byte is overwritten with Klonoa’s Y value, but it’s already updated!

That was something very strange to me, and also left me confused… what!? How did it manage to update the value if all it had before was garbage? Where did it get the old value to update? Or had it already gotten the updated value from somewhere else and just copied it here?
To answer these questions, I started to debug deeper into what came before the instruction at 0800696E. However, No$GBA is very limited for static analysis, and even more so at this level of complexity, it’s not even possible to add comments. Therefore I started to use IDA to debug these instructions.
Besides being able to add comments, another useful feature of IDA is the graph viewer, which isolates each block of instructions in cells and connects them according to calls.

On the right, IDA, with the relevant blocks of instructions highlighted, and with some of the assembly instructions commented.

After hours of debugging, I arrived at the conclusion that to write to byte 03004800 it uses as a source the value of byte 03002926 which, stepping through the assembly, we see is written by the instruction at 0800A4C6. At that moment, I thought that the function which the instruction at 0800A4C6 was in would be the answer to all of my questions, and I debugged deep into it, however, after many hours debugging the assembly (plus the analysis I’ll talk about below), I arrived at the conclusion that this only determines Klonoa’s Y position relative to the camera! This was really frustrating…

As I analyzed the byte 03002926, something that caught my attention by chance was the behavior of a few bytes just before it. I noticed that whenever the character moved, the values were updated very consistently with the movement of the character, but with a few subtleties.
After banging my head some more, I noticed that the bytes 03002920:03002921 and 03002922:03002923 store, respectively, the absolute X and Y positions relative to the map! Exactly what I was looking for!! I noticed this by doing some tests, as the image below shows.

Notice that I moved the character to a slightly higher part of the level, and with this, the value of the absolute Y decreased. However, Klonoa remained at the same height relative to the camera, and thus the byte related to that remained the same value.

In other words, in the end, we found the bytes which define Klonoa’s absolute Y position on the map! And this new reasoning fits well, since we can go somewhere higher or lower in the level, and the value stored at 03004800 (and consequently 07000000 also) doesn’t change proportionally, or it remains the same.

Okay, now that we finally found the bytes which really determine the Y position we are interested in, 03002922:03002923, we’ll again step through the assembly to find where these bytes are updated. So, I arrived at the instruction at 0800FF16. It is always called, and it increases Klonoa’s Y value, even when he is standing on the ground, something really bizarre which makes no sense. However, the instruction at 0801200E reverts to the previous value, and this instruction is only called if he is standing on the ground. In other words, the logic is: move Klonoa down, however, if the new position is invalid, return to where he was before.

Therefore, I needed to find out at what moment the check is made to determine if instruction 0801200E is called or not. That will answer the mystery of why the tiles of the bridge had no physics!
For this, I used IDA’s Graph Viewer to know which instructions came before 0801200E and whether or not they were called when Klonoa was standing on the ground. Something similar to a binary search, let’s say.

The highlighted block towards the bottom is where Klonoa’s Y position is reverted to its previous value, while the block towards the top is where the last instruction called while Klonoa is falling is located, or in other words, it’s where the logic that determines “Does the position need to be reverted or not?” is located.

Now that we’ve found that, let’s decipher the assembly!

; R0 always receives 3D
; R6 receives the ID of the tile which Klonoa is on
; check to see if Klonoa entered a “passable” tile
; if it’s not a passable tile, then it’s invalid,
; and his position will be reverted

After analyzing it, I saw that the register R0 receives a constant value, 0x3D, and the register R6 contains the value stored in 03007C8C, which is the ID corresponding to the tile Klonoa will enter. Then it checks if the value of R6 is greater than the value of R0, and if it is, it goes to the sequence of instructions which will correct Klonoa’s position (or revert it to what it was before, as described earlier).
Remember that empty tiles have ID 00, and we have some tiles which serve as a background, like for example, the signs in the background; in other words, tiles with values less than 3D should be “passable”.

The byte 03007C8C is written to in the instruction 0800FF80, which copies the value of the register R0. And very importantly: it uses as its source the tilemap stored in Slow WRAM (region 0200:256kb), which stores the entire tilemap of the level, unlike what we saw in Fast WRAM, which stores only the currently visible frame of the level!
Since the Slow WRAM has a larger storage than Fast WRAM, and the currently visible frame is always required for rendering, and we can try moving Klonoa to a region which is currently not visible on the screen, this organization makes sense.

With this we can answer our question: the physics are not being applied to our tiles because we wrote them to the tilemap in Fast WRAM, and the physics logic is performed based on the tilemap written in Slow WRAM! The solution to this is obvious: we just need to write our tiles in Slow WRAM, and we easily manage to find it looking at the value of R0 just before the instruction at 0800FF80. In the case of our bridge, it is located at 02008432.

YEAH!!

And we finally managed to create our tiles in a way that really works!! 😄Besides that, our tiles remain even after they leave the field of view. In other words, we are really writing them very close to the source!

I think it’s worth writing a brief addendum here… I spent a few days trying to decipher the assembly before arriving at the conclusions I wrote here. One of the reasons it took me so long, besides my inexperience in the subject, was spending so much time confusing the bytes referring to Klonoa’s Y position relative to the camera instead of his absolute Y position, which led me to completely unrelated functions which I spent time deciphering, just like I briefly explained. It is noteworthy that, in a process of reverse engineering, you will get lost going down wrong paths, and it takes time to find the right path again. For this, I needed to backtrack several steps and restart a few times. This is common, and I’ve already seen other authors say the same thing.

But then, it’s very cool that we finally succeeded in writing tiles which really have physics applied, and we can move forward in our project, which will be to extract the tilemap from the ROM and then plot the entire level map, so we can eventually edit it! In the next chapter that’s what we’ll do! 😋

Next post: Where is the tilemap in the ROM?

--

--

No responses yet