mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-25 23:11:15 +02:00
ca4d3d283c
Crystal boots from a user-supplied ROM, imports a full cache and is playable: copyright, the Crystal intro movie, the animated title, gender select, Oak, and out into Johto. 122 of the cart's 169 script specials are implemented. Import and data - tools/make_crystal_manifest.py derives the manifest by importing make_gold_manifest as a library, with three additive keyword seams. Gold and Silver still regenerate byte-identical, which is the standing requirement for touching that generator. - crystal_symbol_deltas.py and crystal_movie_symbols.py carry the symbol delta: Crystal renames the credits mons, splits the trainer card, Pokegear and pack-pal blocks by gender, and replaces the intro and title outright. - Crystal-only manifest keys: engineFlagOrder (162 flags to Gold's 93, so the badge block sits one higher) and unownCharmap (the main charmap parser stops at the first newcharmap so the two cannot contaminate each other). Extractor - RomExtractorGen2 becomes three-edition. Crystal corrections: PAL_MAP_BANK 0x13, a flat PICS_FIX pic bank, audio bank 0x5e, the mapSongs id-100 hole, seven NPC trades, a TradeTexts stride of 8, the five Crystal tileset anim steps with per-row degrade, and the column-major trainer card portraits. - New: animated front sprites (frames, bitmasks, play and idle scripts), the Battle Tower roster, Kris assets, Mobile System GB art, and the Crystal intro and title via src/import/CrystalMovie.lua. Engine - GameVersion gains engine(id) and fixes(id). Gold and Silver keep their original bugs where the bug is not hardware dependent; Crystal gets the fixes Crystal shipped: Lucky Number boxes 10-14, surfing onto an NPC, and the Reflect and Light Screen defence overflow. - Crystal story: Suicune and Eusine, Celebi behind the GS Ball flag, the Ruins of Alph chambers, Buena, the Move Tutor, the Poke Seer, and the Battle Tower including the wInBattleTowerBattle badge-boost guard. - Kris and the gender flag, animated fronts in battle and the summary screen, and mon caught data. Verification - Every extracted asset is pixel-compared against pret's own source PNGs. - Gold caches are byte-identical before and after, file for file. - New Crystal suites plus a T2 Gen 2 tier; the full suite passes.
464 lines
19 KiB
Lua
464 lines
19 KiB
Lua
-- Crystal's Kris battle/Hall-of-Fame art and the Mobile System GB sheets.
|
|
-- luajit tests/gen2_crystal_mobile_gfx_test.lua
|
|
--
|
|
-- Two imports that had no consumer at the time they landed, so nothing else
|
|
-- would notice them rotting: Kris's backpic and KRIS trainer-class pic (wave A
|
|
-- shipped the female protagonist but battles and the Hall of Fame still drew
|
|
-- Chris), and the whole gfx/mobile + gfx/mystery_gift set, imported ahead of
|
|
-- the phase that uses it so Crystal never has to re-import for it.
|
|
--
|
|
-- What is held down: the stage exists and run() calls it, it is gated on the
|
|
-- crystal edition so Gold and Silver emit nothing, every label it reads is in
|
|
-- the generated Crystal manifest at the address pokecrystal.sym gives, and the
|
|
-- blobs it wrote are ../pokecrystal/gfx/mobile byte for byte.
|
|
--
|
|
-- ROM-free. The decomp half SKIPs with no ../pokecrystal beside the repo, the
|
|
-- cache half with no Crystal cache.
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local S = require("tests.harness").suite("gen2 crystal mobile gfx")
|
|
local check, eq = S.check, S.eq
|
|
|
|
love = require("tests.love_stub")
|
|
|
|
local function readFile(path)
|
|
local f = io.open(path, "rb")
|
|
if not f then return nil end
|
|
local body = f:read("*a")
|
|
f:close()
|
|
return body
|
|
end
|
|
|
|
-- ------------------------------------------------------- the stage itself
|
|
|
|
local extractorSource = assert(readFile("src/import/RomExtractorGen2.lua"))
|
|
local RomExtractorGen2 = require("src.import.RomExtractorGen2")
|
|
|
|
check(type(RomExtractorGen2.extractMobileGfx) == "function",
|
|
"RomExtractorGen2:extractMobileGfx exists")
|
|
check(extractorSource:find("results.mobileGfx = self:extractMobileGfx()",
|
|
1, true) ~= nil, "and RomExtractorGen2:run calls it")
|
|
check(extractorSource:find(
|
|
'if self.edition ~= "crystal" then return nil end', 1, true) ~= nil,
|
|
"gated on the edition, so Gold and Silver write no mobile art")
|
|
-- The two suites that pin the progress bar read this same literal; a new
|
|
-- stage must not move it (tests/gen2_diploma_test.lua:45).
|
|
check(extractorSource:find("local STAGE_COUNT = 27", 1, true) ~= nil,
|
|
"and it did not disturb STAGE_COUNT")
|
|
|
|
-- ------------------------------------------------------------ the symbols
|
|
|
|
-- ../pokecrystal-symbols/pokecrystal.sym, international v1.0. Every one of
|
|
-- these resolves in that build: the Japanese-only part of Mobile System GB is
|
|
-- the text and the menu entries that reach it, not the art.
|
|
local SYMBOLS = {
|
|
AsciiFontGFX = { 0x5c, 0x5db1 },
|
|
PichuAnimatedMobileGFX = { 0x5c, 0x4d16 },
|
|
ElectroBallMobileGFX = { 0x5c, 0x55a4 },
|
|
PichuBorderMobileGFX = { 0x5c, 0x5848 },
|
|
Stadium2N64GFX = { 0x5c, 0x6f1f },
|
|
Stadium2N64Tilemap = { 0x5c, 0x73af },
|
|
Stadium2N64Attrmap = { 0x5c, 0x7517 },
|
|
PasswordTopTilemap = { 0x5c, 0x6491 },
|
|
PasswordBottomTilemap = { 0x5c, 0x651d },
|
|
PasswordShiftTilemap = { 0x5c, 0x65f9 },
|
|
ChooseMobileCenterTilemap = { 0x5c, 0x6685 },
|
|
MobilePasswordAttrmap = { 0x5c, 0x67ed },
|
|
ChooseMobileCenterAttrmap = { 0x5c, 0x6955 },
|
|
MobilePasswordPalettes = { 0x5c, 0x5d71 },
|
|
MobileCardGFX = { 0x5e, 0x59ef },
|
|
ChrisSilhouetteGFX = { 0x5e, 0x5bef },
|
|
KrisSilhouetteGFX = { 0x5e, 0x5e1f },
|
|
MobileCard2GFX = { 0x5e, 0x604f },
|
|
CardLargeSpriteAndFolderGFX = { 0x5e, 0x61bf },
|
|
CardSpriteGFX = { 0x5e, 0x664f },
|
|
DialpadTilemap = { 0x5e, 0x6cd5 },
|
|
DialpadAttrmap = { 0x5e, 0x6e3d },
|
|
DialpadGFX = { 0x5e, 0x6fa5 },
|
|
DialpadCursorGFX = { 0x5e, 0x7465 },
|
|
MobileCardListGFX = { 0x5e, 0x74b9 },
|
|
HaveWantGFX = { 0x5f, 0x4083 },
|
|
MobileSelectGFX = { 0x5f, 0x4983 },
|
|
HaveWantMap = { 0x5f, 0x4b83 },
|
|
PokemonNewsGFX = { 0x5f, 0x66fe },
|
|
PokemonNewsTileAttrmap = { 0x5f, 0x6b8e },
|
|
PokemonNewsPalettes = { 0x5f, 0x6ff6 },
|
|
["MobileSystemSplashScreen_InitGFX.Tiles"] = { 0x5b, 0x4173 },
|
|
["MobileSystemSplashScreen_InitGFX.Tilemap"] = { 0x5b, 0x4633 },
|
|
["MobileSystemSplashScreen_InitGFX.Attrmap"] = { 0x5b, 0x479b },
|
|
MobileSplashScreenPalettes = { 0x5b, 0x4903 },
|
|
MobileAdapterCheckGFX = { 0x5b, 0x4ca3 },
|
|
MobileTradeSpritesGFX = { 0x42, 0x4d27 },
|
|
MobileTradeGFX = { 0x42, 0x4da7 },
|
|
MobileTradeTilemapLZ = { 0x42, 0x4fe7 },
|
|
MobileTradeAttrmapLZ = { 0x42, 0x50a7 },
|
|
MobileCable1GFX = { 0x42, 0x51c7 },
|
|
MobileCable2GFX = { 0x42, 0x52c7 },
|
|
UnusedMobilePulsePalettes = { 0x42, 0x50f7 },
|
|
MobileTradeBGPalettes = { 0x42, 0x5107 },
|
|
MobileTradeOB1Palettes = { 0x42, 0x5147 },
|
|
MobileTradeOB2Palettes = { 0x42, 0x5187 },
|
|
MobileAdapterPalettes = { 0x42, 0x53c7 },
|
|
MobileTradeLightsGFX = { 0x40, 0x72a2 },
|
|
MobileTradeLightsPalettes = { 0x40, 0x72e2 },
|
|
PichuBorderMobileOBPalettes = { 0x45, 0x730e },
|
|
PichuBorderMobileBGPalettes = { 0x45, 0x734e },
|
|
PichuBorderMobileTilemapAttrmap = { 0x45, 0x7356 },
|
|
MobileDialingGFX = { 0x45, 0x601a },
|
|
MobileUpArrowGFX = { 0x12, 0x48c3 },
|
|
MobileDownArrowGFX = { 0x12, 0x48cb },
|
|
EZChatCursorGFX = { 0x22, 0x540b },
|
|
MobileDialingFrameGFX = { 0x41, 0x6514 },
|
|
SelectStartGFX = { 0x47, 0x567e },
|
|
MobileMenuGFX = { 0x12, 0x5c0c },
|
|
MobilePhoneTilesGFX = { 0x3e, 0x5234 },
|
|
MysteryGiftGFX = { 0x41, 0x5258 },
|
|
CardTradeGFX = { 0x41, 0x5930 },
|
|
CardTradeSpriteGFX = { 0x41, 0x5d30 },
|
|
}
|
|
|
|
-- Kris's own two pics. KrisBackpic needs no delta row: embedded_symbols in
|
|
-- make_gold_manifest.py adds every unqualified *Backpic label on sight.
|
|
local KRIS_SYMBOLS = {
|
|
KrisBackpic = { 0x22, 0x4ed6 },
|
|
KrisPic = { 0x22, 0x4bb9 },
|
|
ChrisPic = { 0x22, 0x48a9 },
|
|
}
|
|
|
|
do
|
|
local deltas = assert(readFile("tools/crystal_symbol_deltas.py"))
|
|
for label in pairs(SYMBOLS) do
|
|
check(deltas:find('"' .. label .. '"', 1, true) ~= nil,
|
|
label .. " is in crystal_symbol_deltas.MOBILE_SYMBOLS")
|
|
end
|
|
end
|
|
|
|
local Json = require("src.link.Json")
|
|
local function manifestSymbols(path)
|
|
local body = readFile(path)
|
|
if not body then return nil end
|
|
return (Json.decode(body) or {}).symbols or {}
|
|
end
|
|
|
|
do
|
|
local symbols = manifestSymbols("tools/rom_manifest_crystal.json")
|
|
if not symbols then
|
|
check(true, "no tools/rom_manifest_crystal.json (SKIP)")
|
|
else
|
|
local wrong = {}
|
|
for label, at in pairs(SYMBOLS) do
|
|
local got = symbols[label]
|
|
if not got or got[1] ~= at[1] or got[2] ~= at[2] then
|
|
wrong[#wrong + 1] = label
|
|
end
|
|
end
|
|
check(#wrong == 0, #wrong == 0
|
|
and "the Crystal manifest carries all 63 mobile symbols where "
|
|
.. "pokecrystal.sym puts them"
|
|
or ("mobile symbols missing or misplaced: " .. table.concat(wrong, ", ")))
|
|
for label, at in pairs(KRIS_SYMBOLS) do
|
|
local got = symbols[label]
|
|
if not got then
|
|
check(false, "the Crystal manifest carries " .. label)
|
|
else
|
|
eq(got[1], at[1], label .. " is in the bank pokecrystal.sym says")
|
|
eq(got[2], at[2], label .. " is at the address it says")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
do
|
|
-- Gold has no mobile banks; a delta that leaked into the shared set would
|
|
-- break the Gold import rather than show up here as a missing sheet.
|
|
local symbols = manifestSymbols("tools/rom_manifest_gold.json")
|
|
if not symbols then
|
|
check(true, "no tools/rom_manifest_gold.json (SKIP)")
|
|
else
|
|
local leaked = {}
|
|
for label in pairs(SYMBOLS) do
|
|
if symbols[label] then leaked[#leaked + 1] = label end
|
|
end
|
|
check(#leaked == 0, #leaked == 0
|
|
and "and the Gold manifest carries none of them"
|
|
or ("mobile symbols leaked into Gold: " .. table.concat(leaked, ", ")))
|
|
check(symbols.KrisBackpic == nil, "nor Gold's non-existent KrisBackpic")
|
|
end
|
|
end
|
|
|
|
-- --------------------------------------------------------------- the cache
|
|
|
|
local cache = os.getenv("CRYSTAL_CACHE")
|
|
if not cache then
|
|
local home = os.getenv("HOME") or ""
|
|
cache = home .. "/Library/Application Support/LOVE/crystal-dev/crystal"
|
|
end
|
|
|
|
local function loadCache(rel)
|
|
local chunk = loadfile(cache .. "/data/generated/" .. rel .. ".lua")
|
|
if not chunk then return nil end
|
|
local ok, value = pcall(chunk)
|
|
return ok and value or nil
|
|
end
|
|
|
|
-- PNG IHDR: width and height are big-endian at bytes 17 and 21.
|
|
local function pngSize(relative)
|
|
local png = readFile(cache .. "/" .. relative)
|
|
if not png then return nil end
|
|
local function be32(s, i)
|
|
local a, b, c, d = s:byte(i, i + 3)
|
|
return ((a * 256 + b) * 256 + c) * 256 + d
|
|
end
|
|
return be32(png, 17), be32(png, 21)
|
|
end
|
|
|
|
-- ../pokecrystal/gfx/player/kris_back.png is 48x48 and kris.png 56x56, the
|
|
-- same six and seven tiles square Chris uses.
|
|
local BACK_PIC_PX = 48
|
|
local TRAINER_PIC_PX = 56
|
|
|
|
-- Kris's art and the mobile sheets ride one import, so mobile_gfx.lua doubles
|
|
-- as the marker for both: a cache built before this unit has neither, and the
|
|
-- whole cache half stands down rather than failing on an old sandbox.
|
|
local mobile = loadCache("mobile_gfx")
|
|
local menuGfx = mobile and loadCache("menu_gfx") or nil
|
|
if not menuGfx then
|
|
check(true, "no Crystal cache with mobile_gfx.lua at " .. cache
|
|
.. " : re-import for gfx/mobile (SKIP)")
|
|
else
|
|
local hud = menuGfx.battleHud or {}
|
|
eq(hud.playerBackFemale, "assets/generated/battle/player_back_female.png",
|
|
"menu_gfx points battles and the Hall of Fame at Kris's backpic")
|
|
local w, h = pngSize("assets/generated/battle/player_back_female.png")
|
|
eq(w, BACK_PIC_PX, "GetKrisBackpic's pic is 48px wide")
|
|
eq(h, BACK_PIC_PX, "and 48 tall, not the 7*7 tiles of VRAM it asks for")
|
|
check(hud.playerBack ~= hud.playerBackFemale,
|
|
"and it is not the same file Chris gets")
|
|
|
|
local pics = hud.trainerPics or {}
|
|
eq(pics.KRIS, "assets/generated/battle/trainers/kris.png",
|
|
"HOF_LoadTrainerFrontpic's KRIS class has a pic of its own")
|
|
eq(pics.CHRIS, "assets/generated/battle/trainers/chris.png",
|
|
"as does CHRIS, which TrainerPicPointers has no row for")
|
|
for _, key in ipairs({ "kris", "chris" }) do
|
|
local pw, ph = pngSize("assets/generated/battle/trainers/" .. key .. ".png")
|
|
eq(pw, TRAINER_PIC_PX, key .. ".png is 56px wide")
|
|
eq(ph, TRAINER_PIC_PX, "and 56 tall, like every other trainer pic")
|
|
end
|
|
end
|
|
|
|
local oakSpeech = mobile and loadCache("oak_speech") or nil
|
|
if not oakSpeech then
|
|
check(true, "no oak_speech.lua beside a mobile_gfx.lua cache (SKIP)")
|
|
else
|
|
eq(oakSpeech.playerPicFemale, "assets/generated/intro/kris.png",
|
|
"and DrawIntroPlayerPic's female arm is named in oak_speech.lua")
|
|
end
|
|
|
|
-- Sheets whose tile count fills the pret PNG's grid exactly once padded, so
|
|
-- the cache PNG must come out at the source PNG's own size. The three left
|
|
-- out (mobile_splash, electro_ball, phone_tiles) are built with
|
|
-- --remove-duplicates / --remove-whitespace, so the ROM holds fewer tiles than
|
|
-- the PNG does and only the tilemap puts them back (../pokecrystal/Makefile:
|
|
-- 343,344,348).
|
|
local SHEETS = {
|
|
{ "asciiFont", "mobile/ascii_font.png", 110, 16, "gfx/mobile/ascii_font.png" },
|
|
{ "card", "mobile/card.png", 32, 16, "gfx/mobile/card.png" },
|
|
{ "card2", "mobile/card_2.png", 23, 12, "gfx/mobile/card_2.png" },
|
|
{ "cardLargeSprite", "mobile/card_large_sprite.png", 8, 4,
|
|
"gfx/mobile/card_large_sprite.png" },
|
|
{ "cardFolder", "mobile/card_folder.png", 65, 6,
|
|
"gfx/mobile/card_folder.png" },
|
|
{ "cardList", "mobile/card_list.png", 24, 12, "gfx/mobile/card_list.png" },
|
|
{ "cardSprite", "mobile/card_sprite.png", 4, 2, "gfx/mobile/card_sprite.png" },
|
|
{ "chrisSilhouette", "mobile/chris_silhouette.png", 35, 5,
|
|
"gfx/mobile/chris_silhouette.png" },
|
|
{ "krisSilhouette", "mobile/kris_silhouette.png", 35, 5,
|
|
"gfx/mobile/kris_silhouette.png" },
|
|
{ "dialing", "mobile/dialing.png", 20, 2, "gfx/mobile/dialing.png" },
|
|
{ "dialingFrame", "mobile/dialing_frame.png", 8, 2,
|
|
"gfx/mobile/dialing_frame.png" },
|
|
{ "dialpad", "mobile/dialpad.png", 76, 16, "gfx/mobile/dialpad.png" },
|
|
{ "dialpadCursor", "mobile/dialpad_cursor.png", 5, 2,
|
|
"gfx/mobile/dialpad_cursor.png" },
|
|
{ "ezChatCursor", "mobile/ez_chat_cursor.png", 2, 1,
|
|
"gfx/mobile/ez_chat_cursor.png" },
|
|
{ "haveWant", "mobile/havewant.png", 144, 16, "gfx/mobile/havewant.png" },
|
|
{ "select", "mobile/select.png", 32, 4, "gfx/mobile/select.png" },
|
|
{ "selectStart", "mobile/select_start.png", 6, 3,
|
|
"gfx/mobile/select_start.png" },
|
|
{ "cable1", "mobile/mobile_cable_1.png", 16, 4,
|
|
"gfx/mobile/mobile_cable_1.png" },
|
|
{ "cable2", "mobile/mobile_cable_2.png", 16, 4,
|
|
"gfx/mobile/mobile_cable_2.png" },
|
|
{ "menu", "mobile/mobile_menu.png", 13, 13, "gfx/mobile/mobile_menu.png" },
|
|
{ "adapterCheck", "mobile/mobile_splash_check.png", 48, 16,
|
|
"gfx/mobile/mobile_splash_check.png" },
|
|
{ "tradeLights", "mobile/mobile_trade_lights.png", 4, 2,
|
|
"gfx/mobile/mobile_trade_lights.png" },
|
|
{ "pichuBorder", "mobile/pichu_border.png", 24, 4,
|
|
"gfx/mobile/pichu_border.png" },
|
|
{ "pokemonNews", "mobile/pokemon_news.png", 72, 8,
|
|
"gfx/mobile/pokemon_news.png" },
|
|
{ "stadium2N64", "mobile/stadium2_n64.png", 73, 11,
|
|
"gfx/mobile/stadium2_n64.png" },
|
|
{ "pichuAnimated", "mobile/pichu_animated.png", 193, 16,
|
|
"gfx/mobile/pichu_animated.png" },
|
|
{ "trade", "mobile/mobile_trade.png", 128, 8, "gfx/mobile/mobile_trade.png" },
|
|
{ "tradeSprites", "mobile/mobile_trade_sprites.png", 16, 4,
|
|
"gfx/mobile/mobile_trade_sprites.png" },
|
|
{ "upArrow", "mobile/up_arrow.png", 1, 1, "gfx/mobile/up_arrow.png" },
|
|
{ "downArrow", "mobile/down_arrow.png", 1, 1, "gfx/mobile/down_arrow.png" },
|
|
{ "mysteryGift", "mobile/mystery_gift/mystery_gift.png", 67, 16,
|
|
"gfx/mystery_gift/mystery_gift.png" },
|
|
{ "cardTrade", "mobile/mystery_gift/card_trade.png", 64, 16,
|
|
"gfx/mystery_gift/card_trade.png" },
|
|
{ "cardTradeSprite", "mobile/mystery_gift/card_sprite.png", 8, 4,
|
|
"gfx/mystery_gift/card_sprite.png" },
|
|
}
|
|
|
|
-- The three the dedupe passes shrink: only the declared tile count is checked.
|
|
local DEDUPED = {
|
|
{ "splash", "mobile/mobile_splash.png", 76, 14 },
|
|
{ "electroBall", "mobile/electro_ball.png", 83, 16 },
|
|
{ "phoneTiles", "mobile/phone_tiles.png", 17, 2 },
|
|
}
|
|
|
|
-- key, cache file, byte length, ../pokecrystal source it must equal.
|
|
local MAPS = {
|
|
{ "passwordTop", "password_top", 140, "gfx/mobile/password_top.tilemap" },
|
|
{ "passwordBottom", "password_bottom", 220,
|
|
"gfx/mobile/password_bottom.tilemap" },
|
|
{ "passwordShift", "password_shift", 140,
|
|
"gfx/mobile/password_shift.tilemap" },
|
|
{ "passwordAttrmap", "password_attrmap", 360,
|
|
"gfx/mobile/password.attrmap" },
|
|
{ "centerTilemap", "mobile_center_tilemap", 360,
|
|
"gfx/mobile/mobile_center.tilemap" },
|
|
{ "centerAttrmap", "mobile_center_attrmap", 360,
|
|
"gfx/mobile/mobile_center.attrmap" },
|
|
{ "stadium2N64Tilemap", "stadium2_n64_tilemap", 360,
|
|
"gfx/mobile/stadium2_n64.tilemap" },
|
|
{ "stadium2N64Attrmap", "stadium2_n64_attrmap", 360,
|
|
"gfx/mobile/stadium2_n64.attrmap" },
|
|
{ "dialpadTilemap", "dialpad_tilemap", 360, "gfx/mobile/dialpad.tilemap" },
|
|
{ "dialpadAttrmap", "dialpad_attrmap", 360, "gfx/mobile/dialpad.attrmap" },
|
|
{ "haveWantMap", "havewant_map", 1136, "gfx/mobile/havewant_map.bin" },
|
|
{ "newsAttrmap", "pokemon_news_attrmap", 1128,
|
|
"gfx/mobile/pokemon_news.bin" },
|
|
{ "splashTilemap", "mobile_splash_tilemap", 360,
|
|
"gfx/mobile/mobile_splash.tilemap" },
|
|
{ "splashAttrmap", "mobile_splash_attrmap", 360,
|
|
"gfx/mobile/mobile_splash.attrmap" },
|
|
{ "pichuBorderTilemap", "pichu_border_tilemap", 384,
|
|
"gfx/mobile/pichu_border.tilemap" },
|
|
{ "pichuBorderAttrmap", "pichu_border_attrmap", 384,
|
|
"gfx/mobile/pichu_border.attrmap" },
|
|
{ "tradeTilemap", "mobile_trade_tilemap", 1024,
|
|
"gfx/mobile/mobile_trade.tilemap" },
|
|
{ "tradeAttrmap", "mobile_trade_attrmap", 1024,
|
|
"gfx/mobile/mobile_trade.attrmap" },
|
|
}
|
|
|
|
local PALETTE_COLORS = {
|
|
splash = 32, password = 32, pokemonNews = 32, pichuBorderOB = 32,
|
|
pichuBorderBG = 4, tradeBG = 32, tradeOB1 = 32, tradeOB2 = 32,
|
|
tradeLights = 16, adapters = 8, unusedPulses = 8,
|
|
}
|
|
|
|
local pokecrystal = "../pokecrystal"
|
|
local havePret = readFile(pokecrystal .. "/gfx/mobile/ascii_font.2bpp") ~= nil
|
|
|
|
if not mobile then
|
|
check(true, "no mobile_gfx.lua in the Crystal cache (SKIP)")
|
|
else
|
|
eq(mobile.generation, 2, "mobile_gfx.lua is a Gen 2 table")
|
|
local sheets = mobile.sheets or {}
|
|
local maps = mobile.maps or {}
|
|
local palettes = mobile.palettes or {}
|
|
|
|
local function checkSheet(row, pretPng)
|
|
local key, rel, tiles, wide = row[1], row[2], row[3], row[4]
|
|
local entry = sheets[key]
|
|
if type(entry) ~= "table" then
|
|
check(false, "mobile_gfx carries the " .. key .. " sheet")
|
|
return
|
|
end
|
|
eq(entry.tiles, tiles, key .. " is the tile count the ROM block holds")
|
|
eq(entry.tilesWide, wide, "and is written " .. wide .. " tiles across")
|
|
eq(entry.path, "assets/generated/" .. rel, "at the path it advertises")
|
|
local w, h = pngSize("assets/generated/" .. rel)
|
|
eq(w, wide * 8, key .. ".png is " .. (wide * 8) .. "px wide")
|
|
eq(h, math.ceil(tiles / wide) * 8, "and tall enough for every tile")
|
|
if pretPng and havePret then
|
|
local png = readFile(pokecrystal .. "/" .. pretPng)
|
|
if png then
|
|
local function be32(s, i)
|
|
local a, b, c, d = s:byte(i, i + 3)
|
|
return ((a * 256 + b) * 256 + c) * 256 + d
|
|
end
|
|
eq(w, be32(png, 17), "which is " .. pretPng .. "'s own width")
|
|
eq(h, be32(png, 21), "and its own height")
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, row in ipairs(SHEETS) do checkSheet(row, row[5]) end
|
|
for _, row in ipairs(DEDUPED) do checkSheet(row, nil) end
|
|
|
|
for _, row in ipairs(MAPS) do
|
|
local key, file, bytes, source = row[1], row[2], row[3], row[4]
|
|
local entry = maps[key]
|
|
local rel = "assets/generated/mobile/" .. file .. ".bin"
|
|
if type(entry) ~= "table" then
|
|
check(false, "mobile_gfx carries the " .. key .. " map")
|
|
else
|
|
eq(entry.bytes, bytes, key .. " is " .. bytes .. " bytes")
|
|
eq(entry.path, rel, "at the path it advertises")
|
|
local blob = readFile(cache .. "/" .. rel)
|
|
if not blob then
|
|
check(false, rel .. " was written")
|
|
else
|
|
eq(#blob, bytes, "and the file on disk is that long")
|
|
local want = havePret and readFile(pokecrystal .. "/" .. source) or nil
|
|
if not want then
|
|
check(true, "no ../pokecrystal: " .. source .. " not diffed (SKIP)")
|
|
else
|
|
check(blob == want:sub(1, bytes),
|
|
"it is ../pokecrystal/" .. source .. " byte for byte")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
for key, colors in pairs(PALETTE_COLORS) do
|
|
local pal = palettes[key]
|
|
if type(pal) ~= "table" then
|
|
check(false, "mobile_gfx carries the " .. key .. " palette block")
|
|
else
|
|
eq(#pal, colors, key .. " is " .. colors .. " RGB words")
|
|
eq(#(pal[1] or {}), 3, "each one decoded to an r,g,b triple")
|
|
end
|
|
end
|
|
end
|
|
|
|
-- A Gold cache must have gone nowhere near any of this.
|
|
local goldCache = os.getenv("GOLD_CACHE")
|
|
if not goldCache then
|
|
local home = os.getenv("HOME") or ""
|
|
goldCache = home .. "/Library/Application Support/LOVE/gold-dev/gold"
|
|
end
|
|
if not loadfile(goldCache .. "/data/generated/menu_gfx.lua") then
|
|
check(true, "no Gold cache at " .. goldCache .. " (SKIP)")
|
|
else
|
|
check(loadfile(goldCache .. "/data/generated/mobile_gfx.lua") == nil,
|
|
"a Gold cache has no mobile_gfx.lua")
|
|
local chunk = loadfile(goldCache .. "/data/generated/menu_gfx.lua")
|
|
local ok, gold = pcall(chunk)
|
|
local hud = (ok and gold or {}).battleHud or {}
|
|
check(hud.playerBackFemale == nil, "and no Kris backpic")
|
|
check((hud.trainerPics or {}).KRIS == nil, "and no KRIS trainer pic")
|
|
end
|
|
|
|
S.finish()
|