From 44fd38f4f80fbb70f2b8c0735839be8101d6923f Mon Sep 17 00:00:00 2001 From: Alex Bates <16batesa@gmail.com> Date: Sat, 31 Oct 2020 18:30:16 +0000 Subject: [PATCH] add image splits --- .gitignore | 1 + .vscode/settings.json | 1 - Makefile | 50 +++++++++-- requirements.txt | 1 + sources.mk | 3 + tools/convert_image.py | 193 +++++++++++++++++++++++++++++++++++++++++ tools/n64splat | 2 +- tools/splat.yaml | 69 ++++++++++++++- 8 files changed, 309 insertions(+), 11 deletions(-) create mode 100755 tools/convert_image.py diff --git a/.gitignore b/.gitignore index e08969475e..3600fdfb40 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ settings.mk *.i *.Yay0 bin/ +img/ build/ docs/doxygen/ include/ld_addrs.h diff --git a/.vscode/settings.json b/.vscode/settings.json index b9a9ded7bd..c2b3c50402 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,7 +10,6 @@ "-D_LANGUAGE_C", "-DSCRIPT(...)={}", ], - "python.pythonPath": "/usr/bin/python3", "git.ignoreLimitWarning": true, "search.exclude": { "build": true, diff --git a/Makefile b/Makefile index ece296eab5..fcdcdbf959 100644 --- a/Makefile +++ b/Makefile @@ -98,14 +98,14 @@ submodules: git submodule update --init --recursive split: - rm -rf bin - $(SPLAT) --modes ld bin Yay0 PaperMarioMapFS + rm -rf bin img + $(SPLAT) --modes ld bin Yay0 PaperMarioMapFS rgba16 rgba32 ia4 ia8 ia16 i4 i8 ci4 ci8 -split-bin: - $(SPLAT) --modes ld bin +split-%: + $(SPLAT) --modes ld $* split-all: - rm -rf bin + rm -rf bin img $(SPLAT) --modes all test: $(ROM) @@ -137,13 +137,51 @@ $(BUILD_DIR)/%.c.o: %.c $(MDEPS) # Compile C files (with DSL macros) $(foreach cfile, $(DSL_C_FILES), $(BUILD_DIR)/$(cfile).o): $(BUILD_DIR)/%.c.o: %.c $(MDEPS) @mkdir -p $(shell dirname $@) - $(CPP) $(CPPFLAGS) -o - $< $(CPPMFLAGS) | tools/compile_dsl_macros.py | $(CC) $(CFLAGS) -o - | $(OLD_AS) $(OLDASFLAGS) -o $@ - + $(CPP) $(CPPFLAGS) -o - $< $(CPPMFLAGS) | $(PYTHON) tools/compile_dsl_macros.py | $(CC) $(CFLAGS) -o - | $(OLD_AS) $(OLDASFLAGS) -o $@ - # Assemble handwritten ASM $(BUILD_DIR)/%.s.o: %.s @mkdir -p $(shell dirname $@) $(AS) $(ASFLAGS) -o $@ $< +# Images +$(BUILD_DIR)/%.png.o: $(BUILD_DIR)/%.png + $(LD) -r -b binary -o $@ $< +$(BUILD_DIR)/%.rgba16.png: %.png + @mkdir -p $(shell dirname $@) + $(PYTHON) tools/convert_image.py rgba16 $< $@ $(IMG_FLAGS) +$(BUILD_DIR)/%.rgba32.png: %.png + @mkdir -p $(shell dirname $@) + $(PYTHON) tools/convert_image.py rgba32 $< $@ $(IMG_FLAGS) +$(BUILD_DIR)/%.ci8.png: %.png + @mkdir -p $(shell dirname $@) + $(PYTHON) tools/convert_image.py ci8 $< $@ $(IMG_FLAGS) +$(BUILD_DIR)/%.ci8palette.png: %.png + @mkdir -p $(shell dirname $@) + $(PYTHON) tools/convert_image.py ci8palette $< $@ $(IMG_FLAGS) +$(BUILD_DIR)/%.ci4.png: %.png + @mkdir -p $(shell dirname $@) + $(PYTHON) tools/convert_image.py ci4 $< $@ $(IMG_FLAGS) +$(BUILD_DIR)/%.ci4palette.png: %.png + @mkdir -p $(shell dirname $@) + $(PYTHON) tools/convert_image.py ci4palette $< $@ $(IMG_FLAGS) +$(BUILD_DIR)/%.ia4.png: %.png + @mkdir -p $(shell dirname $@) + $(PYTHON) tools/convert_image.py ia4 $< $@ $(IMG_FLAGS) +$(BUILD_DIR)/%.ia8.png: %.png + @mkdir -p $(shell dirname $@) + $(PYTHON) tools/convert_image.py ia8 $< $@ $(IMG_FLAGS) +$(BUILD_DIR)/%.ia16.png: %.png + @mkdir -p $(shell dirname $@) + $(PYTHON) tools/convert_image.py ia16 $< $@ $(IMG_FLAGS) +$(BUILD_DIR)/%.i4.png: %.png + @mkdir -p $(shell dirname $@) + $(PYTHON) tools/convert_image.py i4 $< $@ $(IMG_FLAGS) +$(BUILD_DIR)/%.i8.png: %.png + @mkdir -p $(shell dirname $@) + $(PYTHON) tools/convert_image.py i8 $< $@ $(IMG_FLAGS) + + ASSET_FILES := $(foreach asset, $(ASSETS), $(BUILD_DIR)/bin/assets/$(asset)) YAY0_ASSET_FILES := $(foreach asset, $(filter-out %_tex, $(ASSET_FILES)), $(asset).Yay0) diff --git a/requirements.txt b/requirements.txt index 361a2122c4..450238e89e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ capstone PyYAML lark-parser +pypng diff --git a/sources.mk b/sources.mk index 47d7169a4c..f2c0e86888 100644 --- a/sources.mk +++ b/sources.mk @@ -55,3 +55,6 @@ ASSETS := \ kmr_bg nok_bg sbk_bg sbk3_bg iwa_bg hos_bg arn_bg obk_bg omo_bg yos_bg jan_bg fla_bg flb_bg sra_bg yki_bg sam_bg kpa_bg title_bg \ title_data \ party_kurio party_kameki party_pinki party_pareta party_resa party_akari party_opuku party_pokopi + +# Image settings +$(BUILD_DIR)/img/battle/text_action_command_ratings.ia4.png: IMG_FLAGS = --flip-y diff --git a/tools/convert_image.py b/tools/convert_image.py new file mode 100755 index 0000000000..afc92d94bc --- /dev/null +++ b/tools/convert_image.py @@ -0,0 +1,193 @@ +#! /usr/bin/python3 + +from sys import argv, stderr +from math import floor, ceil +from itertools import zip_longest +import png + +def unpack_color(s): + r = (s >> 11) & 0x1F + g = (s >> 6) & 0x1F + b = (s >> 1) & 0x1F + a = (s & 1) * 0xFF + + r = ceil(0xFF * (r / 31)) + g = ceil(0xFF * (g / 31)) + b = ceil(0xFF * (b / 31)) + + return r, g, b, a + +def pack_color(r, g, b, a): + r = floor(31 * (r / 255)) + g = floor(31 * (g / 255)) + b = floor(31 * (b / 255)) + + s = round(a / 0xFF) + s |= (r & 0x1F) << 11 + s |= (g & 0x1F) << 6 + s |= (b & 0x1F) << 1 + + return s + +def rgb_to_intensity(r, g, b): + return round(r * 0.2126 + g * 0.7152 + 0.0722 * b) + +def iter_in_groups(iterable, n, fillvalue=None): + args = [iter(iterable)] * n + return zip_longest(*args, fillvalue=fillvalue) + + + +def reversed_if(iterator, cond): + if cond: + return reversed(list(iterator)) + else: + return iterator + +class Converter(): + def __init__(self, mode, infile, outfile, *argv): + self.mode = mode + self.infile = infile + self.outfile = outfile + self.flip_y = "--flip-y" in argv + + self.warned = False + + def warn(self, msg): + if not self.warned: + self.warned = True + print(self.infile + ": warning: " + msg, file=stderr) + + def convert(self): + img = png.Reader(self.infile) + + if self.mode == "rgba32": + with open(self.outfile, "wb") as f: + for row in reversed_if(img.asRGBA()[2], self.flip_y): + f.write(row) + elif self.mode == "rgba16": + with open(self.outfile, "wb") as f: + for row in reversed_if(img.asRGBA()[2], self.flip_y): + for rgba in iter_in_groups(row, 4): + 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")) + elif self.mode == "ci8": + with open(self.outfile, "wb") as f: + for row in reversed_if(img.read()[2], self.flip_y): + f.write(row) + elif self.mode == "ci4": + with open(self.outfile, "wb") as f: + for row in reversed_if(img.read()[2], self.flip_y): + for a, b in iter_in_groups(row, 2): + byte = (a << 4) | b + f.write(byte.to_bytes(1, byteorder="big")) + elif self.mode == "ci8palette" or self.mode == "ci4palette": + img.preamble(True) + palette = img.palette(alpha="force") + + with open(self.outfile, "wb") as f: + 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")) + elif self.mode == "ia4": + with open(self.outfile, "wb") as f: + for row in reversed_if(img.asRGBA()[2], self.flip_y): + for c1, c2 in iter_in_groups(iter_in_groups(row, 4), 2): + i1 = rgb_to_intensity(*c1[:3]) + a1 = c1[3] + + i2 = rgb_to_intensity(*c2[:3]) + a2 = c2[3] + + i1 = floor(7 * (i1 / 0xFF)) + i2 = floor(7 * (i2 / 0xFF)) + + if a1 not in (0, 0xFF) or a2 not in (0, 0xFF): + self.warn("alpha mask mode but translucent pixels used") + if c1[0] != c1[1] != c1[2]: + self.warn("grayscale mode but image is not") + if c2[0] != c2[1] != c2[2]: + self.warn("grayscale mode but image is not") + + a1 = 1 if a1 > 128 else 0 + a2 = 1 if a2 > 128 else 0 + + h = (i1 << 1) | a1 + l = (i2 << 1) | a2 + + byte = (h << 4) | l + f.write(byte.to_bytes(1, byteorder="big")) + elif self.mode == "ia8": + with open(self.outfile, "wb") as f: + for row in reversed_if(img.asRGBA()[2], self.flip_y): + for rgba in iter_in_groups(row, 4): + i = rgb_to_intensity(*rgba[:3]) + a = rgba[3] + + i = floor(15 * (i / 0xFF)) + a = floor(15 * (a / 0xFF)) + + if rgba[0] != rgba[1] != rgba[2]: + self.warn("grayscale mode but image is not") + + byte = (i << 4) | a + f.write(byte.to_bytes(1, byteorder="big")) + elif self.mode == "ia16": + with open(self.outfile, "wb") as f: + for row in reversed_if(img.asRGBA()[2], self.flip_y): + for rgba in iter_in_groups(row, 4): + i = rgb_to_intensity(*rgba[:3]) + a = rgba[3] + + if rgba[0] != rgba[1] != rgba[2]: + self.warn("grayscale mode but image is not") + + f.write(bytes((i, a))) + elif self.mode == "i4": + with open(self.outfile, "wb") as f: + for row in reversed_if(img.asRGBA()[2], self.flip_y): + for c1, c2 in iter_in_groups(iter_in_groups(row, 4), 2): + if c1[3] != 0xFF or c2[3] != 0xFF: + self.warn("discarding alpha channel") + + i1 = rgb_to_intensity(*c1[:3]) + i2 = rgb_to_intensity(*c2[:3]) + + i1 = floor(15 * (i1 / 0xFF)) + i2 = floor(15 * (i2 / 0xFF)) + + if c1[0] != c1[1] != c1[2]: + self.warn("grayscale mode but image is not") + if c2[0] != c2[1] != c2[2]: + self.warn("grayscale mode but image is not") + + byte = (i1 << 4) | i2 + f.write(byte.to_bytes(1, byteorder="big")) + elif self.mode == "i8": + with open(self.outfile, "wb") as f: + for row in reversed_if(img.asRGBA()[2], self.flip_y): + for rgba in iter_in_groups(row, 4): + if rgba[3] != 0xFF or rgba[3] != 0xFF: + self.warn("discarding alpha channel") + if rgba[0] != rgba[1] != rgba[2]: + self.warn("grayscale mode but image is not") + + i = rgb_to_intensity(*rgba[:3]) + f.write(i.to_bytes(1, byteorder="big")) + else: + print("unsupported mode", file=stderr) + exit(1) + + +if __name__ == "__main__": + if len(argv) < 4: + print("usage: convert_image.py MODE INFILE OUTFILE [--flip-y]") + exit(1) + + Converter(*argv[1:]).convert() diff --git a/tools/n64splat b/tools/n64splat index 037d80e00c..6443fba42f 160000 --- a/tools/n64splat +++ b/tools/n64splat @@ -1 +1 @@ -Subproject commit 037d80e00cb3aeae37bdad77bfac871036982456 +Subproject commit 6443fba42f530ef99187a79cd93b9e5d95161ef7 diff --git a/tools/splat.yaml b/tools/splat.yaml index 68395935ff..61fc4ee65e 100644 --- a/tools/splat.yaml +++ b/tools/splat.yaml @@ -212,6 +212,14 @@ segments: - [0x8a860, "c", "code_8a860_len_3f30"] - [0x8e790, "c", "code_8e790_len_2850"] - [0x90fe0, "bin"] + - [0x93CD0, ci4, ui/hammer, 32, 32] + - [0x93ED0, ci4palette, ui/hammer] + - [0x93EF0, ci4palette, ui/hammer.disabled] + - [0x93FF0, bin] + - [0x9D658, rgba32, ui/stat_heart, 16, 16] + - [0x9DA58, bin] + - [0x9DA60, rgba32, ui/stat_flower, 16, 16] + - [0x9DE60, bin] - type: code start: 0xA5DD0 vram: 0x8010F6D0 @@ -268,8 +276,17 @@ segments: - [0x1086a0, "c", "code_1086a0_len_fc0"] - [0x109660, "c", "code_109660_len_1270"] - [0x10A8D0, "c", "code_10A8D0"] - - [0x10A9F0, "bin"] # todo split this further - - [0x131340, "bin"] # 0x8023E000 + - [0x10A9F0, i4, shadow/square, 16, 16] + - [0x10AA70, i4, shadow/circle, 16, 16] + - [0x10AAF0, bin] + - [0x10AB70, i4, shadow/10AB70, 32, 32] + - [0x10AD70, i4, shadow/10AD70, 32, 32] + - [0x10AF70, bin] # TODO + - [0x1164B8, ci8, peach_letter, 150, 105] + - [0x11A23E, bin] + - [0x11A240, ci8palette, peach_letter] + - [0x11A440, bin] + - [0x131340, bin] - type: code start: 0x135EE0 vram: 0x80242BA0 @@ -280,6 +297,28 @@ segments: - [0x140C70, "c"] - [0x1421C0, "c"] - [0x1422A0, "bin"] + - [0x1443F0, ci8, pause/world_map, 320, 320] + - [0x15D3F0, ci8palette, pause/world_map] + - [0x15D5F0, ci4, pause/spirits_bg, 128, 110] + - [0x15F170, ci4palette, pause/spirits_bg] + - [0x15F270, bin] + - [0x15F970, ci4, pause/banner_hp, 64, 16] + - [0x15FB70, ci4palette, pause/banner_hp] + - [0x15FD70, ci4, pause/banner_fp, 64, 16] + - [0x15FF70, ci4palette, pause/banner_fp] + - [0x160170, ci4, pause/banner_bp, 64, 16] + - [0x160370, ci4palette, pause/banner_bp] + - [0x160570, ci4, pause/banner_boots, 48, 16] + - [0x1606F0, ci4palette, pause/banner_boots] + - [0x1608F0, ci4, pause/banner_hammer, 48, 16] + - [0x160A70, ci4palette, pause/banner_hammer] + - [0x160C70, ci4, pause/banner_star_energy, 48, 16] + - [0x160DF0, ci4palette, pause/banner_star_energy] + - [0x160FF0, ci4, pause/available, 64, 16] + - [0x1611F0, ci4palette, pause/available] + - [0x1613F0, ci4, pause/prompt_check_abilities, 128, 16] + - [0x1617F0, ci4palette, pause/prompt_check_abilities] + - [0x1619F0, bin] - type: code start: 0x163400 vram: 0x80242BA0 @@ -313,6 +352,21 @@ segments: - [0x1AF230, "c"] - [0x1AF2D0, "bin"] - [0x1CC310, "bin"] # icon images and palettes, vram unknown + - [0x1FE1B0, rgba16, title/logo_n64, 128, 112] + - [0x2051B0, rgba16, title/logo_is, 256, 122] + - [0x2131B0, rgba16, title/logo_nintendo, 256, 48] + - [0x2191B0, ci8, title/bg_1, 264, 162] + - [0x2238C0, ci8palette, title/bg_1] + - [0x223AC0, ci8, title/bg_2, 264, 162] + - [0x22E1D0, ci8palette, title/bg_2] + - [0x22E3D0, ci8, title/bg_3, 264, 162] + - [0x238AE0, ci8palette, title/bg_3] + - [0x238CE0, ci8, title/bg_4, 264, 162] + - [0x2433F0, ci8palette, title/bg_4] + - [0x2435F0, ia8, title/tape, 128, 128] + - [0x2475F0, ci8, title/bowser_silhouette, 128, 128] + - [0x24B5F0, ci8palette, title/bowser_silhouette] + - [0x24B7F0, bin] - type: code start: 0x3169F0 vram: 0x80200000 @@ -482,13 +536,13 @@ segments: files: - [0x33CDF0, "c"] - [0x33d5d0, "bin"] + - [0x33D610, "bin"] - type: code start: 0x33E8C0 vram: 0xE002A000 files: - [0x33E8C0, "c"] - [0x33efe0, "bin"] - - [0x33D610, "bin"] - type: code start: 0x33FE80 vram: 0xE002C000 @@ -731,6 +785,13 @@ segments: - [0x38F900, "c"] - [0x390340, "bin"] - [0x3903D0, "bin"] + - start: 0x390810 + type: ia4 + name: battle/text_action_command_ratings + width: 64 + height: 125 + flip: vertical + - [0x3917B0, bin] - type: code start: 0x391D30 vram: 0xE0092000 @@ -814,6 +875,8 @@ segments: - [0x3BA030, "c"] - [0x3BAC60, "bin"] - [0x3BAEA0, "bin"] # todo split this further ADD STUFF AFTER HERE + - [0x3ED4E0, "ia8", "world/text_chapter", 128, 38] + - [0x3EE7E0, "bin"] - type: code start: 0x415D90 vram: 0x802A1000