mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
273 lines
9.6 KiB
Lua
273 lines
9.6 KiB
Lua
#!/usr/bin/env luajit
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local failed = 0
|
|
local function check(cond, msg)
|
|
if cond then
|
|
print("[ok] " .. msg)
|
|
else
|
|
failed = failed + 1
|
|
print("[FAIL] " .. msg)
|
|
end
|
|
end
|
|
|
|
local function finish()
|
|
if failed > 0 then
|
|
print("[test] FAILED " .. failed)
|
|
os.exit(1)
|
|
end
|
|
print("[test] all passed")
|
|
os.exit(0)
|
|
end
|
|
|
|
local Extract = require("src.import.gba.extract_scripts")
|
|
local TextIR = require("src.core.game3.scripting.text_ir")
|
|
local Opcodes = require("src.core.game3.scripting.opcodes")
|
|
local Braille = require("src.ui.game3.braille")
|
|
|
|
local BATTLE = { battle = setmetatable({}, { __index = function() return "" end }) }
|
|
local function plain(ir)
|
|
return TextIR.toPlain(ir or {}, BATTLE) or ""
|
|
end
|
|
|
|
local BRAILLE_INC = "../pokefirered/data/text/braille.inc"
|
|
local PRET_LABEL, PRET_BODIES = {}, {}
|
|
do
|
|
local f = io.open(BRAILLE_INC, "rb")
|
|
if f then
|
|
local src = f:read("*a")
|
|
f:close()
|
|
for label, body in src:gmatch("(Braille_Text_[%w_]+)::%s*\n%s*%.braille%s+\"(.-)%$\"") do
|
|
PRET_LABEL[label] = body
|
|
PRET_BODIES[#PRET_BODIES + 1] = body
|
|
end
|
|
end
|
|
end
|
|
local havePret = #PRET_BODIES > 0
|
|
|
|
local function checkText(got, label, msg)
|
|
local want = PRET_LABEL[label]
|
|
if not want then
|
|
print("[skip] " .. msg .. ": no " .. BRAILLE_INC)
|
|
return
|
|
end
|
|
check(got == want, msg .. ", got " .. string.format("%q", got))
|
|
end
|
|
|
|
print("[test] 1. the braille charmap is the inverse of pret's braille encoder")
|
|
local map = Extract.BRAILLE_CHARMAP
|
|
check(type(map) == "table", "ExtractScripts.BRAILLE_CHARMAP exists")
|
|
check(type(Extract.decodeBraille) == "function", "ExtractScripts.decodeBraille exists")
|
|
if type(map) ~= "table" or type(Extract.decodeBraille) ~= "function" then finish() end
|
|
local entries = 0
|
|
local badInverse = nil
|
|
for code, ch in pairs(map or {}) do
|
|
entries = entries + 1
|
|
if Braille.CODE[ch] ~= code then
|
|
badInverse = badInverse or string.format("0x%02X -> %q re-encodes to %s", code, ch,
|
|
tostring(Braille.CODE[ch]))
|
|
end
|
|
end
|
|
-- include/characters.h:285
|
|
check(entries == 39, "39 meaningful braille codes mapped (got " .. entries .. ")")
|
|
check(badInverse == nil, "every code round-trips through Braille.CODE: " .. tostring(badInverse))
|
|
check(map[0x00] == " " and map[0x01] == "A" and map[0x39] == "Z" and map[0x3A] == "#",
|
|
"space, A, Z and the number indicator")
|
|
check(map[0x02] == nil and map[0x08] == nil and map[0x3D] == nil,
|
|
"the meaningless dot combinations stay unmapped")
|
|
|
|
print("[test] 2. decodeBraille on raw cart bytes")
|
|
-- data/text/braille.inc:47
|
|
local EVERYTHING = { 0x09, 0x35, 0x09, 0x1D, 0x3B, 0x1E, 0x0D, 0x06, 0x1B, 0x0F, 0xFF }
|
|
local ir = Extract.decodeBraille(EVERYTHING)
|
|
checkText(plain(ir), "Braille_Text_Everything", "0x09 0x35 ... 0x0F decodes to pret's literal")
|
|
check(ir[#ir] and ir[#ir].t == "eos", "0xFF terminates the string")
|
|
-- data/text/braille.inc:50
|
|
local HAS_MEANING = { 0x0D, 0x01, 0x16, 0x00, 0x13, 0x09, 0x01, 0x1B, 0x06, 0x1B, 0x0F, 0xFF }
|
|
checkText(plain(Extract.decodeBraille(HAS_MEANING)), "Braille_Text_HasMeaning1",
|
|
"0x00 is a space, not a terminator")
|
|
local nl = Extract.decodeBraille({ 0x31, 0x17, 0xFE, 0x0B, 0x19, 0x2E, 0x1B, 0xFF })
|
|
if havePret then
|
|
check(plain(nl) == PRET_LABEL.Braille_Text_Up .. "\n" .. PRET_LABEL.Braille_Text_Down,
|
|
"0xFE is a line break, got " .. string.format("%q", plain(nl)))
|
|
else
|
|
print("[skip] the 0xFE line break check: no " .. BRAILLE_INC)
|
|
end
|
|
check(plain(Extract.decodeBraille({ 0x3D, 0xFF })) == "?",
|
|
"an unmapped dot combination falls through to ?")
|
|
|
|
print("[test] 3. the same bytes through the latin charmap are the old garbage")
|
|
check(plain(TextIR.decode(EVERYTHING)) ~= plain(Extract.decodeBraille(EVERYTHING)),
|
|
"TextIR.decode still reads braille bytes as latin, so the braille path has to be separate")
|
|
|
|
print("[test] 4. bfsFromSeeds remaps both braille opcodes")
|
|
local BRAILLE_AT = 0x40
|
|
local LATIN_AT = 0x60
|
|
local buf = {}
|
|
local function put(off, ...)
|
|
local bytes = { ... }
|
|
for i = 1, #bytes do buf[off + i] = bytes[i] end
|
|
end
|
|
local function putPtr(off, addr)
|
|
put(off, addr % 256, math.floor(addr / 256) % 256,
|
|
math.floor(addr / 65536) % 256, math.floor(addr / 16777216) % 256)
|
|
end
|
|
-- data/script_cmd_table.inc:107,124,215
|
|
put(0, 0x78); putPtr(1, 0x08000000 + BRAILLE_AT)
|
|
put(5, 0xD3); putPtr(6, 0x08000000 + BRAILLE_AT)
|
|
put(10, 0x67); putPtr(11, 0x08000000 + LATIN_AT)
|
|
put(15, 0x02)
|
|
for i = 1, #EVERYTHING do buf[BRAILLE_AT + i] = EVERYTHING[i] end
|
|
put(LATIN_AT, 0xBB, 0xBC, 0xBD, 0xFF)
|
|
|
|
local rom = {}
|
|
rom.size = 0x100
|
|
function rom:get(off)
|
|
if off < 0 or off >= self.size then error(("ROM OOB 0x%X"):format(off)) end
|
|
return buf[off + 1] or 0
|
|
end
|
|
function rom:readBytes(off, len)
|
|
local out = {}
|
|
for i = 0, len - 1 do
|
|
if off + i >= self.size then break end
|
|
out[i + 1] = self:get(off + i)
|
|
end
|
|
return out
|
|
end
|
|
function rom:ptrOffset(ptr)
|
|
if ptr < 0x08000000 or ptr >= 0x0A000000 then return nil end
|
|
return ptr - 0x08000000
|
|
end
|
|
|
|
local bfs = Extract.bfsFromSeeds(rom, { 0x08000000 })
|
|
local rows = bfs.scripts[Opcodes.key(0x08000000)]
|
|
check(type(rows) == "table" and #rows == 4, "the stub script disassembled into 4 rows")
|
|
local bKey = Opcodes.key(0x08000000 + BRAILLE_AT)
|
|
local lKey = Opcodes.key(0x08000000 + LATIN_AT)
|
|
check(rows[1] and rows[1].op == "braillemessage" and rows[1][1] == bKey and rows[1].ptr == bKey,
|
|
"braillemessage operand remapped to " .. bKey)
|
|
check(rows[2] and rows[2].op == "getbraillestringwidth" and rows[2][1] == bKey
|
|
and rows[2].ptr == bKey,
|
|
"getbraillestringwidth operand remapped instead of staying a raw ROM pointer")
|
|
checkText(plain(bfs.text[bKey]), "Braille_Text_Everything",
|
|
"the braille body decoded with the braille charmap")
|
|
check(plain(bfs.text[lKey]) == "ABC",
|
|
"the neighbouring latin message still decodes with the latin charmap, got "
|
|
.. plain(bfs.text[lKey]))
|
|
|
|
print("[test] 5. the cart itself")
|
|
local romPath = "../pokefirered/pokefirered.gba"
|
|
local f = io.open(romPath, "rb")
|
|
if not f then
|
|
print("[skip] cart byte check: no " .. romPath)
|
|
else
|
|
-- data/text/braille.inc:46
|
|
f:seek("set", 0x1A92C5)
|
|
local raw = f:read(11) or ""
|
|
f:close()
|
|
local bytes = {}
|
|
for i = 1, #raw do bytes[i] = raw:byte(i) end
|
|
checkText(plain(Extract.decodeBraille(bytes)), "Braille_Text_Everything",
|
|
"ROM 0x1A92C5 decodes to pret's literal")
|
|
end
|
|
|
|
print("[test] 6. the imported cache")
|
|
local Cache = require("tests.game3_cache")
|
|
local bundle = Cache.bundle("scripts/scripts.lua")
|
|
if not bundle then
|
|
print("[skip] game3_stitchimp_braille_text_test cache checks: " .. tostring(Cache.reason))
|
|
finish()
|
|
end
|
|
|
|
local brailleKeys, order = {}, {}
|
|
for _, script in pairs(bundle.scripts or {}) do
|
|
if type(script) == "table" then
|
|
for _, row in ipairs(script) do
|
|
if type(row) == "table"
|
|
and (row.op == "braillemessage" or row.op == "getbraillestringwidth") then
|
|
local key = row.ptr or row[1]
|
|
if not brailleKeys[key] then
|
|
brailleKeys[key] = {}
|
|
order[#order + 1] = key
|
|
end
|
|
brailleKeys[key][row.op] = (brailleKeys[key][row.op] or 0) + 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
table.sort(order)
|
|
if havePret then
|
|
check(#order == #PRET_BODIES,
|
|
#PRET_BODIES .. " distinct braille strings reached by the BFS (got " .. #order .. ")")
|
|
else
|
|
print("[skip] the braille string count: no " .. BRAILLE_INC)
|
|
end
|
|
|
|
local rawOperand, missing, unmapped, mismatch = nil, nil, nil, nil
|
|
local seen = {}
|
|
for _, key in ipairs(order) do
|
|
if type(key) ~= "string" then
|
|
rawOperand = rawOperand or tostring(key)
|
|
else
|
|
local body = plain(bundle.text[key])
|
|
if body == "" then
|
|
missing = missing or key
|
|
else
|
|
seen[body] = (seen[body] or 0) + 1
|
|
for ch in body:gmatch("[^\n]") do
|
|
if Braille.CODE[ch] == nil then
|
|
unmapped = unmapped or (key .. " has " .. string.format("%q", ch))
|
|
end
|
|
end
|
|
local glyphs = Braille.countGlyphs(body)
|
|
local want = select(2, body:gsub("[^\n]", ""))
|
|
if glyphs ~= want then
|
|
mismatch = mismatch or (key .. " " .. glyphs .. " glyphs for " .. want .. " chars")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
check(rawOperand == nil, "no braille operand left as a raw ROM pointer: " .. tostring(rawOperand))
|
|
check(missing == nil, "every braille key has a body in scripts/text.lua: " .. tostring(missing))
|
|
check(unmapped == nil,
|
|
"every decoded glyph is a braille character the wall can draw: " .. tostring(unmapped))
|
|
check(mismatch == nil,
|
|
"Braille.encode gives one glyph per decoded character: " .. tostring(mismatch))
|
|
|
|
if havePret then
|
|
local wantCount = {}
|
|
for _, body in ipairs(PRET_BODIES) do wantCount[body] = (wantCount[body] or 0) + 1 end
|
|
local absent, duped = nil, nil
|
|
for body, n in pairs(wantCount) do
|
|
if not seen[body] then
|
|
absent = absent or body
|
|
elseif seen[body] ~= n then
|
|
duped = duped or string.format("%q cached %d times, pret writes it %d", body, seen[body], n)
|
|
end
|
|
end
|
|
check(absent == nil,
|
|
"every pret .braille literal is in the cache verbatim: " .. tostring(absent))
|
|
check(duped == nil,
|
|
"each repeated literal is its own string in the cache: " .. tostring(duped))
|
|
else
|
|
print("[skip] the cache against pret's literals: no " .. BRAILLE_INC)
|
|
end
|
|
|
|
-- asm/macros/event.inc:1845
|
|
local paired = 0
|
|
for _, key in ipairs(order) do
|
|
local ops = brailleKeys[key]
|
|
if ops.braillemessage and ops.getbraillestringwidth then paired = paired + 1 end
|
|
end
|
|
check(paired == 22, "22 braillemessage_wait pairs share a text key (got " .. paired .. ")")
|
|
|
|
local lowercase = 0
|
|
for _, t in pairs(bundle.text or {}) do
|
|
if type(t) == "table" and plain(t):find("%l") then lowercase = lowercase + 1 end
|
|
end
|
|
check(lowercase > 1000,
|
|
"the latin text pack is untouched, " .. lowercase .. " entries still have lowercase")
|
|
|
|
finish()
|