StarFox Command Control Hacking Tutorial

Preface

This tutorial will go over the methods used to create the StarFox Command D-pad hack. It will require knowledge of cheat finding as well as understanding how hooking works. For the former you can check out the tutorial at the EnHacklopedia. (I use no$gba debug with ArtMoney to find addresses.) For the latter you should read my DS Hooking Tutorial. You will also need to be able to use a hex editor!

Required Knowledge

Cheat finding
Hex editor usage
Programming knowledge (ARM assembly preferred)

Required Tools

DS emulator -- iDeaS or no$gba*
Text editor
Hex editor
ARM7Fixa
ARM7extractor
ARM ASM kit
*Note that the method used for this hack requires using a break on write in no$gba to find the function to hook into. no$gba is the only emulator capable of setting breakpoints so you will need to pay for the debug version if you want to hack a different game in the same fashion. (If a dynamic address isn't used for the value(s) you need to modify then this isn't required since a regular hook function will be sufficient for a static address. For the purpose of following along with this tutorial the debugger in iDeaS will be sufficient.

Setting up

Extract arm7fix.rar, arm7extractor.rar, armasmkit.rar to the same folder

Information gathering

Through cheat searches I found that 0x22FAFF8 contained the up/down movement counter and 0x22FAFFA contained the left/right movement counter. These counters control how the ship still needs to move. Ex: If the value of the up/down counter is negative then it means the ship needs to move down. It will load the value in a sub-function and then lower the Y-axis of the ship and add to the counter until it is 0.

In pseudocode:
	signed short x=[value of up/down counter]
	if x<0 {
		ship.currentx-=0xC8
		x+=0xC8
		if x>0 then x=0
		[up/down counter]=x
	}
	if x>0 {
		ship.currentx+=0xC8
		x-=0xC8
		if x<0 then x=0
		[up/down counter]=x
	}
Unfortunately, after more testing I found out that the addresses that held the counters could change to different locations (a dynamic address). So that is when I realised the best option was to find the area in the game's programming that had a pointer to the area in RAM that stored the current 'variables'. What better place to hook into than a function for checking the status of the counters...

Through some debugging I found the place in the game where this occurs. 0x21E9D34 is the start of the sub-routine to read the current counters. this code is in Thumb so anywhere from 2 to 4 opcodes will need to be overwritten. In order to find a good place to place our jump code we must make sure that there aren't any opcodes that get destroyed that can't be recovered by a patch (or that would require an un-needed workaround).

Opcodes to avoid overwriting:
ldr r1,=0x2FFF
Any branch
Any conditional opcode (eq,ne,lt,le,gt,ge,cs,cc,hi,lo,etc)
Ex: moveq, addne, strlt, etc.
If you have the no$gba debug version the set a break on reading one of the counters -- [0x22FAFF8]?

We get a hit on a breakpoint for reading from 0x22FAFF8 at 0x21E9D36. We can see that the function spans from 0x21E9D34 to 0x21E9D46 where it does a bx r14 (this is the most common method of returning from a sub-routine but pop r15 or mov r15 can be used when not moving between ARM and Thumb).

We can see that r1 contains the pointer used for accessing the counters -- 0x22FAF60 currently. So we subtract the counter addresses from the value in r1 and get our offsets -- 0x98 for up/down & 0x9A for left/right.

You will now need to find 4 opcodes that can be overwritten within the boundaries of this function that MUST start at an offset divisible by 4 so we don't have an alignment problem. Luckily there is enough room at the start of the sub-routine to patch in a jump to our D-pad handler so we can plae it at 0x21E9D34. r0,r1,r2 can't be used for the return jump. By stepping through the program we can see that jumps to the sub-function doesn't seem to be using r3 at all so we will try it and see if there are any side effects.

Through more cheat finding I found that the flags for brake and boost were stored in an address far away from where the address of the movement counters were found. Since it was so far off it was easy to see that a different pointer was used. This means we will have to hook into another sub-routine in the original code.

The flag for boost is located at 0x233A48E and the flag for brake is located at 0x233A48E. 0 = off / 1 = on

We get a hit on a breakpoint for reading from 0x233A48E at 0x21EE4CC.

r4 contains the pointer address -- 0x233A380 currently. We subtract the addresses of our flags and get an offset of 0x10E and 0x10F respectively.

We will also need the offset for the value of the boost meter so we can check it in our handler. It is located at 0x233A43C currently so the offset is 0xBC.

Now for placement... We can see that the function starts at 0x21EE4C4 (looking for a push r14 is usually an easy way to find it -- failing that a push of another register will usually do). In this case the function splits off into different sub functions so space isn't an issue at all. By looking at the code 0x21EE4CC looks like a good place to put the jump to our handler. Note how at 0x21EE4D4 r0 is overwritten -- lsl r0,r2,0x1e. So we know we can safely use r0 to jump back to the function.

Now we have all the information to make our hack.

Since the opcodes that need to be overwritten are in the ARM9 and they are compressed inside the ROM we will need to create an IRQ hook that will check for the right opcodes to be present before it overwrites anything.
How it works

Booting up
	The ARM9 waits for the ARM7
	The ARM7 starts execution at 0x2380000
	It jumps from 0x2380000 to the code copying function @ 0x23A86B0
	Replaces the IRQ handler address with our key handler installer address
	Replaces the key mask so D-pad,A,B input is ignored
	A return jump is made to 0x2380008

IRQ handler hook
	Jumps to our key handler installer
	Jumps to the real IRQ handler

X/Y movement sub-routine
	Starts at 0x21E9D34
	Jumps to our D-pad handler @ 0x2000000
	Movement counters are updated if the D-pad is touched
	A return jump is made to 0x21E9D3C

Boost/Brake routine
	Starts at 0x21EE4CC
	Jumps to our A/B handler @ 0x200006C
	Boost or brake flag is set if A or B is pressed and the boost meter isn't empty
	A return jump is made to 0x21EE4D4

The code

;@ SFC Control Hack v5 by cracker
BASE_ADDR = 0x2000000 ;@ #define BASE_ADDR 0x2000000

;@ Appended to arm7.bin @ 0x23A86B0
;@ Jumped to from 0x2380000 at boot
;@ This function dynamically copies
;@ the custom key handler code as
;@ well as the hook installers
ldr r4,irqpointer
ldr r0,[r4]
str r0,realirq				;@ save the original IRQ handler address
ldr r3,copylength			;@ r3 = counter of 32-bit ints to copy
ldr r2,dpadhandler			;@ r2 = destination address
add  r1,r15,#(top-copyloop-4) 		;@ r1 = source address (points to 'top')
copyloop:
ldr r0,[r1],#+0x4			;@ read a 32-bit int from the source and increase the source pointer
str r0,[r2],#+0x4			;@ write the int to the destination and increase the destination pointer
subs r3,r3,#0x1				;@ decrease the counter
bne copyloop				;@ loop until the counter is 0
ldr r0,newirq
str r0,[r4]				;@ replace the original IRQ handler address with our key handler installer address
mov  r12,#0x4000000			;@ Patch in overwritten opcodes from 0x2380000-0x2380007
str r12,[r12, #+0x208]
ldr r15,returnaddress			;@ jump to the original ARM7 bootup code
returnaddress: .long 0x2380008
irqpointer: .long 0x23800f4
;@ End of dynamic copy code
copylength: .long (bottom-top)/4	;@ used to determine the number of words to copy at runtime

;@ Start of code to be copied
;@ to a safe area in RAM
top:
;@ Start of D-pad handler
stmdb r13!,{r0-r12,r14}			;@ save registers on the stack
mov r4,#0x4000000
ldr r5,[r4,#+0x130]			;@ read key register
ldrh r3,[r1,#+0x9a]			;@ load current X-axis movement counter
ands r6,r5,#0x20			;@ left - if r6 = 0, left is being pressed
bne noleft
add r3,r3,#0x100			;@ add to the movement counter
noleft:
ands r6,r5,#0x10			;@ right - if r6 = 0, right is being pressed
bne noright
sub r3,r3,#0x100			;@ subtract from the movement counter
noright:
strh r3,[r1,#+0x9a]			;@ save the new X-axis movement counter
ldrh r3,[r1,#+0x98]			;@ load current Y-axis movement counter
ands r6,r5,#0x40			;@up - if r6 = 0, up is being pressed
bne noup
add r3,r3,#0x100			;@ add to the movement counter
noup:
ands r6,r5,#0x80 			;@down - if r6 = 0, down is being pressed
bne nodown
sub r3,r3,#0x100			;@ subtract from the movement counter
nodown:
strh r3,[r1,#+0x98]			;@ save the new Y-axis movement counter
ldmia r13!,{r0-r12,r14}			;@ restore registers from the stack
;@ patch 4 overwritten opcodes
;@ from 0x21e9d34-0x21e9d3b
mov r2,#0x98
ldsh r2,[r1,r2]
strh r2,[r0]
mov r2,#0x9a
;@ patch end
ldr r3,returnaddr
bx r3					;@ return from d-pad handler 
returnaddr: .long 0x21e9d3d		;@ return address ORed 1 to switch to Thumb mode
;@ End of D-pad handler

boostbrake:
;@ Start of brake, boost handler
stmdb r13!,{r0-r12,r14}
mov r1,#0x4000000
ldr r1,[r1,#+0x130]			;@ read key register
;@ code to reset ability to brake or
;@ boost after a and/or b are released
and r0,r1,#0x3				;@ mask out everything but a, b
eors r0,#0x3				;@ toggle first two bits
moveq r0,#0x1				;@ if neither a nor b pressed reset flag to allow braking and boosting
streq r0,canuseboost
;@ code end
ldr r0,canuseboost
cmp r0,#0x0
beq noboost
add r4,#0x100				;@ increase pointer address
mov r0,#0x49000				;@ max boost used value - 0 = full meter
ldr r2,[r4,#-0x44]			;@ load current boost used value
cmp r2,r0				;@ compare the value to the max boost used
movge r0,#0x0				;@ no boost left
movlt r0,#0x1				;@ boost left
str r0,canuseboost			;@ save the flag to disable boost or brake
bge noboost
ands r0,r1,#0x3				;@ a + b pressed?
streqb r0,[r4,#+0xf]			;@ turn brake flag off
beq onlyboost				;@ only boost
and r0,r1,#0x2				;@ b pressed? - brake
lsr r0,#0x1				;@ shift r0 one bit to the right - quick way of dividing by 2	
eor r0,#0x1				;@ invert value since 0 = button down, 1 = button up
strb r0,[r4,#+0xf]			;@ store brake flag
onlyboost:
ands r0,r1,#0x1				;@ a - boost
eor r0,#0x1
strb r0,[r4,#+0xe]			;@ store boost flag
noboost:
ldmia r13!,{r0-r12,r14}
;@ patch 4 overwritten opcodes
;@ from 0x21ee4cc-0x21ee4d3
ldrb r3,[r4,+r0]
add r0,#0x3
add r2,r4,r0
mov r1,#0xff
;@ patch end
ldr r0,bbreturn
bx r0
bbreturn: .long 0x21ee4d5
canuseboost: .long 0x1
;@ End of brake, boost handler

hookinstaller:
;@ Start of hook installer
;@ Installs key handlers
;@ with the IRQ hook
stmdb r13!,{r0-r12,r14}
ldr r1,dpadhookaddr
ldr r2,[r1]				;@ load longword from dpadhookaddr
ldr r3,dpadhooktest
cmp r2,r3				;@ test opcodes to see if the right code is in memory
bne nottheredpad
ldr r2,dpadhook
str r2,[r1],#0x4			;@ write opcodes - ldr r2,dpadhandler / bx r2
ldr r2,dpadhandler
str r2,[r1]				;@ write address of d-pad handler
ldr r1,keymaskaddr
mov r2,#0xc
strb r2,[r1]				;@ mask out all of the original input handling for the d-pad, a, b
nottheredpad:
ldr r1,bbhookaddr
ldr r2,[r1]
ldr r3,bbhooktest
cmp r2,r3
bne nottherebb
ldr r2,bbhook
str r2,[r1],#0x4			;@ write opcodes - ldr r3,bbhandler / bx r3
ldr r2,bbhandler
str r2,[r1]
nottherebb:
ldmia r13!,{r0-r12,r14}
ldr r15,realirq
realirq: .long 0x0
dpadhookaddr: .long 0x21e9d34
dpadhooktest: .long 0x5e8a2298
dpadhook: .long 0x47104a00
dpadhandler: .long BASE_ADDR
bbhookaddr: .long 0x21ee4cc
bbhooktest: .long 0x48dd5c23
bbhook: .long 0x47184b00
bbhandler: .long BASE_ADDR+(boostbrake-top)
keymaskaddr: .long 0x203d414
;@ End of hook installer
bottom:
;@ End of code to be copied to a safe area in RAM
newirq: .long BASE_ADDR+(hookinstaller-top)
Note that this code is for 'classic' flight controls where up and down are reversed. If you want to have normal controls change add<->sub for the up and down key handlers.

Applying the patch

Extract the arm7.bin from the ROM with ARM7extractor

Save the code in a text file -- sfchack.asm
Assemble the code with arm-eabi-as*:

	arm-eabi-as -o sfchack.o -mlittle-endian sfchack.asm

This will create an ELF file which will need to have our code extracted from it.
Note: This will not work if your code uses pools. For those new to assembly, a pool will be created/used if you use an opcode like ldr r0,=0x4000130. Instead define the data with a label and load it from there. (ldr r0,keyreg / keyreg: .long 0x4000130)
Search for 41 13 00 00 00 61 65 61 62 69

The address that the search string is found at is the start of the un-needed ELF data

Copy from 0x32 to the address before the search hit (0x20B in this case)*

Append this to the end of the arm7.bin (at 0x286B0)

This can be done in a hex editor or with a command prompt or batch file

	copy /B arm7.bin+sfchack.bin arm7.bin

Replace code in the arm7.bin at 0x0:

	ldr r15,returnaddress
	returnaddress: .long 0x23A86B0

Assembled:

	04 F0 1F E5 B0 86 3A 02

Save the new arm7.bin

Rebuild the new ROM with the new arm7.bin with ARM7Fixa
*Note that the ARM ASM kit contains assemble.bat that will automatically assemble the source file that is dropped onto it and cut out everything un-needed from the ELF. The resulting file is will be named filename.bin. I recommend doing it by hand the first time so you can better understand the process.