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.
This commit is contained in:
1jamie
2026-08-20 20:40:37 -05:00
parent 5714555847
commit 780246c4f6
4 changed files with 230 additions and 19 deletions
+20
View File
@@ -125,6 +125,26 @@ function ImageWriter.columnsToRows(raw, tilesWide, tilesHigh, bytesPerTile)
return out return out
end 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) function ImageWriter.save(image, path)
local ok, fileData = pcall(image.encode, image, "png") local ok, fileData = pcall(image.encode, image, "png")
if not ok then error("could not encode " .. path .. ": " .. tostring(fileData)) end if not ok then error("could not encode " .. path .. ": " .. tostring(fileData)) end
+130 -19
View File
@@ -5272,36 +5272,146 @@ function RomExtractorGen2:extractMenuGfx()
end end
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 local slots = nil
if self.symbols["Slots1LZ"] then if self.symbols["Slots1LZ"] then
-- --trim-whitespace drops the final empty tile (37 of 38).
local raw1 = self:decompressLz3Symbol("Slots1LZ") 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 = slots or {}
slots.sheet1 = "assets/generated/slots/gold_slots_1.png" slots.sheet1 = "assets/generated/slots/gold_slots_1.png"
end end
if self.symbols["Slots2LZ"] then if self.symbols["Slots2LZ"] then
local raw2 = self:decompressLz3Symbol("Slots2LZ") local raw2 = ImageWriter.deinterleave(
-- In Pokemon Gold ROM, Seven symbol (first 4 tiles = 64 bytes) has inverted bit polarity self:decompressLz3Symbol("Slots2LZ"), SLOTS2_W)
-- Commercial Gold stores the Seven symbol with inverted bit polarity.
for i = 1, math.min(64, #raw2) do for i = 1, math.min(64, #raw2) do
raw2[i] = bit.band(bit.bnot(raw2[i]), 0xFF) raw2[i] = bit.band(bit.bnot(raw2[i]), 0xFF)
end 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 = slots or {}
slots.sheet2 = "assets/generated/slots/gold_slots_2.png" slots.sheet2 = "assets/generated/slots/gold_slots_2.png"
end end
if self.symbols["Slots3LZ"] then if self.symbols["Slots3LZ"] then
local raw3 = self:decompressLz3Symbol("Slots3LZ") local raw3 = self:decompressLz3Symbol("Slots3LZ")
self:write2bpp(raw3, 24, #raw3 / 6, "slots/gold_slots_3.png", true) local actors = composeSlotsActors(raw3)
-- Slots3LZ is a 24px-wide (3 tiles), 240px-tall (30 tiles) sprite sheet containing: self:save(actors, "slots/gold_slots_3.png")
-- Y=0: Golem 1 (Standing, 24x32) self:save(actors, "slots/gold_slots_actors.png")
-- 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)
slots = slots or {} slots = slots or {}
slots.sheet3 = "assets/generated/slots/gold_slots_3.png" slots.sheet3 = "assets/generated/slots/gold_slots_3.png"
end end
@@ -5317,20 +5427,21 @@ function RomExtractorGen2:extractMenuGfx()
-- Goldenrod Game Corner: Card Flip graphics assets -- Goldenrod Game Corner: Card Flip graphics assets
local cardFlip = nil local cardFlip = nil
if self.symbols["CardFlipLZ01"] then if self.symbols["CardFlipLZ01"] then
-- --trim-whitespace: 62 of 64 tiles in the ROM stream.
local raw1 = self:decompressLz3Symbol("CardFlipLZ01") 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 = cardFlip or {}
cardFlip.sheet1 = "assets/generated/card_flip/card_flip_1.png" cardFlip.sheet1 = "assets/generated/card_flip/card_flip_1.png"
end end
if self.symbols["CardFlipLZ02"] then if self.symbols["CardFlipLZ02"] then
local raw2 = self:decompressLz3Symbol("CardFlipLZ02") local raw2 = expandCardFlip2(self:decompressLz3Symbol("CardFlipLZ02"))
self:write2bpp(raw2, 24, #raw2 / 6, "card_flip/card_flip_2.png") writeSheet(raw2, CARD2_W, CARD2_H, "card_flip/card_flip_2.png")
cardFlip = cardFlip or {} cardFlip = cardFlip or {}
cardFlip.sheet2 = "assets/generated/card_flip/card_flip_2.png" cardFlip.sheet2 = "assets/generated/card_flip/card_flip_2.png"
end end
if self.symbols["CardFlipLZ03"] then if self.symbols["CardFlipLZ03"] then
local raw3 = self:decompressLz3Symbol("CardFlipLZ03") 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 = cardFlip or {}
cardFlip.sheet3 = "assets/generated/card_flip/card_flip_3.png" cardFlip.sheet3 = "assets/generated/card_flip/card_flip_3.png"
end end
+40
View File
@@ -17667,6 +17667,46 @@
"wBaseUnusedFrontpic": [ "wBaseUnusedFrontpic": [
1, 1,
53554 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": { "tilesets": {
+40
View File
@@ -17667,6 +17667,46 @@
"wBaseUnusedFrontpic": [ "wBaseUnusedFrontpic": [
1, 1,
53554 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": { "tilesets": {