From 32faed578a0f1ffa61296230610ab1696a7fcd29 Mon Sep 17 00:00:00 2001 From: Alex Bates Date: Thu, 4 Feb 2021 01:46:40 +0000 Subject: [PATCH 1/4] splat party images --- .gitignore | 1 + tools/splat_ext/PaperMarioMapFS.py | 40 ++++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 54e0d3226d..d09bebee8d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ venv/ ctx.c expected/ .vscode/launch.json +/tools/star-rod # Build artifacts build.ninja diff --git a/tools/splat_ext/PaperMarioMapFS.py b/tools/splat_ext/PaperMarioMapFS.py index 3550ffa124..ea14666fa4 100644 --- a/tools/splat_ext/PaperMarioMapFS.py +++ b/tools/splat_ext/PaperMarioMapFS.py @@ -2,6 +2,9 @@ import os from segtypes.n64.segment import N64Segment from pathlib import Path from util.n64 import Yay0decompress +from util.color import unpack_color +from util.iter import iter_in_groups +import png def decode_null_terminated_ascii(data): @@ -14,12 +17,22 @@ def decode_null_terminated_ascii(data): return data[:length].decode('ascii') +def parse_palette(data): + palette = [] + + for a, b in iter_in_groups(data, 2): + palette.append(unpack_color([a, b])) + + return palette + + class N64SegPaperMarioMapFS(N64Segment): def __init__(self, segment, next_segment, options): super().__init__(segment, next_segment, options) def split(self, rom_bytes, base_path): bin_dir = self.create_split_dir(base_path, "bin/assets") + img_party_dir = self.create_split_dir(base_path, "img/party") data = rom_bytes[self.rom_start: self.rom_end] @@ -37,6 +50,9 @@ class N64SegPaperMarioMapFS(N64Segment): if offset == 0: path = None + elif name.startswith("party_"): + path = "{}.png".format(name) + self.create_parent_dir(img_party_dir, path) else: path = "{}.bin".format(name) self.create_parent_dir(bin_dir, path) @@ -44,16 +60,24 @@ class N64SegPaperMarioMapFS(N64Segment): if name == "end_data": break - with open(os.path.join(bin_dir, path), "wb") as f: - bytes = rom_bytes[self.rom_start + 0x20 + - offset: self.rom_start + 0x20 + offset + size] + bytes = rom_bytes[self.rom_start + 0x20 + + offset: self.rom_start + 0x20 + offset + size] - if is_compressed: - self.log(f"Decompressing {name}...") - bytes = Yay0decompress.decompress_yay0(bytes) + if is_compressed: + self.log(f"Decompressing {name}...") + bytes = Yay0decompress.decompress_yay0(bytes) - f.write(bytes) - self.log(f"Wrote {name} to {Path(bin_dir, path)}") + + if name.startswith("party_"): + with open(os.path.join(img_party_dir, path), "wb") as f: + # CI-8 + w = png.Writer(150, 105, palette=parse_palette(bytes[:0x200])) + w.write_array(f, bytes[0x200:]) + else: + with open(os.path.join(bin_dir, path), "wb") as f: + f.write(bytes) + + self.log(f"Wrote {name} to {Path(bin_dir, path)}") asset_idx += 1 From 209f623a8860176b6659dda26c5124e5b1b82b21 Mon Sep 17 00:00:00 2001 From: Alex Bates Date: Thu, 4 Feb 2021 02:52:35 +0000 Subject: [PATCH 2/4] partner bgs --- configure.py | 12 ++++++++++++ tools/convert_image.py | 21 +++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/configure.py b/configure.py index de574f1348..f90af826b0 100755 --- a/configure.py +++ b/configure.py @@ -418,6 +418,18 @@ async def main(): if asset_name.endswith("_tex"): # uncompressed asset_files.append(f"bin/assets/{asset_name}.bin") asset_files.append(f"bin/assets/{asset_name}.bin") + elif asset_name.startswith("party_"): + source_file = f"$builddir/bin/assets/{asset_name}.bin" + asset_file = f"$builddir/bin/assets/{asset_name}.Yay0" + + n.build(source_file, "img", f"img/party/{asset_name}.png", implicit="tools/convert_image.py", variables={ + "img_type": "party", + "img_flags": "", + }) + + asset_files.append(source_file) + asset_files.append(asset_file) + n.build(asset_file, "yay0compress", source_file, implicit="tools/Yay0compress") else: # uncompressed source_file = f"bin/assets/{asset_name}.bin" asset_file = f"$builddir/assets/{asset_name}.Yay0" diff --git a/tools/convert_image.py b/tools/convert_image.py index d9fc14a4d7..f3436e3102 100755 --- a/tools/convert_image.py +++ b/tools/convert_image.py @@ -180,6 +180,27 @@ class Converter(): i = rgb_to_intensity(*rgba[:3]) f.write(i.to_bytes(1, byteorder="big")) + elif self.mode == "party": + data = img.read()[2] + img.preamble(True) + palette = img.palette(alpha="force") + + with open(self.outfile, "wb") as f: + # palette + for rgba in palette: + if rgba[3] not in (0, 0xFF): + self.warn("alpha mask mode but translucent pixels used") + + color = pack_color(*rgba) + f.write(color.to_bytes(2, byteorder="big")) + + assert f.tell() == 0x200, "palette has wrong size" + + # ci 8 + for row in reversed_if(data, self.flip_y): + f.write(row) + + f.write(b"\0\0\0\0\0\0\0\0\0\0") # padding else: print("unsupported mode", file=stderr) exit(1) From 137a93231d625f09035aaac304fd8e46f559b6d6 Mon Sep 17 00:00:00 2001 From: Alex Bates Date: Sat, 6 Feb 2021 16:15:22 +0000 Subject: [PATCH 3/4] prompt re-dump --- configure.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/configure.py b/configure.py index ed7e1d3ed7..c3eed55176 100755 --- a/configure.py +++ b/configure.py @@ -144,7 +144,10 @@ def find_asset_dir(path): if os.path.exists(d + "/" + path): return d - print("unable to find asset: " + path) + print("Unable to find asset: " + path) + print("The asset dump may be incomplete. Run") + print(" rm .splat_cache") + print("And then run ./configure.py again.") exit(1) def find_asset(path): From 1df36e757a0959bbd6899422f2befec93ed277e6 Mon Sep 17 00:00:00 2001 From: Alex Bates Date: Sat, 6 Feb 2021 17:36:35 +0000 Subject: [PATCH 4/4] splat *_bg.png assets --- configure.py | 20 ++++++++++++--- include/common_structs.h | 4 +-- tools/img/build.py | 41 ++++++++++++++++++++++++++++++ tools/splat_ext/PaperMarioMapFS.py | 25 +++++++++++++++++- 4 files changed, 83 insertions(+), 7 deletions(-) diff --git a/configure.py b/configure.py index c3eed55176..302646244f 100755 --- a/configure.py +++ b/configure.py @@ -123,7 +123,7 @@ def build_image(f: str, segment): out = "$builddir/" + path + "." + img_type + ".png" flags = "" - if img_type != "palette": + if img_type != "palette" and not isinstance(segment, dict): if segment.flip_horizontal: flags += "--flip-x" if segment.flip_vertical: @@ -481,8 +481,8 @@ async def main(): for asset_name in ASSETS: if asset_name.endswith("_tex"): # uncompressed - asset_files.append(find_asset(f"map/texture/{asset_name}.bin")) - asset_files.append(find_asset(f"map/texture/{asset_name}.bin")) + asset_files.append(find_asset(f"map/{asset_name}.bin")) + asset_files.append(find_asset(f"map/{asset_name}.bin")) elif asset_name.startswith("party_"): source_file = f"$builddir/{asset_name}.bin" asset_file = f"$builddir/{asset_name}.Yay0" @@ -495,7 +495,19 @@ async def main(): asset_files.append(source_file) asset_files.append(asset_file) n.build(asset_file, "yay0compress", source_file, implicit="tools/Yay0compress") - else: # uncompressed + elif asset_name.endswith("_bg"): + source_file = f"$builddir/{asset_name}.bin" + asset_file = f"$builddir/{asset_name}.Yay0" + + n.build(source_file, "img", find_asset(f"map/{asset_name}.png"), implicit="tools/img/build.py", variables={ + "img_type": "bg", + "img_flags": "", + }) + + asset_files.append(source_file) + asset_files.append(asset_file) + n.build(asset_file, "yay0compress", source_file, implicit="tools/Yay0compress") + else: source_file = find_asset(f"{asset_name}.bin") asset_file = f"$builddir/assets/{asset_name}.Yay0" diff --git a/include/common_structs.h b/include/common_structs.h index 05ddbf7136..617a2ae422 100644 --- a/include/common_structs.h +++ b/include/common_structs.h @@ -1418,8 +1418,8 @@ typedef struct TileDescriptor { } TileDescriptor; // size = 0x30 typedef struct BackgroundHeader { - /* 0x00 */ UNK_PTR raster; - /* 0x04 */ UNK_PTR palette; + /* 0x00 */ void* raster; + /* 0x04 */ void* palette; /* 0x08 */ u16 startX; /* 0x0A */ u16 startY; /* 0x0C */ u16 width; diff --git a/tools/img/build.py b/tools/img/build.py index eafa3e70b6..0c90ae59e8 100755 --- a/tools/img/build.py +++ b/tools/img/build.py @@ -3,6 +3,7 @@ from sys import argv, stderr from math import floor, ceil from itertools import zip_longest +from glob import glob import png def unpack_color(s): @@ -202,6 +203,46 @@ class Converter(): f.write(row) f.write(b"\0\0\0\0\0\0\0\0\0\0") # padding + elif self.mode == "bg": + width, height, data, info = img.read() + img.preamble(True) + palettes = [img.palette(alpha="force")] + + for palettepath in glob(self.infile.split(".")[0] + ".*.png"): + pal = png.Reader(palettepath) + pal.preamble(True) + palettes.append(pal.palette(alpha="force")) + + with open(self.outfile, "wb") as f: + baseaddr = 0x80200000 # gBackgroundImage + headers_len = 0x10 * len(palettes) + palettes_len = 0x200 * len(palettes) + + # header (struct BackgroundHeader) + for i, palette in enumerate(palettes): + f.write((baseaddr + palettes_len + headers_len).to_bytes(4, byteorder="big")) # raster offset + f.write((baseaddr + headers_len + 0x200 * i).to_bytes(4, byteorder="big")) # palette offset + f.write((12).to_bytes(2, byteorder="big")) # startX + f.write((20).to_bytes(2, byteorder="big")) # startY + f.write((width).to_bytes(2, byteorder="big")) # width + f.write((height).to_bytes(2, byteorder="big")) # height + + assert f.tell() == headers_len + + for palette in palettes: + # palette + for rgba in palette: + if rgba[3] not in (0, 0xFF): + self.warn("alpha mask mode but translucent pixels used") + + color = pack_color(*rgba) + f.write(color.to_bytes(2, byteorder="big")) + + assert f.tell() == palettes_len + headers_len + + # ci 8 + for row in reversed_if(data, self.flip_y): + f.write(row) else: print("unsupported mode", file=stderr) exit(1) diff --git a/tools/splat_ext/PaperMarioMapFS.py b/tools/splat_ext/PaperMarioMapFS.py index d172d06ecb..3a642c9624 100644 --- a/tools/splat_ext/PaperMarioMapFS.py +++ b/tools/splat_ext/PaperMarioMapFS.py @@ -58,8 +58,11 @@ class N64SegPaperMarioMapFS(N64Segment): path = os.path.join(map_dir, "{}.bin".format(name)) elif name.endswith("_tex"): - map_dir = self.create_split_dir(base_path, self.options.get("assets_dir", "bin") + f"/map/texture") + map_dir = self.create_split_dir(base_path, self.options.get("assets_dir", "bin") + f"/map") path = os.path.join(map_dir, "{}.bin".format(name)) + elif name.endswith("_bg"): + map_dir = self.create_split_dir(base_path, self.options.get("assets_dir", "bin") + f"/map") + path = os.path.join(map_dir, "{}.png".format(name)) else: path = os.path.join(bin_dir, "{}.bin".format(name)) @@ -80,6 +83,26 @@ class N64SegPaperMarioMapFS(N64Segment): # CI-8 w = png.Writer(150, 105, palette=parse_palette(bytes[:0x200])) w.write_array(f, bytes[0x200:]) + elif name.endswith("_bg"): + def write_bg_png(bytes, path, header_offset=0): + header = bytes[header_offset:header_offset+0x10] + + raster_offset = int.from_bytes(header[0:4], byteorder="big") - 0x80200000 + palette_offset = int.from_bytes(header[4:8], byteorder="big") - 0x80200000 + assert int.from_bytes(header[8:12], byteorder="big") == 0x000C0014 # draw pos + width = int.from_bytes(header[12:14], byteorder="big") + height = int.from_bytes(header[14:16], byteorder="big") + + with open(path, "wb") as f: + # CI-8 + w = png.Writer(width, height, palette=parse_palette(bytes[palette_offset:palette_offset+512])) + w.write_array(f, bytes[raster_offset:]) + + write_bg_png(bytes, path) + + # sbk_bg has an alternative palette + if name == "sbk_bg": + write_bg_png(bytes, path.split(".")[0] + ".alt.png", header_offset=0x10) else: with open(path, "wb") as f: f.write(bytes)