6.9 KiB
Decompiling
Code starts out in asm/
. When decompiled to C, it goes into src/
. The goal is to decompile all the code.
Some of the code in asm/
is handwritten assembly. It can't and shouldn't be decompiled. It's already commented, so there's no further work to do on these files.
asm/crt0.s
asm/libagbsyscall.s
asm/libgcnmultiboot.s
asm/m4a_1.s
asm/m4a_3.s
The rest of the .s
files in asm/
are fair game.
The basic decompilation process is:
- Choose a file in
asm/
, i.e.asm/x.s
. Create a C file calledsrc/x.c
. - Translate the first function in
asm/x.s
to C insrc/x.c
. make compare
, and tweak the function until it matches.- Clean up the code and comment.
- Repeat for each function until
asm/x.s
is empty.
For example, let's decompile asm/cable_car.s
.
1. Create src/cable_car.c
#include "global.h"
global.h
contains typedefs for GBA programming and more.
It must be the first include in the file. Other includes will assume you have included it.
2. Include it in the rom
Include src/cable_car.c
in the rom by adding src/cable_car.c
to ld_script.txt
:
asm/battle_message.o(.text);
asm/choose_party.o(.text);
+ src/cable_car.o(.text);
asm/cable_car.o(.text);
asm/roulette_util.o(.text);
Do not remove asm/cable_car.o(.text)
. We want both src/cable_car.c
and asm/cable_car.s
in the rom.
3. Translate the function to C
Take the first function in asm/cable_car.s
. Either comment it out or remove it, whichever is easier.
thumb_func_start sub_81231EC
sub_81231EC: @ 81231EC
push {r4,lr}
lsls r0, 24
lsrs r4, r0, 24
ldr r0, _08123210 @ =gPaletteFade
ldrb r1, [r0, 0x7]
movs r0, 0x80
ands r0, r1
cmp r0, 0
bne _0812320A
ldr r0, _08123214 @ =sub_8123244
bl SetMainCallback2
adds r0, r4, 0
bl DestroyTask
_0812320A:
pop {r4}
pop {r0}
bx r0
.align 2, 0
_08123210: .4byte gPaletteFade
_08123214: .4byte sub_8123244
thumb_func_end sub_81231EC
Then, start translating the code to src/cable_car.c
, bit by bit:
lsls r0, 24
lsrs r4, r0, 24
void sub_81231EC(u8 r4) {
ldr r0, _08123210 @ =gPaletteFade
ldrb r1, [r0, 0x7]
movs r0, 0x80
ands r0, r1
r0 = (u8 *)(&gPaletteFade + 7) & 0x80;
cmp r0, 0
bne _0812320A
if (!r0) {
ldr r0, _08123214 @ =sub_8123244
bl SetMainCallback2
SetMainCallback2(&sub_8123244);
adds r0, r4, 0
bl DestroyTask
DestroyTask(r4);
_0812320A:
}
pop {r4}
pop {r0}
bx r0
return;
The type signature of the function depends on the return type.
bx r0
:void
bx r1
:*
bx lr
:void
,*
You will need to look at the caller and the function prologue to determine the exact type if not void.
Since it used bx r0
, it's void
for sure.
Putting it all together, we get:
void sub_81231EC(u8 r4) {
r0 = (u8 *)(&gPaletteFade + 7) & 0x80;
if (!r0) {
SetMainCallback2(&sub_8123244);
DestroyTask(r4);
}
return;
}
4. Simplify and document
This line doesn't look quite right.
r0 = (u8 *)(&gPaletteFade + 7) & 0x80;
What is gPaletteFade
? You can find out where stuff is with git grep
:
git grep "gPaletteFade" include/
include/palette.h:extern struct PaletteFadeControl gPaletteFade;
So it's a struct called PaletteFadeControl
. Let's look in palette.h
:
struct PaletteFadeControl
{
u32 multipurpose1;
u8 delayCounter:6;
u16 y:5; // blend coefficient
u16 targetY:5; // target blend coefficient
u16 blendColor:15;
u16 active:1;
u16 multipurpose2:6;
u16 yDec:1; // whether blend coefficient is decreasing
u16 bufferTransferDisabled:1;
u16 mode:2;
u16 shouldResetBlendRegisters:1;
u16 hardwareFadeFinishing:1;
u16 softwareFadeFinishingCounter:5;
u16 softwareFadeFinishing:1;
u16 objPaletteToggle:1;
u8 deltaY:4; // rate of change of blend coefficient
};
What's the 7th byte in this struct?
u32 multipurpose1; // 0-3
u8 delayCounter:6; // 4
u16 y:5; // 5
u16 targetY:5; // 5-6
u16 blendColor:15; // 7
u16 active:1; // 7
Byte 7 has both .blendColor
and .active
.
Okay, what's 0x80 mean? It's 0b10000000
, which is the highest bit in a byte.
.active
comes after, which means it's higher, but it's also only one bit, so it's a safe bet.
r0 = gPaletteFade.active;
Much better.
void sub_81231EC(u8 r4) {
r0 = gPaletteFade.active;
if (!r0) {
SetMainCallback2(&sub_8123244);
DestroyTask(r4);
}
return;
}
Now the temp variable r0
is a little pointless. We can simplify this to:
void sub_81231EC(u8 taskId) {
if (!gPaletteFade.active) {
SetMainCallback2(&sub_8123244);
DestroyTask(taskId);
}
}
Looks done, right? This function is pretty simple, so it doesn't need any comments right now.
But what about sub_8123244
? It's still not obvious what that function does. We can find out by decompiling it later.
5. Build
make compare
src/cable_car.c: In function `sub_81231EC':
src/cable_car.c:4: `gPaletteFade' undeclared (first use in this function)
src/cable_car.c:4: (Each undeclared identifier is reported only once for each function it appears in.)
src/cable_car.c:5: warning: implicit declaration of function `SetMainCallback2'
src/cable_car.c:5: `sub_8123244' undeclared (first use in this function)
src/cable_car.c:6: warning: implicit declaration of function `DestroyTask'
We got some errors. We need to tell the compiler what gPaletteFade
, SetMainCallback2
, sub_8123244
, and DestroyTask
are.
We know gPaletteFade
is from palette.h
. We can do the same with the others. Declare them above the function:
#include "palette.h"
#include "main.h"
#include "task.h"
The odd one out is sub_8123244
, which is in asm/cable_car.s
! What then?
void sub_8123244();
Normally, we would do extern void sub_8123244();
, but it won't be extern
when we're done this file.
Now our file looks like this:
#include "global.h"
#include "palette.h"
#include "main.h"
#include "task.h"
void sub_8123244();
void sub_81231EC(u8 taskId) {
if (!gPaletteFade.active) {
SetMainCallback2(&sub_8123244);
DestroyTask(taskId);
}
}
Build again, and we get:
make compare
pokeruby.gba: OK
This means the function matches. Congratulations!
If it doesn't match, you will get:
pokeruby.gba: FAILED
sha1sum: WARNING: 1 computed checksum did NOT match
If you forgot to remove the function from asm/cable_car.s
, you will get this error:
asm/cable_car.o: In function `sub_81231EC':
(.text+0x0): multiple definition of `sub_81231EC'
src/cable_car.o:(.text+0x0): first defined here
6. Repeat until asm/cable_car.s
is empty
Once you're done, you can delete asm/cable_car.s
, and remove it from ld_script.txt
.