Part1 - Adventures of code
Finding the code.
When starting this project, I thougth, my disassembler is following the code. That should mark most of the code directly…
OH BOY, I was wrong. I quickly noticed only a small portion of the rom was disassembled as code. The first cause was quickly to spot. Jumptables. Example:
ld HL, data_38ee
ld B, $00
ld C, A
add HL, BC
add HL, BC
ld A, [HL+]
ld H, [HL]
ld L, A
jp HL
data_38ee:
dw $34e7, $34f4, $3502, $351a, $357d, $3582
Now, these are all over the place. Luckily, I was accounting for this. Marking the data_38ee
label with an ;@jumptable
is usually enough.
It will make all pointers after it as labels to code. And it will stop as soon as it encounters something that is marked as something already.
As the first pointer is usually directly after the table, it will stop there.
In some rare cases, it requires to specify the size of the table. Like: ;@jumptable amount=16
. This quickly expanded the amount of code,
as from any jumptable pointer the disassembler traced any code called from it.
The odd one… farcalling.
After marking most of the jumptables, a pattern arose. At the start of any bank with code (except for bank0F), there is a jump table at the start of the bank.
And digging a tiny bit more, it clicked. Some roms have a farcall
function to allow to call a function in a different bank. But FFA does not have that, instead
it has the option to call a function from that first jumptable. I’m not sure why they did it that way, it uses quite a bit of code. But it does preserve all
registers while calling, which is something not often seen with farcalls.
Example:
callFunction02InBank01:
push AF
ld A, $02 ; Function index
jp callFunctionInBank01
callFunctionInBank01:
ld [wScratchBankCallFunctionNumber], A
pop AF
ld [wScratchBankCallA], A
ld A, H
ld [wScratchBankCallH], A
ld A, L
ld [wScratchBankCallL], A
ld HL, returnFromBankCall
push HL ; push code that has to run after returning from the function, which will restore the bank
ld A, $01
call pushBankNrAndSwitch
ld A, [wScratchBankCallFunctionNumber]
add A, A
ld L, A
ld H, $40
ld A, [HL+]
ld H, [HL]
ld L, A
push HL ; push the function address on the stack, so the RET at the end will goto it
ld A, [wScratchBankCallH]
ld H, A
ld A, [wScratchBankCallL]
ld L, A
ld A, [wScratchBankCallA]
ret
returnFromBankCall:
push AF
push HL
call popBankNrAndSwitch
pop HL
pop AF
ret
The first short part exists once per function, and the second part exists once per bank.
After that…
After this, I’m confident all code that is actually used is disassembled. And there is a lot of it.
I’m used to reading the code of LADX, and that’s very different from FFA. While LADX has lots of very direct code, FFA on the other hand, it very indirect. It’s very often writting something to a WRAM location to later use as trigger or data.
And there are a few other things noticed after this. Bank0F deals with sound and music. It’s obvious, because it’s the only code that is touching the sound registers. Bank0D and Bank0E are “odd”, they have patterns like code, but are not code. More on that later, as that’s a lot to take in.
Banks 05, 06 and 07 are just all data. With 08, 09, 0A, 0B and 0C all being mostly graphics, it gives a pretty overal picture. The big blocks of data are quite certainly part of map data. So let us look into that next.
Bank encoded pointers
While trying to find more pointers to data hiding in data. We find something that the disassembler cannot handle without customization. Bank encoded pointers, as a bank is only $4000
bytes big, you only need 14 bits to store a pointer to it. And in some cases, the last 2 remaining bits are then used to encode which bank it is pointing at. The bank to start from depends on the pointer.
Whenever I referrer to a bank encoded pointer, it’s this style of pointer.
- Previous: Part0 - Humble beginnings
- Next: Part2 - The quest for maps