mirror of
https://github.com/Xeeynamo/sotn-decomp.git
synced 2024-11-23 13:09:44 +00:00
Extract WRP stage graphics
This commit is contained in:
parent
ddd2a91696
commit
a0050b3c1e
6
Makefile
6
Makefile
@ -56,6 +56,7 @@ GO := $(TOOLS_DIR)/go/bin/go
|
||||
GOPATH := $(HOME)/go
|
||||
ASPATCH := $(GOPATH)/bin/aspatch
|
||||
SOTNDISK := $(GOPATH)/bin/sotn-disk
|
||||
GFXSTAGE := $(PYTHON) $(TOOLS_DIR)/gfxstage.py
|
||||
|
||||
define list_src_files
|
||||
$(foreach dir,$(ASM_DIR)/$(1),$(wildcard $(dir)/**.s))
|
||||
@ -158,9 +159,11 @@ st0: stst0_dirs $(BUILD_DIR)/ST0.BIN
|
||||
$(BUILD_DIR)/ST0.BIN: $(BUILD_DIR)/stst0.elf
|
||||
$(OBJCOPY) -O binary $< $@
|
||||
|
||||
wrp: stwrp_dirs $(BUILD_DIR)/WRP.BIN
|
||||
wrp: stwrp_dirs $(BUILD_DIR)/WRP.BIN $(BUILD_DIR)/F_WRP.BIN
|
||||
$(BUILD_DIR)/WRP.BIN: $(BUILD_DIR)/stwrp.elf
|
||||
$(OBJCOPY) -O binary $< $@
|
||||
$(BUILD_DIR)/F_WRP.BIN:
|
||||
$(GFXSTAGE) e assets/st/wrp $@
|
||||
|
||||
rwrp: strwrp_dirs $(BUILD_DIR)/RWRP.BIN
|
||||
$(BUILD_DIR)/RWRP.BIN: $(BUILD_DIR)/strwrp.elf
|
||||
@ -219,6 +222,7 @@ extract_stmad: $(SPLAT_APP)
|
||||
extract_st%: $(SPLAT_APP)
|
||||
cat $(CONFIG_DIR)/symbols.$(VERSION).txt $(CONFIG_DIR)/symbols.$(VERSION).st$*.txt > $(CONFIG_DIR)/generated.symbols.$(VERSION).st$*.txt
|
||||
$(SPLAT) $(CONFIG_DIR)/splat.$(VERSION).st$*.yaml
|
||||
$(GFXSTAGE) d disks/$(VERSION)/ST/$$(echo '$*' | tr '[:lower:]' '[:upper:]')/F_$$(echo '$*' | tr '[:lower:]' '[:upper:]').BIN $(ASSETS_DIR)/st/$*
|
||||
extract_tt_%: $(SPLAT_APP)
|
||||
cat $(CONFIG_DIR)/symbols.$(VERSION).txt $(CONFIG_DIR)/symbols.$(VERSION).tt_$*.txt > $(CONFIG_DIR)/generated.symbols.$(VERSION).tt_$*.txt
|
||||
$(SPLAT) $(CONFIG_DIR)/splat.$(VERSION).tt_$*.yaml
|
||||
|
@ -10,5 +10,6 @@ b10b9c2be721cf9cbed3aa94be468ba9e23bc68b build/us/NZ0.BIN
|
||||
a919a53a760107972049bfdeadde33376428eace build/us/SEL.BIN
|
||||
bc2fabbe5ef0d1288490b6f1ddbf11092a2c0c57 build/us/ST0.BIN
|
||||
2ae313f4e394422e4c5f37a2d8e976e92f9e3cda build/us/WRP.BIN
|
||||
c1284f0c662f3c8fdb8a7c17cebf8e5cf1d96c1b build/us/F_WRP.BIN
|
||||
3bbdd3b73f8f86cf5f6c88652e9e6452a7fb5992 build/us/RWRP.BIN
|
||||
82dd4ae1c4e3dc0fd483a49e5e4ab4fc5e25ada7 build/us/TT_000.BIN
|
214
tools/gfxstage.py
Executable file
214
tools/gfxstage.py
Executable file
@ -0,0 +1,214 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import argparse
|
||||
from typing import List, Tuple
|
||||
import n64img.image
|
||||
import os
|
||||
import png
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def ensure_dir_exists(file_path):
|
||||
path = Path(os.path.dirname(file_path))
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def encode(input_base: str, output_file: str) -> str | None:
|
||||
|
||||
def unroll_rows(rows):
|
||||
list_of_rows = []
|
||||
for row in rows:
|
||||
list_of_rows.append(row)
|
||||
return list_of_rows
|
||||
|
||||
def encode_quadrant(dst: bytearray, start: int, rows, quad: int):
|
||||
assert (quad >= 0 and quad < 4)
|
||||
start_row = 128 if (quad & 2) == 2 else 0
|
||||
start_col = 128 if (quad & 1) == 1 else 0
|
||||
|
||||
y = 0
|
||||
for row in rows[start_row:]:
|
||||
row = row[start_col:]
|
||||
for x in range(0, 64):
|
||||
c = (row[x * 2 + 0] & 0xF) | ((row[x * 2 + 1] & 0xF) << 4)
|
||||
dst[start + y * 0x40 + x] = c
|
||||
y += 1
|
||||
|
||||
def encode_image(dst: bytearray, idx: int, file_name: str) -> str | None:
|
||||
img = png.Reader(file_name).read()
|
||||
width = img[0]
|
||||
height = img[1]
|
||||
rows = img[2]
|
||||
info = img[3]
|
||||
palette = info["palette"]
|
||||
if width != 256:
|
||||
return f"'{file_name}' width must be 256 but found {width} instead"
|
||||
if height != 240 and height != 256:
|
||||
return f"'{file_name}' width must be 240 or 256 but found {height} instead"
|
||||
if info["planes"] != 1 or info["bitdepth"] != 8:
|
||||
return f"'{file_name}' must be an indexed image"
|
||||
if len(palette) != 16:
|
||||
return f"'{file_name}' palette must be of 16 colors but found {len(palette)} instead"
|
||||
|
||||
row_list = unroll_rows(rows)
|
||||
encode_quadrant(dst, (idx * 4 + 0) * 0x2000, row_list, 0)
|
||||
encode_quadrant(dst, (idx * 4 + 1) * 0x2000, row_list, 1)
|
||||
encode_quadrant(dst, (idx * 4 + 2) * 0x2000, row_list, 2)
|
||||
encode_quadrant(dst, (idx * 4 + 3) * 0x2000, row_list, 3)
|
||||
return None
|
||||
|
||||
def encode_pal(dst, idx, palette):
|
||||
for i in range(0, 16):
|
||||
r = palette[i * 4 + 0]
|
||||
g = palette[i * 4 + 1]
|
||||
b = palette[i * 4 + 2]
|
||||
a = 0 if palette[i * 4 + 3] >= 64 else 0x8000
|
||||
c = (r >> 3) | ((g >> 3) << 5) | ((b >> 3) << 10) | a
|
||||
dst[idx + i * 2 + 0] = c & 0xFF
|
||||
dst[idx + i * 2 + 1] = (c >> 8) & 0xFF
|
||||
|
||||
def encode_clut(dst: bytearray, file_name: str) -> str | None:
|
||||
img = png.Reader(file_name).read()
|
||||
width = img[0]
|
||||
height = img[1]
|
||||
rows = img[2]
|
||||
info = img[3]
|
||||
if width != 16:
|
||||
return f"'{file_name}' width must be 16 but found {width} instead"
|
||||
if height != 256:
|
||||
return f"'{file_name}' width must be 256 but found {height} instead"
|
||||
if info["planes"] != 4 or info["bitdepth"] != 8:
|
||||
return f"'{file_name}' must be a 32-bit RGBA image"
|
||||
|
||||
row_list = unroll_rows(rows)
|
||||
for i in range(0, 16):
|
||||
encode_pal(dst, 0x8000*0+0x5C00+0x20*0+0x40*i, row_list[i*16+0x0])
|
||||
encode_pal(dst, 0x8000*0+0x5C00+0x20*1+0x40*i, row_list[i*16+0x1])
|
||||
encode_pal(dst, 0x8000*0+0x7C00+0x20*0+0x40*i, row_list[i*16+0x2])
|
||||
encode_pal(dst, 0x8000*0+0x7C00+0x20*1+0x40*i, row_list[i*16+0x3])
|
||||
encode_pal(dst, 0x8000*1+0x5C00+0x20*0+0x40*i, row_list[i*16+0x4])
|
||||
encode_pal(dst, 0x8000*1+0x5C00+0x20*1+0x40*i, row_list[i*16+0x5])
|
||||
encode_pal(dst, 0x8000*1+0x7C00+0x20*0+0x40*i, row_list[i*16+0x6])
|
||||
encode_pal(dst, 0x8000*1+0x7C00+0x20*1+0x40*i, row_list[i*16+0x7])
|
||||
encode_pal(dst, 0x8000*2+0x5C00+0x20*0+0x40*i, row_list[i*16+0x8])
|
||||
encode_pal(dst, 0x8000*2+0x5C00+0x20*1+0x40*i, row_list[i*16+0x9])
|
||||
encode_pal(dst, 0x8000*2+0x7C00+0x20*0+0x40*i, row_list[i*16+0xA])
|
||||
encode_pal(dst, 0x8000*2+0x7C00+0x20*1+0x40*i, row_list[i*16+0xB])
|
||||
encode_pal(dst, 0x8000*3+0x5C00+0x20*0+0x40*i, row_list[i*16+0xC])
|
||||
encode_pal(dst, 0x8000*3+0x5C00+0x20*1+0x40*i, row_list[i*16+0xD])
|
||||
encode_pal(dst, 0x8000*3+0x7C00+0x20*0+0x40*i, row_list[i*16+0xE])
|
||||
encode_pal(dst, 0x8000*3+0x7C00+0x20*1+0x40*i, row_list[i*16+0xF])
|
||||
|
||||
return None
|
||||
|
||||
data = bytearray(0x40000)
|
||||
for i in range(0, 8):
|
||||
err = encode_image(data, i, f"{input_base}_{i}.png")
|
||||
if err != None:
|
||||
return err
|
||||
|
||||
err = encode_clut(data, f"{input_base}_clut.png")
|
||||
if err != None:
|
||||
return err
|
||||
|
||||
with open(output_file, "wb") as f_out:
|
||||
f_out.write(data)
|
||||
return None
|
||||
|
||||
|
||||
def decode(input_file: str, output_base: str):
|
||||
def copy_quadrant(dst: bytearray, src: bytes, h: int, src_idx: int, dst_quad: int):
|
||||
src = src[src_idx * 128 * 64:][: 128*64]
|
||||
dst_idx = (dst_quad & 1) * 64 + int(dst_quad / 2) * 128 * 128
|
||||
for y in range(0, h):
|
||||
for x in range(0, 64):
|
||||
ch = src[y*0x40+x]
|
||||
dst[dst_idx + y * 128 + x] = ((ch & 0xF) << 4) | (ch >> 4)
|
||||
|
||||
def generate_grey_palette() -> List[Tuple[int, int, int, int]]:
|
||||
def generate_grey_color(intensity: int) -> Tuple[int, int, int, int]:
|
||||
return intensity, intensity, intensity, 255
|
||||
return [
|
||||
generate_grey_color(0x00), generate_grey_color(0x11),
|
||||
generate_grey_color(0x22), generate_grey_color(0x33),
|
||||
generate_grey_color(0x44), generate_grey_color(0x55),
|
||||
generate_grey_color(0x66), generate_grey_color(0x77),
|
||||
generate_grey_color(0x88), generate_grey_color(0x99),
|
||||
generate_grey_color(0xAA), generate_grey_color(0xBB),
|
||||
generate_grey_color(0xCC), generate_grey_color(0xDD),
|
||||
generate_grey_color(0xEE), generate_grey_color(0xFF),
|
||||
]
|
||||
|
||||
def generate_tileset(output_file: str, src: bytes, idx: int, has_clut: bool):
|
||||
img: n64img.image.Image = n64img.image.CI4(None, 0, 0)
|
||||
img.width = 256
|
||||
img.height = 240 if has_clut else 256
|
||||
img.palette = generate_grey_palette()
|
||||
img.data = bytearray(img.size())
|
||||
hhh = 112 if has_clut else 128
|
||||
copy_quadrant(img.data, src, 128, idx * 4 + 0, 0)
|
||||
copy_quadrant(img.data, src, 128, idx * 4 + 1, 1)
|
||||
copy_quadrant(img.data, src, hhh, idx * 4 + 2, 2)
|
||||
copy_quadrant(img.data, src, hhh, idx * 4 + 3, 3)
|
||||
ensure_dir_exists(output_file)
|
||||
img.write(output_file)
|
||||
|
||||
def copy_pal(dst: bytearray, pal_idx: int, src: bytes):
|
||||
start = pal_idx * 32
|
||||
for i in range(0, 16):
|
||||
c = src[i * 2 + 0] | (src[i * 2 + 1] << 8)
|
||||
c = ((c & 0x1F) << 11) | ((c & 0x3E0) << 1) | (
|
||||
(c & 0x7C00) >> 9) | ((c ^ 0x8000) >> 15)
|
||||
dst[start + i * 2 + 1] = c & 0xFF
|
||||
dst[start + i * 2 + 0] = (c >> 8) & 0xFF
|
||||
|
||||
def generate_clut(output_file: str, src: bytes):
|
||||
img: n64img.image.Image = n64img.image.RGBA16(None, 16, 256)
|
||||
img.data = bytearray(img.size())
|
||||
for i in range(0, 16):
|
||||
copy_pal(img.data, i*0x10+0x0, src[0x8000*0+0x5C00+0x20*0+0x40*i:])
|
||||
copy_pal(img.data, i*0x10+0x1, src[0x8000*0+0x5C00+0x20*1+0x40*i:])
|
||||
copy_pal(img.data, i*0x10+0x2, src[0x8000*0+0x7C00+0x20*0+0x40*i:])
|
||||
copy_pal(img.data, i*0x10+0x3, src[0x8000*0+0x7C00+0x20*1+0x40*i:])
|
||||
copy_pal(img.data, i*0x10+0x4, src[0x8000*1+0x5C00+0x20*0+0x40*i:])
|
||||
copy_pal(img.data, i*0x10+0x5, src[0x8000*1+0x5C00+0x20*1+0x40*i:])
|
||||
copy_pal(img.data, i*0x10+0x6, src[0x8000*1+0x7C00+0x20*0+0x40*i:])
|
||||
copy_pal(img.data, i*0x10+0x7, src[0x8000*1+0x7C00+0x20*1+0x40*i:])
|
||||
copy_pal(img.data, i*0x10+0x8, src[0x8000*2+0x5C00+0x20*0+0x40*i:])
|
||||
copy_pal(img.data, i*0x10+0x9, src[0x8000*2+0x5C00+0x20*1+0x40*i:])
|
||||
copy_pal(img.data, i*0x10+0xA, src[0x8000*2+0x7C00+0x20*0+0x40*i:])
|
||||
copy_pal(img.data, i*0x10+0xB, src[0x8000*2+0x7C00+0x20*1+0x40*i:])
|
||||
copy_pal(img.data, i*0x10+0xC, src[0x8000*3+0x5C00+0x20*0+0x40*i:])
|
||||
copy_pal(img.data, i*0x10+0xD, src[0x8000*3+0x5C00+0x20*1+0x40*i:])
|
||||
copy_pal(img.data, i*0x10+0xE, src[0x8000*3+0x7C00+0x20*0+0x40*i:])
|
||||
copy_pal(img.data, i*0x10+0xF, src[0x8000*3+0x7C00+0x20*1+0x40*i:])
|
||||
img.write(output_file)
|
||||
|
||||
with open(input_file, "rb") as f_in:
|
||||
src_data = f_in.read()
|
||||
|
||||
max_tilesets = 8 if len(src_data) == 0x40000 else 6
|
||||
|
||||
for i in range(0, 4):
|
||||
generate_tileset(f"{output_base}_{i}.png", src_data, i, True)
|
||||
for i in range(4, max_tilesets):
|
||||
generate_tileset(f"{output_base}_{i}.png", src_data, i, False)
|
||||
generate_clut(f"{output_base}_clut.png", src_data)
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description="convert stage graphics")
|
||||
parser.add_argument("mode",
|
||||
choices=['e', 'd'],
|
||||
help="(e)ncode PNG to stage graphics or (d)ecode stage graphics to PNG")
|
||||
parser.add_argument("input")
|
||||
parser.add_argument("output")
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
if args.mode == 'e':
|
||||
err = encode(args.input, args.output)
|
||||
if err != None:
|
||||
sys.stderr.write(f"ERROR: {err}")
|
||||
elif args.mode == 'd':
|
||||
decode(args.input, args.output)
|
Loading…
Reference in New Issue
Block a user