diff options
-rw-r--r-- | CONTRIBUTING.md | 340 | ||||
-rw-r--r-- | README.md | 8 |
2 files changed, 344 insertions, 4 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..15ff9dfb3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,340 @@ +# 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` + +```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`: +```diff + 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. + +```asm + 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: + +```asm + lsls r0, 24 + lsrs r4, r0, 24 +``` +```c +void sub_81231EC(u8 r4) { +``` +--- +```asm + ldr r0, _08123210 @ =gPaletteFade + ldrb r1, [r0, 0x7] + movs r0, 0x80 + ands r0, r1 +``` +```c + r0 = (u8 *)(&gPaletteFade + 7) & 0x80; +``` +--- + +--- +```asm + cmp r0, 0 + bne _0812320A +``` +```c + if (!r0) { +``` +--- +```asm + ldr r0, _08123214 @ =sub_8123244 + bl SetMainCallback2 +``` +```c + SetMainCallback2(&sub_8123244); +``` +--- +```asm + adds r0, r4, 0 + bl DestroyTask +``` +```c + DestroyTask(r4); +``` +--- +```asm +_0812320A: +``` +```c + } +``` +--- +```asm + pop {r4} + pop {r0} + bx r0 +``` +```c + 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: +```c +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. + +```c + r0 = (u8 *)(&gPaletteFade + 7) & 0x80; +``` + +What is `gPaletteFade`? You can find out where stuff is with `git grep`: + +```sh +git grep "gPaletteFade" include/ +``` +```grep +include/palette.h:extern struct PaletteFadeControl gPaletteFade; +``` + +So it's a struct called `PaletteFadeControl`. Let's look in `palette.h`: + +```c +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? +```c + 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. + +```c + r0 = gPaletteFade.active; +``` + +Much better. + +--- + +```c +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: + +```c +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 + +```sh +make compare +``` +```gcc +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: +```c +#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? +```c +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: +```c +#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: +```sh +make compare +``` +```sha1sum +pokeruby.gba: OK +``` + +This means the function matches. Congratulations! + +--- + +If it doesn't match, you will get: +```sha1sum +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: +```gcc +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`. + @@ -1,4 +1,4 @@ -# Pokémon Ruby and Sapphire [](https://travis-ci.org/pret/pokeruby) +# Pokémon Ruby and Sapphire [![Build Status][travis-badge]][travis] This is a disassembly of Pokémon Ruby and Sapphire. @@ -9,8 +9,8 @@ It builds the following roms: To set up the repository, see [INSTALL.md](INSTALL.md). -## See also - -irc: [freenode#pret][irc] +### irc: [freenode#pret][irc] [irc]: https://kiwiirc.com/client/irc.freenode.net/?#pret +[travis]: https://travis-ci.org/pret/pokeruby +[travis-badge]: https://travis-ci.org/pret/pokeruby.svg?branch=master |