💾 Archived View for tilde.team › ~khuxkm › gemlog › pokered-password.gmi captured on 2023-06-16 at 19:13:34. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2021-11-30)
-=-=-=-=-=-=-
Published: 2021-10-26
Tags: technology
Note: At the time of writing this, I cannot find the post that inspired this thought process. If I do, I will update the post with a link. As it stands, this tangent was inspired by a video about Pokemon Red given it used a password system a la Metroid.
So Pokemon Red uses lots of save data. All 4 save banks provided by the MBC have use. Assuming we remove the save battery (thus allowing the sprite data use of SRAM), how much info needs to be stored in the password?
There's too much data in the game's save to port everything 1 to 1. However, if we cut out the Pokemon boxen, remove custom name choices, and optimize what's left, we can get a halfway-usable password length that's definitely an improvement.
Without removing any save features, how much info would need to be saved?
|Bank|Start|End |Size |Information Stored | |----|-----|-----|------|------------------------------------------| |0 |$A000|$B857|0x1857|Sprite decompression and Hall of Fame data| |1 |$A598|$B523|0x0F8B|Main information for the save | |2 |$A000|$BA52|0x1A52|Pokemon boxen 1-6 | |3 |$A000|$BA52|0x1A52|Pokemon boxen 7-12 | Figure 1 - SRAM usage map
Note that we don't have to save bank 0, as the only thing of worth is Hall of Fame data, which can be safely ignored.
This brings our total to 442Fh bytes, or 17455 bytes. This very obviously isn't possible, as it requires 34910 characters to be remembered and accurately input. Nobody can remember that. Maybe an elephant.
As good as the boxen are for storing Pokemon and executing arbitrary code, they're over 77% of the info. Eliminating them means we just need to store F8Bh bytes, or 3979 bytes. Much more manageable, but we could go further.
Here's the SRAM usage footprints of bank 1:
|Label |Usage |Start|End |Size | |-----------------|----------------|-----|-----|------| |sPlayerName |Player's name |$A598|$A5A2|0x000B| |sMainData |Main data |$A5A3|$AD2B|0x0789| |sSpriteData |Sprite data |$AD2C|$AF2B|0x0200| |sPartyData |Party data |$AF2C|$B0BF|0x0194| |sCurBoxData |Current box data|$B0C0|$B521|0x0462| |sTilesetType |Current tileset |$B522|- |0x0001| |sMainDataChecksum|Bank 1 checksum |$B523|- |0x0001| Figure 2 - Bank 1 SRAM chart
We can immediately toss out `sCurBoxData`, as we removed the boxen. `sTilesetType` can be calculated from the map. `sPlayerName` is an interesting case.
We could remove custom names and restrict users to the 3 choices at the beginning. This would allow us to do a bitmap and pack the rival's name in there too (`0000PPRR`, where P is the player's name, and R is the rival's name). However, we could still leave the custom name option in. For the purposes of this experiment, as we ARE trying to make the best-for-password system, we'll take custom names out.
`sSpriteData` is sprite state data which doesn't exactly need to be saved.
|Label |Usage |Start|End |Size | |-----------------|----------------------|-----|-----|------| |sNames |Player and rival names|$A598|- |0x0001| |sMainData |Main data |$A599|$AD22|0x0789| |sPartyData |Party data |$AF23|$B0B7|0x0194| |sMainDataChecksum|Bank 1 checksum |$B0B8|- |0x0001| Figure 3 - New SRAM chart
By applying all the space-saving features we have at our disposal, the final total comes to 91Fh, or 2335 bytes. We're getting closer to an acceptable number, but we should start by optimizing the biggest space-taker: `sMainData`.
(Please note that the addresses in Figures 4 and 5 are WRAM. They're relative to $A599 and $AF23 in the SRAM, respectively.)
|Label |Usage |Start|End |Size | |-------------------------------|----------------------------------------------------|-----|-----|------| |wPokedexOwned |Pokedex flags for owned Pokemon |$D2F7|$D309|0x0013| |wPokedexSeen |Pokedex flags for seen Pokemon |$D30A|$D31C|0x0013| |wNumBagItems |Number of items in bag |$D31D|- |0x0001| |wBagItems |Items in bag (ID, count) |$D31E|$D346|0x0029| |wPlayerMoney |Player's money (stored as BCD) |$D347|$D349|0x0003| |wRivalName |Rival's name |$D34A|$D354|0x000B| |wOptions |Game settings |$D355|- |0x0001| |wObtainedBadges |Tracks badge progress |$D356|- |0x0001| |NONE |Wasted byte |$D357|- |0x0001| |wLetterPrintingDelayFlags |Flags for printing delay |$D358|- |0x0001| |wPlayerID |Used to track which Pokemon are the player's |$D359|$D35A|0x0002| |wMapMusicSoundID |Sound ID of the map's music |$D35B|- |0x0001| |wMapMusicROMBank |ROM bank of the map's music |$D35C|- |0x0001| |wMapPalOffset |Palette offset |$D35D|- |0x0001| |wCurMap |The map the player is currently on |$D35E|- |0x0001| |wCurrentTileBlockMapViewPointer|I don't even know what this is |$D3FF|$D400|0x0002| |wYCoord |The Y coordinate of the player in the map |$D401|- |0x0001| |wXCoord |The X coordinate of the player in the map |$D402|- |0x0001| |wYBlockCoord |The Y coordinate of the block the player is on |$D403|- |0x0001| |wXBlockCoord |The X coordinate of the block the player is on |$D404|- |0x0001| |wLastMap |The last map the player was on (used for exit warps)|$D505|- |0x0001| Figure 4 - sMainData chart
We can remove the Pokedex, since it's going to be hard to catch 'em all (since you don't have access to boxen, so you have to have an open party slot always). This frees up 38 bytes. We can remove the wasted byte, as well as the rival's name (we removed custom names, so the rival name choice is stored in `sNames`). This frees up 11 bytes. We can cut the amount of items in half, giving you 20 more bytes. The letter printing delay flags don't need to be stored (not to mention that they can be stored in wOptions), and the sound ID, bank, and palette offset for the map can be calculated from the map as easily as `sTilesetType`. This frees up even more space.
|Label |Description |Start|End |Length| |---------------|--------------------------------------------|-----|-----|------| |wNumBagItems |Number of items in bag |$D2F7|- |0x0001| |wBagItems |Items in bag (ID, count) |$D2F8|$D30C|0x0015| |wPlayerMoney |Player's money (stored as BCD) |$D30D|$D30F|0x0003| |wOptions |Game settings |$D310|- |0x0001| |wObtainedBadges|Tracks badge progress |$D311|- |0x0001| |wPlayerID |Used to track which Pokemon are the player's|$D312|$D313|0x0002| Figure 5 - sMainData after the pruning
This takes sMainData down to 0x29 bytes. As far as I can tell, this is as far as you'll get. If you remove trading, you could shave off 14 bytes (since any
Pokemon in your game would be yours), but unlike the name option, this would actually affect the game's mechanics outside of a cosmetic change (and unlike the boxen, it's only to save 14 bytes!), so we'll keep it.
This takes our new save-data chart to:
|Label |Description |Start|End |Length| |-----------------|----------------------|-----|-----|------| |sNames |Player and rival names|$A598|- |0x0001| |sMainData |Main data |$A599|$A5C1|0x0029| |sPartyData |Party data |$A5C2|$A755|0x0194| |sMainDataChecksum|Checksum |$A756|- |0x0001| Figure 6 - Save data chart
This is down to 447 bytes. If we implement base64 in a ROM bank, we can store this for the user as 596 characters. Much better than the 34910 characters from the start of the article.
I may revisit this topic at a later date and work on further optimizing the data stored, but for now, this is enough.