Files
gen1recomp/src/save_convert/Gen2Layout.lua
T
Colson Rice ebd315b01e Import Gen 2 cart saves
Gold, Silver and Crystal battery saves import now. Export is still refused.

GenSave.lua is pokered's SRAM window and nothing else, which is why the
guard refusing Gen 2 was right to be there. This adds Gen2Save.lua beside
it, covering pokegold and pokecrystal.

Every offset is generated, not transcribed. tools/gen2_sram_offsets.py
reads pokegold.sym and pokecrystal.sym from a pret build and emits
Gen2Layout.lua, including the text table from constants/charmap.asm and
Crystal's backup-save layout. Gen 2 copies a contiguous WRAM block into
SRAM bank 1, so a field's file offset is sPlayerData + (wField -
wPlayerData); the generator asserts that relation against sPokemonData
rather than assuming it, and range-guards anything outside
sGameData..sGameDataEnd.

Gold and Crystal are separate tables because they disagree about nearly
every field. Reading a Crystal save with Gold's numbers gives a party
count of 133 and 13113 hours played, with a checksum that validates.

The cart stores numbers and the engine is keyed by name, so the codec
crosswalks species, moves and items through the generated tables the same
way GenSave.crosswalks does for Gen 1. Without that, an import looks
perfect and the engine cannot read a byte of it.

Shapes that have to match what the engine reads:
  * events is byte index -> packed byte, which Save.scrubEvents validates
    with tonumber. A set of booleans is silently emptied.
  * the bag is one flat save.inventory keyed by item id, which PackMenu
    buckets by each item's pocket. Nothing reads save.keyItems or
    save.balls, and the TM/HM pocket lands here too.
  * position carries the map id, or Save.summary falls through to
    save.spawn and the player resumes somewhere else at their old
    coordinates.
  * mon.status is an ItemEffects.STATUS_CLASS key with statusTurns beside
    it, nil when healthy. 0 is truthy in Lua.

A save the real cartridge would open is not refused: TryLoadSaveFile falls
back to VerifyBackupChecksum, so this does too. Crystal's backup is
contiguous and laid out like the primary; Gold and Silver split theirs
across three sections and have none to offer.

Three suites that pinned Gen 2 import being refused now pin what refuses
instead. Tests live in tests/engine so the ROM-free tier actually runs
them.

./scripts/test.sh passes end to end, and luacheck is clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 10:32:40 -04:00

