pokeruby/CONTRIBUTING.md
2017-05-27 17:18:20 -04:00

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 called src/x.c.
  • Translate the first function in asm/x.s to C in src/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.