Submission #8587: CasualPokePlayer & DrD2k9's GB Donkey Kong "save glitch" in 10:31.09

Game Boy
save glitch
(Submitted: save glitch)
(Submitted: Donkey Kong (World).gb World)
BizHawk 2.9.1
37694 (cycle count 1323491704)
59.72840422730749
5814
PowerOn
Submitted by CasualPokePlayer on 9/8/2023 11:44 PM
Submission Comments

Summary

This is a TAS of the classic Donkey Kong game on the Game Boy (also known as Donkey Kong '94 / DK94), using save corruption in order to beat it in record time.
The current branchless TAS can be referred to for much of the non-save glitch portion of the TAS.

Save glitch

The original makings of the save glitch used here go back to late May of 2021, when I was looking at various GB games to see if their save systems were exploitable. This is where I noticed a very peculiar quirk in DK94: selecting different "checkpoints" (i.e. 1-1, 1-5, etc.) in the file select caused writes to SRAM (i.e., the save file). This even included pressing Up at the "limit" of current progress, which would first write the next checkpoint index to SRAM, then it would see that that much progress has not yet been made, and subsequently zero out the checkpoint index. Since this is happening in SRAM and the actual save file, this means a well timed reset can let the next checkpoint index through without it being reset, thus skipping stages one checkpoint at a time.
Of course, the game has protections against save corruption, primarily with the use of a checksum. Since doing this reset blindly without any setup would just result in the calculated checksum being +1 off the stored checksum, the game will try to load the backup save, and when it's finished with that it will re-calculate the checksums and store those new checksums.
The checkpoint index is very early in the save, and so on restoring the backup checksum it will be overwritten very quickly. There was in fact only 1 variable which seemed to come before the checkpoint index: the lives counter.
This seemed to be perfect here, as we can abuse this fact in order to maintain the correct checksum like so:
  1. Play until 1-1, at which point the game allows for doing a full save.
  2. Play 1-1, and collect 1 extra life while there (possibly from the bonus stage from collecting all the items in the stage).
  3. Save the game again, but reset the game before the backup save is written, so it is still the backup save from before 1-1 is played.
  4. Press Up on file 1, so the next checkpoint (1-5) is selected, but reset the game before it is zeroed out.
  5. The game will see the checksum is wrong and try to restore the backup save. Let it do this for the lives counter, but reset before it can write the checkpoint index.
Since the lives counter in the backup save is 1 less than the lives counter in the main save, this results in the calculate checksum changing by -1. 1 - 1 = 0, and thus the stored checksum matches the calculated checksum. On loading the save, the game will accept the main save and give us access to 1-5.
This process can be repeated, and so allows us to skip stages 1 checkpoint at a time. However, once 6-5 is reached, things get slightly tricky, due to the way the game seemed to store save data. The game appears to split bytes into nybbles and stores them in separate bytes. So 1 byte takes 2 bytes to store instead of 1. A nybble only has a range of 0 to 15, and 6-5 has a checkpoint index of 15. So when it goes to the next checkpoint, it has to go to change the next byte from 0 to 1, and will subsequently change the lower nybble to 0.
However, this ends up working to our advantage. We can simply let the backup save restore the lower nybble to 15. The lower nybble is stored before the upper nybble here, so we can hard reset before the upper nybble is set to 0. With this, the checkpoint index will end up becoming 31, which ends up just being a glitched checkpoint. The game allows at this point to press Down until we end up reaching the last non-glitch checkpoint, 9-5, at which point the rest of the game can be completed in a more or less glitchless manner.
At some point in figuring all this out, I made a test TAS and posted it in the TASVideos discord, which had DrD2k9, the maker of the current branchless TAS, contact me and asked what was going on. After explaining what I knew, I asked him to collaborate here to mainly do the non-save glitch portions of the TAS (as I more or less just half baked strung together the branchless TAS to test the save glitch out while having my own probably bad input to do 1-1 that collected all the items).
That was the status of the save glitch TAS, and while DrD2k9 agreed to collaborate, for a long while nothing happened as we were both busy with other things.
Fast forward to mid August of 2023, and I decided to look back through the save glitch here and see if I missed any detail which can improve the glitch portion. At this point I couldn't find wherever I stored my notes for save file addresses and such, so I had to re-reverse engineer them, and this time I decided to do it fully so I would fully understand the relevant save format.
BFBE-BFD9 : Main Save
BFDA-BFDB : Main Save Checksum
BFDC-BFF7 : Backup Save
BFF8-BFF9 : Backup Save Checksum
BFFA-BFFF : Sentinel values 090400020008

Save file struct:

Each variable is 1 byte large
The variable is stored as two bytes, the second byte just being the first byte nybble swapped
The upper nybbles appear to be ignored when the bytes are used, so this is more just effectively splits the low and high nybbles of the variable into 2 bytes?

Lives Counter
File Select Checkpoint Index
Unknown / Padding?

Save struct:

BFBE: Current file selected in menu, 0 indexed
BFBF: Bitfield for active files (bit 0 = file 1, bit 1 = file 2, bit 2 = file 3)
BFC0-BFC7: Save file 1
BFC8-BFCF: Save file 2
BFD0-BFD7: Save file 3
BFD8-BFD9: Sentinel values 0207

(repeat for BFDC-BFF7 for backup save)

Checksum:

The lower nybble of all bytes of the save files (BFC0-BFD7) at added together into one byte, split into nybbles like save file variables
Now, a very interesting detail emerges here: the checksum comprises of all 3 save files, not just a single save file. This means data from another save can influence the checksum. Such as the checkpoint index in another save.
Using this, I realized the save corruption could be simplified like so:
  1. On file 1, play until 1-1, at which point the game allows for doing a full save. Soft reset after saving.
  2. On file 2, play until 1-1, at which point the game allows for doing a full save. Soft reset after saving.
  3. Lower the checkpoint index on file 2 (1-1 to 0-1). This decreases the calculated checksum by 1.
  4. Increase the checkpoint index on file 1 (1-1 to 1-5), and hard reset before it is reset to 0. This increases the calculated checksum by 1.
At this point, the calculated checksum will end up matching the stored checksum (as -1 + 1 = 0), so the game will let file 1 be at 1-5. For repeating this, we simply have to raise the checkpoint index on file 2 back to 1-1, then go in the game, enter 1-1, then we can save the game in the pause menu then soft reset, then you can repeat the lower checkpoint index on file 2 with the increase checkpoint index on file 1 to skip another checkpoint, as so on.
Of course, going in game, entering 1-1, pausing, saving, and soft resetting takes time. This is when I discovered a way to avoid ever going in game for the glitching process: deleting a save file will result in the stored checksum being updated (kind of duh, as all save files are part of the checksum). What's more interesting here is the game allows for deleting already deleted save files, and doing so will still update the stored checksum!
So for repeating the glitch, all we have to do is:
  1. Increase the checkpoint index on file 2 (0-1 to 1-1).
  2. Delete file 3. This will update the stored checksum so it will now match the calculated checksum.
  3. Lower the checkpoint index on file 2 (1-1 to 0-1). This decreases the calculated checksum by 1.
  4. Increase the checkpoint index on file 1, and hard reset before it is reset to 0. This increases the calculated checksum by 1.
This can be repeated until 6-5, where we again run into the complicated of the split nybbles. This luckily doesn't actually mean much in practice, we can continue doing the same 4 steps as always but we have to add an extra step. As the stored checksum won't actually match the calculated checksum, we'll end up having the backup save being restored. We can simply now hard reset after the lower nybble of the checkpoint index is restored from the backup save, but before the upper nybble is set to 0, and so we get the glitched checkpoint index 31. Lower it down to 9-5 and the rest of the game can be played in a more or less glitchless manner.

Non-save glitch portions

Done by DrD2k9, with the beginning portion being more or less just me pulling in the branchless TAS input and re-adjusting it.

ThunderAxe31: Claiming for judging.
ThunderAxe31: Excellent job. Accepting as a new branch.

EZGames69: Processing...
ViGadeomes: replacing the movie fixing the platform.
Last Edited by ViGadeomes on 9/22/2023 3:29 PM
Page History Latest diff List referrers