mirror of
https://github.com/pret/pokeruby.git
synced 2024-12-11 14:54:06 +00:00
341 lines
6.9 KiB
Markdown
341 lines
6.9 KiB
Markdown
|
# 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`.
|
||
|
|