From 1bd6e3593148b25c19432c8b8080a5b49d473c8f Mon Sep 17 00:00:00 2001 From: bryanthaboi Date: Mon, 24 Aug 2026 09:02:56 -0400 Subject: [PATCH] copy rintro --- src/import/CacheContract.lua | 1 + src/import/CrystalMovie.lua | 45 +++++++++++++++++++++- src/ui/gen2/CopyrightSplash.lua | 23 ++++++++--- tests/engine/copyright_splash_backdrop.lua | 45 ++++++++++++++++++++++ 4 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 tests/engine/copyright_splash_backdrop.lua diff --git a/src/import/CacheContract.lua b/src/import/CacheContract.lua index 0459e895..dd4e8503 100644 --- a/src/import/CacheContract.lua +++ b/src/import/CacheContract.lua @@ -98,6 +98,7 @@ CacheContract.VERSION_REQUIRED_FILES_OVERRIDE = { "assets/generated/title/crystal_logo.png", "assets/generated/title/crystal_wordmark.png", "assets/generated/title/crystal_suicune.png", + "assets/generated/title/copyright_splash.png", "assets/generated/splash/ditto.png", "assets/generated/intro/chris.png", "assets/generated/intro/kris.png", diff --git a/src/import/CrystalMovie.lua b/src/import/CrystalMovie.lua index b0d943cb..fb9892b3 100644 --- a/src/import/CrystalMovie.lua +++ b/src/import/CrystalMovie.lua @@ -258,6 +258,44 @@ local function colorize(image, palFor) return out end +-- engine/movie/splash.asm:20-30 sets SCGB_GAMEFREAK_LOGO before Copyright, so +-- the card runs on gfx/sgb/predef.pal:79 PREDEFPAL_GAMEFREAK_LOGO_BG. +local COPYRIGHT_TILES = 29 +local COPYRIGHT_BG = { + { 0, 0, 0 }, { 66, 90, 90 }, { 173, 173, 173 }, { 255, 255, 255 }, +} + +-- data/copyright.asm at hlcoord 2, 7 (engine/menus/intro_menu.asm:1315-1326). +local COPYRIGHT_LINES = { + { 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c }, + { 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x7a, 0x7b, 0x7c }, + { 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c }, +} + +local function extractCopyright(self) + local symbol = self:symbol("CopyrightGFX") + local raw = self.rom:bytes(symbol.bank, symbol.address, + COPYRIGHT_TILES * TILE_BYTES) + self:write2bpp(raw, COPYRIGHT_TILES * 8, 8, "title/copyright.png") + + local painted = {} + for index, tile in ipairs(tilesFrom2bpp(raw)) do + painted[index] = colorize(tile, function() return COPYRIGHT_BG end) + end + local backdrop = COPYRIGHT_BG[1] + local splash = ImageWriter.blank(160, 144, + backdrop[1] / 255, backdrop[2] / 255, backdrop[3] / 255, 1) + for row, line in ipairs(COPYRIGHT_LINES) do + for column, id in ipairs(line) do + blitTile(splash, painted[id - 0x60 + 1], (1 + column) * 8, (6 + row) * 8) + end + end + self:save(splash, "title/copyright_splash.png") +end + function CrystalMovie.extractTitle(self) self:beginStage("Title screen") @@ -348,6 +386,7 @@ function CrystalMovie.extractTitle(self) local gemTinted = colorize(gem, function() return pals.obj[1] end) self:save(gemTinted, "title/crystal_gem.png") self:save(gem, "title/crystal_gem_gray.png") + extractCopyright(self) self:tick("Title screen", 4, 5) local function unit(color) return { color[1] / 255, color[2] / 255, color[3] / 255 } end @@ -356,7 +395,7 @@ function CrystalMovie.extractTitle(self) generation = 2, layout = "crystal_title", source = "ROM:TitleSuicuneGFX + TitleLogoGFX + TitleCrystalGFX" - .. " + TitleScreenPalettes", + .. " + TitleScreenPalettes + CopyrightGFX", screen = "assets/generated/title/crystal_screen.png", screenGray = "assets/generated/title/crystal_screen_gray.png", image = "assets/generated/title/crystal_logo.png", @@ -371,6 +410,10 @@ function CrystalMovie.extractTitle(self) suicuneEvery = 8, gem = "assets/generated/title/crystal_gem.png", gemGray = "assets/generated/title/crystal_gem_gray.png", + copyright = "assets/generated/title/copyright.png", + copyrightSplash = "assets/generated/title/copyright_splash.png", + copyrightBackdrop = unit(COPYRIGHT_BG[1]), + copyrightInk = unit(COPYRIGHT_BG[4]), -- Strip 0 starts at OAM (64, -$22) and stops at OAM y 22, i.e. screen -- (56, 6) (engine/movie/title.asm:306-311,340-362). gemX = 56, diff --git a/src/ui/gen2/CopyrightSplash.lua b/src/ui/gen2/CopyrightSplash.lua index abeed958..9b22ff15 100644 --- a/src/ui/gen2/CopyrightSplash.lua +++ b/src/ui/gen2/CopyrightSplash.lua @@ -23,6 +23,11 @@ local HOLD_FRAMES = 100 -- Only used when the extracted splash image is missing. local LINE_Y = { 48, 64, 80 } +-- Gold boots on the default BGP; Crystal inverts under SCGB_GAMEFREAK_LOGO +-- (../pokecrystal/engine/movie/splash.asm:20-30). +local DEFAULT_BACKDROP = { 1, 1, 1 } +local DEFAULT_INK = { 0, 0, 0 } + local function tryImage(path) if not path then return nil end local ok, image = pcall(Assets.image, path) @@ -43,6 +48,8 @@ function CopyrightSplash.new(game, opts) self.image = tryImage(opts.image) or tryImage(title.copyrightSplash) or tryImage("assets/generated/title/copyright_splash.png") + self.backdrop = opts.backdrop or title.copyrightBackdrop or DEFAULT_BACKDROP + self.ink = opts.ink or title.copyrightInk or DEFAULT_INK -- Text fallback only when the ROM extract is missing (tests / bare boots). self.lines = opts.lines self.frames = 0 @@ -78,17 +85,24 @@ function CopyrightSplash:update(_dt) end end +function CopyrightSplash:fillBackdrop(width, height) + local G = love.graphics + local c = self.backdrop + G.setColor(c[1], c[2], c[3], 1) + G.rectangle("fill", 0, 0, width, height) +end + function CopyrightSplash:drawPanel() local G = love.graphics - G.setColor(1, 1, 1, 1) - G.rectangle("fill", 0, 0, SCREEN_W, SCREEN_H) + self:fillBackdrop(SCREEN_W, SCREEN_H) if self.image then + G.setColor(1, 1, 1, 1) G.draw(self.image, 0, 0) return end if not self.lines then return end -- The Gen 2 font is a fixed 8px cell, so centring is a character count. - G.setColor(0, 0, 0, 1) + G.setColor(self.ink[1], self.ink[2], self.ink[3], 1) for index, line in ipairs(self.lines) do local y = LINE_Y[index] if y then @@ -104,8 +118,7 @@ end function CopyrightSplash:drawWidescreen(winW, winH) local G = love.graphics - G.setColor(1, 1, 1, 1) - G.rectangle("fill", 0, 0, winW, winH) + self:fillBackdrop(winW, winH) local scale = Chrome.fitScale(winW, winH) G.push() G.translate(Chrome.fitOrigin(winW, winH, scale)) diff --git a/tests/engine/copyright_splash_backdrop.lua b/tests/engine/copyright_splash_backdrop.lua new file mode 100644 index 00000000..9e82b458 --- /dev/null +++ b/tests/engine/copyright_splash_backdrop.lua @@ -0,0 +1,45 @@ +-- Crystal's boot copyright card is white text on black: SplashScreen sets +-- SCGB_GAMEFREAK_LOGO before calling Copyright, so the card runs on +-- PREDEFPAL_GAMEFREAK_LOGO_BG rather than the default BGP Gold's DMG boot +-- leaves up (../pokecrystal/engine/movie/splash.asm:21-30). The card used to +-- hardcode a white fill and Crystal shipped no extracted splash at all, so the +-- first screen of the game was blank white. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local CopyrightSplash = require("src.ui.gen2.CopyrightSplash") + +local function colorsEq(got, want, message) + T.eq(#got, 3, message .. " (three channels)") + for index = 1, 3 do + T.eq(got[index], want[index], message .. " channel " .. index) + end +end + +local gold = CopyrightSplash.new({}, { title = {} }) +colorsEq(gold.backdrop, { 1, 1, 1 }, "Gold keeps the white card") +colorsEq(gold.ink, { 0, 0, 0 }, "Gold keeps black text") + +local crystal = CopyrightSplash.new({}, { + title = { + copyrightBackdrop = { 0, 0, 0 }, + copyrightInk = { 1, 1, 1 }, + }, +}) +colorsEq(crystal.backdrop, { 0, 0, 0 }, "Crystal's card is black") +colorsEq(crystal.ink, { 1, 1, 1 }, "Crystal's text is white") + +-- The extractor has to emit the image too, or the card is an empty backdrop. +local CrystalMovie = require("src.import.CrystalMovie") +T.eq(type(CrystalMovie.extractTitle), "function", + "CrystalMovie still owns the Crystal title stage") + +local CacheContract = require("src.import.CacheContract") +local required = CacheContract.VERSION_REQUIRED_FILES_OVERRIDE.crystal +local found = false +for _, path in ipairs(required) do + if path == "assets/generated/title/copyright_splash.png" then found = true end +end +T.eq(found, true, "a Crystal cache without the splash is rebuilt") + +T.finish("copyright splash backdrop")