Glitch City Laboratories Archives

Glitch City Laboratories closed on 1 September 2020 (announcement). This is an archived copy of a thread from Glitch City Laboratories Forums.

You can join Glitch City Research Institute to ask questions or discuss current developments.

You may also download the archive of this forum in .tar.gz, .sql.gz, or .sqlite.gz formats.

Generation II Glitch Discussion

????? party overloading - analysis and exploitation - Page 1

????? party overloading - analysis and exploitation

Posted by: ISSOtm
Date: 2017-09-29 11:47:39
Krys3000 asked me to take a look at "????? party overloading". Since that glitch has quite some potential, I figured it would be a good idea to check out how it works, to make developing techniques using it easier.

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.

Re: ????? party overloading - analysis and exploitation

Posted by: Torchickens
Date: 2017-09-29 13:07:11

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.


Wow that's pretty cool. :) Good luck with your research!

Re: ????? party overloading - analysis and exploitation

Posted by: ISSOtm
Date: 2017-09-29 16:41:36
Result so far : [img]http://puu.sh/xM3dM/a7a3096fb9.gif[/img].

Problem is, it's almost not doable without directly editing memory.

Here's the current method :
- Have a Pokémon with 49152~49407 (00C000~00C0FF) experience
- Have a Pokémon which redirects (exact requirements to be defined)
- Have a way to fix the stack (run a "add sp, $16" or equivalent somewhere before returning)

Have 7 Pokémon in your party.
Withdraw 9 Pokémon.
Withdraw the "redirect" Pokémon.
Withdraw 3 Pokémon. The last two will be permanently lost.
The withdraws up to this point don't have to be consecutive, just keep track of how many withdraws you did.
Withdraw the "C0 experience" Pokémon. The game will execute code starting at C000, which is an unused byte in the "redirect" Pokémon, thus exec will slide to C001, which is the "redirect" Pokémon's current HP.

The way I did it was by pure hex editing, so I didn't work out any "legit" setup yet.

@C001 : C3 75 DB (jp DB75 ; wBoxNames)
@DB75 : 3E 42 D7 CD 47 58 3E 11 C3 00 01

ld a, $42 ; BANK(Credits)
rst $10 ; Bankswitch
call $5847 ; Credits
ld a, $11
jp $0100

For a more "conventional" ACE, you'd need to add $16 to sp before returning ; this can be done using "add sp, $16", "ld hl, sp+$16" then "ld sp, hl", or doing "pop"s 11 times.
This is because the last withdrawing overwrites 32 bytes of the stack, including a return address (which is exactly what grants ACE), but also a lot of other data. Fortunately "fixing" the stack this way simply sets us back at the main menu of Bill's PC.

Now, for the downsides…
This truly destroys the player's party. In my case I had mons with unterminated names which prevented the party from being fixed (damn !), but I believe this could be fixed by using Pokémon with $50's in their data.
The "redirect" Pokémon must be withdrawn again after a reset. Sadly, its data isn't saved by the game. This could probably be fixed by having the corrupted return target somewhere different than C000.
The party overloading will corrupt your Pokédex, Unown Dex, roamer Pokémon data,


But hey, it's still ACE ! It requires a ?????, though.

Luv ya, Gamefreak ! <3



My notes :
In its current form, this is Crystal-only. Only tested in an US version, but porting it to EU versions should be doable still.
The initial exec can be done anywhere, in theory. The high byte is highly manipulable (I assumed any value could be reached), but the low byte is most probably 0, or 1. That's indeed a restriction.
I just read Crystal_'s post, which suggests instead applying a change to the save file to give a TM. Maybe that ?