Submission #6746: CasualPokePlayer's GBC Pokémon: Crystal Version "save glitch" in 03:55.59

Game Boy Color
(Submitted: Pokémon: Crystal Version)
save glitch
(Submitted: save glitch)
(Submitted: pokecrystal.gbc USA/Europe v1.0)
Bizhawk 2.4.1
14073
59.735386626710074
3928
Unknown
Submitted by CasualPokePlayer on 5/14/2020 9:38 AM
Submission Comments

Introduction

Meet MAT, a young boy destined to exit Prof. Elm's lab from the opposite side, struggle to send mail through the glitch city postal system, and instantly beat the legendary Red. Seems legit.

Emulator used: Bizhawk 2.4.1

  • SubGBHawk was used for this movie, as it requires a very precise subframe reset (~60 μs)
  • CGB in GBA is enabled, as to conform to RTA standards, and for theoretical future console verification.

Categories

  • Aims for fastest completion of the game
  • Heavy glitch abuse
  • Corrupts save data
  • Corrupts memory
  • No luck manipulation[1]
  • Attempts to send mail through the glitch city postal system

Game objectives

Save Corruption

You may recall that Crystal stores a 16-bit checksum for the main save, for detecting corruption and loading the backup save when needed. You may also recall that this checksum is very weak,[2] simply being the sum of all the bytes in the main save data. Cleared save data has a checksum of $0000, which can be reached with box names. However, in this movie, box names cannot be used, so it is not possible to raise the checksum enough to overflow $ffff to $0000.[3] This movie opts to save sometime before going into the lab, then collide with that save's checksum. This movie specifically wants to zero out Cyndaquil's moves, which only has a ~1/16 ms window to work with. SubGBHawk makes this fairly easy to do, thankfully.

Type 0xD1 Corruption

Move 0x00 has the glitch type 0xD1 on the move list screen. This type's name is sourced from $8091, which is in VRAM. VRAM is subject to locking, where all reads return 0xFF and writes have no affect. Viewing this type will cause a buffer overflow and will corrupt the map, but with the right VRAM timing and with some string buffers filled, it will be terminated before the corruption crashes the game, leaving us with a glitched lab, and most importantly, the walls are now gone and we can now go into map 0xFF00.

Glitch Map 0xFF00 Corruption

A quick run down of the relevant parts of the video, Gen 2 marks "no connection" by setting wMapGroup to $FF, wMapNumber is whatever was on the last map connection. Upon a S/Q, wMapNumber for "no connection" is set to $00.[4] Map 0xFF00 is not a valid map, and causes massive corruption in our items, badges, money, etc. due to a buffer overflow of its map objects (it has 36 map objects, the maximum typically allowed is 15). However, it is sourced entirely in ROM, meaning it causes consistent corruption, and that corruption happens to allow us to quickly do item underflow. However, typically this map causes a crash. This can be avoided though, by simply entering the map from the north side of the lab, which will immediately transport us to map 0x071C,[5] after the corruption.

Item Underflow

Item underflow is very simple, if there somehow happens to be an item with x255 quantity, and you toss any tossable[6] item that is above that x255 item, then the game will simply copy the x255 item to the slot above it along with decreasing the item count. You can repeat this until 00 underflows to 255, then you can access items below the main items pocket, which allows for effective creation of any item.

Item Creation

To understand how this item creation works, you need to understand how Gen 2 structures the pockets used to store items. The player gets 5 pockets for storing items. They are the TM/HMs Pocket, the Main Items Pocket, the Key Items Pocket, the Balls Pocket, and the PC.[7] These pockets are lined up in RAM in that order, but they do not all share the same structure. The TM/HMs pocket allocates a byte for each TM/HM, and the value in that byte determines how much of that TM you have. Which TM is simply assumed based on where the byte is. The Main Items, Balls, and PC share the same structure, the first byte is the item ID, and the second byte is the quantity. The Key Items pocket, however, is different, forgoing the quantity byte, as Key Items don't have quantities. However, if say an item that does use a quantity ends up in the Key Items pocket, then it will take the item ID of the next item as its quantity.[8] Item creation simply abuses these structural differences, now that with item underflow we effectively have x255 items in the Main Items pocket, we can swap a main item into the Key Items Pocket (remember, the Key Items Pocket is right below the Main Items Pocket), with the quantity of that main item becoming an item in the Key Items Pocket now... Well, actually that's what the initial submission did, the Balls Pocket happens to be misaligned relative to the Main Items Pocket, making quantities Item IDs and Item IDs quantities.
Item Tossing
One input per frame. Left/Right decrease/increase the toss count by 10, Up/Down increase/decrease the toss count by 1, all respectively. Toss counts will wrap around accordingly. The same directional cannot be pressed twice in a row. Different directions (e.g. Up/Right) cannot be pressed on the same frame.
In this movie, PP Ups are tossed to specifically create Music Mail,[9] for the purpose of the first payload.

