Pokémon Crystal any% speedrun route

From Glitch City Wiki
Revision as of 21:01, 19 January 2021 by Bbbbbbbbba (talk | contribs) (Text replacement - "(\bld(?:|i|l|d|h) (?:.+, ?)?)\((.+)\)" to "$1[$2]")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

The current Pokémon Crystal any% glitched speedrun route is based on 0x1500 control code arbitrary code execution. It triggers the glitch by using the bad clone glitch to get a Pokémon with an unterminated nickname, and viewing its name in the PC withdraw screen. It also makes use of two luck manipulations to get desirable values for the Trainer ID, the Lucky ID, and DVs of the starter. This page documents the route and analyzes the glitches used in detail.

The content of this page is based on the advanced route guide by entrpntr. Details unrelated to glitches/manipulations (i.e. for time saving only) are omitted.


  • Clear saves between resets.
    • This is required any time you start a New Game, since your lucky ID persists regardless of having a save file.
  • Manipulate for a Trainer ID of 0x26FB (09979) and Lucky ID of 0x186F (06255).
  • Manipulate for a Cyndaquil with DVs E9xx (see the original route guide for detailed inputs).
    • This manipulation needs the player name to be 3 characters (the preset name "MAT" will do).
  • Swap Leer and Tackle during the Rival fight to set up the move order.
  • Win the rival fight to level up and learn Smokescreen.
    • The moves of Cyndaquil should be Leer, Tackle, Smokescreen in this order (Tackle, Smokescreen, Leer also works, but takes more time).
  • Catch any encounter (2 Pokémon are required for cloning).
  • Run from all other encounters (guarantees safe experience values).
    • The experience and stat experience values after defeating the rival do affect the ACE. They are not a crucial part of it (if they were "safe" values, the box names can be changed to account for that), but this particular route needs those values.

Bad clone glitch

  • Go to the PC on the 2nd floor of Cherrygrove Pokémon Center.
  • Rename boxes:
    • BOX 1: T 0 'v é N 5 'v 's
    • BOX 2: é n 6 'v f é R 5
    • BOX 3: f é o 6 p é é 6
    • BOX 4: é 5 5 PK PK 'd
    • Note: Characters in italic don't matter (they are overwritten by the ACE).
  • Move Cyndaquil to the second slot in party, and save the game ("move PkMn w/o mail" is the fastest way to achieve both).
  • Do the bad clone glitch by depositing one of the Pokémon, switching boxes and hard resetting during the save.
    • Either a "real" bad clone (level 0) or a "pseudo" bad clone (correct level, but unterminated nickname; referred to as "friendly clone" in the original route guide) will work.

