entrpntr

[Pokémon TCG] Down+A glitch technical info

Jan 24th, 2020 (edited)
2,902
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.97 KB | None | 0 0
  1. +===========================+
  2. | Background links and info |
  3. +===========================+
  4.  
  5. Video w/inputs: https://www.youtube.com/watch?v=ieyYHsurOcQ
  6.  
  7. Any% No ACE guide: https://www.speedrun.com/pkmntcg/guide/52pes
  8.  
  9. Any% ACE guide: https://www.speedrun.com/pkmntcg/guide/aci9l
  10.  
  11. *IMPORTANT NOTE* :: The specifics mentioned below ONLY APPLY (in full) to the TCG1 (USA, Australia) release
  12. - TCG1 PAL and JPN versions result in similar unintended/undefined behavior, but the game code is different
  13. ; most significantly for speedrunning, duel escape from the tutorial appears impossible in these versions
  14. - A similar mechanic exists in TCG2 (both JPN and ENG translation patch); not thoroughly researched as of this writing
  15.  
  16.  
  17. +======================+
  18. | Basics of the glitch |
  19. +======================+
  20.  
  21. "Out of bounds cursor positions"
  22. - INPLAYAREA_PLAYER_PLAY_AREA = $10
  23. - INPLAYAREA_OPP_PLAY_AREA = $11
  24.  
  25. A presses were not accounted for when cursor position is "out of bounds"
  26. - OpenInPlayAreaScreen.HandleInput (rough code flow)
  27. (1) process dpad ("preserve" cursor position, then update current cursor position)
  28. (2) check button presses
  29. (3) if A pressed, handle selection ("OpenInPlayAreaScreen.selection")
  30. > NOTE: this routine overwrites the "preserved" cursor position (dpad+A not accounted for)
  31. (4) else if cursor == $10: show turnholder play area
  32. (5) else if cursor == $11: show non-turnholder play area
  33. - Can be triggered by hitting inputs on the same frame in the play area screen
  34. ; DOWN+A from lowest player Pokémon (or UP+A from highest enemy Pokémon)
  35. ; made more trivial by the fact that inputs can be "buffered" during screen transitions
  36.  
  37. OpenInPlayAreaScreen.selection uses a "jumptable" based on what the current cursor position is
  38. - only jumptable indices $00-$0F were intended
  39. - $10 and $11 are invalid indices; their data comes from the instructions that follow (not meant to be read as data)
  40. ; https://github.com/pret/poketcg/blob/19dfdb2a3ea22ee69736a525b3aed28e691f1c33/src/engine/bank06.asm#L389-L390
  41. ; ld a, [$52CE] = FA 52 CE
  42. ; inc a = 3C
  43. - $10 jumps to 06:52FA (leads to glitched behavior)
  44. - $11 jumps to 00:3CCE (leads to harmless behavior)
  45.  
  46.  
  47. In sum, the glitch is triggered by simultaneous DOWN+A inputs on the play area screen when the cursor is situated at the lowest possible position on the player's side.
  48.  
  49.  
  50. +====================+
  51. | "DOWN+A" code flow |
  52. +====================+
  53.  
  54. 06:52FA inc bc
  55. ...
  56. 06:53D1 ld bc, $2002 ; bc=2002
  57. 06:53D4 nop
  58. 06:53D5 ld [bc], a ; a=D7 ; interpreted as a bankswitch to bank $17 (upper bits ignored)
  59. ... ; execution continues at 17:53D6
  60. ...
  61. 17:5769 halt ; see "50/50 explanation" for importance of halt
  62. ...
  63. 17:581B halt ; see "50/50 explanation" for importance of halt
  64. ...
  65. 17:5879 ld l, [hl] ; hl=2038
  66. 17:587A daa
  67. 17:587B ld [hl], h ; interpreted as a bankswitch to bank $20
  68. ... ; execution continues at 20:587C
  69. ...
  70. 20:58C3 jp $0E43
  71. ...
  72. 00:0E46 ret nz ; [sp]=4168
  73. ...
  74. 20:416B pop bc
  75. 20:416C pop hl
  76. 20:416D ret ; [sp]=459A (if "duel escape")
  77. ; [sp]=68AB (if "select x2 ACE")
  78.  
  79.  
  80. Stack addresses to note
  81. - 06:4168 = https://github.com/pret/poketcg/blob/19dfdb2a3ea22ee69736a525b3aed28e691f1c33/src/engine/bank06.asm#L245
  82. ; line after JumpToFunctionInTable (function that uses the invalid jumptable index)
  83.  
  84. - 01:459A = https://github.com/pret/poketcg/blob/19dfdb2a3ea22ee69736a525b3aed28e691f1c33/src/engine/bank01.asm#L826
  85. ; if opened play area from SELECT button from main duel menu
  86.  
  87. - 01:68AB = https://github.com/pret/poketcg/blob/19dfdb2a3ea22ee69736a525b3aed28e691f1c33/src/engine/bank01.asm#L6468
  88. ; if opened play area from SELECT x2 on a forced switch (knockout or move effect)
  89.  
  90.  
  91. +=========================+
  92. | "Duel Escape" Code Flow |
  93. +=========================+
  94.  
  95. (from end of "DOWN+A")
  96.  
  97. ...
  98. 20:459A push hl
  99. 20:459B push bc
  100. ...
  101. 20:45A7 pop bc
  102. 20:45A8 pop hl
  103. 20:45A9 ret ; [sp]=4594
  104. ...
  105. 20:4596 pop de
  106. 20:4597 pop bc
  107. 20:4598 pop hl
  108. 20:4599 ret ; [sp]=38D9
  109.  
  110. From there, the duel effectively ends. Logic eventually continues to "FindEndOfBattleScript" (03:652C), which reads wDuelResult (hasn't changed since previous duel, so it reuses the previous result).
  111.  
  112.  
  113. Stack addresses to note
  114. - 01:4594 = https://github.com/pret/poketcg/blob/19dfdb2a3ea22ee69736a525b3aed28e691f1c33/src/engine/bank01.asm#L821
  115. ; if opened play area from SELECT button from main duel menu
  116.  
  117. - 00:38D9 = https://github.com/pret/poketcg/blob/19dfdb2a3ea22ee69736a525b3aed28e691f1c33/src/engine/home.asm#L10766
  118. ; escapes the main duel loop
  119.  
  120.  
  121. +===========================+
  122. | "Select x2 ACE" Code Flow |
  123. +===========================+
  124.  
  125. (from end of "DOWN+A")
  126.  
  127. ...
  128. 20:68AB adc e
  129. ...
  130. 20:68AD inc sp ; (#1)
  131. ...
  132. 20:68B0 inc sp ; (#2)
  133. ...
  134. 20:68BC inc sp ; (#3)
  135. 20:68BD pop af
  136. ...
  137. 20:68D4 pop af
  138. ...
  139. 20:68D9 rst $28
  140. 20:68DA db $14, $ED, $F2
  141.  
  142. = `farcall 14:F2ED`
  143. ; bankswitches to bank $14 (irrelevant since the address is in echo ram)
  144. ; pushes $68DD onto the stack, as ret address
  145. ; pushes $0006 onto the stack, from hBankROM (game thinks it should still in bank $06)
  146. ; calls $F2ED, [sp]=09DC (`SwitchToBankAtSP`)
  147. ; with a good setup, a ret can eventually be reached
  148.  
  149. ECH1:F2ED rst $38
  150. ECH1:F2EE ld a, a
  151. ...
  152. ECH1:F3XX ret ; returns to 06:68DD (middle of PrintPlayerNameFromInput routine)
  153. ...
  154. 06:68DD ld e, a
  155. ...
  156. 06:68E7 pop de
  157. ...
  158. 06:68F1 ret ; [sp]=A56F (if knocked out)
  159. ; [sp]=C300 (if "switch defending pokemon" move effect)
  160.  
  161. > NOTE: $A56F = SRAM (which is disabled), and eventually slides all the way to $C000. From there you'll eventually reach $C200.
  162.  
  163. - $C200 = wPlayerDuelVariables
  164. - $C300 = wOpponentDuelVariables
  165. - $C400 = wPlayerDeck
  166.  
  167.  
  168. Stack addresses to note
  169. - 00:09DC = https://github.com/pret/poketcg/blob/19dfdb2a3ea22ee69736a525b3aed28e691f1c33/src/engine/home.asm#L1797
  170. ; ensures a return to 06:68DD
  171.  
  172. - 01:6Fxx = https://github.com/pret/poketcg/blob/19dfdb2a3ea22ee69736a525b3aed28e691f1c33/src/engine/bank01.asm#L7433-L7525
  173. ; logic dealing with knocked out pokemon
  174.  
  175. - 0B:44C3 = <not disassembled>
  176. ; logic dealing with a move effect that forces the player to switch out the defending pokemon
  177.  
  178.  
  179. "Switch Defending Pokemon" move effect (C300 coincidence)
  180.  
  181. When the player gets hit by effect command type $0A = EFFECTCMDTYPE_SWITCH_DEFENDING_PKMN, which is one of Terror Strike (from Arbok), Ram (from Rhydon), or Whirlwind (from Butterfree/Pidgey/Pidgeotto), game logic will run into bank $0B (not in the disassembly currently).
  182.  
  183. So long as the player has at least 2 pokemon in the play area, execution will eventually reach a `Bank1Call` to `OpenPlayAreaScreenForSelection` (for selecting replacement poke) at 0B:44C0. To ensure it returns to the right place, the game pushes 44C3 and 000B to the stack (for an 0B:44C3 return address). Because of the stack getting shifted an odd number of times in bank $20, the $C3 and $00 eventually get combined into a return address of $C300.
  184.  
  185. It was unclear originally why such a neat address in wram was a return address on the stack; turns out it is just a happy little accident.
  186.  
  187.  
  188. +========================+
  189. | "Select x2 ACE" basics |
  190. +========================+
  191.  
  192. The part of bank $20 that gets executed with the Select x2 mechanism is unclear from the disassembly, but was certainly not meant to be executed as code. The odd number of `inc sp` messes with the stack, which are meant to be treated as 16-bit addresses. It also eventually runs into a `farcall` to $F2ED, which is a copy of $D2ED in wram.
  193.  
  194. In the duel, a lot of memory executed will be fixed:
  195. - D2ED-D316 = alternating $FF (`rst $38`, essentially a `nop` in TCG) and $7F (`ld a, a`)
  196.  
  197. The memory after that will be controllable by the player:
  198. - D317-D348 = stuff related to map/sprite/owscript/animation variables (not disassembled)
  199. ; mostly fixed, depending on last overworld map movement pattern performed
  200.  
  201. The general goal is to find a setup (overworld movement and duel choice) that leads to a safely executable code flow, and eventually hits a `ret`. D317-D347 in the current RTA ACE route setup look like this:
  202.  
  203. 00 1E 4B xx yy FF FF FF FF FF FF
  204. 01 00 00 00 00 00 00 00 00 00 00 00 09 18 08 ; 0C 40 60 00 00 00 1E 00 (skipped by the jr from 18 08)
  205. 01 04 02 38 01 02 73 64 00 00 B6 00 00 FF zz
  206.  
  207. xx = D31A = 03 or 0B (in Science)
  208. ; `inc bc` or `dec bc`
  209.  
  210. yy = D31B = 00-0B (in Science)
  211. ; all essentially harmless
  212.  
  213. zz = D347 = D0 (for Mason -> Science)
  214. ; `ret nc`
  215.  
  216. D347-D348 seem to be some sprite or animation-related X/Y values (unclear exactly what), that are fixed depending on where you moved from -> to last on the overworld map. The preceding data isn't too important to delve through, what's important is it doesn't throw off execution, and eventually a `ret nc` is reached (and taken).
  217.  
  218. As described above, you'll eventually return effectively to either $C200 or $C300 (which will be randomized from the shuffle). From there, the main idea is to safely reach $C400, and to use the player deck as the main code to execute. Thus, because of the issues described in the TAS writeup (http://tasvideos.org/forum/viewtopic.php?t=21580), you very likely need to manipulate duel RNG to ensure the shuffle is safe to execute, regardless of which method is taken.
  219.  
  220. The current RTA ACE route manipulates for duel RNG on Joseph that leads to turn 1 double colorless energy into Whirlwind from Pidgey, gets safe execution to $C400, and ultimately uses the same deck payload as the TAS to jump to the credits.
  221.  
  222.  
  223. +=================================================+
  224. | Explanation for why the glitch is "50/50" (ish) |
  225. +=================================================+
  226.  
  227. "Bankswitching" and the "timer interrupt" are the two key components to understand in order to make sense of the effective randomness of the glitch.
  228.  
  229. While in bank $06 (OpenInPlayAreaScreen.selection), the invalid jumptable index results in a jump to move animation data that was not meant to be executed as code; in that code is an instruction `ld [$2002], a` (when a=D7), which is interpreted as a bankswitch to bank $17. The game normally wants to call its actual `BankswitchROM` routine when switching banks, so it can keep track of what bank to return to if an interrupt occurs. After this bankswitch, the game still has `hBankROM=06`, even though execution has switched to bank $17. Execution in bank $17 runs through game text data (which also was not meant to be executed as code).
  230.  
  231. The timer interrupt is fired about 4 times a frame, and can happen at any time. In the context of this glitch, the interrupt is overwhelmingly likely to happen at the two `halt` opcodes in bank $17 (17:5769 and 17:581B), since `halt` specifically waits for an interrupt to happen.
  232.  
  233. TCG's timer interrupt handler (`TimerHandler` routine, found at 00:01E6) increments a value called `wTimerCounter`; if `wTimerCounter` is divisible by 4 when it enters the interrupt handler code (i.e. before it is incremented), it will run some extra code it normally skips. Since the timer interrupt fires about 4 times a frame, this code runs roughly once a frame.
  234.  
  235. This extra code (https://github.com/pret/poketcg/blob/19dfdb2a3ea22ee69736a525b3aed28e691f1c33/src/engine/home.asm#L130-L146) does some basic stuff (e.g. increment play time), but one thing it does it update music, which requires a bankswitch to bank $3D to run the relevant routines. Thus, if this extra code gets run, there is some cleanup afterwards that results in execution returning to bank $06 (where the game still thinks it should be).
  236.  
  237. Putting this all together, there are two standard results:
  238. (1) The "infinite party screen" (the standard "failure" case) occurs if the extra code is triggered on the timer interrupt from either halt (if triggered on the first halt, execution returns to 06:576A; if triggered on the second halt, execution returns to 06:581C).
  239. (2) "Duel escape" or "Select x2 ACE" (the standard "success" cases) occur if the extra code is not triggered on the timer interrupt from either of the 2 halts.
  240.  
  241. However, this only takes into account the cases where the timer interrupt fires while the CPU is halted. The timer interrupt can fire at other points of time besides the halts (it's just far less likely). When exactly the interrupt gets fired depends on the timer's relative offset from vblank, which is a product of when/how the LCD has been disabled/enabled (since power on), and a number of other factors that are difficult to quantify precisely.
  242.  
  243. If the timer interrupt runs its extra code at these odd times, execution will return back to bank $06 (instead of bank $17) at whatever bank $17 address execution was at when the timer interrupt was fired. For instance, gifvex's ACE TAS concept aimed to have the interrupt fire to ensure execution would return to bank $06 before the `dec sp` at 06:5610 was reached. (Note that this approach is not at all RTA viable, as the timer interrupt would have to fire at one of 17:55D7, 17:55D9, 17:55DA, 17:55DB, or 17:55DD, in addition to `wTimerCounter` being divisible by 4).
  244.  
  245. Thus, in actuality, the glitch is more like 49.9/49.9/0.2 (made up numbers for now), where the 0.2% is all sorts of "weird shit" (scientific terminology you'll find in essentially all advanced literature on computation theory). Because interrupts could theoretically happen after any instruction, the types of "weird shit" that can occur are difficult to enumerate (but some types of "weird shit" are likely more common than others).
  246.  
  247.  
  248. +=============+
  249. | Other notes |
  250. +=============+
  251.  
  252. - After backing out of the "infinite party screen", there is a 1 frame window on the play area screen for accepting inputs:
  253. ; A (or START) = reattempt the glitch
  254. ; B = return to the main duel menu
  255. ; UP = return cursor to an "in bounds" position
  256. ; SELECT = return cursor to active Pokémon
  257. ; otherwise = return to the "infinite party screen" automatically (since "preserved" cursor position was out of bounds)
  258.  
  259. - If SELECT is still held when entering the play area screen from the main duel menu, the first input is "skipped":
  260. ; if SELECT is still held and dpad is buffered (with or without A), you'll enter the party screen
  261. ; backing out from the party screen from this state backs out to the main duel menu
  262.  
  263. - If came from Check -> In Play Menu, you're guaranteed to hit the infinite party screen (but retrying the glitch works normally)
  264. ; where [sp]=459A above, it will instead be 4041
  265. ; 02:4041 = https://github.com/pret/poketcg/blob/19dfdb2a3ea22ee69736a525b3aed28e691f1c33/src/engine/bank02.asm#L40
  266. ; don't get extra pops when returning to 20:4041, so not enough comes off the stack for first try duel escape
  267.  
  268. - Previous duel result is persisted in the save data, so if you lose a duel, you'll have to win a duel before you can proceed to get the "instant victory" condition back for duel escape.
  269.  
  270. - TODO (but not by me): Other potential behavior to trace based on others' testing (non-critical and mostly academic at this point)
  271. ; "if you try to do it in the challenge machine, it crashes the game"
  272. ; interplay with L68 Zapdos / Devolution Spray glitches ("once it softlocked the game, another time it just ended the duel")
Add Comment
Please, Sign In to add comment