From 780246c4f6626f09914dd5b7249481429a8798e3 Mon Sep 17 00:00:00 2001 From: 1jamie Date: Thu, 20 Aug 2026 20:40:37 -0500 Subject: [PATCH] feat: add deinterleave function and enhance graphics extraction for Slots and Card Flip Implemented a new deinterleave function in ImageWriter to restore row-major order for PNGs. Updated RomExtractorGen2 to utilize this function for extracting Slot Machine graphics, and added new functions for padding and writing graphics sheets. Updated the ROM manifests to include necessary symbols for Slots and Card Flip assets. --- src/import/ImageWriter.lua | 20 +++++ src/import/RomExtractorGen2.lua | 149 ++++++++++++++++++++++++++++---- tools/rom_manifest_gold.json | 40 +++++++++ tools/rom_manifest_silver.json | 40 +++++++++ 4 files changed, 230 insertions(+), 19 deletions(-) diff --git a/src/import/ImageWriter.lua b/src/import/ImageWriter.lua index 9ab64fc7..3b57e347 100644 --- a/src/import/ImageWriter.lua +++ b/src/import/ImageWriter.lua @@ -125,6 +125,26 @@ function ImageWriter.columnsToRows(raw, tilesWide, tilesHigh, bytesPerTile) return out end +-- Inverse of pret tools/gfx --interleave (pokecrystal tools/gfx.c). +-- Build-time interleave stores each vertical 8x16 pair as consecutive 8x8 +-- tiles for OBJ mode; this restores row-major sheet order for PNGs. +function ImageWriter.deinterleave(raw, width, bytesPerTile) + bytesPerTile = bytesPerTile or 16 + local widthTiles = width / 8 + local numTiles = #raw / bytesPerTile + local out = {} + for i = 0, numTiles - 1 do + local row = math.floor(i / widthTiles) + local src = i * 2 - (row % 2 == 1 + and widthTiles * (row + 1) - 1 + or widthTiles * row) + for offset = 1, bytesPerTile do + out[i * bytesPerTile + offset] = raw[src * bytesPerTile + offset] + end + end + return out +end + function ImageWriter.save(image, path) local ok, fileData = pcall(image.encode, image, "png") if not ok then error("could not encode " .. path .. ": " .. tostring(fileData)) end diff --git a/src/import/RomExtractorGen2.lua b/src/import/RomExtractorGen2.lua index a044833c..d2ff2435 100644 --- a/src/import/RomExtractorGen2.lua +++ b/src/import/RomExtractorGen2.lua @@ -5272,36 +5272,146 @@ function RomExtractorGen2:extractMenuGfx() end end + -- Canonical sheet sizes match the cart art the UI indexes (and pret's + -- gfx/slots + gfx/card_flip PNGs). ROM LZ streams are those sheets after + -- Makefile gfx transforms; reverse what the decompressed bytes still carry. + local SLOTS1_W, SLOTS1_H = 16, 152 + local SLOTS2_W, SLOTS2_H = 16, 256 + local SLOTS3_W, SLOTS3_H = 24, 240 + local CARD1_W, CARD1_H = 128, 32 + local CARD2_W, CARD2_H = 24, 160 + local CARD3_W, CARD3_H = 8, 56 + + local function pad2bpp(raw, width, height) + local need = width * height / 4 + while #raw < need do raw[#raw + 1] = 0 end + while #raw > need do table.remove(raw) end + return raw + end + + local function writeSheet(raw, width, height, relative, transparent) + self:write2bpp(pad2bpp(raw, width, height), width, height, relative, + transparent) + end + + -- Slots3LZ is unique 8x16 OBJ columns (interleave + remove-duplicates + + -- remove-xflip). Rebuild the 24x240 actor sheet the UI quads expect from + -- OAMData_SlotsGolem / Chansey* / Egg (data/sprite_anims/oam.asm), same + -- pattern as title-screen Ho-Oh frame composition above. + local function composeSlotsActors(raw) + local tileCount = math.floor(#raw / 16) + local tiles = {} + for index = 0, tileCount - 1 do + local one = {} + for b = 1, 16 do one[b] = raw[index * 16 + b] or 0 end + tiles[index] = ImageWriter.decode2bpp(one, 8, 8, true) + end + local sheet = ImageWriter.blank(SLOTS3_W, SLOTS3_H, 1, 1, 1, 0) + local function blit8x16(tileId, dx, dy, flipX) + local top, bot = tiles[tileId], tiles[tileId + 1] + if not (top and bot) then return end + ImageWriter.blit(sheet, top, dx, dy, 0, 0, 8, 8, flipX) + ImageWriter.blit(sheet, bot, dx, dy + 8, 0, 0, 8, 8, flipX) + end + local function blitPose(poseY, base, entries) + for _, e in ipairs(entries) do + blit8x16(base + e.t, (e.x + 2) * 8, poseY + (e.y + 2) * 8, e.xf) + end + end + local golem = { + { x = -2, y = -2, t = 0x00 }, { x = -1, y = -2, t = 0x02 }, + { x = 0, y = -2, t = 0x00, xf = true }, + { x = -2, y = 0, t = 0x04 }, { x = -1, y = 0, t = 0x06 }, + { x = 0, y = 0, t = 0x04, xf = true }, + } + local chansey = { + { + { x = -2, y = -2, t = 0x00 }, { x = -1, y = -2, t = 0x02 }, + { x = 0, y = -2, t = 0x04 }, + { x = -2, y = 0, t = 0x06 }, { x = -1, y = 0, t = 0x08 }, + { x = 0, y = 0, t = 0x0a }, + }, + { + { x = -2, y = -2, t = 0x00 }, { x = -1, y = -2, t = 0x02 }, + { x = 0, y = -2, t = 0x04 }, + { x = -2, y = 0, t = 0x0c }, { x = -1, y = 0, t = 0x0e }, + { x = 0, y = 0, t = 0x10 }, + }, + { + { x = -2, y = -2, t = 0x00 }, { x = -1, y = -2, t = 0x02 }, + { x = 0, y = -2, t = 0x04 }, + { x = -2, y = 0, t = 0x12 }, { x = -1, y = 0, t = 0x14 }, + { x = 0, y = 0, t = 0x16 }, + }, + { + { x = -2, y = -2, t = 0x00 }, { x = -1, y = -2, t = 0x02 }, + { x = 0, y = -2, t = 0x04 }, + { x = -2, y = 0, t = 0x18 }, { x = -1, y = 0, t = 0x1a }, + { x = 0, y = 0, t = 0x1c }, + }, + { + { x = -2, y = -2, t = 0x1e }, { x = -1, y = -2, t = 0x20 }, + { x = 0, y = -2, t = 0x22 }, + { x = -2, y = 0, t = 0x24 }, { x = -1, y = 0, t = 0x26 }, + { x = 0, y = 0, t = 0x28 }, + }, + } + blitPose(0, 0x00, golem) + blitPose(32, 0x08, golem) + for index, frame in ipairs(chansey) do + blitPose(32 + index * 32, 0x10, frame) + end + blit8x16(0x3a, 0, 224, false) + return sheet + end + + -- card_flip_2.2bpp uses --remove-whitespace: blank tiles in column 2 of the + -- 3-wide header strip (indices 2,5,...,23) are dropped from the ROM stream. + -- Re-insert them so HEADER_TILE_MAP / MON_ANCHORS (pret sheet indices) work. + local function expandCardFlip2(compact) + local need = CARD2_W * CARD2_H / 4 + local out = {} + for i = 1, need do out[i] = 0 end + local whitespace = { + [2] = true, [5] = true, [8] = true, [11] = true, + [14] = true, [17] = true, [20] = true, [23] = true, + } + local src = 0 + for tile = 0, 59 do + if not whitespace[tile] then + for b = 1, 16 do + out[tile * 16 + b] = compact[src * 16 + b] or 0 + end + src = src + 1 + end + end + return out + end + local slots = nil if self.symbols["Slots1LZ"] then + -- --trim-whitespace drops the final empty tile (37 of 38). local raw1 = self:decompressLz3Symbol("Slots1LZ") - self:write2bpp(raw1, 16, #raw1 / 4, "slots/gold_slots_1.png") + writeSheet(raw1, SLOTS1_W, SLOTS1_H, "slots/gold_slots_1.png") slots = slots or {} slots.sheet1 = "assets/generated/slots/gold_slots_1.png" end if self.symbols["Slots2LZ"] then - local raw2 = self:decompressLz3Symbol("Slots2LZ") - -- In Pokemon Gold ROM, Seven symbol (first 4 tiles = 64 bytes) has inverted bit polarity + local raw2 = ImageWriter.deinterleave( + self:decompressLz3Symbol("Slots2LZ"), SLOTS2_W) + -- Commercial Gold stores the Seven symbol with inverted bit polarity. for i = 1, math.min(64, #raw2) do raw2[i] = bit.band(bit.bnot(raw2[i]), 0xFF) end - self:write2bpp(raw2, 16, #raw2 / 4, "slots/gold_slots_2.png") + writeSheet(raw2, SLOTS2_W, SLOTS2_H, "slots/gold_slots_2.png") slots = slots or {} slots.sheet2 = "assets/generated/slots/gold_slots_2.png" end if self.symbols["Slots3LZ"] then local raw3 = self:decompressLz3Symbol("Slots3LZ") - self:write2bpp(raw3, 24, #raw3 / 6, "slots/gold_slots_3.png", true) - -- Slots3LZ is a 24px-wide (3 tiles), 240px-tall (30 tiles) sprite sheet containing: - -- Y=0: Golem 1 (Standing, 24x32) - -- Y=32: Golem 2 (Ball, 24x32) - -- Y=64: Chansey 1 (Standing / Step 1, 24x32) - -- Y=96: Chansey 2 (Step 2, 24x32) - -- Y=128: Chansey 3 (Step 3, 24x32) - -- Y=160: Chansey 4 (Arm raised / Step 4, 24x32) - -- Y=192: Chansey 5 (Egg Drop pose, 24x32) - -- Y=224: Egg (8x16 at X=0) - self:write2bpp(raw3, 24, #raw3 / 6, "slots/gold_slots_actors.png", true) + local actors = composeSlotsActors(raw3) + self:save(actors, "slots/gold_slots_3.png") + self:save(actors, "slots/gold_slots_actors.png") slots = slots or {} slots.sheet3 = "assets/generated/slots/gold_slots_3.png" end @@ -5317,20 +5427,21 @@ function RomExtractorGen2:extractMenuGfx() -- Goldenrod Game Corner: Card Flip graphics assets local cardFlip = nil if self.symbols["CardFlipLZ01"] then + -- --trim-whitespace: 62 of 64 tiles in the ROM stream. local raw1 = self:decompressLz3Symbol("CardFlipLZ01") - self:write2bpp(raw1, 128, #raw1 / 32, "card_flip/card_flip_1.png") + writeSheet(raw1, CARD1_W, CARD1_H, "card_flip/card_flip_1.png") cardFlip = cardFlip or {} cardFlip.sheet1 = "assets/generated/card_flip/card_flip_1.png" end if self.symbols["CardFlipLZ02"] then - local raw2 = self:decompressLz3Symbol("CardFlipLZ02") - self:write2bpp(raw2, 24, #raw2 / 6, "card_flip/card_flip_2.png") + local raw2 = expandCardFlip2(self:decompressLz3Symbol("CardFlipLZ02")) + writeSheet(raw2, CARD2_W, CARD2_H, "card_flip/card_flip_2.png") cardFlip = cardFlip or {} cardFlip.sheet2 = "assets/generated/card_flip/card_flip_2.png" end if self.symbols["CardFlipLZ03"] then local raw3 = self:decompressLz3Symbol("CardFlipLZ03") - self:write2bpp(raw3, 8, #raw3 / 2, "card_flip/card_flip_3.png") + writeSheet(raw3, CARD3_W, CARD3_H, "card_flip/card_flip_3.png") cardFlip = cardFlip or {} cardFlip.sheet3 = "assets/generated/card_flip/card_flip_3.png" end diff --git a/tools/rom_manifest_gold.json b/tools/rom_manifest_gold.json index 2a54f118..8e191e63 100644 --- a/tools/rom_manifest_gold.json +++ b/tools/rom_manifest_gold.json @@ -17667,6 +17667,46 @@ "wBaseUnusedFrontpic": [ 1, 53554 + ], + "Slots1LZ": [ + 36, + 31138 + ], + "Slots2LZ": [ + 36, + 31522 + ], + "Slots3LZ": [ + 36, + 32130 + ], + "SlotsTilemap": [ + 36, + 30898 + ], + "CardFlipLZ01": [ + 56, + 21795 + ], + "CardFlipLZ02": [ + 56, + 22197 + ], + "CardFlipLZ03": [ + 56, + 21736 + ], + "CardFlipOnButtonGFX": [ + 56, + 21779 + ], + "CardFlipOffButtonGFX": [ + 56, + 21763 + ], + "CardFlipTilemap": [ + 56, + 22809 ] }, "tilesets": { diff --git a/tools/rom_manifest_silver.json b/tools/rom_manifest_silver.json index 2992385f..ff9b91d4 100644 --- a/tools/rom_manifest_silver.json +++ b/tools/rom_manifest_silver.json @@ -17667,6 +17667,46 @@ "wBaseUnusedFrontpic": [ 1, 53554 + ], + "Slots1LZ": [ + 36, + 31138 + ], + "Slots2LZ": [ + 36, + 31522 + ], + "Slots3LZ": [ + 36, + 32130 + ], + "SlotsTilemap": [ + 36, + 30898 + ], + "CardFlipLZ01": [ + 56, + 21795 + ], + "CardFlipLZ02": [ + 56, + 22197 + ], + "CardFlipLZ03": [ + 56, + 21736 + ], + "CardFlipOnButtonGFX": [ + 56, + 21779 + ], + "CardFlipOffButtonGFX": [ + 56, + 21763 + ], + "CardFlipTilemap": [ + 56, + 22809 ] }, "tilesets": {