Arbitrary code execution

  • After reloading the save, go down 1 tile, then right 4 tiles.
  • Open Pokédex and scroll to the position of Spearow (0x15).
    • This sets the temporary variable $D265 to 0x15. $D266 (wFailedToFlee) should remain 0x00 after reset.
    • Notice that where the accessible Pokédex ends will depend on what Pokémon you have seen. In case the only wild Pokémon you have seen is Pidgey, Spearow would be out of reach in new Pokédex mode, so the player will need to go to old Pokédex mode (where Cyndaquil comes after Spearow).
  • Close out of Pokédex, go left 4 tiles then up 1 tile to get back to PC.
    • This movement sets up an array at $CD70 that stores some pointers for the background map to begin with "D8 9B DA 9B DC 9B ...".
  • Open PC and go to the withdraw screen.
    • The game will try to show the unterminated nickname starting from $D073. There are a lot of data between there and $D265, but after a reset they are predictable, and won't contain any terminators or other problematic control characters. Finally, after a few B presses for control characters, the text engine reaches $D265 and triggers 0x1500 control code arbitrary code execution, making the program counter jump to $CD52.
    • Relevant register states:
      • a = 0x52 (temporary variable for reading out the jump destination $CD52)
      • e = 0xFF (jumptable index minus one)
      • carry flag unset (calculating the address of jumptable entry 255 didn't overflow)
Program counter Hex ASM In-game meaning In-game value Comments
$CD52 – $CD6F 00 (*30) nop
$CD70 D8 ret c Pointers to background map tiles $9BD8, $9BDA, $9BDC... Jump not taken
$CD71 9B sbc e Sets the carry flag (0x52 - 0xFF)
$CD72 DA 9B DC jp c, $DC9B Jump taken
$DC9B – $DC9E 00 (*4) nop Unused
$DC9F 18 6F jr 0x6F Lucky ID 06255 (0x186F) Jumps to $DD10
$DD10 AD / 00 xor l / nop Held item of second party Pokémon (Cyndaquil) Berry / nothing
$DD11 2B dec hl First move of Cyndaquil Leer
$DD12 21 6C 00 ld hl, 0x006C Second to fourth move of Cyndaquil Tackle, Smokescreen, empty slot hl = 0x006C
$DD15 26 FB ld h, 0xFB Original Trainer ID of Cyndaquil 09979 (0x26FB) hl = 0xFB6C
$DD17 – $DD18 00 00 nop Experience of Cyndaquil

205 (0x0000CD)
$DD19 CD 00 32 call $3200 Calls WaitBGMap2, which calls DelayFrames;
a = 0x00, c = 0x00
Stat experience (HP) of Cyndaquil 50 (0x0032)
$DD1C 00 nop Stat experience (Attack) of Cyndaquil 65 (0x0041)
$DD1D 41 ld b, c b = 0x00
$DD1E 00 nop Stat experience (Defense) of Cyndaquil 64 (0x0040)
$DD1F 40 ld b, b
$DD20 00 nop Stat experience (Speed) of Cyndaquil 43 (0x002B)
$DD21 2B dec hl hl = 0xFB6B
$DD22 00 nop Stat experience (Special) of Cyndaquil 44 (0x002C)
$DD23 2C inc l hl = 0xFB6C
$DD24 E9 jp hl First byte of DVs of Cyndaquil 0xE9 Jumps to 0xFB6C (Echo RAM, equivalent to $DB6C)
$FB6C – $FB71 00 (*6) nop Unused
$FB72 00 nop Index of current box (0-based) Box 1
$FB73 – $FB74 00 00 nop Unused
$FB75 93 sub e Box 1 name T a = 0x01
$FB76 F6 D6 or a, 0xD6 0 'v a = 0xD7
$FB78 EA 8D FB ld [$FB8D], a é N 5 ($FB8D) = 0xD7
$FB7B D6 D4 sub a, 0xD4 'v 's a = 0x03
$FB7D 50 ld d, b (terminator)
$FB7E EA AD FC ld [$FCAD], a Box 2 name é n 6 [wBackupMapGroup] = 0x03
$FB81 D6 A5 sub a, 0xA5 'v f a = 0x5E
$FB83 EA B1 FB ld [$FB91], a é R 5 ($FB91) = 0x5E
$FB86 50 ld d, b (terminator)
$FB87 A5 and l Box 3 name ($FB8D overwritten by previous code) f a = 0x4C
$FB88 EA AE FC ld [$FCAE], a é o 6 [wBackupMapNumber] = 0x4C
$FB8B AF xor a p a = 0x00; clears the carry flag
$FB8C EA D7 FC ld [$FCD7], a é (0xD7) 6 [wPartyCount] = 0x00
$FB8F 50 ld d, b (terminator) d = 0x00
$FB90 EA 5E FB ld [$FB5E], a Box 4 name ($FB91 overwritten by previous code) é (0x5E) 5 [wEventFlags + 236] = 0x00
$FB93 E1 pop hl PK Pops text pointer
$FB94 E1 pop hl PK Pops return pointer in RunMobileScript
$FB94 D0 ret nc 'd Returns to MobileScriptChar
  • The text engine continues to try to display text from de = 0x00FF, prints some more garbage, but eventually encounters a 0x50 terminator.
  • Close the PC, and go downstairs.
    • Since the 2nd floor of Pokémon centers are in fact a shared map, the downstairs warp uses wBackupMapGroup and wBackupMapNumber to determine which map to return to. Map 0x4C in map group 0x03 is Red's room in Mt. Silver. Before the ACE, the game is supposed to return the player to warp 0x03 in the 1st floor of Cherrygrove Pokémon Center, so now the game will try to return the player to warp 0x03 in Red's room. Warp 0x03 is invalid, but it turns out to be at coordinate (13, 7), which is conveniently close to Red at (9, 10).
  • Talk to Red.
    • Normally, Red only appears in Mt. Silver after the Hall of Fame sequence, but setting [wEventFlags + 236] = 0x00 resets the appropriate event flag and lets him appear.
    • At this point, we have no Pokémon in our party (more precisely, our party count is 0), so the "instant victory effect" is triggered. Since the player never entered a battle since loading the save file, the overworld script acts as if the player won. In the case of Red, this means going to the credit roll.

YouTube video

YouTube video by Pokeguy84

See also