Files
gen1recomp/tests/engine/gen3_save_rs_codec_test.lua
T
bryanthaboi 6774889505 Add Pokemon Ruby and Sapphire
Ruby and Sapphire 1.0/1.1/1.2 import, play, save, convert and link. Gen 3 shaders, braille, weather and reflection parity, link room players, RS custom carts and launcher support, Gen 3 link fixes for Emerald and FireRed/LeafGreen.
2026-10-05 05:07:42 -04:00

126 lines
6.2 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
love = require("tests.love_stub")
local T = require("tests.harness")
local eq, check = T.eq, T.check
local B = require("tests.fixtures.save.bytes")
local Mon = require("tests.fixtures.save.gen3_build")
local Gen3 = require("src.save_convert.Gen3Save")
local Compat = require("src.save_convert.Compat")
-- src/save.c:110
local chunks = { [0]=0x890,0xF80,0xF80,0xF80,0xC40,0xF80,0xF80,0xF80,0xF80,0xF80,0xF80,0xF80,0xF80,0x7D0 }
local function fixture(game)
local sb2,sb1,storage = B.new(0x890),B.new(0x3AC0),B.new(0x83D0)
for i,v in ipairs(Mon.text("BRENDAN",8)) do sb2[i-1]=v end
B.le(sb2,0xA,0x1234,2);B.le(sb2,0xC,0x5678,2)
B.put(sb1,4,25,40,255,0);B.le(sb1,8,2,2);B.le(sb1,10,2,2)
B.le(sb1,0x490,123456,4);B.le(sb1,0x494,4321,2)
for _, pocket in ipairs({{0x560,13,19},{0x5B0,259,1},{0x600,4,20},{0x640,289,2},{0x740,133,99}}) do
B.le(sb1,pocket[1],pocket[2],2);B.le(sb1,pocket[1]+2,pocket[3],2)
end
B.le(sb1,0x1540+7*4,0x12345678,4)
local parent = Mon.mon({species=280,nick="TORCHIC",ot="BRENDAN",origins=5+(game=="ruby" and 2 or 1)*128+4*2048})
for slot=0,1 do
parent.pid=0x12345678+slot
for i,v in ipairs(Mon.encodeMon(parent,false)) do sb1[0x2F9C+slot*80+i-1]=v end
local mail=0x2F9C+0xA0+slot*56
for i=0,8 do B.le(sb1,mail+i*2,i+slot*10,2) end
for i,v in ipairs(Mon.text("BRENDAN",8)) do sb1[mail+0x12+i-1]=v end
B.le(sb1,mail+0x1A,0x56781234,4);B.le(sb1,mail+0x1E,280,2);B.le(sb1,mail+0x20,121,2)
for i,v in ipairs(Mon.text("OTNAME",8)) do sb1[mail+36+i-1]=v end
for i,v in ipairs(Mon.text("TORCHIC",11)) do sb1[mail+44+i-1]=v end
end
B.le(sb1,0x30AC,0x01020304,4);B.le(sb1,0x30B0,0xA0B0C0D0,4)
B.le(sb1,0x30B4,0xFECA,2);B.put(sb1,0x30B6,77)
for _, raw in ipairs({{0x311B,20},{0x312F,21},{0x3160,1328},{0x3690,1004}}) do
for i=0,raw[2]-1 do sb1[raw[1]+i]=(i*37+11)%256 end
end
for i=0,899 do sb1[0x2738+i]=(i*17+23)%256 end
for i=0,24 do sb1[0x2738+i*36]=255 end
local flash = B.new(0x20000,255)
local parts = {[0]={sb2,0}}
local offset=0
for id=1,4 do parts[id]={sb1,offset};offset=offset+chunks[id] end
offset=0
for id=5,13 do parts[id]={storage,offset};offset=offset+chunks[id] end
for id=0,13 do
local at=(14+id)*0x1000
B.fill(flash,at,0xF80,0)
local source,start=parts[id][1],parts[id][2]
for i=0,chunks[id]-1 do flash[at+i]=source[start+i] end
local sum=0
for i=0,chunks[id]-4,4 do sum=(sum+B.getLE(flash,at+i,4))%4294967296 end
B.le(flash,at+0xFF4,id,2);B.le(flash,at+0xFF6,(sum+math.floor(sum/65536))%65536,2)
B.le(flash,at+0xFF8,0x08012025,4);B.le(flash,at+0xFFC,3,4)
end
return B.pack(flash),B.pack(sb1)
end
for _,game in ipairs({"ruby","sapphire"}) do
local codec=Gen3.forVersion(game)
eq(codec.L.FAMILY,"rs",game.." own native layout")
eq(codec.L.GAME,game,game.." edition routing")
eq(codec.L.BLOCKS[1].size,0x890,"native SaveBlock2 size")
eq(codec.L.BLOCKS[2].size,0x3AC0,"native SaveBlock1 size")
local image,raw=fixture(game)
eq(Gen3.sniff(image),"rs","RS family sniff")
local cart,blocks=codec.decode(image)
check(cart~=nil,"native fixture decodes: "..tostring(blocks))
if cart then
eq(cart.money,123456,"unencrypted money")
eq(cart.coins,4321,"unencrypted coins")
eq(cart.pockets.ITEMS[1].qty,19,"unencrypted item quantity")
eq(cart.pockets.KEY_ITEMS[1].qty,1,"native key-items offset")
eq(cart.pockets.POKE_BALLS[1].qty,20,"native balls offset")
eq(cart.pockets.TM_CASE[1].qty,2,"native TMs offset")
eq(cart.pockets.BERRY_POUCH[1].qty,99,"native berries offset")
eq(cart.gameStats[7],0x12345678,"unencrypted stat")
eq(cart.daycare.mons[1].species,280,"first parent before mails")
eq(cart.daycare.mons[2].species,280,"second parent before mails")
eq(cart.daycare.steps[1],0x01020304,"first steps after both mails")
eq(cart.daycare.steps[2],0xA0B0C0D0,"second steps after both mails")
eq(cart.daycare.mail[1].otName,"OTNAME","first parent mail")
eq(cart.daycare.mail[2].message.words[1],10,"second parent mail")
eq(cart.daycare.offspringPersonality,0xFECA,"RS offspring u16")
eq(cart.daycare.stepCounter,77,"RS step counter")
local save,why=codec.importPort(image,game)
check(save~=nil,"native cart imports into port: "..tostring(why))
if save then
eq(save.map,game=="ruby" and "RU_INSIDE_OF_TRUCK" or "SA_INSIDE_OF_TRUCK","own edition map prefix")
check(save.modData[game.."_daycare"]~=nil,"edition daycare save key")
eq(save.encryptionKey,nil,"no runtime encryption key")
local prefix=codec.recordMixTvPrefix(save,save.tvShows)
eq(prefix,raw:sub(0x2738+1,0x2738+256),"RS record mixing takes native first 256 TV bytes")
save.tvShows[0].active=false
local changed=codec.recordMixTvPrefix(save,save.tvShows)
eq(changed:byte(2),0,"RS record mixing applies active-header mutation")
eq(changed:sub(3),prefix:sub(3),"RS record mixing preserves opaque remaining native payload")
save.tvShows[0].active=true
save.money=654321
local output,note=codec.exportPort(save,{
version=game,metGame=game=="ruby" and 2 or 1,
toNational=function(species) return species==280 and 255 or species end,
itemId=function(item) return tonumber(item) end,
})
check(output~=nil,"native cart exports: "..tostring(note))
if output then
local newer,newBlocks=codec.decode(output)
check(newer~=nil,"export decodes")
if newer then
eq(newer.money,654321,"edited money roundtrip")
eq(newer.daycare.steps[2],0xA0B0C0D0,"daycare steps retained")
eq(newer.daycare.mail[2].message.words[1],10,"daycare mail retained")
for _,span in ipairs({{0x2738,900},{0x311B,20},{0x312F,21},{0x3160,1328},{0x3690,1004}}) do
eq(newBlocks.sb1:sub(span[1]+1,span[1]+span[2]),raw:sub(span[1]+1,span[1]+span[2]),"opaque native span "..span[1])
end
local direct=B.fromString(newBlocks.sb1)
eq(B.getLE(direct,0x490,4),654321,"exported currency bytes are plain")
eq(Gen3.sniff(output),"rs","export keeps RS family")
end
end
end
end
eq(Compat.generationOf(game),3,"compat generation")
end
T.finish("gen3_save_rs_codec")