mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-09 01:59:15 -04:00
291 lines
11 KiB
Lua
291 lines
11 KiB
Lua
local bit = require("bit")
|
|
local B = require("tests.fixtures.save.bytes")
|
|
local Compat = require("src.save_convert.Compat")
|
|
|
|
local G3 = {}
|
|
|
|
G3.SIZE = 0x20000
|
|
|
|
G3.CHUNKS = {
|
|
frlg = { [0] = 0xF24, 0xF80, 0xF80, 0xF80, 0xEE8, 0xF80, 0xF80, 0xF80, 0xF80, 0xF80, 0xF80, 0xF80, 0xF80, 0x7D0 },
|
|
emerald = { [0] = 0xF2C, 0xF80, 0xF80, 0xF80, 0xF08, 0xF80, 0xF80, 0xF80, 0xF80, 0xF80, 0xF80, 0xF80, 0xF80, 0x7D0 },
|
|
}
|
|
|
|
G3.F = {
|
|
frlg = { key = 0xF20, powder = 0xAF8, party = 0x38, partyCount = 0x34, money = 0x290, coins = 0x294,
|
|
pcItems = { 0x298, 30 }, pockets = { { 0x310, 42 }, { 0x3B8, 30 }, { 0x430, 13 }, { 0x464, 58 }, { 0x54C, 43 } },
|
|
dexSeen = { 0x5F8, 0x3A18 }, gameStats = 0x1200 },
|
|
emerald = { key = 0xAC, powder = 0x1F4, party = 0x238, partyCount = 0x234, money = 0x490, coins = 0x494,
|
|
pcItems = { 0x498, 50 }, pockets = { { 0x560, 30 }, { 0x5D8, 30 }, { 0x650, 16 }, { 0x690, 64 }, { 0x790, 46 } },
|
|
dexSeen = { 0x988, 0x3B24 }, gameStats = 0x159C },
|
|
}
|
|
|
|
local ORDERS = {
|
|
"GAEM", "GAME", "GEAM", "GEMA", "GMAE", "GMEA", "AGEM", "AGME", "AEGM", "AEMG", "AMGE", "AMEG",
|
|
"EGAM", "EGMA", "EAGM", "EAMG", "EMGA", "EMAG", "MGAE", "MGEA", "MAGE", "MAEG", "MEGA", "MEAG",
|
|
}
|
|
|
|
function G3.text(s, len)
|
|
local out = {}
|
|
for i = 1, #s do
|
|
local c = s:sub(i, i)
|
|
local v
|
|
if c:match("%u") then v = 0xBB + c:byte() - 65
|
|
elseif c:match("%l") then v = 0xD5 + c:byte() - 97
|
|
elseif c:match("%d") then v = 0xA1 + c:byte() - 48
|
|
elseif c == " " then v = 0x00
|
|
else v = 0xAC end
|
|
out[i] = v
|
|
end
|
|
out[#out + 1] = 0xFF
|
|
while #out < len do out[#out + 1] = 0xFF end
|
|
return out
|
|
end
|
|
|
|
local function u32(x) return x % 4294967296 end
|
|
|
|
function G3.mon(o)
|
|
local m = {
|
|
pid = 0x12345678, tid = 0x1234, sid = 0x5678, nick = "MON", lang = 2, egg = false, badEgg = false,
|
|
ot = "RED", markings = 0, species = 4, item = 0, exp = 135, ppBonuses = 0, friendship = 70,
|
|
moves = { 10, 45, 0, 0 }, pp = { 35, 40, 0, 0 }, evs = { 0, 0, 0, 0, 0, 0 }, contest = { 0, 0, 0, 0, 0, 0 },
|
|
pokerus = 0, metLocation = 88, origins = 5 + 4 * 128 + 4 * 2048, ivs = { 1, 2, 3, 4, 5, 6 }, ability = 0,
|
|
ribbons = 0, status = 0, level = 5, hp = 20, maxHp = 20, stats = { 11, 10, 12, 11, 10 }, mail = 0xFF,
|
|
breakChecksum = false,
|
|
}
|
|
for k, v in pairs(o or {}) do m[k] = v end
|
|
return m
|
|
end
|
|
|
|
function G3.encodeMon(m, party)
|
|
local b = B.new(party and 100 or 80, 0)
|
|
B.le(b, 0, m.pid, 4)
|
|
B.le(b, 4, m.tid + m.sid * 65536, 4)
|
|
local nick = G3.text(m.nick, 10)
|
|
for i = 1, 10 do b[7 + i] = nick[i] end
|
|
b[0x12] = m.lang
|
|
b[0x13] = (m.badEgg and 1 or 0) + 2 + (m.egg and 4 or 0)
|
|
local ot = G3.text(m.ot, 7)
|
|
for i = 1, 7 do b[0x13 + i] = ot[i] end
|
|
b[0x1B] = m.markings
|
|
local sub = { G = B.new(12, 0), A = B.new(12, 0), E = B.new(12, 0), M = B.new(12, 0) }
|
|
B.le(sub.G, 0, m.species, 2); B.le(sub.G, 2, m.item, 2); B.le(sub.G, 4, m.exp, 4)
|
|
B.put(sub.G, 8, m.ppBonuses, m.friendship)
|
|
for i = 1, 4 do B.le(sub.A, (i - 1) * 2, m.moves[i], 2); B.put(sub.A, 7 + i, m.pp[i]) end
|
|
for i = 1, 6 do B.put(sub.E, i - 1, m.evs[i]); B.put(sub.E, 5 + i, m.contest[i]) end
|
|
B.put(sub.M, 0, m.pokerus, m.metLocation)
|
|
B.le(sub.M, 2, m.origins, 2)
|
|
local iv = 0
|
|
for i = 1, 6 do iv = iv + m.ivs[i] * 2 ^ ((i - 1) * 5) end
|
|
iv = iv + (m.egg and 2 ^ 30 or 0) + (m.ability == 1 and 2 ^ 31 or 0)
|
|
B.le(sub.M, 4, iv, 4)
|
|
B.le(sub.M, 8, m.ribbons, 4)
|
|
local order = ORDERS[m.pid % 24 + 1]
|
|
local plain = B.new(48, 0)
|
|
for i = 1, 4 do
|
|
local s = sub[order:sub(i, i)]
|
|
for k = 0, 11 do plain[(i - 1) * 12 + k] = s[k] end
|
|
end
|
|
local sum = 0
|
|
for i = 0, 47, 2 do sum = (sum + plain[i] + plain[i + 1] * 256) % 65536 end
|
|
if m.breakChecksum then sum = (sum + 1) % 65536 end
|
|
B.le(b, 0x1C, sum, 2)
|
|
local key = u32(bit.bxor(m.pid, m.tid + m.sid * 65536))
|
|
for i = 0, 11 do
|
|
local w = B.getLE(plain, i * 4, 4)
|
|
B.le(b, 0x20 + i * 4, u32(bit.bxor(w, key)), 4)
|
|
end
|
|
if party then
|
|
B.le(b, 80, m.status, 4)
|
|
B.put(b, 84, m.level, m.mail)
|
|
B.le(b, 86, m.hp, 2); B.le(b, 88, m.maxHp, 2)
|
|
for i = 1, 5 do B.le(b, 88 + i * 2, m.stats[i], 2) end
|
|
end
|
|
local out = {}
|
|
for i = 0, b.size - 1 do out[i + 1] = b[i] end
|
|
return out
|
|
end
|
|
|
|
local function unrle(s)
|
|
local out = {}
|
|
for tok in s:gmatch("%S+") do
|
|
local k, n = tok:match("^([ZF])(%x+)$")
|
|
if k then out[#out + 1] = string.rep(k == "Z" and "\0" or "\255", tonumber(n, 16))
|
|
else out[#out + 1] = (tok:gsub("%x%x", function(h) return string.char(tonumber(h, 16)) end)) end
|
|
end
|
|
return table.concat(out)
|
|
end
|
|
|
|
local BASES = {
|
|
firered = { "tests.fixture_data.gen3_saves", "fr_rich_game", "frlg" },
|
|
leafgreen = { "tests.fixture_data.gen3_saves", "lg_rich_game", "frlg" },
|
|
emerald = { "tests.fixture_data.gen3_saves_emerald", "em_fresh", "emerald" },
|
|
}
|
|
|
|
function G3.base(version)
|
|
local spec = BASES[version]
|
|
local image = unrle(require(spec[1]).images[spec[2]])
|
|
local family = spec[3]
|
|
local blocks = assert(Compat.gen3Blocks(image, family))
|
|
local w = {
|
|
version = version, family = family, F = G3.F[family], trailer = image:sub(G3.SIZE + 1),
|
|
image = B.fromString(image:sub(1, G3.SIZE)), at = blocks.slots[blocks.slot].at,
|
|
sb2 = B.fromString(blocks.sb2), sb1 = B.fromString(blocks.sb1), storage = B.fromString(blocks.storage),
|
|
}
|
|
w.key = B.getLE(w.sb2, w.F.key, 4)
|
|
return w
|
|
end
|
|
|
|
function G3.setMoney(w, money)
|
|
B.le(w.sb1, w.F.money, u32(bit.bxor(money, w.key)), 4)
|
|
end
|
|
|
|
function G3.setPocket(w, which, list)
|
|
local p = w.F.pockets[which]
|
|
B.fill(w.sb1, p[1], p[2] * 4, 0)
|
|
for i, it in ipairs(list) do
|
|
B.le(w.sb1, p[1] + (i - 1) * 4, it[1], 2)
|
|
B.le(w.sb1, p[1] + (i - 1) * 4 + 2, bit.bxor(it[2], w.key % 65536) % 65536, 2)
|
|
end
|
|
end
|
|
|
|
function G3.setParty(w, mons)
|
|
B.put(w.sb1, w.F.partyCount, #mons)
|
|
B.fill(w.sb1, w.F.party, 600, 0)
|
|
-- src/pokemon.c:1737
|
|
for i = #mons + 1, 6 do w.sb1[w.F.party + (i - 1) * 100 + 85] = 0xFF end
|
|
for i, m in ipairs(mons) do
|
|
local bytes = G3.encodeMon(m, true)
|
|
for k = 1, 100 do w.sb1[w.F.party + (i - 1) * 100 + k - 1] = bytes[k] end
|
|
end
|
|
end
|
|
|
|
function G3.setBoxMon(w, box, slot, m)
|
|
local at = 4 + ((box - 1) * 30 + slot - 1) * 80
|
|
if not m then B.fill(w.storage, at, 80, 0) return end
|
|
local bytes = G3.encodeMon(m, false)
|
|
for k = 1, 80 do w.storage[at + k - 1] = bytes[k] end
|
|
end
|
|
|
|
function G3.setDexAll(w)
|
|
for i = 0, 385 do
|
|
B.setBit(w.sb2, 0x28, i, true)
|
|
B.setBit(w.sb2, 0x5C, i, true)
|
|
B.setBit(w.sb1, w.F.dexSeen[1], i, true)
|
|
B.setBit(w.sb1, w.F.dexSeen[2], i, true)
|
|
end
|
|
end
|
|
|
|
local function writeChunk(w, id, src, srcOff)
|
|
local off = w.at[id]
|
|
local size = G3.CHUNKS[w.family][id]
|
|
for k = 0, size - 1 do w.image[off + k] = src[srcOff + k] end
|
|
for k = size, 0xF7F do w.image[off + k] = 0 end
|
|
local s = {}
|
|
for k = 0, 0xF7F do s[k + 1] = string.char(w.image[off + k]) end
|
|
B.le(w.image, off + 0xFF6, Compat.gen3SectorChecksum(table.concat(s), 0, size), 2)
|
|
end
|
|
|
|
function G3.emit(w, keepTrailer)
|
|
writeChunk(w, 0, w.sb2, 0)
|
|
local pos = 0
|
|
for id = 1, 4 do writeChunk(w, id, w.sb1, pos); pos = pos + G3.CHUNKS[w.family][id] end
|
|
pos = 0
|
|
for id = 5, 13 do writeChunk(w, id, w.storage, pos); pos = pos + G3.CHUNKS[w.family][id] end
|
|
if not keepTrailer then return B.pack(w.image) end
|
|
return B.pack(w.image) .. (w.trailer ~= "" and w.trailer or string.rep("\0", 16))
|
|
end
|
|
|
|
function G3.writeHof(w, teams)
|
|
local buf = B.new(0x2000, 0)
|
|
for t = 0, teams - 1 do
|
|
for s = 0, 5 do
|
|
local at = t * 120 + s * 20
|
|
B.le(buf, at, 0x1234 + 0x56780000, 4)
|
|
B.le(buf, at + 4, 0x9ABC0000 + t * 6 + s, 4)
|
|
B.le(buf, at + 8, (s + 1) + 5 * 512, 2)
|
|
local nick = G3.text("HOF" .. s, 10)
|
|
for k = 1, 10 do buf[at + 9 + k] = nick[k] end
|
|
end
|
|
end
|
|
for half = 0, 1 do
|
|
local off = 0x1C000 + half * 0x1000
|
|
for k = 0, 0xF7F do w.image[off + k] = buf[half * 0xF80 + k] or 0 end
|
|
for k = 0xF80, 0xFFF do w.image[off + k] = 0 end
|
|
local s = {}
|
|
for k = 0, 0xF7F do s[k + 1] = string.char(w.image[off + k]) end
|
|
local ck = Compat.gen3SectorChecksum(table.concat(s), 0, 0xF80)
|
|
B.le(w.image, off + 0xFF4, ck, 2)
|
|
B.le(w.image, off + 0xFF6, ck, 2)
|
|
B.le(w.image, off + 0xFF8, 0x08012025, 4)
|
|
end
|
|
end
|
|
|
|
local function boxMon(i, family)
|
|
return G3.mon({ pid = 0x01000193 * i % 4294967296, species = (i % 250) + 1, nick = "B" .. (i % 1000),
|
|
exp = 100 + i, friendship = 70, tid = 0x1234, sid = 0x5678, ivs = { i % 32, (i * 3) % 32, 1, 2, 3, 4 },
|
|
origins = 5 + (family == "emerald" and 3 or 4) * 128 + 4 * 2048 })
|
|
end
|
|
|
|
function G3.cases()
|
|
local C = {}
|
|
local function add(id, version, fn, extra)
|
|
local w = G3.base(version)
|
|
local keep = fn and fn(w)
|
|
local c = { id = id, gen = 3, version = version, bytes = G3.emit(w, keep == "trailer") }
|
|
for k, v in pairs(extra or {}) do c[k] = v end
|
|
C[#C + 1] = c
|
|
end
|
|
for _, v in ipairs({ "firered", "leafgreen", "emerald" }) do
|
|
local p = "g3." .. v .. "."
|
|
C[#C + 1] = { id = p .. "fresh_cart", gen = 3, version = v, refuse = true, bytes = string.rep("\255", G3.SIZE) }
|
|
add(p .. "basic", v, nil)
|
|
add(p .. "full_party_statuses", v, function(w)
|
|
local statuses = { 0, 3, 0x08, 0x10, 0x20, 0x40 }
|
|
local mons = {}
|
|
for i = 1, 6 do
|
|
mons[i] = G3.mon({ pid = 0x0F0F0000 + i * 977, status = statuses[i], nick = "P" .. i, level = 30 + i,
|
|
species = 1 + i * 3, pokerus = i == 2 and 0x13 or 0 })
|
|
end
|
|
G3.setParty(w, mons)
|
|
end)
|
|
add(p .. "toxic_counter", v, function(w)
|
|
G3.setParty(w, { G3.mon({ status = 0x80 + 3 * 256, nick = "TOXIC" }) })
|
|
end)
|
|
add(p .. "full_boxes", v, function(w)
|
|
for b = 1, 14 do for s = 1, 30 do G3.setBoxMon(w, b, s, boxMon(b * 30 + s, w.family)) end end
|
|
w.storage[0] = 13
|
|
end)
|
|
add(p .. "eggs", v, function(w)
|
|
G3.setParty(w, { G3.mon(), G3.mon({ pid = 0x2222, egg = true, nick = "EGG", friendship = 20, species = 172 }) })
|
|
G3.setBoxMon(w, 3, 7, G3.mon({ pid = 0x3333, egg = true, nick = "EGG", friendship = 5, species = 175 }))
|
|
end)
|
|
add(p .. "bad_checksum_mon", v, function(w)
|
|
G3.setBoxMon(w, 2, 1, G3.mon({ pid = 0x4444, breakChecksum = true }))
|
|
end)
|
|
add(p .. "hoenn_species", v, function(w)
|
|
G3.setBoxMon(w, 1, 30, G3.mon({ pid = 0x5555, species = 300, nick = "HOENN" }))
|
|
end)
|
|
add(p .. "foreign_species", v, function(w)
|
|
G3.setBoxMon(w, 1, 29, G3.mon({ pid = 0x6666, species = 500, nick = "HACK" }))
|
|
end)
|
|
add(p .. "max_stacks", v, function(w)
|
|
local list = {}
|
|
for i = 1, #w.F.pockets do
|
|
list = {}
|
|
for k = 1, w.F.pockets[i][2] do list[k] = { 13 + k, 999 } end
|
|
G3.setPocket(w, i, list)
|
|
end
|
|
G3.setMoney(w, 999999)
|
|
end)
|
|
add(p .. "all_dex", v, G3.setDexAll)
|
|
add(p .. "box_index_0", v, function(w) w.storage[0] = 0 end)
|
|
add(p .. "wallpaper_collision", v, function(w) w.storage[0x83C2 + 4] = 4 end)
|
|
add(p .. "hall_of_fame", v, function(w) G3.writeHof(w, 50) end)
|
|
add(p .. "trailer", v, function() return "trailer" end)
|
|
end
|
|
return C
|
|
end
|
|
|
|
return G3
|