How to Reprogram Tetris By Playing It - Behind the Code Leveled Up

65,675
0
Published 2024-05-03
Let's exploit the logic that crashes Tetris and write code by using the High Score Tables! All you need are a few extra controllers...

If you would like to support this channel, here is a link to the Displaced Gamers Patreon page - www.patreon.com/displacedgamers

Twitter: twitter.com/DisplacedGamers
Facebook: www.facebook.com/DisplacedGamers/
Instagram: www.instagram.com/displacedgamers/

Music by:
youtube.com/user/HariboOSX
   / @wolfandraven  

0:00 Here, hold my tacos and watch this.
1:38 The Crash Theory Spreadsheet of Awesomeness
4:26 Our Target
7:14 Controllers, ports, and code explained
9:52 Has anyone seen D1 on the Controller Port?
10:43 240p Test Suite Cameo!
11:12 Two controllers for Each Player, huh?
12:08 Previously on "Behind the Code..."
12:35 We need to know what Temp_0 and Temp1 are
13:37 Original, Orange, and Yellow Interrupt Scenarios
17:54 Built my own controller. With switches.
19:27 What part of the High Score Table(s) will we use?
20:38 The Setup Work for Redirection
21:58 Opcodes explained
24:06 Programmin' Time!
29:25 Testing on the Emulator
30:43 Testing on Real Hardware
31:33 Outro


#NES #Programming #Tetris

