Grab ACE exit
How to exit ACE?
Under normal circumstances, when swapping two Pokémon in the PC the game does the following:
- Sets the current
PokeStorageTask
toTask_ShiftMon
Task_ShiftMon
callsDoMonPlaceChange
, waiting for the function to returnfalse
to continue.DoMonPlaceChange
calls membermonPlaceChangeFunc
stored ingStorage
which should normally be the pointer ofMonPlaceChange_Shift
during a swap.- The return type of
DoMonPlaceChange
is specified asu8
(unsigned 8-bit integer). - A boolean’s numerical form is typically
0
isfalse
, everything else istrue
.
- The return type of
MonPlaceChange_Shift
does the actual operation of swapping the two Pokémon, eventually returningfalse
, allowingTask_ShiftMon
to continue and change thePokeStorageTask
toTask_PokeStorageMain
.
However when perform this swap with a glitch Pokémon with a particularly long name, a few things happen:
- Parts of the long name overwrite the pointer stored in
monPlaceChangeFunc
each time the long name is loaded (which is after the first iteration ofDoMonPlaceChange
during an animation). - As a result when its time for
DoMonPlaceChange
to callmonPlaceChangeFunc
, it ends up jumping to whatever “pointer” the very long name happen to overwritemonPlaceChangeFunc
to. - In most cases, this leads to a crash but for some (like species 0x351) this allows for arbitrary code execution!
While arbitrary code execution is neat, once we done what we wanted to do, how do we safely return control back to the game?
If we look at what the game does normally while swapping two Pokémon, Task_ShfitMon
which called the “pointer” that the long name wrote in is expecting a u8
that equals to false
which is 0
.
Translating this to GBA ARM assembly, at its simplest form, we get this:
MOV r0, #0x0 ; setting return value to false, r0 is used as the return value
BX lr ; jump to return address
While this looks simple to implement, due to the circumstances of the ACE environment, the exit have been reimplemented in a number of different ways which will be covered in the next section.
Implementation of this exit
The standard exit(s)
While MOV r0, #0x0
is not writable in its standard form, MOVS r0, #0x0
is (it sets the Z
flag, but that does not matter).
BIC r0, #0xFF
is also an acceptable alternative since the return type of DoMonPlaceChange
is u8
, clearing the lower 8 bits of r0
is enough to return false
.
However BX lr
is not writable with the standard character set, glitchers have to come up with a way to be able to write BX lr
.
The opcode for BX lr
can be computed into a register then be stored in a word-aligned address after the current value of the pc
register.
This means that the STR
instruction must be located on the first half of Box 13, or earlier, as read-ahead will mean that the BX lr
instruction will not be executed.
Thus while this method works, will also add to the length of the box name payload, the only upside to this method is that it works wihtout needing a genuine GBA BIOS.
BX lr computation instructions
E3E0B6EE MVN r11, #0xEE00000
E2CBB0DF SBC r11, #0xDF
E2CBB6FF SBC r11, #0xFF00000 ; r11 = E12FFF1E (BX lr)
or this:
E3E0B0E0 MVN r11, #0xE0
E3CBB6EE BIC r11, #0xEE00000
E2CBB6FF SBC r11, #0xFF00000 ; r11 = E12FFF1E (BX lr)
Note
There is also a BIOS jump for BX r0
, the MOVS
instruction looks like:
E3B0FFC9 MOVS pc, #0x324
But merrp and luckytyphlosion found an even shorter method of executing BX lr
with the limited character set available, calling MOVS pc, <immediate>
with the immediate being the address of an ARM mode BX lr
.
It just so happens that one such compatible address is located in the BIOS producing the following end result:
E3B0FFD5 MOVS pc, #0x354
Which when combined with MOVS r0, #0x0
looks like:
E3B00000 MOVS r0, #0x0
E3B0FFD5 MOVS pc, #0x354
It also happens that MOVS pc, <immediate>
is only writable using one of the EOF terminators, so with this exit code the code can be 3, 6 or 11 box names long.
In box name form, it looks like this:
Box 10: _ _ _ _ _ … o a [ …oa]
Box 11: … o […o]
This form of the exit is also used, for codes that need to fit one more instruction then what the standard exit allows:
E3C000FF BIC r0, #0xFF ; r0 & 0xFF = 0, set return value to false
; (insert extra unrelated instruction)
E3B0FFD5 MOVS pc, #0x354
In box name form, it looks like this:
Box 10: _ F o * * * * a [ Fo****a]
Box 11: … o […o]
* = extra instruction
Box 14 exit
This exit consists of the following instructions in the name of Box 14:
E3C000FF BIC r0, #0xFF ; set return value to false
E12FFF1E BX lr
and in box form, it looks like this:
Box 14: _ F o ì [ Foì]
Below are the instructions of the code used to setup the Box 14 exit
E3E0B6EE MVN r11, #0xEE00000
E2CBB0DF SBC r11, #0xDF
E2CBB6FF SBC r11, #0xFF00000 ; r11 = E12FFF1E
E2CFCFE2 SBC r12, pc, #0x388 ; r12 = Address of Box 14 name - 0x3ED
0000FF00 ; (filler)
E5ACB3ED STR r11, [r12, #0x3ED]! ; Store BX LR in Box 14 name
...
E3C000FF BIC r0, #0xFF
00000000 ; becomes BX lr (E12FFF1E)
Improved box 14 exit
Mettrich came up with this exit which does not require a set up code to write a BX instruction to the name of box 14.
Instead it just requires changing the name of Box 14 as well as changing the box wallpapers.
This code jumps to the BX lr
instruction located in the GBA BIOS.
The exit consists of the following instructions:
E3B0CFFF MOVS r12, #0x3FC
E3B00000 MOVS r0, #0x0 ; set return value to false
020CFFD5 ANDEQ pc, r12, #0x354 ; jump to 0x354 (BX lr)
The box name component of the exit looks like this:
Box 14: U … o _ _ … o a [U…o …oa]
And below are the box wallpaper settings needed for the exit:
- Box 1: STARS (0x0C)
- Box 2: DESERT (0x02)
r0
bootstrap
The current exit code bootstrap (called the r0
bootstrap to distinguish it from an older bootstrap) consists of two parts.
It is a shiny female Beedrill with name Â␣␣nÔ␣␣v␣␣
and OT ␣î␣C␣␣␣
.
The nickname consists of the following instructions:
E28F0003 ADD r0, pc, #0x3 ; sets r0 to address of OT
EA00000F B pc+0x44
As bit 0 of r0
is set; when a BX r0
instruction is executed, the CPU will be executing instructions there in Thumb mode, rather than ARM.
Thumb mode allows the exit code written in the OT to be smaller byte-wise, and it also makes the setup code shorter as well.
The OT consists of the following Thumb instructions:
2000 MOV r0, #0x0
BD00 POP {pc}
In this context, POP {pc}
jumps to the middle of Task_ShiftMon
where it checks whether the return value of DoMonPlaceChange
evaluates to false
, this is just another way of jumping to a return address (though it is not the same return address as the one defined in lr
, which is located in the middle of DoMonPlaceChange
).
Below is the instructions of the box name code used to create the bootstrap (note that it exits using a box 14 exit set up earlier):
E2CFBDA5 SBC r11, pc, #0x2940 ; r11 = address of Box 10, Slot 19 + 9
E3B0CCE2 MOVS r12, #0xE200
E3CCCAFF BIC r12, #0xFF000 ; r12 = 0200, hasSpecies set
E1CBC0BA STRH r12, [r11, #0xA] ; Store hasSpecies flag
0000FF00 ; (filler)
E3B0C5E7 MOVS r12, #0x39C00000
00FF0000 ; (filler)
E2ACC3EA ADC r12, #0xA8000003
FF000000 ; (filler)
E2ACC8CF ADC r12, #0xCF0000 ; r12 = E28F0003 (ADD r0, pc, #0x3)
E5ABC000 STR r12, [r11]! ; Store in nickname chars 0-3
E3B0C0FF MOVS r12, #0xFF
E2ABB3C0 ADC r11, #0x3 ; r11 = Box 10, Slot 19 + 12
0000FF00 ; (filler)
E2ACC4EA ADC r12, #0xEA000000
00FF0000 ; (filler)
E3CCCECF BIC r12, #0xCF0 ; r12 = EA00000F (B pc+0x44)
FF000000 ; (filler)
E1CBC1B0 STRH r12, [r11, #0x10] ; Store checksum
E1CBC1B4 STRH r12, [r11, #0x14] ; Store species
E3B0ACFF MOVS r10, #0xFF00 ; Temporarily store in r10
E5ABC000 STR r12, [r11]! ; Store in nickname chars 4-7
0000FF00 ; (filler)
E2ABB2B0 ADC r11, #0xB ; r11 = Box 10, Slot 19 + 23
00FF0000 ; (filler)
E2AAC4BD ADC r10, r12, #0xBD000000 ; move r10 into r12
FF000000 ; (filler)
E3CCCCDF BIC r12, #0xDF00 ; r12 = BD002000 (MOV r0, #0 ; POP {pc})
E5ABC000 STR r12, [r11]! ; Store in OT
Legacy bootstrap
This bootstrap can be made in a variety of ways, but all it does is setting r0
to 0
and uses a BX lr
written somewhere else to complete an exit.
The current version of this bootstrap is made entirely by mail corruption and below is the mail message with the words replaced by their hexadecimal indexes:
0200 0200
0000 1200
003F 0A00
003F 1A00
0000
Which is interpreted as:
02000200 ANDEQ r0, #0x0 ; encoding ensures resulting bad egg is visible
12000000 ANDNE r0, #0x0
0A00003F B pc+0x104 ; jumps to ~3 box slots ahead
1A00003F B pc+0x104
Acknowledgements
- pret (Pokémon Reverse Engineering Tools) for the
pokefirered
decompilation which allowed me to make sense of the exit - Adrichu00 for clarifying to me how the game handles the task call when a glitch Pokémon with a long name is involved
- merrp for showing various tricks which allowed
- RationalPsycho for suggesting the use of mail to shorten the process of writing payloads
- Sleipnir17 for the
BX r0
set up code which has been adapted forBX lr
- E-Sh4rk for his CodeGenerator which was used to create many of the box name codes
- RETIRE for suggesting a better way to jump to a return address in Thumb (
POP {pc}
) - デテロニー for the Japanese FR/LG ACE setup which also convinced me to use
POP {pc}
. - Mettrich on the Glitch City Research Institute Discord server for creating the improved box 14 exit.