diff --git a/src/import/RomExtractor.lua b/src/import/RomExtractor.lua index d9b25c77..6c4319cc 100644 --- a/src/import/RomExtractor.lua +++ b/src/import/RomExtractor.lua @@ -1756,7 +1756,7 @@ end function RomExtractor:extractField() self:beginStage("Interface artwork") - local done, total = 0, 50 + local done, total = 0, 51 local function tick() done = done + 1 self:tick("Interface artwork", math.min(done, total), total) @@ -1772,6 +1772,16 @@ function RomExtractor:extractField() "title/copyright.png"); tick() self:raw2bpp("GameFreakLogoGraphics", 72, 8, "title/gamefreak_inc.png"); tick() + + do + local gf = self.symbols["GameFreakLogoGraphics"] + local tb = self.symbols["TextBoxGraphics"] + if gf and tb and tb[2] == gf[2] + 9 * 16 + 16 then + local raw = self.rom:bytes(gf[1], gf[2] + 9 * 16, 16) + self:save(ImageWriter.decode2bpp(raw, 8, 8, false), "title/nine.png") + end + end + tick() -- Yellow fixed Pikachu title art (no-op on Red/Blue manifests). self:extractYellowTitleArt(); tick() diff --git a/src/ui/Credits.lua b/src/ui/Credits.lua index 580381f0..b12db5fb 100644 --- a/src/ui/Credits.lua +++ b/src/ui/Credits.lua @@ -31,6 +31,7 @@ -- to just THE END. local Font = require("src.render.Font") +local GameVersion = require("src.core.GameVersion") local Music = require("src.core.Music") local Strings = require("src.core.Strings") @@ -64,11 +65,12 @@ local MON_PREP_FRAMES = 9 -- LoadCopyrightTiles (engine/movie/title.asm CopyrightTextString): tile -- sequences into the extracted title/copyright.png strip (tiles $60-$72: --- (c)'95.'96.'98 + Nintendo + Creatures inc.); the GAME FREAK inc. row is --- the title/gamefreak_inc.png strip (GameFreakLogoGraphics, tiles --- $73-$7B), with the intro's composed gamefreak_text.png as a fallback --- for pre-regeneration data. -local COPY_PREFIX = { 0, 1, 2, 1, 3, 1, 4 } -- (c)'95.'96.'98 +-- Red/Blue (c)'95.'96.'98, Yellow (c)1995-1999 + NineTile) + Nintendo + +-- Creatures inc.; the GAME FREAK inc. row is title/gamefreak_inc.png +-- (GameFreakLogoGraphics, tiles $73-$7B), with the intro's composed +-- gamefreak_text.png as a fallback for pre-regeneration data. +local COPY_PREFIX_RB = { 0, 1, 2, 1, 3, 1, 4 } -- (c)'95.'96.'98 +local COPY_PREFIX_YELLOW = { 0, 1, 2, 3, 1, 2 } -- (c)1995-199 local COPY_NINTENDO = { 5, 6, 7, 8, 9, 10 } -- Nintendo local COPY_CREATURES = { 11, 12, 13, 14, 15, 16, 17, 18 } -- Creatures inc. @@ -140,6 +142,12 @@ function Credits.new(game, onDone, onTheEnd) and title.gamefreakInc.path) or tryImage(intro and intro.gamefreakText and intro.gamefreakText.path) + self.yellowCopy = GameVersion.isYellow() + or (title and title.layout == "yellow_pikachu") + self.copyPrefix = self.yellowCopy and COPY_PREFIX_YELLOW or COPY_PREFIX_RB + self.nineImg = self.yellowCopy and tryImage( + title and title.nine and title.nine.path + or "assets/generated/title/nine.png") or nil return self end @@ -255,6 +263,17 @@ function Credits:drawPage(screen, xoff, shade) if screen.copyright then self:drawCopyright(xoff) end end +function Credits:drawCopyPrefix(x, y) + local img = self.copyImg + for _, t in ipairs(self.copyPrefix) do + love.graphics.draw(img, self.copyQuads[t], x, y) + x = x + 8 + end + if self.nineImg then + love.graphics.draw(self.nineImg, x, y) + end +end + function Credits:drawCopyright(xoff) local img = self.copyImg if img then @@ -267,11 +286,11 @@ function Credits:drawCopyright(xoff) end return x end - row(COPY_PREFIX, xoff + 16, 56) + self:drawCopyPrefix(xoff + 16, 56) row(COPY_NINTENDO, xoff + 80, 56) - row(COPY_PREFIX, xoff + 16, 72) + self:drawCopyPrefix(xoff + 16, 72) row(COPY_CREATURES, xoff + 80, 72) - row(COPY_PREFIX, xoff + 16, 88) + self:drawCopyPrefix(xoff + 16, 88) if self.gfImg then love.graphics.draw(self.gfImg, xoff + 80, 88) else diff --git a/src/ui/IntroMovie.lua b/src/ui/IntroMovie.lua index fb66220b..a7a0e197 100644 --- a/src/ui/IntroMovie.lua +++ b/src/ui/IntroMovie.lua @@ -2,6 +2,7 @@ -- ..(engine/movie/splash.asm ln 27) local Font = require("src.render.Font") +local GameVersion = require("src.core.GameVersion") local Music = require("src.core.Music") local Sound = require("src.core.Sound") local Strings = require("src.core.Strings") @@ -56,8 +57,13 @@ local SPLASH_FRAMES = WAVES_END + 40 -- ld c, 40 (intro.asm:329-331) local LOGO_X, LOGO_Y = 72, 56 local TEXT_X, TEXT_Y = 40, 80 --- ..(engine/movie/title.asm ln 390) -local COPY_PREFIX = { 0, 1, 2, 1, 3, 1, 4 } +-- CopyrightTextString (engine/movie/title.asm). Red/Blue years are +-- (c)'95.'96.'98; Yellow's sheet spells (c)1995-1999 and finishes the +-- last digit with NineTile (title screen). Intro originally overflows +-- into the font "A" tile for that digit; we draw NineTile so both +-- screens read 1999. +local COPY_PREFIX_RB = { 0, 1, 2, 1, 3, 1, 4 } +local COPY_PREFIX_YELLOW = { 0, 1, 2, 3, 1, 2 } local COPY_NINTENDO = { 5, 6, 7, 8, 9, 10 } local COPY_CREATURES = { 11, 12, 13, 14, 15, 16, 17, 18 } @@ -143,6 +149,12 @@ function IntroMovie.new(game, onDone) end self.gfInc = img(titleCfg.gamefreakInc) or tryImage("assets/generated/title/gamefreak_inc.png") + -- ..(pokeyellow engine/movie/title.asm CopyrightTextString / NineTile) + self.yellowCopy = GameVersion.isYellow() + or titleCfg.layout == "yellow_pikachu" + self.copyPrefix = self.yellowCopy and COPY_PREFIX_YELLOW or COPY_PREFIX_RB + self.nineImg = self.yellowCopy and ( + img(titleCfg.nine) or tryImage("assets/generated/title/nine.png")) or nil self.studioLogo = tryImage(self.studio.logo) if self.studioLogo then self.studioLogo:setFilter("nearest", "nearest") @@ -364,6 +376,16 @@ function IntroMovie:drawFight() end end +function IntroMovie:drawCopyPrefix(x, y) + for _, t in ipairs(self.copyPrefix) do + love.graphics.draw(self.copyright, self.copyQuads[t], x, y) + x = x + 8 + end + if self.nineImg then + love.graphics.draw(self.nineImg, x, y) + end +end + function IntroMovie:drawCopyright() if self.studio.card or self.studio.credit then local card = self.studio.card or "" @@ -378,7 +400,7 @@ function IntroMovie:drawCopyright() x = x + 8 end end - for _, y in ipairs({ 56, 72, 88 }) do row(COPY_PREFIX, 16, y) end + for _, y in ipairs({ 56, 72, 88 }) do self:drawCopyPrefix(16, y) end row(COPY_NINTENDO, 80, 56) row(COPY_CREATURES, 80, 72) love.graphics.draw(self.gfInc, 80, 88) diff --git a/src/ui/TitleState.lua b/src/ui/TitleState.lua index 0c366afd..f628c1db 100644 --- a/src/ui/TitleState.lua +++ b/src/ui/TitleState.lua @@ -226,6 +226,11 @@ function TitleState.new(game, opts) self.blue = GameVersion.isBlue() self.yellow = GameVersion.isYellow() or title.layout == "yellow_pikachu" + -- ..(pokeyellow engine/movie/title.asm .tileScreenCopyrightTiles / NineTile) + self.nineImg = self.yellow and tryImage(imagePath(title.nine) + or "assets/generated/title/nine.png") or nil + self.copyPrefix = self.yellow + and { 0, 1, 2, 3, 1, 2 } or { 0, 1, 2, 1, 3, 1, 4 } -- Yellow title is a fixed Pikachu composition (title_yellow.asm), not -- TitleMons cycling. Prefer composed pikachu.png from the Yellow import. self.yellowPikachu = self.yellow and tryImage(imagePath(title.pikachu) @@ -665,16 +670,17 @@ function TitleState:draw() self:drawCopyright(136 + (preRibbon and 0 or scrollY)) end --- ..(engine/movie/title.asm ln 117) -local COPY_PREFIX = { 0, 1, 2, 1, 3, 1, 4 } - function TitleState:drawCopyright(y) if not self.title.copyrightText and self.copyImg and self.gfInc then local x = 16 - for _, t in ipairs(COPY_PREFIX) do + for _, t in ipairs(self.copyPrefix) do love.graphics.draw(self.copyImg, self.copyQuads[t], x, y) x = x + 8 end + if self.nineImg then + love.graphics.draw(self.nineImg, x, y) + x = x + 8 + end love.graphics.draw(self.gfInc, x, y) return end diff --git a/tests/engine/nx_yellow_boot_test.lua b/tests/engine/nx_yellow_boot_test.lua index 61d903e7..a0ac0bf5 100644 --- a/tests/engine/nx_yellow_boot_test.lua +++ b/tests/engine/nx_yellow_boot_test.lua @@ -37,7 +37,8 @@ end local Y_TITLE = { "pikachu.png", "pika_bubble.png", "eyes_half.png", "eyes_closed.png", - "player.png", "copyright.png", "gamefreak_inc.png", "yellow_version.png", + "player.png", "copyright.png", "gamefreak_inc.png", "nine.png", + "yellow_version.png", } for _, name in ipairs(Y_TITLE) do seed("yellow/assets/generated/title/" .. name) @@ -207,6 +208,11 @@ check(pre ~= nil, "the IntroMovie pre-roll was constructed") eq(pre and pre.copyright and pre.copyright.path, "yellow/assets/generated/title/copyright.png", "copyright card resolves to the yellow/ copy") +eq(pre and pre.nineImg and pre.nineImg.path, + "yellow/assets/generated/title/nine.png", + "copyright NineTile resolves to the yellow/ copy") +check(pre and pre.yellowCopy == true, + "IntroMovie selects the Yellow (c)1995-1999 tile sequence") eq(pre and pre.studioLogo and pre.studioLogo.path, "yellow/assets/generated/intro/studio_logo.png", "data-driven studio logo resolves to the yellow/ copy") diff --git a/tools/build_rom_data.py b/tools/build_rom_data.py index c4020d30..8c4a9325 100755 --- a/tools/build_rom_data.py +++ b/tools/build_rom_data.py @@ -1747,6 +1747,17 @@ def extract_field(rom, symbols, manifest, out_dir, assets_dir): "title/copyright.png") raw_2bpp( "GameFreakLogoGraphics", 72, 8, "title/gamefreak_inc.png") + # Yellow NineTile (pokeyellow gfx/font.asm): final "9" of (c)1995-1999, + # parked in the 16 bytes between GameFreakLogoGraphics and TextBoxGraphics. + if _has_symbol(symbols, "GameFreakLogoGraphics") \ + and _has_symbol(symbols, "TextBoxGraphics"): + gf = _symbol(symbols, "GameFreakLogoGraphics") + tb = _symbol(symbols, "TextBoxGraphics") + if tb.address == gf.address + 9 * 16 + 16: + nine_raw = rom.bytes(gf.bank, gf.address + 9 * 16, 16) + _save_png( + _decode_2bpp(nine_raw, 8, 8, False), + os.path.join(assets_dir, "title/nine.png")) # Yellow fixed Pikachu title (pret/pokeyellow title_yellow.asm): tilemap # composition over both tile banks -- PokemonLogoGraphics in vChars2 diff --git a/tools/extract/field.py b/tools/extract/field.py index 36740642..839be555 100644 --- a/tools/extract/field.py +++ b/tools/extract/field.py @@ -1471,9 +1471,11 @@ def extract(pokered, out_dir): util.die("bike riding tileset extraction sanity check failed") if indoor_encounters["firstIndoorMap"] != 0x25: util.die("indoor encounter boundary sanity check failed") - if len(title) != 5 or any(not v["width"] for v in title.values()) \ + if len(title) not in (5, 6) or any(not v["width"] for v in title.values()) \ or (title["gamefreakInc"]["width"], - title["gamefreakInc"]["height"]) != (72, 8): + title["gamefreakInc"]["height"]) != (72, 8) \ + or ("nine" in title and (title["nine"]["width"], + title["nine"]["height"]) != (8, 8)): util.die("title asset extraction sanity check failed") if any((intro["gengar"][f]["width"], intro["gengar"][f]["height"]) != (56, 56) for f in ("frame1", "frame2", "frame3")) \ diff --git a/tools/extract/gfx.py b/tools/extract/gfx.py index 6a75483b..4dcbbf5b 100644 --- a/tools/extract/gfx.py +++ b/tools/extract/gfx.py @@ -130,6 +130,12 @@ TITLE_GRAPHICS = [ ("gamefreakInc", "gfx/title/gamefreak_inc.png", False), ] +# Yellow only (pokeyellow gfx/font.asm NineTile): final "9" of (c)1995-1999 +# on the title copyright line and intro/credits copyright card. +OPTIONAL_TITLE_GRAPHICS = [ + ("nine", "gfx/title/nine.png", False), +] + def extract_title(pokered, assets_dir): """Convert the title-screen graphics to assets/generated/title/. @@ -150,6 +156,19 @@ def extract_title(pokered, assets_dir): "height": size[1], "source": src_rel, } + for key, src_rel, matte in OPTIONAL_TITLE_GRAPHICS: + src = os.path.join(pokered, src_rel) + if not os.path.isfile(src): + continue + base = os.path.basename(src_rel) + size = convert_png(src, os.path.join(assets_dir, "title", base), + transparent_matte=matte) + out[key] = { + "path": f"assets/generated/title/{base}", + "width": size[0], + "height": size[1], + "source": src_rel, + } return out