All Comments (21)
  • @DisplacedGamers
    This one took a very long time to produce, and I am super happy with it. I hope you enjoyed it. If you know someone that you think would like it, please share it with them. I'd really appreciate it!
  • @B3Band
    24 hours until a pro Tetris player pulls this off on stream
  • @ejmc6378
    "I built my own controller. With switches. Because... computer science." - Displaced Gamers, 2024
  • @hydrantdude504
    Wow, incredible video! Very cool to see someone else independently engineer an ACE setup, with a different idea - I never thought of resetting the score to prevent the crash. Inside the community, we've made our own setups; to prevent the crash, you can block blue and green crashes by just holding P3/P4 such that the code just goes where it was supposed to anyway, no ACE involved. Blue/green are the highest priority to fix because they're the ones that come into play at 255 for placing pieces- nobody's been able to play with flashing the nextbox to dodge it. For general purpose, we have a fully-built bootstrapper in the highscore table, but to get to it, we allowed the PC to jump to the beginning of ram and used the next piece plus the current rng value to have it jump to the names and such. Building code around the name/score limitations is a fascinating challenge; we used a couple invalid opcodes to get around it. A constraint you didn't touch on is that the scores have to be in order; your code fit neatly into single scores, but for us that was a big roadblock, as we used all 6 names, highscores, and levels. The code we write is used to build another bootstrapper which builds another bootstrapper that grants full control over all of RAM. The TAS has kinda just been sitting around for 2 years because none of us know what to do with total control over the game lol.
  • @minirop
    - Stop playing your silly video games - But mum, I doing my programming classes.
  • @matthewbmilton
    Next steps: "I didn't like Tetris, so I reprogrammed it to run Doom."
  • @fractal161
    Brilliant video. Love how you reasoned about the ACE setup. It was totally different from anything we came up with and your explained your thought process brilliantly. You've inspired me to attempt unmodified human ACE sometime in the future... if so, my current idea is to enable the 2-player mode. It's tough to program more intricate things using our current knowledge, but maybe in the future we will overcome this obstacle as well.
  • @KIFulgore
    The precision here is insane, awesome work. I miss working with simple hardware without dozens of abstraction layers over the metal.
  • @policychanges
    I've done a lot of tetris modding and it still amazes me how people have broken this game down to its molecules.
  • @chaosahoyhoy
    Did... did you just patch a calculation that crashed the game due to taking too long by gaslighting the program into thinking it was actually a way smaller number mid-cycle? Amazing.
  • @TakuikaNinja
    FYI, this is is exactly how ACE TASes are engineered. It's really cool to see the code injection process and its limitations explained in this manner.
  • @magikman79
    He blinded me! With... computer science...
  • @WammyGiveaway
    Coding via high score table is something brand new. You want to know what else does high scores? Arcade games. I would love to see if there are any coin-op arcade games that can be "reprogrammed" via manipulating the high score tables a la Tetris. This is a very great find, Chris.
  • @kri249
    As a kid I always thought Tetris was such a boring game. As an adult you found a way to change that. Great video mate.
  • @ragzard
    I´m not a programmer, therefore have a hard time getting some concepts fast enough, but oh good gods I love your videos. It´s EXTREMELLY fascinating to me and I have a lot of fun trying to understand.
  • @aezlack7254
    The custom controller got me. Amazing video as always, man
  • Amazing how you can explain such complex topics in a graphically coherent and understandable way
  • @Sixfortyfive
    How timely. Turns out that tacos are up today in my meal kit box.
  • @skilz8098
    "16 bits, 16 button presses, where do you want to go today..." now that's epic reverse engineering there!
  • @electra_
    23:25 Okay I tried to solve this. I have a solution, not 100% sure if it would work but I think it would. The general idea is that you put yourself in a loop where now you are using controller inputs to write arbitrary data to arbitrary memory locations. By doing this, you can write a much larger payload without restriction*, and then overwrite the end of the loop in order to jump there. The controller input scheme will work by checking whether the high byte ($01) is positive or negative. If positive, then we are in "data" mode, and the low byte $00 will be stored as the next byte to write. If negative, then we are in "address" mode, and the stored value will be written. You'll set up your controllers, probably with toggleable switches as was shown earlier. Start by flipping bit 7 of $01 to be on to be in data mode, and set $00 to be whatever you want. Now, flip bit 7 back off. The one thing you have to be careful of here is that you'll start writing that value to whatever you have your controllers set to. I think this is probably fine, as the code only exists in a tiny portion of memory and so you can just set $01 to some memory region far away from anything you're currently updating, set $00 to be what you want, then change $01 to be in the memory region you're looking to edit. * Unfortunately, due to the allowed jump instructions, there is a restriction that we must only use positive data values. But given that we can access a much larger portion of memory with this, this is overall a much less restrictive setup than the initial one. If needed, we could create a very similar piece of code in that area which did the same thing but without this restriction. There's also probably a way to avoid it - I would only need to save two bytes to do so, due to two PLA instructions being required to remove stuff on the stack. And also, if stack overflow wasn't a concern (It might just wrap around instead of crashing, I'm not sure) then all you need to do is replace 720-722 with a JSR instead of a BPL. Here's the code: NAMES 706: PLP // Previously, we had to jump into this as a subroutine to allow a jump backwards. 707: PLP // This pops the two pushed bytes off so we don't cause a stack overflow. 708: AND zpg // Clear the A register. 709: $ZZ // This can be any spot on the zero page that has a 0 at the time, I will just assume some such location exists. 70A: ORA zpg // Load $01 into the A register to check the sign bit. 70B: $00 70C: BPL rel // Branch to positive or negative locations. 70D: +21 // $723 70E: AND zpg // Continuing if negative (data mode), clear the A register again. 70F: $ZZ 710: BPL rel // Jump past the zeroed out parts. This always triggers since we just wrote 0 which is positive. 711: +12 // $71E 712-71D: A-type Name 4 (zeroed) and B-type Name 1 (containing $27 $07) 71E: ORA zpg // Continuing the negative path (data mode), load the data. 71F: $00 720: BPL rel // Jump to the score section. Of note: Because this must be a BPL instruction, all data must be positive. 721: +18 // $734 722: * // Unused. 723: AND zpg // Continuing if positive (address mode), clear the A register again. 724: $ZZ 725: BPL zpg // Jump to the score section. This always triggers since we just wrote 0 which is positive. 726: +9 // $730 727: JSR abs // Jump to the start of the loop. This uses JSR to go back, since we don't have access to small neg offsets. 728: $06 729: $07 72A-72F: B-type Name 4 (zeroed) 730: ORA zpg // Load the saved data. 731: $02 732: STA ind,Y // Store the data at the address specified by the controller inputs. Note that Y is added... we'll likely need to know what this is to play around it, but this should be fine. 733: $00 734: STA zpg // Store the data. This is executed on both address and data mode, since in address mode we just loaded the data so saving it back is fine. 735: $02 736: JSR // Jump back to the start of the loop. This uses JSR to go back, since we don't have access to small neg offsets. 737: $06 738: $07 Okay with that done I'll finish watching the video. I'm sure there's a better solution than this, you'd just need to save a few bytes to improve things and remove some restrictions, but at the very least I think it should work. Was a fun puzzle to figure out. I think the biggest challenge was there were effectively no ways to interact with X and Y, so I couldn't really find a method to use these at all. And of course, there are only limited means of branching.