336 lines
6.9 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- GENERATED by tools/gen2_sram_offsets.py. Do not edit by hand.
-- Regenerate from a pret/pokegold + pret/pokecrystal build; see that
-- script's header for the derivation and the assertion behind it.
local Gen2Layout = {}
Gen2Layout.goldSilver = {
sCheckValue1 = 0x2008,
sCheckValue2 = 0x2D6B,
sChecksum = 0x2D69,
sGameData = 0x2009,
sGameDataEnd = 0x2D69,
wPlayerName = 0x200B,
wPlayerID = 0x2009,
wMoney = 0x23DB,
wCoins = 0x23E2,
wBadges = 0x23E4,
wKantoBadges = 0x23E5,
wRivalName = 0x2021,
wMomsName = 0x2016,
wPartyCount = 0x288A,
wPartySpecies = 0x288B,
wPartyMons = 0x2892,
wPartyMonNicknames = 0x29F4,
wPartyMonOTs = 0x29B2,
wNumItems = 0x241F,
wItems = 0x2420,
wNumKeyItems = 0x2449,
wKeyItems = 0x244A,
wNumBalls = 0x2464,
wBalls = 0x2465,
wTMsHMs = 0x23E6,
wPokedexCaught = 0x2A4C,
wPokedexSeen = 0x2A6C,
wCurBox = 0x2724,
wBoxNames = 0x2727,
wMapGroup = 0x2868,
wMapNumber = 0x2869,
wXCoord = 0x286B,
wYCoord = 0x286A,
wEventFlags = 0x261F,
wPlayerState = 0x24EA,
wGameTimeHours = 0x2053,
wGameTimeMinutes = 0x2055,
-- The 14 archived boxes, listed rather than strided (see BOX_COUNT).
boxes = { 0x4000, 0x4450, 0x48A0, 0x4CF0, 0x5140, 0x5590, 0x59E0, 0x6000, 0x6450, 0x68A0, 0x6CF0, 0x7140, 0x7590, 0x79E0 },
}
Gen2Layout.crystal = {
sCheckValue1 = 0x2008,
sCheckValue2 = 0x2D0F,
sChecksum = 0x2D0D,
sGameData = 0x2009,
sGameDataEnd = 0x2B83,
wPlayerName = 0x200B,
wPlayerID = 0x2009,
wMoney = 0x23DC,
wCoins = 0x23E3,
wBadges = 0x23E5,
wKantoBadges = 0x23E6,
wRivalName = 0x2021,
wMomsName = 0x2016,
wPartyCount = 0x2865,
wPartySpecies = 0x2866,
wPartyMons = 0x286D,
wPartyMonNicknames = 0x29CF,
wPartyMonOTs = 0x298D,
wNumItems = 0x2420,
wItems = 0x2421,
wNumKeyItems = 0x244A,
wKeyItems = 0x244B,
wNumBalls = 0x2465,
wBalls = 0x2466,
wTMsHMs = 0x23E7,
wPokedexCaught = 0x2A27,
wPokedexSeen = 0x2A47,
wCurBox = 0x2700,
wBoxNames = 0x2703,
wMapGroup = 0x2843,
wMapNumber = 0x2844,
wXCoord = 0x2846,
wYCoord = 0x2845,
wEventFlags = 0x2600,
wPlayerState = 0x24EB,
wGameTimeHours = 0x2052,
wGameTimeMinutes = 0x2054,
-- The 14 archived boxes, listed rather than strided (see BOX_COUNT).
boxes = { 0x4000, 0x4450, 0x48A0, 0x4CF0, 0x5140, 0x5590, 0x59E0, 0x6000, 0x6450, 0x68A0, 0x6CF0, 0x7140, 0x7590, 0x79E0 },
-- The backup copy the game falls back to when the primary
-- checksum fails. Same shape, shifted.
backup = {
sCheckValue1 = 0x1208,
sCheckValue2 = 0x1F0F,
sChecksum = 0x1F0D,
sGameData = 0x1209,
sGameDataEnd = 0x1D83,
wPlayerName = 0x120B,
wPlayerID = 0x1209,
wMoney = 0x15DC,
wCoins = 0x15E3,
wBadges = 0x15E5,
wKantoBadges = 0x15E6,
wRivalName = 0x1221,
wMomsName = 0x1216,
wPartyCount = 0x1A65,
wPartySpecies = 0x1A66,
wPartyMons = 0x1A6D,
wPartyMonNicknames = 0x1BCF,
wPartyMonOTs = 0x1B8D,
wNumItems = 0x1620,
wItems = 0x1621,
wNumKeyItems = 0x164A,
wKeyItems = 0x164B,
wNumBalls = 0x1665,
wBalls = 0x1666,
wTMsHMs = 0x15E7,
wPokedexCaught = 0x1C27,
wPokedexSeen = 0x1C47,
wCurBox = 0x1900,
wBoxNames = 0x1903,
wMapGroup = 0x1A43,
wMapNumber = 0x1A44,
wXCoord = 0x1A46,
wYCoord = 0x1A45,
wEventFlags = 0x1800,
wPlayerState = 0x16EB,
wGameTimeHours = 0x1252,
wGameTimeMinutes = 0x1254,
boxes = { 0x4000, 0x4450, 0x48A0, 0x4CF0, 0x5140, 0x5590, 0x59E0, 0x6000, 0x6450, 0x68A0, 0x6CF0, 0x7140, 0x7590, 0x79E0 },
},
}
Gen2Layout.charmap = {
[0x05] = "",
[0x06] = "",
[0x07] = "",
[0x08] = "",
[0x09] = "",
[0x0A] = "",
[0x0B] = "",
[0x0C] = "",
[0x0D] = "",
[0x0E] = "",
[0x0F] = "",
[0x10] = "",
[0x11] = "",
[0x12] = "",
[0x13] = "",
[0x19] = "",
[0x1A] = "",
[0x1B] = "",
[0x1C] = "",
[0x26] = "",
[0x27] = "",
[0x28] = "",
[0x29] = "",
[0x2A] = "",
[0x2B] = "",
[0x2C] = "",
[0x2D] = "",
[0x2E] = "",
[0x2F] = "",
[0x30] = "",
[0x31] = "",
[0x32] = "",
[0x33] = "",
[0x34] = "",
[0x3A] = "",
[0x3B] = "",
[0x3C] = "",
[0x3D] = "",
[0x3E] = "",
[0x3F] = "",
[0x40] = "",
[0x41] = "",
[0x42] = "",
[0x43] = "",
[0x44] = "",
[0x45] = "",
[0x46] = "",
[0x47] = "",
[0x48] = "",
[0x50] = "@",
[0x54] = "#",
[0x60] = "",
[0x61] = "",
[0x62] = "",
[0x6E] = "",
[0x6F] = "",
[0x70] = "PO",
[0x71] = "KE",
[0x72] = "",
[0x73] = "",
[0x74] = "·",
[0x75] = "",
[0x76] = "",
[0x77] = "",
[0x78] = "",
[0x79] = "",
[0x7A] = "",
[0x7B] = "",
[0x7C] = "",
[0x7D] = "",
[0x7E] = "",
[0x7F] = " ",
[0x80] = "A",
[0x81] = "B",
[0x82] = "C",
[0x83] = "D",
[0x84] = "E",
[0x85] = "F",
[0x86] = "G",
[0x87] = "H",
[0x88] = "I",
[0x89] = "J",
[0x8A] = "K",
[0x8B] = "L",
[0x8C] = "M",
[0x8D] = "N",
[0x8E] = "O",
[0x8F] = "P",
[0x90] = "Q",
[0x91] = "R",
[0x92] = "S",
[0x93] = "T",
[0x94] = "U",
[0x95] = "V",
[0x96] = "W",
[0x97] = "X",
[0x98] = "Y",
[0x99] = "Z",
[0x9A] = "(",
[0x9B] = ")",
[0x9C] = ":",
[0x9D] = ";",
[0x9E] = "[",
[0x9F] = "]",
[0xA0] = "a",
[0xA1] = "b",
[0xA2] = "c",
[0xA3] = "d",
[0xA4] = "e",
[0xA5] = "f",
[0xA6] = "g",
[0xA7] = "h",
[0xA8] = "i",
[0xA9] = "j",
[0xAA] = "k",
[0xAB] = "l",
[0xAC] = "m",
[0xAD] = "n",
[0xAE] = "o",
[0xAF] = "p",
[0xB0] = "q",
[0xB1] = "r",
[0xB2] = "s",
[0xB3] = "t",
[0xB4] = "u",
[0xB5] = "v",
[0xB6] = "w",
[0xB7] = "x",
[0xB8] = "y",
[0xB9] = "z",
[0xBA] = "",
[0xBB] = "",
[0xBC] = "",
[0xBD] = "",
[0xBE] = "",
[0xBF] = "",
[0xC0] = "Ä",
[0xC1] = "Ö",
[0xC2] = "Ü",
[0xC3] = "ä",
[0xC4] = "ö",
[0xC5] = "ü",
[0xC6] = "",
[0xC7] = "",
[0xC8] = "",
[0xC9] = "",
[0xCA] = "",
[0xCB] = "",
[0xCC] = "",
[0xCD] = "",
[0xCE] = "",
[0xCF] = "",
[0xD0] = "'d",
[0xD1] = "'l",
[0xD2] = "'m",
[0xD3] = "'r",
[0xD4] = "'s",
[0xD5] = "'t",
[0xD6] = "'v",
[0xD7] = "",
[0xD8] = "",
[0xD9] = "",
[0xDA] = "",
[0xDB] = "",
[0xDC] = "",
[0xDD] = "",
[0xDE] = "",
[0xDF] = "",
[0xE0] = "'",
[0xE1] = "PK",
[0xE2] = "MN",
[0xE3] = "-",
[0xE4] = "",
[0xE5] = "",
[0xE6] = "?",
[0xE7] = "!",
[0xE8] = ".",
[0xE9] = "&",
[0xEA] = "é",
[0xEB] = "",
[0xEC] = "",
[0xED] = "",
[0xEE] = "",
[0xEF] = "",
[0xF0] = "¥",
[0xF1] = "×",
[0xF2] = "",
[0xF3] = "/",
[0xF4] = ",",
[0xF5] = "",
[0xF6] = "0",
[0xF7] = "1",
[0xF8] = "2",
[0xF9] = "3",
[0xFA] = "4",
[0xFB] = "5",
[0xFC] = "6",
[0xFD] = "7",
[0xFE] = "8",
[0xFF] = "9",
}
return Gen2Layout