Wrong Pocket TM22

Recall that the TM/HM pocket's structure is different than the Main Items/Balls/PC. As a note, the game allows the player to store TMs/HMs in the PC, meaning all TMs/HMs have item IDs to be compatible with the PC. So, what happens if you use a TM/HM in a pocket that allows the player to use items? The game goes past the valid pointers and interprets some asm instructions as pointers, some allowing for ACE. This is typically not used in speedruns, as 0x1500 ACE is faster most of the time, but with the corrupted item pack and the game conveniently giving us TM22, this is faster.
TM22 is used specifically because of its pointer, $d106.[10] This points to wCurItem, which slides down to two fully controllable bytes, wItemQuantityChangeBuffer and wItemQuantityBuffer... well, post-initial submission I realized from this video I have another mostly controllable byte: wCurItemQuantity (and this label is a lie, it's just the position the item is in the pocket). I pretend to toss 233 items, and put TM22 into the 37th slot create the bootstrap. This bootstrap will jump into the mail buffer, which has enough space for the first payload.
;af = $0600
;bc = $0007
;de = $d078
;hl = $d106

dec h ; wCurItemQuantity, h = $d0
~~~
jp hl ; wItemQuantityChangeBuffer
This jumps to $d006, which is in the middle of the mail buffer.[11]

Arbitrary Code Execution

This movie uses MrWint's joypad to opcode payload, but it has been modified to work with mail. Refer to his submission for how it works. Note, the first 2 bytes of wTempMail will be overwritten by item swaps, so another item swap is used to turn them into harmless opcodes. There is another catch, f cannot be maintained for the input opcode, due to the position of the written opcode. The following mimics gifvex's submission:
Mail Message
Very similar to box names.[12] One input every 2 frames. The same button cannot be pressed two inputs in a row (or it's a hold). Directionals can be pressed consecutively if a new button or directional is also pressed. Example: Up 2 can be done in two inputs with UP -> UP|LEFT. Priority for two directionals at once is UP > DOWN > LEFT > RIGHT. If A and a direction are input together, A is processed then the cursor moves. The same is true for Start; Start is processed then the cursor moves.[13] Where the cursor moves when pressing Up/Down on UPPER/lower/DEL/END depends on where those slots are entered; Start defaults to the right side. Start and A cannot be used together, nor Start and Select.
Thanks to MrWint for the format:
BytesInstructionComment
($effb)(any)Execute opcode written last cycle
00 x6nop x6Slides back down to the mail buffer
ea cd 75ld (75cd),aDoes nothing
f5push afSave a and f for next cycle
f0 a4ldh a,($ffa4)Reads current joypad inputs into a
aaxor dd stores last joypad input: find out differences to current input
ea fb efld ($effb),aWrite difference; will be executed as opcode later in the next cycle
aaxor dRestore current joypad input value
f5push afCopy current joypad input from a...
d1pop de... to d (store it as last joypad input)
7fld a,aFiller, does nothing
f1pop afRestore a and f from the previous cycle
fe 4ecp $4eLine break is $4e, which is a bad opcode, cp makes it taken as data
a7and aClears carry flag, needed for the jump
d2 fb efjp nc, $effbLoop back to written opcode; carry will never be set
The second payload can be found here. It largely does the same thing as gifvex's payload, although I also have to set wTileUp to something with collision,[14] or else I won't be able to warp.

Route

Intro

  • Save data is cleared for morality reasons. This isn't really needed as we collide with valid save data anyways, but it conforms to RTA standards, and breaking the 4 minute barrier by forgoing this clear would be a very shallow victory.
    • Minor note, the above was said when the submission was barely sub 4, now it's 3:55. Regardless, the same message is true, forgoing wiping save data doesn't actually provide any actual game-play related time save, so it shouldn't be considered real time save.
  • Options are not set as text can print at the fast speed when A or B is held anyway.
  • Unlike most glitch runs, the trainer ID is not manipulated. The TID is not used (and can't be used) for the bootstrap, and the trainer ID does not affect collision.
  • The player is selected to be the boy. The choice of boy and girl does not affect collision, and the boy has a shorter default name.
  • The default name MAT is chosen. The player name does not affect collision, and the player name is not seen enough to warrant naming him a 1 character name.

New Bark Town

  • Mom is talked to directly to avoid the exclamation point animation that plays if the player tries to walk past. It is a couple frames faster to do this.
  • The game is saved right outside the house to setup collision.
  • Cyndaquil is chosen as it is the closest starter to the player. Its DVs do not matter for ACE or collision, so they are ignored. As you can guess by now, the nickname does not affect collision, so no nickname is given.
  • The game is saved inside the lab, but it is reset before Cyndaquil's moves are written. The save is delayed by some frames to line up the IGT for checksum collision.
  • The Aide cutscene is taken, as it will fill up two string buffers, which greatly prevents move 0x00 from crashing the game from its corruption.[15]
  • Move 0x00's type is viewed from the moves list. With the right VRAM timing, this causes the map to become corrupted, allowing entry to map 0xFF00.

Glitch City

  • Cyndaquil's held item was corrupted, and needs to be taken off before item underflow is done.
  • HP Up is moved to the top of the item list, tossed, then PP Up x255 is then tossed 6 times for item underflow.
  • Music Mail is created and used to store the first payload.
  • After swapping TM22 to the 37th slot, the pocket is swapped to the Main Items pocket, where I pretend to toss 233 items, then swap TM42, then the pocket is swapped back to the Balls Pocket, where TM22 is used.
  • Auto input takes over and talks to Red, completing the run.

Suggested Screenshots

I like footnotes

[1] Technically, there is some luck manipulation in the form of getting the right VRAM timing, but it is minor if anything.
[2] For the time and with the GBC's capabilities, it was a good enough checksumming system, but it is a joke compared to anything today.
[3] From testing, the most I could raise the checksum was to the $dxxx range.
[4] This isn't actually too relevant, since the north map connection would be 0xFF00 anyways, but it makes it clear why we will always get map 0xFF00 upon a S/Q, which I do anyways for checksum collison.
[5] Map 0x071C is actually this room, but the player enters way outside of the room.
[6] If an item is in the wrong pocket, e.g. a TM in the Main Items pocket, the game does not let you toss it, and such item underflow does not work with such items.
[7] The PC isn't technically a "pocket" per se, but it is functionally similar to a pocket, so it will be referred to as such for simplicity.
[8] Note that you can't actually toss anything in the Key Items pocket for the reason listed in footnote #6.
[9] Music Mail and Mirage Mail are the fastest to obtain, but Music Mail is used due to "? received x Mail" textbox, saving 1 frame over the Mirage Mail.
[10] This pointer is derived from the wCurItem in this line.
[11] wTempMail - wTempMailMessageEnd is $d002 - $d022
[12] Main difference is that the first input can be A B START or SELECT.
[13] This is true for box names too, I am noticing that gifvex's TAS did not use this tech within their TAS. Likely was an oversight.
[14] $FF was chosen for the collision value as it could easily be obtained by reading $FF00 (a disabled register).
[15] The correct corruption can technically happen without using the Aide cutscene, but it would take too long to get the right VRAM timing, and it would not be entertaining in my opinion.

ThunderAxe31: Judging.
ThunderAxe31: File replaced with a 839 vblanks 257 frames improvement.
ThunderAxe31: File replaced with a version that correctly returns to Mt. Silver after the credits, at cost of 7 additional frames.
ThunderAxe31: The ending is performed as expected, including the cutscene where Red disappears into nothingness and the credits getting rolled.
There are some aspects I want to note about the initialization of the save file. Before starting the game, this movie uses an input combination for clearing the save data. Secret input combinations are forbidden for use, as they are considered as in-game cheats, unless they are mentioned on the official game manual, which is the case here.
It is normally not required to wipe out the SRAM of a game, or otherwise overwriting uninitialized bytes. However, Pokémon Gen II games have an unique quirk, for which I considered to be a good practice to use the built-in functionality for formatting the save file. The fact is that most Game Boy games (as well as many other consoles) are usually programmed to consider the SRAM as properly empty or formatted if it consists of 0xFF bytes, in its entirety or in specific SRAM addresses. This is the reason why our emulators are programmed to initialize the SRAM of Game Boy games as 0xFF bytes, as in many cases it proved to avoid unintended game behaviors, even though on real console the initial data of these bytes is always random and unpredictable (this is probably also why most games use a basic checksum for save file validity). However, Pokémon Gen II games are an exception, as there are strong evidences pointing out that these games instead consider the save data as empty when it's made of 0x00 bytes. In particular, the built-in functionality for formatting the save file does overwrite the whole SRAM to 0x00 bytes, as opposed to 0xFF for Pokémon Gen I games. This is why I consider that it should be required to format the save data during the movie, as it clears any doubts about the validity of the save glitch performed. On a side note, wiping the save data shouldn't be required if the save glitch doesn't rely on reading uninitialized bytes, however for this movie I confirm that it desyncs if the SRAM is kept to be 0xFF bytes.
Accepting for obsoleting the published Pokémon Crystal "save glitch" movie.
Note for the publisher: the movie length displayed in the submission is incorrect, as the site's movie parser still needs to be updated for reading the CycleCount value of SubGBHawk movies. The correct timing for this submission should be 3:55.59. Also note that BizHawk 2.4.2 doesn't correctly count the cycles for SubGBHawk movies that feature hard resets, so the latest dev build should be used for that instead. I already corrected the CycleCount value for this movie, though. It should be 988133805.
Spikestuff: Publishing. Time got fixed.
Last Edited by adelikat on 11/4/2023 1:24 PM
Page History Latest diff List referrers