# Pokemon Red but it uses passwords 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? ## TLDR 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. ## Stage 1: No data removal Without removing any save features, how much info would need to be saved? ```Figure 1: SRAM usage map. Bank 0 is the sprite decompression buffer and where the Hall of Fame data is stored, bank 1 is the main save information, and banks 2 and 3 are exclusively Pokemon boxen. |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. ## Stage 2 - No boxen 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. ## Stage 3 - Optimizing Bank 1 Here's the SRAM usage footprints of bank 1: ```Figure 2: Bank 1 SRAM chart. The player name takes 11 bytes, the main data block takes over 1,900 bytes, the sprite data is 512 bytes, the party data is 404 bytes, the current box data is over 1,000 bytes, the tileset type is one byte, and the checksum is one final byte. |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. ```Figure 3: New SRAM chart. The name choices are now just 1 byte, the main data block is still over 1,900 bytes (we'll get to that), the party data is still 404 bytes, and the checksum is one final byte. |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.) ```Figure 4: Main data block map. The pokedex flags, seen and owned, take up 38 bytes, the bag takes up 42 bytes, the player money, stored as binary coded decimal, takes up 3 bytes, the rival's name takes 11 bytes, the game settings are 1 byte, the bitfield for badges is one byte, there's a wasted byte in there, followed by the flags for letter delay (the text speed, in other words) which take 1 byte, the player ID, which is 2 bytes, the sound ID and ROM bank for the map's music, which both take a byte, the map's palette offset and ID, which each take 1 byte, a 2 byte pointer to some piece of memory I don't understand, the player's coordinates, X and Y, which each take a byte, the X and Y coordinates of the block the player is in, which each take a byte, and the last map the player was on, which takes a byte. |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. ```Figure 5: Main data block after pruning. A much more manageable chart, it contains the number of items in the bag, which is a byte, followed by the bag items themselves, which take 21 bytes at most, the player money stored in binary coded decimal, which takes 3 bytes, the game settings byte, the badge bitfield, and the 2 byte player ID. |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: ```Figure 6: Our final save data chart. The name choices byte, the main data (which now only takes 41 bytes), the party data (which still takes 404 bytes), and the final checksum byte. |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.