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.
#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.
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.
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;
}
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.
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
Once you're done, you can delete asm/cable_car.s
, and remove it from ld_script.txt
.