User File #637956051181818139

Upload All User Files

#637956051181818139 - The Legend of Zelda Subframe Crash on title screen

Zelda_Subframe_Crash.bk2
In 00:00.00 (0 frames), 592 rerecords
2 comments, 80 downloads
Uploaded 8/9/2022 1:25 AM by OnehundredthCoin (see all 10)
If this works on console, Bizhawk is terrifyingly accurate.
Done in Bizhawk 2.8, SubNesHawk core.
So, TLoZ has the entire game loop inside an interrupt, with the exception of spinning. Of course, the Non Maskable Interrupt is disabled for this entire duration until the moment before spinning. However, there is a very brief 5 CPU cycle window where the NMI can happen before the RTI, so if timed correctly you can nest an interrupt inside the interrupt. This requires subframe button mashing for a ridiculous amount of time even just to line it up for a single nested interrupt, but should you nest too many, the stack overflows! We can then resolve 84 RTIs in a row, eventually pulling off some "garbage" as a return address. This leads to a BRK, which leads to a BRK, which leads to a BRK... and the infinite loop might as well be a game crash, since the NMI isn't going to run anymore.
You could probably make this happen somewhere other than the title screen to begin executing code somewhere else, but it needs more experimenting.
This was achieved through a crummy LUA script that just kept adding a new input, stalling for 7 frames, and seeing if another NMI happened before the RTI. It could probably be improved, since I was specifically checking for an exact address being pushed to the stack, when there are actually 2 different addresses that could be pushed between enabling the NMI and executing the RTI.
Hilariously, since the entire code is inside the NMI, when I need to perfectly time the NMI to happen before the RTI, the entire frame happens, so the game continues to play slowly whenever I can nest another NMI.
DJ_Incendration
on 8/9/2022 2:43 AM
How did you do this? That is crazy!
OnehundredthCoin
on 8/9/2022 4:30 AM
I could share the LUA script I used for the brute forcing, but it's not my best work, ha! I initially came up with the idea for this TAS a few weeks ago. I've been wanting to find another game to break with subframe inputs, and Zelda being such a large title made for a very intriguing foe. In the past, I subframe mashed for an entire frame of Kirby's Adventure to push extra data to the stack, that way I could exploit the unrelated data on the stack. In the case of Zelda, subframe mashing for a whole frame achieves nothing, since the NMI is disabled inside the NMI. I noticed the 5 cycle window where the NMI is enabled before the RTI, and got the idea of stalling for multiple frames in order to precisely time the NMI within that window.
I decided to try and overflow the stack, let some data be pushed, then return all the way around to that unrelated data, which is slightly offset, since the size of the stack is not a multiple of 3 (the RTI pulls off 3 bytes, and overflowing the stack will store bytes slightly offset from where it writes before overflowing) it lets us pull off the high byte from an unrelated jump, with the low byte of another unrelated jump. (which is why I think there's potential is making this happen somewhere else in the game)
It's like the Mario 3 TAS, but you need to press buttons at 8 kilohertz for 17 seconds, while also having incredibly precise moments where you let go of the controller in order to make the non maskable interrupt happen inside a 5 cycle window. Mario 3 just happens to break the button reading loop with the IRQ, jumping to unloaded code. With Zelda you really need to time things right.