Files
gen1recomp/tests/game3_w0_online_assets_test.lua
2026-09-24 05:17:32 -04:00

572 lines
27 KiB
Lua

#!/usr/bin/env luajit
-- src/berry_crush.c:647, src/dodrio_berry_picking.c:3323, src/pokemon_jump.c:2901
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(string.format("[result] %d CHECK(S) FAILED", failed))
os.exit(1)
end
print("[result] all checks passed")
os.exit(0)
end
local GV = require("src.core.GameVersion")
local Versions = require("src.import.gba.versions")
local AssetPack = require("src.import.gba.asset_pack")
local CacheContract = require("src.import.CacheContract")
local EXTRACTORS = { "online_ui_extract", "berry_crush_extract", "dodrio_extract", "pokemon_jump_extract" }
local ADDRESSES = {
{ "SCROLL_ARROW_PAL", 0x463308, 0x462D28 },
{ "SCROLL_ARROW_GFX", 0x463328, 0x462D48 },
{ "SELECTOR_OUTLINE_GFX", 0x463398, 0x462DB8 },
{ "RED_ARROW_CURSOR_GFX", 0x4633D8, 0x462DF8 },
{ "SCROLL_INDICATOR_TEMPLATES", 0x46325C, 0x462C7C },
{ "MG_COUNTDOWN_PAL", 0x47A328, 0x479C04 },
{ "MG_COUNTDOWN_GFX", 0x47A348, 0x479C24 },
{ "MG_321START_PAL", 0x46AFE8, 0x46A8C4 },
{ "MG_321START_GFX", 0x46B008, 0x46A8E4 },
{ "MG_DIGITS_PAL", 0x479668, 0x478F44 },
{ "MG_DIGITS_GFX", 0x479688, 0x478F64 },
{ "WONDER_STAMP_SHADOW_PALS", 0x467DF4, 0x4676D0 },
{ "WONDER_STAMP_SHADOW_GFX", 0x467EF4, 0x4677D0 },
{ "MYSTERY_GIFT_BORDER_PAL", 0x466D10, 0x4665EC },
{ "MYSTERY_GIFT_BORDER_GFX", 0x466D30, 0x46660C },
{ "UR_OBJ_GFX_IDS", 0x4570D8, 0x456AF8 },
{ "UR_PLAYER_COORDS", 0x4570EC, 0x456B0C },
{ "UR_GROUP_OFFSETS", 0x45710C, 0x456B2C },
{ "UR_OPPOSITE_FACING", 0x457116, 0x456B36 },
{ "UR_MEMBER_FACING", 0x45711B, 0x456B3B },
{ "BC_CRUSHER_PAL", 0xEAFEA0, 0xEB0194 },
{ "BC_CRUSHER_GFX", 0xEAFFC0, 0xEB02B4 },
{ "BC_TEXT_WINDOWS_MAP", 0xEB0ADC, 0xEB0DD0 },
{ "BC_CORE_PAL", 0x46E470, 0x46DD4C },
{ "BC_EFFECT_PAL", 0x46E490, 0x46DD6C },
{ "BC_TIMER_PAL", 0x46E4B0, 0x46DD8C },
{ "BC_CORE_GFX", 0x46E4D0, 0x46DDAC },
{ "BC_IMPACT_GFX", 0x46E7FC, 0x46E0D8 },
{ "BC_SPARKLE_GFX", 0x46EB78, 0x46E454 },
{ "BC_TIMER_GFX", 0x46ECC4, 0x46E5A0 },
{ "BC_CRUSHER_TOP_MAP", 0x46ED90, 0x46E66C },
{ "BC_CONTAINER_CAP_MAP", 0x46EEC0, 0x46E79C },
{ "BC_BG_MAP", 0x46F058, 0x46E934 },
{ "BC_BERRY_DATA", 0x3DFC9C, 0x3DFAD8 },
{ "BC_SYNC_PRESS_BONUS", 0x46E2E8, 0x46DBC4 },
{ "BC_INTRO_OUTRO_VIBRATION", 0x46E2F0, 0x46DBCC },
{ "BC_VIBRATION", 0x46E314, 0x46DBF0 },
{ "BC_SPARKLE_THRESHOLDS", 0x46E3B4, 0x46DC90 },
{ "BC_BIG_SPARKLE_THRESHOLDS", 0x46E3C4, 0x46DCA0 },
{ "BC_RECEIVED_PLAYER_BITMASKS", 0x46E3C8, 0x46DCA4 },
{ "BC_BG_TEMPLATES", 0x46E3CC, 0x46DCA8 },
{ "BC_TEXT_COLORS", 0x46E3DC, 0x46DCB8 },
{ "BC_WIN_RANKINGS", 0x46E3F0, 0x46DCCC },
{ "BC_WIN_PLAYER_NAMES", 0x46E3F8, 0x46DCD4 },
{ "BC_WIN_RESULTS", 0x46E428, 0x46DD04 },
{ "BC_RESULTS_WINDOW_HEIGHTS", 0x46E448, 0x46DD24 },
{ "BC_PRESSING_SPEED_TABLE", 0x46E450, 0x46DD2C },
{ "BC_PLAYER_ID_TO_POS_ID", 0x46F280, 0x46EB5C },
{ "BC_PLAYER_COORDS", 0x46F294, 0x46EB70 },
{ "BC_IMPACT_COORDS", 0x46F2D0, 0x46EBAC },
{ "BC_SPARKLE_COORDS", 0x46F2D6, 0x46EBB2 },
{ "BC_DIGIT_TEMPLATES", 0x46F488, 0x46ED64 },
{ "DBP_BG_PAL", 0x4758A8, 0x475184 },
{ "DBP_DODRIO_PAL", 0x4758E8, 0x4751C4 },
{ "DBP_DODRIO_SHINY_PAL", 0x475908, 0x4751E4 },
{ "DBP_STATUS_PAL", 0x475928, 0x475204 },
{ "DBP_BERRIES_PAL", 0x475948, 0x475224 },
{ "DBP_BERRIES_GFX", 0x475968, 0x475244 },
{ "DBP_CLOUD_PAL", 0x475B1C, 0x4753F8 },
{ "DBP_BG_GFX", 0x475B3C, 0x475418 },
{ "DBP_TREE_BORDER_GFX", 0x4763CC, 0x475CA8 },
{ "DBP_STATUS_GFX", 0x477198, 0x476A74 },
{ "DBP_CLOUD_GFX", 0x47722C, 0x476B08 },
{ "DBP_DODRIO_GFX", 0x477374, 0x476C50 },
{ "DBP_BG_MAP", 0x478590, 0x477E6C },
{ "DBP_TREE_RIGHT_MAP", 0x4787FC, 0x4780D8 },
{ "DBP_TREE_LEFT_MAP", 0x478A4C, 0x478328 },
{ "DBP_ACTIVE_COLUMN_MAP", 0x471F50, 0x47182C },
{ "DBP_HEAD_TO_COLUMN_MAP", 0x472063, 0x47193F },
{ "DBP_NEIGHBOR_MAP", 0x4720AE, 0x47198A },
{ "DBP_PLAYER_ID_AT_COLUMN", 0x4720FC, 0x4719D8 },
{ "DBP_UNSHARED_COLUMNS", 0x472133, 0x471A0F },
{ "DBP_BERRY_FALL_DELAYS", 0x47553C, 0x474E18 },
{ "DBP_TREE_BORDER_X", 0x475548, 0x474E24 },
{ "DBP_DIFFICULTY_THRESHOLDS", 0x475550, 0x474E2C },
{ "DBP_PRIZE_BERRY_IDS", 0x475558, 0x474E34 },
{ "DBP_BERRY_SCORE_MULT", 0x4755D8, 0x474EB4 },
{ "DBP_WIN_RECORDS", 0x4755E0, 0x474EBC },
{ "DBP_RECORD_MAX_DIGITS", 0x4755F8, 0x474ED4 },
{ "DBP_RECORD_TEXT_Y", 0x4755FC, 0x474ED8 },
{ "DBP_RECORD_NUM_Y", 0x475602, 0x474EDE },
{ "DBP_BG_TEMPLATES", 0x47565C, 0x474F38 },
{ "DBP_WIN_RESULTS", 0x475674, 0x474F50 },
{ "DBP_WIN_PRIZE", 0x475684, 0x474F60 },
{ "DBP_WIN_PLAY_AGAIN", 0x47568C, 0x474F68 },
{ "DBP_WIN_DROPPED_OUT", 0x47569C, 0x474F78 },
{ "DBP_WIN_COMM_STANDBY", 0x4756A4, 0x474F80 },
{ "DBP_BERRY_ICON_X", 0x478DD4, 0x4786B0 },
{ "DBP_CLOUD_MOVE_DELAYS", 0x478E0C, 0x4786E8 },
{ "DBP_CLOUD_START", 0x478E0E, 0x4786EA },
{ "DBP_TEXT_COLORS", 0x478E38, 0x478714 },
{ "DBP_NAME_WIN_1P", 0x478E44, 0x478720 },
{ "DBP_NAME_WIN_2P", 0x478E48, 0x478724 },
{ "DBP_NAME_WIN_3P", 0x478E50, 0x47872C },
{ "DBP_NAME_WIN_4P", 0x478E5C, 0x478738 },
{ "DBP_NAME_WIN_5P", 0x478E6C, 0x478748 },
{ "DBP_RESULTS_X", 0x478EA8, 0x478784 },
{ "DBP_RESULTS_Y", 0x478EB0, 0x47878C },
{ "DBP_RANKING_Y", 0x478EBA, 0x478796 },
{ "PJ_INTERFACE_PAL", 0x46B794, 0x46B070 },
{ "PJ_BG_PAL", 0x46B7B4, 0x46B090 },
{ "PJ_BG_GFX", 0x46B7D4, 0x46B0B0 },
{ "PJ_BG_MAP", 0x46BA00, 0x46B2DC },
{ "PJ_VENUSAUR_PAL", 0x46BBB0, 0x46B48C },
{ "PJ_VENUSAUR_GFX", 0x46BBD0, 0x46B4AC },
{ "PJ_VENUSAUR_MAP", 0x46C520, 0x46BDFC },
{ "PJ_BONUSES_PAL", 0x46C8D8, 0x46C1B4 },
{ "PJ_BONUSES_GFX", 0x46C8F8, 0x46C1D4 },
{ "PJ_BONUSES_MAP", 0x46D3A8, 0x46CC84 },
{ "PJ_PAL1", 0x46D9E4, 0x46D2C0 },
{ "PJ_PAL2", 0x46DA04, 0x46D2E0 },
{ "PJ_VINE1_GFX", 0x46DA24, 0x46D300 },
{ "PJ_VINE2_GFX", 0x46DB44, 0x46D420 },
{ "PJ_VINE3_GFX", 0x46DD18, 0x46D5F4 },
{ "PJ_VINE4_GFX", 0x46DE48, 0x46D724 },
{ "PJ_STAR_GFX", 0x46DF44, 0x46D820 },
{ "PJ_MONS", 0x46B4BC, 0x46AD98 },
{ "PJ_VINE_BASE_SPEEDS", 0x46B694, 0x46AF70 },
{ "PJ_VINE_SPEED_DELAYS", 0x46B6A4, 0x46AF80 },
{ "PJ_SOUND_EFFECTS", 0x46B6AC, 0x46AF88 },
{ "PJ_JUMP_OFFSETS", 0x46B6B4, 0x46AF90 },
{ "PJ_SCORE_BONUSES", 0x46B744, 0x46B020 },
{ "PJ_PRIZE_ITEMS", 0x46B75C, 0x46B038 },
{ "PJ_PRIZE_QUANTITY", 0x46B76C, 0x46B048 },
{ "PJ_BG_TEMPLATES", 0x46D8D4, 0x46D1B0 },
{ "PJ_WINDOW_TEMPLATES", 0x46D8E4, 0x46D1C0 },
{ "PJ_VENUSAUR_STATES", 0x46D953, 0x46D22F },
{ "PJ_NAME_WIN_2P", 0x46D970, 0x46D24C },
{ "PJ_NAME_WIN_3P", 0x46D978, 0x46D254 },
{ "PJ_NAME_WIN_4P", 0x46D984, 0x46D260 },
{ "PJ_NAME_WIN_5P", 0x46D994, 0x46D270 },
{ "PJ_MON_X_2P", 0x46D9B8, 0x46D294 },
{ "PJ_MON_X_3P", 0x46D9BC, 0x46D298 },
{ "PJ_MON_X_4P", 0x46D9C2, 0x46D29E },
{ "PJ_MON_X_5P", 0x46D9CA, 0x46D2A6 },
{ "PJ_VINE_Y", 0x46E100, 0x46D9DC },
{ "PJ_VINE_X", 0x46E150, 0x46DA2C },
{ "PJ_WIN_RECORDS", 0x46E2CC, 0x46DBA8 },
}
check(Versions.CACHE_VERSION >= 122, "cache version carries the gen 3 online assets")
print("[test] 1. every new address is pret's symbol in both editions")
local bad = 0
for _, row in ipairs(ADDRESSES) do
if Versions[row[1]] ~= row[2] then bad = bad + 1; print(" FireRed " .. row[1]) end
end
Versions.select("leafgreen")
for _, row in ipairs(ADDRESSES) do
if Versions[row[1]] ~= row[3] then bad = bad + 1; print(" LeafGreen " .. row[1]) end
end
Versions.select("firered")
check(bad == 0, #ADDRESSES .. " addresses match pokefirered.elf and pokeleafgreen.elf")
print("[test] 2. the cache contract requires the new outputs for both editions")
local REQUIRED = {
"data/generated/gba/chrome/manifest.lua",
"data/generated/gba/chrome/scroll_arrows.rgba",
"data/generated/gba/link/manifest.lua",
"data/generated/gba/link/minigame_countdown_numbers.rgba",
"data/generated/gba/link/countdown_321.rgba",
"data/generated/gba/mystery_gift/manifest.lua",
"data/generated/gba/mystery_gift/stamp_shadow_0.rgba",
"data/generated/gba/mystery_gift/stamp_shadow_7.rgba",
"data/generated/gba/union_room/avatars.lua",
"data/generated/gba/chrome/fonts/latin_small_fg.rgba",
"data/generated/gba/chrome/fonts/latin_small_shadow.rgba",
"data/generated/gba/chrome/fonts/latin_small_widths.lua",
"data/generated/gba/pokemon/party/slot_main_multi.rgba",
"data/generated/gba/pokemon/party/slot_main_multi_selected.rgba",
"data/generated/gba/pokemon/party/slot_wide_multi.rgba",
"data/generated/gba/pokemon/party/slot_wide_multi_selected.rgba",
"data/generated/gba/union_room/chat_text_entry_cursor.rgba",
"data/generated/gba/union_room/chat_char_select_cursor.rgba",
"data/generated/gba/union_room/chat_r_button.rgba",
}
local GAMES = {
berry_crush = { "crusher", "crusher_base", "impact", "sparkle", "timer_digits", "bg", "container_cap",
"crusher_top", "text_windows" },
dodrio_berry_picking = { "berries", "bg", "cloud", "dodrio", "dodrio_shiny", "status", "tree_border_left",
"tree_border_right" },
pokemon_jump = { "bg", "bonuses", "star", "venusaur", "vine1", "vine2", "vine3", "vine4" },
}
for dir, keys in pairs(GAMES) do
REQUIRED[#REQUIRED + 1] = "data/generated/gba/" .. dir .. "/manifest.lua"
REQUIRED[#REQUIRED + 1] = "data/generated/gba/" .. dir .. "/tables.lua"
for _, key in ipairs(keys) do
REQUIRED[#REQUIRED + 1] = "data/generated/gba/" .. dir .. "/" .. key .. ".rgba"
end
end
for _, version in ipairs({ "firered", "leafgreen" }) do
local set = {}
for _, p in ipairs(CacheContract.requiredFilesFor(version)) do set[p] = true end
local missing = {}
for _, p in ipairs(REQUIRED) do if not set[p] then missing[#missing + 1] = p end end
check(#missing == 0, version .. " requires the online asset outputs " .. table.concat(missing, " "))
end
print("[test] 3. the bundled lock glyphs are 8x8 PNGs")
local function pngSize(path)
local f = io.open(path, "rb")
if not f then return nil end
local head = f:read(24); f:close()
if not head or head:sub(2, 4) ~= "PNG" then return nil end
local function be(i) local a, b, c, d = head:byte(i, i + 3) return ((a * 256 + b) * 256 + c) * 256 + d end
return be(17), be(21)
end
for _, g in ipairs({ { "lock8.png", 8 } }) do
local w, h = pngSize("assets/game3/" .. g[1])
check(w == g[2] and h == g[2], "assets/game3/" .. g[1] .. " is " .. g[2] .. "x" .. g[2])
end
for _, name in ipairs({ "lock.png", "lock8_white.png", "lock16.png", "lock16_white.png" }) do
check(io.open("assets/game3/" .. name, "rb") == nil, "assets/game3/" .. name .. " is not bundled")
end
print("[test] 3b. colour 0 of any bank on an opaque layer shows the backdrop")
do
local gfx = { _len = 64 }
for i = 1, 32 do gfx[i] = 0 end
for i = 33, 64 do gfx[i] = 0x11 end
local banks = { [0] = { [0] = 0x001F, [1] = 0x03E0 }, [1] = { [0] = 0x7C00, [1] = 0x7FFF } }
local map = { 0x00, 0x10, 0x01, 0x10, _len = 4 }
local rgba = AssetPack.bg(gfx, banks, map, 2, 16, 8, { alpha0 = false, backdrop = banks[0][0] })
check(rgba:sub(1, 4) == string.char(255, 0, 0, 255) and rgba:sub(33, 36) == string.char(255, 255, 255, 255),
"bank 1 colour 0 bakes the BG palette 0 backdrop, colour 1 keeps bank 1")
end
print("[test] 4. the serializer round-trips sparse and nested tables")
local chunk = loadstring(AssetPack.serialize({ a = { 1, -2, 3 }, [2] = { x = 1 }, [5] = { 4 }, s = "q" }))
local t = chunk and chunk()
check(t and t.a[2] == -2 and t[2].x == 1 and t[5][1] == 4 and t.s == "q", "serialize keeps sparse integer keys")
local root = os.getenv("POKEFIRERED") or "../pokefirered"
local function readFile(path)
local f = io.open(path, "rb")
if not f then return nil end
local d = f:read("*a"); f:close()
return d
end
local LZ = {
{ "BC_CRUSHER_GFX", "berry_crush/crusher.4bpp" },
{ "BC_CORE_GFX", "berry_crush/crusher_base.4bpp" },
{ "BC_IMPACT_GFX", "berry_crush/impact.4bpp" },
{ "BC_SPARKLE_GFX", "berry_crush/sparkle.4bpp" },
{ "BC_TIMER_GFX", "berry_crush/timer_digits.4bpp" },
{ "BC_CRUSHER_TOP_MAP", "berry_crush/crusher_top.bin" },
{ "BC_CONTAINER_CAP_MAP", "berry_crush/container_cap.bin" },
{ "BC_BG_MAP", "berry_crush/bg.bin" },
{ "BC_TEXT_WINDOWS_MAP", "berry_crush/text_windows.bin" },
{ "DBP_BERRIES_GFX", "dodrio_berry_picking/berries.4bpp" },
{ "DBP_BG_GFX", "dodrio_berry_picking/bg.4bpp" },
{ "DBP_TREE_BORDER_GFX", "dodrio_berry_picking/tree_border.4bpp" },
{ "DBP_STATUS_GFX", "dodrio_berry_picking/status.4bpp" },
{ "DBP_CLOUD_GFX", "dodrio_berry_picking/cloud.4bpp" },
{ "DBP_DODRIO_GFX", "dodrio_berry_picking/dodrio.4bpp" },
{ "DBP_BG_MAP", "dodrio_berry_picking/bg.bin" },
{ "DBP_TREE_RIGHT_MAP", "dodrio_berry_picking/tree_border_right.bin" },
{ "DBP_TREE_LEFT_MAP", "dodrio_berry_picking/tree_border_left.bin" },
{ "PJ_BG_GFX", "pokemon_jump/bg.4bpp" },
{ "PJ_BG_MAP", "pokemon_jump/bg.bin" },
{ "PJ_VENUSAUR_GFX", "pokemon_jump/venusaur.4bpp" },
{ "PJ_VENUSAUR_MAP", "pokemon_jump/venusaur.bin" },
{ "PJ_BONUSES_GFX", "pokemon_jump/bonuses.4bpp" },
{ "PJ_BONUSES_MAP", "pokemon_jump/bonuses.bin" },
{ "PJ_VINE1_GFX", "pokemon_jump/vine1.4bpp" },
{ "PJ_VINE2_GFX", "pokemon_jump/vine2.4bpp" },
{ "PJ_VINE3_GFX", "pokemon_jump/vine3.4bpp" },
{ "PJ_VINE4_GFX", "pokemon_jump/vine4.4bpp" },
{ "PJ_STAR_GFX", "pokemon_jump/star.4bpp" },
{ "SCROLL_ARROW_GFX", "interface/red_arrow_other.4bpp" },
{ "SELECTOR_OUTLINE_GFX", "interface/selector_outline.4bpp" },
{ "RED_ARROW_CURSOR_GFX", "interface/red_arrow.4bpp" },
{ "MG_COUNTDOWN_GFX", "misc/minigame_countdown.4bpp" },
{ "MG_321START_GFX", "link/321start.4bpp" },
{ "MG_DIGITS_GFX", "misc/minigame_digits.4bpp" },
{ "WONDER_STAMP_SHADOW_GFX", "wonder_card/stamp_shadow.4bpp" },
{ "MYSTERY_GIFT_BORDER_GFX", "interface/mystery_gift_textbox_border.4bpp" },
}
local PALS = {
{ "BC_CRUSHER_PAL", { "berry_crush/crusher.gbapal" } },
{ "BC_CORE_PAL", { "berry_crush/crusher_base.gbapal" } },
{ "BC_EFFECT_PAL", { "berry_crush/impact.gbapal" } },
{ "BC_TIMER_PAL", { "berry_crush/timer_digits.gbapal" } },
{ "DBP_BG_PAL", { "dodrio_berry_picking/bg.gbapal", "dodrio_berry_picking/tree_border.gbapal" } },
{ "DBP_DODRIO_PAL", { "dodrio_berry_picking/dodrio.gbapal" } },
{ "DBP_DODRIO_SHINY_PAL", { "dodrio_berry_picking/shiny.gbapal" } },
{ "DBP_STATUS_PAL", { "dodrio_berry_picking/status.gbapal" } },
{ "DBP_BERRIES_PAL", { "dodrio_berry_picking/berries.gbapal" } },
{ "DBP_CLOUD_PAL", { "dodrio_berry_picking/cloud.gbapal" } },
{ "PJ_INTERFACE_PAL", { "pokemon_jump/interface.gbapal" } },
{ "PJ_BG_PAL", { "pokemon_jump/bg.gbapal" } },
{ "PJ_VENUSAUR_PAL", { "pokemon_jump/venusaur.gbapal" } },
{ "PJ_BONUSES_PAL", { "pokemon_jump/bonuses.gbapal" } },
{ "PJ_PAL1", { "pokemon_jump/pal1.gbapal" } },
{ "PJ_PAL2", { "pokemon_jump/pal2.gbapal" } },
{ "SCROLL_ARROW_PAL", { "interface/red_arrow.gbapal" } },
{ "MG_COUNTDOWN_PAL", { "misc/minigame_countdown.gbapal" } },
{ "MG_321START_PAL", { "link/321start.gbapal" } },
{ "MG_DIGITS_PAL", { "misc/minigame_digits.gbapal" } },
{ "WONDER_STAMP_SHADOW_PALS", { "wonder_card/stamp_shadow_0.gbapal", "wonder_card/stamp_shadow_1.gbapal",
"wonder_card/stamp_shadow_2.gbapal", "wonder_card/stamp_shadow_3.gbapal", "wonder_card/stamp_shadow_4.gbapal",
"wonder_card/stamp_shadow_5.gbapal", "wonder_card/stamp_shadow_6.gbapal", "wonder_card/stamp_shadow_7.gbapal" } },
{ "MYSTERY_GIFT_BORDER_PAL", { "interface/mystery_gift_textbox_border.gbapal" } },
}
local ROMS = {
{ "pokefirered.gba", "41cb23d8dccc8ebd7c649cd8fbb58eeace6e2fdc" },
{ "pokeleafgreen.gba", "574fa542ffebb14be69902d1d36f1ec0a4afd71e" },
{ "pokefirered_rev1.gba", "dd5945db9b930750cb39d00c84da8571feebf417" },
{ "pokeleafgreen_rev1.gba", "7862c67bdecbe21d1d69ce082ce34327e1c6ed5e" },
}
local function stubCache()
local written = {}
return written, {
write = function(_, rel, bytes) written[rel] = bytes; return true end,
read = function(_, rel) return written[rel] end,
exists = function(_, rel) return written[rel] ~= nil end,
}
end
local function sizedEntries(written, dir, man, section)
local badFiles = {}
for key, row in pairs(man[section] or {}) do
if type(row) == "table" and row.file and row.width and row.height then
local blob = written[dir .. "/" .. row.file]
if not blob or #blob ~= row.width * row.height * 4 then badFiles[#badFiles + 1] = key end
end
end
return badFiles
end
local function pixelHas(blob, rgb)
for i = 1, #blob, 4 do
if blob:byte(i + 3) ~= 0 and blob:sub(i, i + 2) == rgb then return true end
end
return false
end
local function palIdRgb(pal, id)
local lo, hi = pal:byte(id * 2 + 1, id * 2 + 2)
local r, g, b = require("src.import.gba.bg_bake").bgr555ToRgb8((hi or 0) * 256 + (lo or 0))
return string.char(r, g, b)
end
local deoxys = {}
local ranAny = false
for n, case in ipairs(ROMS) do
local data = readFile(root .. "/" .. case[1])
if not data then
print("[skip] " .. case[1] .. " not built in " .. root)
else
ranAny = true
print("[test] 5." .. n .. " " .. case[1] .. " extracts the online asset set byte-exact to pret")
local rom = assert(require("src.import.gba.rom").open({
info = function() return { size = #data, md5 = case[2] } end,
read = function(_, _, off, len) return data:sub(off + 1, off + len) end,
}, GV.forSha1(case[2])))
local lzBad = {}
for _, row in ipairs(LZ) do
local want = readFile(root .. "/graphics/" .. row[2])
local got = AssetPack.bytes(AssetPack.lz(rom, Versions[row[1]]))
if not want or got ~= want then lzBad[#lzBad + 1] = row[1] end
end
check(#lzBad == 0, #LZ .. " compressed sheets and tilemaps decode to pret's files " .. table.concat(lzBad, " "))
local palBad = {}
for _, row in ipairs(PALS) do
local parts = {}
for i, file in ipairs(row[2]) do parts[i] = readFile(root .. "/graphics/" .. file) or "" end
local want = table.concat(parts)
local got = AssetPack.bytes(AssetPack.raw(rom, Versions[row[1]], #want))
if #want == 0 or got ~= want then palBad[#palBad + 1] = row[1] end
end
check(#palBad == 0, #PALS .. " palettes match pret's .gbapal files " .. table.concat(palBad, " "))
local written, cache = stubCache()
for _, name in ipairs(EXTRACTORS) do
local M = require("src.import.gba." .. name)
check(M.ready(cache, "x") == false, name .. ".ready() starts false")
local ok, err = pcall(M.run, rom, cache, { cacheRoot = "x" })
check(ok, name .. ".run() completes (" .. tostring(err) .. ")")
check(M.ready(cache, "x") == true, name .. ".ready() is true after run()")
end
for dir, keys in pairs(GAMES) do
local man = AssetPack.loadManifest(cache, "x/" .. dir .. "/manifest.lua")
check(man ~= nil and man.format_version == 1, dir .. "/manifest.lua loads")
if man then
local bad = {}
for _, key in ipairs(keys) do
local row = man[key]
local blob = row and written["x/" .. dir .. "/" .. key .. ".rgba"]
if not (row and row.kind and row.file == key .. ".rgba" and blob and #blob == row.width * row.height * 4) then
bad[#bad + 1] = key
end
end
check(#bad == 0, dir .. " carries every interfaces.md 10.3 key at its manifest size " .. table.concat(bad, " "))
local extra = sizedEntries(written, "x/" .. dir, { all = man }, "all")
check(#extra == 0, dir .. " every other manifest entry matches its blob " .. table.concat(extra, " "))
for _, row in pairs(man._palettes or {}) do
local blob = written["x/" .. dir .. "/" .. row.file]
check(blob and #blob == row.banks * 48, dir .. "/" .. row.file .. " holds " .. row.banks .. " banks")
end
end
end
for _, name in ipairs({ "text_chrome_extract", "mystery_gift_extract" }) do
local ok, err = pcall(require("src.import.gba." .. name).run, rom, cache, { cacheRoot = "x" })
check(ok, name .. ".run() completes with the new art (" .. tostring(err) .. ")")
end
do
-- src/text.c:1380
local fg = written["x/chrome/fonts/latin_small_fg.rgba"] or ""
local sh = written["x/chrome/fonts/latin_small_shadow.rgba"] or ""
check(#fg == 256 * 512 * 4 and #sh == 256 * 512 * 4, "latin_small sheets hold all 512 FONT_SMALL glyphs")
local wchunk = loadstring(written["x/chrome/fonts/latin_small_widths.lua"] or "")
local widths = wchunk and wchunk() or {}
local count = 0
for _ in pairs(widths) do count = count + 1 end
check(count == 512 and widths[511] ~= nil, "latin_small_widths has 512 entries")
local gid, lit = 0x1DD, false
local ox, oy = (gid % 16) * 16, math.floor(gid / 16) * 16
for y = oy, oy + 15 do
for x = ox, ox + 15 do
if fg:byte((y * 256 + x) * 4 + 4) == 255 then lit = true end
end
end
check(lit, "latin_small glyph 0x1DD is drawn")
local ls = ((AssetPack.loadManifest(cache, "x/chrome/manifest.lua") or {}).fonts or {}).latin_small or {}
check(ls.width == 256 and ls.height == 512 and ls.glyphs == 512, "chrome manifest lists latin_small at 512 glyphs")
end
do
local pwritten, pcache = stubCache()
local ok, err = pcall(require("src.import.gba.pokemon_extract").run, rom, pcache,
{ cacheRoot = "x", numSpecies = 411 })
check(ok, "pokemon_extract.run() completes (" .. tostring(err) .. ")")
local mchunk = loadstring(pwritten["x/pokemon/meta.lua"] or "")
local meta = mchunk and mchunk() or {}
local ls = (meta[410] or {}).linkStats or {}
-- src/data/pokemon/species_info.h:11199
check(table.concat(ls, ",") == "50,150,50,150,150,50", "Deoxys meta carries the link battle linkStats")
local schunk = loadstring(pwritten["x/pokemon/stats.lua"] or "")
local s = (schunk and schunk() or {})[410] or {}
deoxys[case[1]] = table.concat({ s.hp or 0, s.atk or 0, s.def or 0, s.spe or 0, s.spa or 0, s.spd or 0 }, ",")
ok, err = pcall(require("src.import.gba.party_chrome_extract").run, rom, pcache, { cacheRoot = "x" })
check(ok, "party_chrome_extract.run() completes (" .. tostring(err) .. ")")
local pal = readFile(root .. "/graphics/party_menu/bg.gbapal") or ""
local p = "x/pokemon/party/"
-- src/party_menu.c:2273
for _, row in ipairs({
{ "slot_main_multi", "slot_main", 80 * 56, { 68, 69, 70 }, { 52, 53, 54 } },
{ "slot_main_multi_selected", "slot_main_selected", 80 * 56, { 132, 133, 134 }, { 116, 117, 118 } },
{ "slot_wide_multi", "slot_wide", 144 * 24, { 68, 69, 70 }, { 52, 53, 54 } },
{ "slot_wide_multi_selected", "slot_wide_selected", 144 * 24, { 132, 133, 134 }, { 116, 117, 118 } },
}) do
local blob, plain = pwritten[p .. row[1] .. ".rgba"], pwritten[p .. row[2] .. ".rgba"]
local tinted, used = blob ~= nil and plain ~= nil and #pal == 352, 0
for i, id in ipairs(row[4]) do
if tinted and pixelHas(plain, palIdRgb(pal, row[5][i])) then
used = used + 1
if not pixelHas(blob, palIdRgb(pal, id)) then tinted = false end
end
end
check(blob and plain and #blob == row[3] * 4 and #plain == #blob and blob ~= plain and tinted and used >= 2,
row[1] .. " is " .. row[2] .. " in the multi partner palette")
end
end
for _, rel in ipairs({ "x/chrome/manifest.lua", "x/link/manifest.lua", "x/mystery_gift/manifest.lua" }) do
local man = AssetPack.loadManifest(cache, rel)
local d = rel:match("^(.*)/")
local sizes = {}
for key, row in pairs(man or {}) do
if type(row) == "table" and row.file then
local blob = written[d .. "/" .. row.file]
if not blob or #blob ~= row.width * row.height * 4 then sizes[#sizes + 1] = key end
end
end
check(man ~= nil and #sizes == 0, rel .. " entries match their blobs " .. table.concat(sizes, " "))
end
local chrome = AssetPack.loadManifest(cache, "x/chrome/manifest.lua") or {}
local sa = chrome.scroll_arrows or {}
check(sa.frames == 4 and sa.frame_w == 16 and sa.frame_h == 16 and sa.width == 16 and sa.height == 64
and chrome.fonts ~= nil, "chrome manifest keeps its fonts and gains scroll_arrows")
local gift = AssetPack.loadManifest(cache, "x/mystery_gift/manifest.lua") or {}
check(gift.stamps and gift.stamps.variants == 8 and gift.stamps.width == 32 and gift.stamp_shadow_7
and #(gift.entries or {}) == 16, "mystery gift manifest keeps its entries and gains stamps")
local link = AssetPack.loadManifest(cache, "x/link/manifest.lua") or {}
check(link.countdown_321 and link.countdown_321.frames == 6 and link.minigame_countdown_numbers,
"link manifest carries countdown_321 and the live minigame countdown")
local function tbl(rel)
local man = AssetPack.loadManifest(cache, rel)
return man or {}
end
local bc = tbl("x/berry_crush/tables.lua")
-- src/berry.c:871
check(bc.berry_data and #bc.berry_data == 43 and bc.berry_data[1].difficulty == 50
and bc.berry_data[1].powder == 20 and bc.berry_data[43].powder == 200
and bc.berry_data[42].powder == 750, "berry crush berry data is gBerryCrush_BerryData")
-- src/berry_crush.c:428
check(bc.sync_press_bonus and bc.sync_press_bonus[5] == 5 and bc.player_coords[2].impactXOffset == -28
and bc.player_coords[4].windowGfxX == 20 and bc.pressing_speed_table[1] == 50000000,
"berry crush press bonus, seat coords and speed table")
check(bc.bg_templates and bc.bg_templates[2].screenSize == 2 and bc.bg_templates[2].mapBaseIndex == 13
and bc.win_player_names[4].left == 21 and bc.win_player_names[1].baseBlock == 0x3ED,
"berry crush bg and window templates")
local dbp = tbl("x/dodrio_berry_picking/tables.lua")
-- src/dodrio_berry_picking.c:644
check(dbp.difficulty_thresholds and dbp.difficulty_thresholds[7] == 100 and dbp.tree_border_x[5] == 15
and dbp.berry_fall_delays[1][1] == 40 and dbp.berry_score_multipliers[2] == 30
and dbp.name_window_coords[5][4].left == 1 and dbp.active_column_map[5][1] ~= nil,
"dodrio thresholds, fall delays, score multipliers and name windows")
local pj = tbl("x/pokemon_jump/tables.lua")
-- src/pokemon_jump.c:768
check(pj.jump_mons and #pj.jump_mons == 100 and pj.jump_mons[1].species == 1 and pj.jump_mons[1].jumpType == 2
and pj.vine_base_speeds[8] == 61 and pj.prize_quantity[5].score == 20000 and pj.vine_x[8] == 224
and pj.mon_x_coords[5][5] == 184 and pj.player_name_window_coords[2][2][1] == 16
and pj.score_bonuses[6] == 500 and pj.jump_offsets[1][1] == -3,
"pokemon jump species, vine speeds, prizes and coords")
local av = tbl("x/union_room/avatars.lua")
-- src/union_room_player_avatar.c:33
local gids = {}
for _, g in ipairs((av.gfx_ids or {}).male or {}) do gids[#gids + 1] = g end
for _, g in ipairs((av.gfx_ids or {}).female or {}) do gids[#gids + 1] = g end
local inRange = #gids == 16
for _, g in ipairs(gids) do if g >= Versions.NUM_OBJ_EVENT_GFX then inRange = false end end
check(inRange and av.gfx_ids.male[1] == 41 and av.gfx_ids.female[8] == 29 and av.leader_coords[2][1] == 13,
"union room avatars are 16 OW gids the ow/ extractor covers")
end
end
if not ranAny then
print("[skip] no pret build at " .. root .. "; ROM checks skipped")
end
if deoxys["pokefirered.gba"] and deoxys["pokeleafgreen.gba"] then
check(deoxys["pokefirered.gba"] ~= deoxys["pokeleafgreen.gba"],
"Deoxys base stats differ between FireRed and LeafGreen while linkStats match")
end
finish()