mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
572 lines
27 KiB
Lua
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()
|