????? party overloading - analysis and exploitation
Posted by: ISSOtm
Date: 2017-09-29 11:47:39
First, let's quickly recap how it's done :
- Have a ????? (species FF) at the top of a 6-Pokémon party (Example below)
[img]http://puu.sh/xLJH4/7fb5545668.jpg[/img]
- Use "Move Pkmn w/o Mail" to place a Pokémon on the first party slot
- Select "Withdraw" and start withdrawing Pokémon, which can be done even though there are more than 6.
Each Pokémon withdraw will overwrite an area of memory, such as roaming Pokémon structs, etc.
Now, I'm going to explain how it works, so you can get an idea on this glitch's internals.
I'll use Crystal as a reference because it's way more documented (disassembly-wise) than Gold/Silver, and the glitch works there too.
As to how the glitch works, there are two culprits :
The first culprit is CopyBoxmonSpecies. This function copies the target "box" (the party is considered a box there), and updates wBillsPC_NumMonsInBox to contain the correct number of Pokémon. Problem is, the function stops its copy as soon as it finds a $00 or $FF byte (I'm not sure why the programmers decided that both of these would be terminators, but… that's how it works !). Thus, only the $FF Pokémon's species is copied into the buffer, which tricks BillsPC_CheckSpaceInDestination into thinking there are exactly 0 Pokémon into our party.
Because this function is meant to allow or refuse transfers to our party, it will let us obtain a party containing more than 6 Pokémon. This lets us slide into part 2…
(I didn't investigate more "Move PkMn w/o Mail", and I don't think it would yield a very different corruption than Withdrawing. I may be wrong, though.)
The second culprit is SentGetPkmnIntoFromBox (names like that can't be made up), which is called by TryWithdrawPokemon.
This function is responsible for moving Pokémon around (party, boxes, Daycare). Here, it's called with wPokemonWithdrawDepositParameter being 0, which prompts it to check if the party isn't full. Unfortunately (for the programmers), the check only checks if there are EXACTLY 6 Pokémon in the party !
Because "part 1" allowed us to obtain a 7-Pokémon party, the check returns non-zero and we are allowed to nicely proceed.
For the rest of this explanation, I'll use a small shortcut : N will represent the number of Pokémon in the party prior to withdrawing.
So, what happens when we withdraw a Pokémon ?
First, the game writes the Pokémon's species at (DCD8 + N).
Then it writes a $FF at (DCD8 + N + 1) to properly terminate the list.
Then, it copies the boxed Pokémon's data (0x20 bytes) to (DCDF + N * 0x30)
[Aparté : a boxed Pokémon is 0x20 bytes large ; a party Pokémon is 0x30 bytes large, the first 0x20 being the same bytes as its boxed counterpart, with the remaining 0x10 bytes being the status, an unused byte, and the stats]
Then, it copies the Pokémon's OT name (11 bytes) to (DDFF + N * 11).
Then it copies the Pokémon nickname (11 bytes) to (DE41 + N * 11).
The game then calculates its level, resets its status, and writes its stats. It also sets its HP to its max HP (except if it's an egg, then it sets HP to 0).
SO ! Let's recap the corruption that occurs. Here's a handy list of all the writes that occur, by order of appearance (meaning some could override others !!) :
(Note : I'm not certain about endianness, but assume big-endian unless otherwise stated)
[table][tr]
[td]Start[/td][td]| End[/td][td]| Content[/td]
[/tr]
[tr]
[td]DCD8 + N[/td][td]| DCD8 + N[/td][td]| Species[/td]
[/tr]
[tr]
[td]DCD8 + N + 1[/td][td]| DCD8 + N + 1[/td][td]| $FF[/td]
[/tr]
[tr]
[td]DCDF + N * 0x30[/td][td]| DCDF + N * 0x30[/td][td]| Species[/td]
[/tr]
[tr]
[td]DCDF + N * 0x30 + 1[/td][td]| DCDF + N * 0x30 + 1[/td][td]| Held item[/td]
[/tr]
[tr]
[td]DCDF + N * 0x30 + 2[/td][td]| DCDF + N * 0x30 + 2[/td][td]| Move 1[/td]
[/tr]
[tr]
[td]DCDF + N * 0x30 + 3[/td][td]| DCDF + N * 0x30 + 3[/td][td]| Move 2[/td]
[/tr]
[tr]
[td]DCDF + N * 0x30 + 4[/td][td]| DCDF + N * 0x30 + 4[/td][td]| Move 3[/td]
[/tr]
[tr]
[td]DCDF + N * 0x30 + 5[/td][td]| DCDF + N * 0x30 + 5[/td][td]| Move 4[/td]
[/tr]
[tr]
[td]DCDF + N * 0x30 + 6[/td][td]| DCDF + N * 0x30 + 7[/td][td]| OT ID[/td]
[/tr]
[tr]
[td]DCDF + N * 0x30 + 8[/td][td]| DCDF + N * 0x30 + 10[/td][td]| Experience[/td]
[/tr]
[tr]
[td]DCDF + N * 0x30 + 11[/td][td]| DCDF + N * 0x30 + 12[/td][td]| HP stat experience[/td]
[/tr]
[tr]
[td]DCDF + N * 0x30 + 13[/td][td]| DCDF + N * 0x30 + 14[/td][td]| ATK stat experience[/td]
[/tr]
[tr]
[td]DCDF + N * 0x30 + 15[/td][td]| DCDF + N * 0x30 + 16[/td][td]| DEF stat experience
[/td]
[/tr]
[tr][td]DCDF + N * 0x30 + 17[/td][td]| DCDF + N * 0x30 + 18[/td][td]| SPD stat experience
[/td][/tr]
[tr][td]DCDF + N * 0x30 + 19[/td][td]| DCDF + N * 0x30 + 20[/td][td]| SPE stat experience
[/td][/tr]
[tr][td]DCDF + N * 0x30 + 21[/td][td]| DCDF + N * 0x30 + 22[/td][td]| DVs
[/td][/tr]
[tr][td]DCDF + N * 0x30 + 23[/td][td]| DCDF + N * 0x30 + 23[/td][td]| Move 1 PP
[/td][/tr]
[tr][td]DCDF + N * 0x30 + 24[/td][td]| DCDF + N * 0x30 + 24[/td][td]| Move 2 PP
[/td][/tr]
[tr][td]DCDF + N * 0x30 + 25[/td][td]| DCDF + N * 0x30 + 25[/td][td]| Move 3 PP
[/td][/tr]
[tr][td]DCDF + N * 0x30 + 26[/td][td]| DCDF + N * 0x30 + 26[/td][td]| Move 4 PP
[/td][/tr]
[tr][td]DCDF + N * 0x30 + 27[/td][td]| DCDF + N * 0x30 + 27[/td][td]| Happiness
[/td][/tr]
[tr][td]DCDF + N * 0x30 + 28[/td][td]| DCDF + N * 0x30 + 28[/td][td]| Pokérus status
[/td][/tr]
[tr][td]DCDF + N * 0x30 + 29[/td][td]| DCDF + N * 0x30 + 30[/td][td]| Caught data
[/td][/tr]
[tr][td]DDFF + N * 11[/td][td]| DDFF + N * 11 + 10[/td][td]| OT name[/td][/tr]
[tr][td]DE41 + N * 11[/td][td]| DE41 + N * 11 + 10[/td][td]| Nickname[/td][/tr]
[tr][td]DCDF + N * 0x30 + 31[/td][td]| DCDF + N * 0x30 + 31[/td][td]| Level
[/td][/tr]
[tr][td]DCDF + N * 0x30 + 32[/td][td]| DCDF + N * 0x30 + 32[/td][td]| Status
[/td][/tr]
[tr][td]DCDF + N * 0x30 + 34[/td][td]| DCDF + N * 0x30 + 35[/td][td]| Max HP (or 0 if Egg)
[/td][/tr]
[tr][td]DCDF + N * 0x30 + 36[/td][td]| DCDF + N * 0x30 + 37[/td][td]| Max HP
[/td][/tr]
[tr][td]DCDF + N * 0x30 + 38[/td][td]| DCDF + N * 0x30 + 39[/td][td]| Attack stat
[/td][/tr]
[tr][td]DCDF + N * 0x30 + 40[/td][td]| DCDF + N * 0x30 + 41[/td][td]| Defense stat
[/td][/tr]
[tr][td]DCDF + N * 0x30 + 42[/td][td]| DCDF + N * 0x30 + 43[/td][td]| Speed stat
[/td][/tr]
[tr][td]DCDF + N * 0x30 + 44[/td][td]| DCDF + N * 0x30 + 45[/td][td]| Special attack stat
[/td][/tr]
[tr][td]DCDF + N * 0x30 + 46[/td][td]| DCDF + N * 0x30 + 47[/td][td]| Special def stat
[/td][/tr]
[/table]
This is quite interesting in Crystal, because these are located very near the end of WRAM, thus near the beginning of Echo RAM. At the beginning of Echo RAM is the stack, which could allow for another ACE method ! My next goal is to research ACE on Crystal using this.
Shoutouts to SMF for not allowing borders on their tables. Geniuses.