mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-07 17:31:35 -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.
102 lines
5.7 KiB
Lua
102 lines
5.7 KiB
Lua
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
local T = require("tests.harness")
|
|
local GameVersion = require("src.core.GameVersion")
|
|
local Opcodes = require("src.core.game3.scripting.opcodes")
|
|
local Disasm = require("src.core.game3.scripting.disasm")
|
|
local TextIR = require("src.core.game3.scripting.text_ir")
|
|
local Native = require("src.core.game3.constants.ruby.script_cmds")
|
|
local Builds = require("src.import.gba.rs_builds")
|
|
local Movement = require("src.import.gba.movement_emerald")
|
|
local rs = Opcodes.forGame("ruby")
|
|
T.eq(Opcodes.forGame("sapphire"), rs, "both RS editions use native RS bytecode")
|
|
T.eq(rs.MAX, 0xC5, "RS ends at waitmoncry")
|
|
T.eq(rs:get(0xC6), nil, "Emerald's bufferboxname is absent in RS")
|
|
T.eq(rs.STD.REGISTER_MATCH_CALL, nil, "RS has no Match Call standard script")
|
|
T.eq(rs.STD.PUT_ITEM_AWAY, nil, "RS has no FRLG standard script 8")
|
|
T.eq(rs.brailleFormatSize, 6, "RS braille format is six bytes")
|
|
local moves = Movement.forGame("ruby")
|
|
local rawMoves = require("src.core.game3.constants").of("ruby").movement.byId.MOVEMENT_ACTION_
|
|
for id, name in pairs(rawMoves) do T.check(moves.toCanon[id] ~= nil, "native RS movement translates: " .. name) end
|
|
T.eq(moves.toCanon[0x2D], Movement.canonOf("MOVEMENT_ACTION_WALK_FASTER_DOWN"), "RS Fastest is cart Faster")
|
|
T.eq(moves.toCanon[0x61], Movement.canonOf("MOVEMENT_ACTION_WALK_DOWN_AFFINE"), "RS affine suffix has the same action")
|
|
for byte = 0, Native.count - 1 do
|
|
if byte ~= 0x5C then
|
|
T.eq(rs:get(byte).size, Native.byId[byte].size, "native width at opcode " .. byte)
|
|
end
|
|
end
|
|
|
|
for _, spec in ipairs({
|
|
{ bytes = { 0x93, 1, 2 }, op = "showmoneybox", args = 2 },
|
|
{ bytes = { 0x95, 1, 2 }, op = "updatemoneybox", args = 2 },
|
|
{ bytes = { 0x72, 1, 2, 3, 4 }, op = "drawbox", args = 4 },
|
|
{ bytes = { 0xB1, 1, 2, 0, 3, 0, 4, 0 }, op = "addelevmenuitem", args = 4 },
|
|
}) do
|
|
local stream = {}
|
|
for _, b in ipairs(spec.bytes) do stream[#stream + 1] = b end
|
|
for _, b in ipairs({ 0x16, 0, 0x40, 0x34, 0x12, 0x02 }) do stream[#stream + 1] = b end
|
|
local rows = Disasm.decode(stream, 1, nil, rs)
|
|
T.eq(rows[1].op, spec.op, "RS decodes " .. spec.op)
|
|
T.eq(rows[1][spec.args], spec.bytes[#spec.bytes - (spec.op == "addelevmenuitem" and 1 or 0)], "last operand survives")
|
|
T.eq(rows[2].op, "setvar", "next opcode stays aligned after " .. spec.op)
|
|
T.eq(rows[2][2], 0x1234, "next value stays aligned after " .. spec.op)
|
|
T.eq(rows[3].op, "end", "stream terminates after " .. spec.op)
|
|
end
|
|
|
|
local old = GameVersion.get()
|
|
for _, game in ipairs({ "ruby", "sapphire" }) do
|
|
GameVersion.set(game)
|
|
T.eq(Opcodes.active(), rs, game .. " selects the RS decoder")
|
|
T.eq(TextIR.dialectOf(), "rs", game .. " selects native text dialect")
|
|
local d = TextIR.dialect("rs")
|
|
T.eq(d.B_TXT[2], "B_PLAYER_MON1_NAME", "RS code 2 is the player mon, not COPY_VAR_1")
|
|
T.eq(d.B_TXT_CODE.B_CURRENT_MOVE, 0x11, "RS current move code")
|
|
T.eq(d.B_TXT_CODE.B_BUFF3, 0x2A, "RS buffer 3 code")
|
|
local ir = TextIR.decode({ 0xFD, 8, 0xFD, 9, 0xFF }, { dialect = "rs" })
|
|
T.eq(ir[1].name, "EVIL_TEAM", "RS code 8 is the edition's evil team")
|
|
T.eq(ir[2].name, "GOOD_TEAM", "RS code 9 is the edition's good team")
|
|
local V = require("src.import.gba.games." .. game)
|
|
for _, build in ipairs(Builds[game]) do
|
|
V.select(build.sha1)
|
|
T.eq(V.SYMS.game, build.build, "native symbol revision")
|
|
T.eq(V.NAMED_SCRIPTS.EventScript_TV, V.sym("Event_TV"), "TV canonical alias uses native script")
|
|
T.eq(V.TEXT_PLACEHOLDERS.EVIL_TEAM, V.sym("gExpandedPlaceholder_" .. (game == "ruby" and "Magma" or "Aqua")), "edition's evil team")
|
|
T.eq(V.TEXT_PLACEHOLDERS.GOOD_LEGENDARY, V.sym("gExpandedPlaceholder_" .. (game == "ruby" and "Kyogre" or "Groudon")), "edition's other legendary")
|
|
local found
|
|
for _, row in ipairs(V.SCRIPT_LABELS()) do if row.name == "EventScript_PC" then found = row.off end end
|
|
T.eq(found, V.sym("EventScript_PC"), "script labels preserve the active revision")
|
|
local tableRow
|
|
for _, row in ipairs(V.TEXT_TABLES) do if row.name == "gBattleStringsTable" then tableRow = row end end
|
|
T.eq(tableRow and tableRow.count, 351, "all native battle strings")
|
|
T.eq(tableRow and tableRow.ids, 12, "RS battle table starts at ID 12")
|
|
T.eq(V.BATTLE_STRING_IDS[380], "STRINGID_TRAINER2WINTEXT", "last native battle text ID survives")
|
|
end
|
|
V.select(game)
|
|
end
|
|
GameVersion.set(old)
|
|
local Versions = require("src.import.gba.versions")
|
|
for _, game in ipairs({ "ruby", "sapphire" }) do
|
|
for _, build in ipairs(Builds[game]) do
|
|
Versions.select(game)
|
|
Versions.selectCache(game, { read = function() return string.format('{"version":%q,"romSha1":%q,"md5":%q}', game, build.sha1, build.sha1) end })
|
|
T.eq(Versions.BUILD, build.build, "runtime restores cached native revision")
|
|
end
|
|
end
|
|
local previous = Versions.BUILD
|
|
for _, raw in ipairs({ '{}', '{"romSha1":"deadbeef"}',
|
|
string.format('{"version":"ruby","romSha1":%q}', Builds.sapphire[1].sha1),
|
|
string.format('{"romSha1":%q,"md5":%q}', Builds.ruby[1].sha1, Builds.ruby[2].sha1) }) do
|
|
local ok = pcall(Versions.selectCache, "ruby", { read = function() return raw end })
|
|
T.eq(ok, false, "reject missing, unknown, conflicting or other-edition native identity")
|
|
T.eq(Versions.BUILD, previous, "bad cache does not change selected revision")
|
|
end
|
|
Versions.select("firered")
|
|
local Plans = require("src.import.gba.plans.registry")
|
|
local plan = Plans.of("ruby")
|
|
T.eq(plan, Plans.of("sapphire"), "RS editions share a native import plan")
|
|
T.eq(plan.id, "rs", "RS does not run Emerald's plan")
|
|
for _, module in ipairs(Plans.modules(plan)) do
|
|
T.check(not module:find("frontier", 1, true) and not module:find("mon_anim", 1, true)
|
|
and not module:find("mystery_gift", 1, true), "RS plan excludes Emerald-only extraction")
|
|
end
|
|
T.finish("game3_rs_scripts_test")
|