mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-07 09:27:38 -04:00
6774889505
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.
126 lines
6.2 KiB
Lua
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")
|