copy rintro

This commit is contained in:
bryanthaboi
2026-08-24 09:02:56 -04:00
parent da73a4fe25
commit 1bd6e35931
4 changed files with 108 additions and 6 deletions
+1
View File
@@ -98,6 +98,7 @@ CacheContract.VERSION_REQUIRED_FILES_OVERRIDE = {
"assets/generated/title/crystal_logo.png", "assets/generated/title/crystal_logo.png",
"assets/generated/title/crystal_wordmark.png", "assets/generated/title/crystal_wordmark.png",
"assets/generated/title/crystal_suicune.png", "assets/generated/title/crystal_suicune.png",
"assets/generated/title/copyright_splash.png",
"assets/generated/splash/ditto.png", "assets/generated/splash/ditto.png",
"assets/generated/intro/chris.png", "assets/generated/intro/chris.png",
"assets/generated/intro/kris.png", "assets/generated/intro/kris.png",
+44 -1
View File
@@ -258,6 +258,44 @@ local function colorize(image, palFor)
return out return out
end 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) function CrystalMovie.extractTitle(self)
self:beginStage("Title screen") self:beginStage("Title screen")
@@ -348,6 +386,7 @@ function CrystalMovie.extractTitle(self)
local gemTinted = colorize(gem, function() return pals.obj[1] end) local gemTinted = colorize(gem, function() return pals.obj[1] end)
self:save(gemTinted, "title/crystal_gem.png") self:save(gemTinted, "title/crystal_gem.png")
self:save(gem, "title/crystal_gem_gray.png") self:save(gem, "title/crystal_gem_gray.png")
extractCopyright(self)
self:tick("Title screen", 4, 5) self:tick("Title screen", 4, 5)
local function unit(color) return { color[1] / 255, color[2] / 255, color[3] / 255 } end 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, generation = 2,
layout = "crystal_title", layout = "crystal_title",
source = "ROM:TitleSuicuneGFX + TitleLogoGFX + TitleCrystalGFX" source = "ROM:TitleSuicuneGFX + TitleLogoGFX + TitleCrystalGFX"
.. " + TitleScreenPalettes", .. " + TitleScreenPalettes + CopyrightGFX",
screen = "assets/generated/title/crystal_screen.png", screen = "assets/generated/title/crystal_screen.png",
screenGray = "assets/generated/title/crystal_screen_gray.png", screenGray = "assets/generated/title/crystal_screen_gray.png",
image = "assets/generated/title/crystal_logo.png", image = "assets/generated/title/crystal_logo.png",
@@ -371,6 +410,10 @@ function CrystalMovie.extractTitle(self)
suicuneEvery = 8, suicuneEvery = 8,
gem = "assets/generated/title/crystal_gem.png", gem = "assets/generated/title/crystal_gem.png",
gemGray = "assets/generated/title/crystal_gem_gray.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 -- 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). -- (56, 6) (engine/movie/title.asm:306-311,340-362).
gemX = 56, gemX = 56,
+18 -5
View File
@@ -23,6 +23,11 @@ local HOLD_FRAMES = 100
-- Only used when the extracted splash image is missing. -- Only used when the extracted splash image is missing.
local LINE_Y = { 48, 64, 80 } 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) local function tryImage(path)
if not path then return nil end if not path then return nil end
local ok, image = pcall(Assets.image, path) local ok, image = pcall(Assets.image, path)
@@ -43,6 +48,8 @@ function CopyrightSplash.new(game, opts)
self.image = tryImage(opts.image) self.image = tryImage(opts.image)
or tryImage(title.copyrightSplash) or tryImage(title.copyrightSplash)
or tryImage("assets/generated/title/copyright_splash.png") 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). -- Text fallback only when the ROM extract is missing (tests / bare boots).
self.lines = opts.lines self.lines = opts.lines
self.frames = 0 self.frames = 0
@@ -78,17 +85,24 @@ function CopyrightSplash:update(_dt)
end end
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() function CopyrightSplash:drawPanel()
local G = love.graphics local G = love.graphics
G.setColor(1, 1, 1, 1) self:fillBackdrop(SCREEN_W, SCREEN_H)
G.rectangle("fill", 0, 0, SCREEN_W, SCREEN_H)
if self.image then if self.image then
G.setColor(1, 1, 1, 1)
G.draw(self.image, 0, 0) G.draw(self.image, 0, 0)
return return
end end
if not self.lines then return end if not self.lines then return end
-- The Gen 2 font is a fixed 8px cell, so centring is a character count. -- 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 for index, line in ipairs(self.lines) do
local y = LINE_Y[index] local y = LINE_Y[index]
if y then if y then
@@ -104,8 +118,7 @@ end
function CopyrightSplash:drawWidescreen(winW, winH) function CopyrightSplash:drawWidescreen(winW, winH)
local G = love.graphics local G = love.graphics
G.setColor(1, 1, 1, 1) self:fillBackdrop(winW, winH)
G.rectangle("fill", 0, 0, winW, winH)
local scale = Chrome.fitScale(winW, winH) local scale = Chrome.fitScale(winW, winH)
G.push() G.push()
G.translate(Chrome.fitOrigin(winW, winH, scale)) G.translate(Chrome.fitOrigin(winW, winH, scale))
@@ -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")