2023-07-13 10:56:16 +02:00
|
|
|
from math import ceil
|
2023-06-29 14:06:23 +02:00
|
|
|
import os, sys
|
2022-07-08 14:57:08 +02:00
|
|
|
from pathlib import Path
|
2021-01-24 15:45:43 +01:00
|
|
|
from segtypes.n64.segment import N64Segment
|
2022-12-11 08:43:29 +01:00
|
|
|
from util.n64.Yay0decompress import Yay0Decompressor
|
2022-10-04 16:09:23 +02:00
|
|
|
from segtypes.n64.palette import iter_in_groups
|
2021-03-23 03:29:47 +01:00
|
|
|
from util import options
|
2023-06-26 12:27:37 +02:00
|
|
|
import png # type: ignore
|
2022-07-08 14:57:08 +02:00
|
|
|
import yaml as yaml_loader
|
2022-09-28 22:52:12 +02:00
|
|
|
import n64img.image
|
2022-07-08 14:57:08 +02:00
|
|
|
|
2023-06-29 14:06:23 +02:00
|
|
|
SPLAT_EXT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
sys.path.append(str(Path(SPLAT_EXT_DIR)))
|
|
|
|
from tex_archives import TexArchive
|
|
|
|
|
2022-07-08 14:57:08 +02:00
|
|
|
script_dir = Path(os.path.dirname(os.path.realpath(__file__)))
|
2020-11-11 22:21:25 +01:00
|
|
|
|
2022-09-28 22:52:12 +02:00
|
|
|
|
2020-11-11 22:21:25 +01:00
|
|
|
def decode_null_terminated_ascii(data):
|
|
|
|
length = 0
|
|
|
|
for byte in data:
|
|
|
|
if byte == 0:
|
|
|
|
break
|
|
|
|
length += 1
|
|
|
|
|
2022-09-28 22:52:12 +02:00
|
|
|
return data[:length].decode("ascii")
|
|
|
|
|
2020-11-11 22:21:25 +01:00
|
|
|
|
2021-02-06 23:26:35 +01:00
|
|
|
def parse_palette(data):
|
2021-04-13 09:47:52 +02:00
|
|
|
palette = []
|
2021-02-06 23:26:35 +01:00
|
|
|
|
2023-07-13 10:56:16 +02:00
|
|
|
# RRRRRGGG GGBBBBBA
|
|
|
|
def unpack_color(data):
|
|
|
|
s = int.from_bytes(data[0:2], byteorder="big")
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2021-04-13 09:47:52 +02:00
|
|
|
for a, b in iter_in_groups(data, 2):
|
|
|
|
palette.append(unpack_color([a, b]))
|
2021-02-06 23:26:35 +01:00
|
|
|
|
2021-04-13 09:47:52 +02:00
|
|
|
return palette
|
2021-02-06 23:26:35 +01:00
|
|
|
|
2022-09-28 22:52:12 +02:00
|
|
|
|
2023-07-18 11:07:58 +02:00
|
|
|
def add_file_ext(name: str, linker: bool = False) -> str:
|
2021-04-13 09:47:52 +02:00
|
|
|
if name.startswith("party_"):
|
2023-04-29 20:33:56 +02:00
|
|
|
return "party/" + name + ".png"
|
2023-07-18 11:07:58 +02:00
|
|
|
elif name.endswith("_hit"):
|
|
|
|
return "geom/" + name + ".bin"
|
|
|
|
elif name.endswith("_shape"):
|
|
|
|
if linker:
|
|
|
|
name += "_built"
|
|
|
|
return "geom/" + name + ".bin"
|
2021-04-13 09:47:52 +02:00
|
|
|
elif name.endswith("_tex"):
|
2023-07-18 11:07:58 +02:00
|
|
|
return "tex/" + name + ".bin"
|
2021-04-13 09:47:52 +02:00
|
|
|
elif name.endswith("_bg"):
|
2023-04-29 20:33:56 +02:00
|
|
|
return "bg/" + name + ".png"
|
2021-04-13 09:47:52 +02:00
|
|
|
else:
|
|
|
|
return name + ".bin"
|
2021-02-06 23:26:35 +01:00
|
|
|
|
2022-09-28 22:52:12 +02:00
|
|
|
|
2021-07-16 11:28:37 +02:00
|
|
|
class N64SegPm_map_data(N64Segment):
|
2022-06-12 17:33:32 +02:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
rom_start,
|
|
|
|
rom_end,
|
|
|
|
type,
|
|
|
|
name,
|
|
|
|
vram_start,
|
|
|
|
args,
|
|
|
|
yaml,
|
|
|
|
):
|
|
|
|
super().__init__(
|
|
|
|
rom_start,
|
|
|
|
rom_end,
|
|
|
|
type,
|
|
|
|
name,
|
|
|
|
vram_start,
|
|
|
|
args=args,
|
|
|
|
yaml=yaml,
|
|
|
|
)
|
2020-11-11 22:21:25 +01:00
|
|
|
|
2022-07-08 14:57:08 +02:00
|
|
|
with open(script_dir / "map_data.yaml") as f:
|
|
|
|
self.files = yaml_loader.load(f.read(), Loader=yaml_loader.SafeLoader)
|
2021-04-13 09:47:52 +02:00
|
|
|
|
|
|
|
def split(self, rom_bytes):
|
2023-06-29 14:06:23 +02:00
|
|
|
assert isinstance(self.rom_start, int)
|
|
|
|
|
2022-09-28 22:52:12 +02:00
|
|
|
fs_dir = options.opts.asset_path / self.dir / self.name
|
2021-05-08 08:54:34 +02:00
|
|
|
(fs_dir / "title").mkdir(parents=True, exist_ok=True)
|
2023-04-29 20:33:56 +02:00
|
|
|
(fs_dir / "party").mkdir(parents=True, exist_ok=True)
|
|
|
|
(fs_dir / "bg").mkdir(parents=True, exist_ok=True)
|
|
|
|
(fs_dir / "tex").mkdir(parents=True, exist_ok=True)
|
|
|
|
(fs_dir / "geom").mkdir(parents=True, exist_ok=True)
|
2020-11-11 22:21:25 +01:00
|
|
|
|
2022-09-28 22:52:12 +02:00
|
|
|
data = rom_bytes[self.rom_start : self.rom_end]
|
2020-11-11 22:21:25 +01:00
|
|
|
|
|
|
|
asset_idx = 0
|
|
|
|
while True:
|
2022-09-28 22:52:12 +02:00
|
|
|
asset_data = data[0x20 + asset_idx * 0x1C :]
|
2020-11-11 22:21:25 +01:00
|
|
|
|
|
|
|
name = decode_null_terminated_ascii(asset_data[0:])
|
|
|
|
offset = int.from_bytes(asset_data[0x10:0x14], byteorder="big")
|
|
|
|
size = int.from_bytes(asset_data[0x14:0x18], byteorder="big")
|
2022-09-28 22:52:12 +02:00
|
|
|
decompressed_size = int.from_bytes(asset_data[0x18:0x1C], byteorder="big")
|
2020-11-11 22:21:25 +01:00
|
|
|
|
|
|
|
is_compressed = size != decompressed_size
|
|
|
|
|
|
|
|
if offset == 0:
|
|
|
|
path = None
|
|
|
|
else:
|
2021-04-13 09:47:52 +02:00
|
|
|
path = fs_dir / add_file_ext(name)
|
2020-11-11 22:21:25 +01:00
|
|
|
|
|
|
|
if name == "end_data":
|
|
|
|
break
|
|
|
|
|
2021-04-13 09:47:52 +02:00
|
|
|
bytes_start = self.rom_start + 0x20 + offset
|
|
|
|
bytes = rom_bytes[bytes_start : bytes_start + size]
|
2021-02-06 23:26:35 +01:00
|
|
|
|
|
|
|
if is_compressed:
|
2022-12-11 08:43:29 +01:00
|
|
|
bytes = Yay0Decompressor.decompress_python(bytes)
|
2020-11-11 22:21:25 +01:00
|
|
|
|
2021-02-06 23:26:35 +01:00
|
|
|
if name.startswith("party_"):
|
2023-06-29 14:06:23 +02:00
|
|
|
assert path is not None
|
2021-02-06 23:26:35 +01:00
|
|
|
with open(path, "wb") as f:
|
|
|
|
# CI-8
|
|
|
|
w = png.Writer(150, 105, palette=parse_palette(bytes[:0x200]))
|
|
|
|
w.write_array(f, bytes[0x200:])
|
2021-05-08 08:54:34 +02:00
|
|
|
elif name == "title_data":
|
2023-06-26 12:27:37 +02:00
|
|
|
if "ver/us" in str(options.opts.target_path) or "ver/pal" in str(
|
|
|
|
options.opts.target_path
|
|
|
|
):
|
2022-09-28 22:52:12 +02:00
|
|
|
w = 200
|
|
|
|
h = 112
|
|
|
|
img = n64img.image.RGBA32(
|
|
|
|
data=bytes[0x2210 : 0x2210 + w * h * 4], width=w, height=h
|
|
|
|
)
|
|
|
|
img.write(fs_dir / "title/logotype.png")
|
|
|
|
|
|
|
|
w = 144
|
|
|
|
h = 32
|
|
|
|
img = n64img.image.IA8(
|
|
|
|
data=bytes[0x10 : 0x10 + w * h], width=w, height=h
|
|
|
|
)
|
|
|
|
img.write(fs_dir / "title/copyright.png")
|
|
|
|
|
|
|
|
w = 128
|
|
|
|
h = 32
|
|
|
|
img = n64img.image.IA8(
|
|
|
|
data=bytes[0x1210 : 0x1210 + w * h], width=w, height=h
|
|
|
|
)
|
|
|
|
img.write(fs_dir / "title/press_start.png")
|
2021-07-16 13:08:22 +02:00
|
|
|
else:
|
2022-09-28 22:52:12 +02:00
|
|
|
w = 272
|
|
|
|
h = 88
|
|
|
|
img = n64img.image.RGBA32(
|
|
|
|
data=bytes[0x1830 : 0x1830 + w * h * 4], width=w, height=h
|
|
|
|
)
|
|
|
|
img.write(fs_dir / "title/logotype.png")
|
|
|
|
|
|
|
|
w = 128
|
|
|
|
h = 32
|
|
|
|
img = n64img.image.CI4(
|
2023-06-29 14:06:23 +02:00
|
|
|
data=bytes[0x10 : 0x10 + (w * h // 2)], width=w, height=h
|
2022-09-28 22:52:12 +02:00
|
|
|
)
|
|
|
|
img.palette = parse_palette(bytes[0x810:0x830])
|
|
|
|
img.write(fs_dir / "title/copyright.png")
|
|
|
|
|
|
|
|
w = 128
|
|
|
|
h = 32
|
|
|
|
img = n64img.image.IA8(
|
|
|
|
data=bytes[0x830 : 0x830 + w * h], width=w, height=h
|
|
|
|
)
|
|
|
|
img.write(fs_dir / "title/press_start.png")
|
2021-02-06 23:26:35 +01:00
|
|
|
elif name.endswith("_bg"):
|
|
|
|
|
2022-09-28 22:52:12 +02:00
|
|
|
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
|
2021-02-06 23:26:35 +01:00
|
|
|
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
|
2022-09-28 22:52:12 +02:00
|
|
|
w = png.Writer(
|
|
|
|
width,
|
|
|
|
height,
|
|
|
|
palette=parse_palette(
|
|
|
|
bytes[palette_offset : palette_offset + 512]
|
|
|
|
),
|
|
|
|
)
|
2021-02-06 23:26:35 +01:00
|
|
|
w.write_array(f, bytes[raster_offset:])
|
|
|
|
|
2023-04-29 20:33:56 +02:00
|
|
|
write_bg_png(bytes, fs_dir / "bg" / f"{name}.png")
|
2021-02-06 23:26:35 +01:00
|
|
|
|
|
|
|
# sbk_bg has an alternative palette
|
|
|
|
if name == "sbk_bg":
|
2023-06-26 12:27:37 +02:00
|
|
|
write_bg_png(
|
|
|
|
bytes, fs_dir / "bg" / f"{name}.alt.png", header_offset=0x10
|
|
|
|
)
|
2023-06-29 14:06:23 +02:00
|
|
|
elif name.endswith("_tex"):
|
|
|
|
TexArchive.extract(bytes, fs_dir / "tex" / name)
|
2021-02-06 23:26:35 +01:00
|
|
|
else:
|
2023-06-29 14:06:23 +02:00
|
|
|
assert path is not None
|
2021-02-06 23:26:35 +01:00
|
|
|
with open(path, "wb") as f:
|
|
|
|
f.write(bytes)
|
2020-11-11 22:21:25 +01:00
|
|
|
|
|
|
|
asset_idx += 1
|
|
|
|
|
2021-04-13 09:47:52 +02:00
|
|
|
def get_linker_entries(self):
|
|
|
|
from segtypes.linker_entry import LinkerEntry
|
2020-11-11 22:21:25 +01:00
|
|
|
|
2022-09-28 22:52:12 +02:00
|
|
|
fs_dir = options.opts.asset_path / self.dir / self.name
|
2020-11-11 22:21:25 +01:00
|
|
|
|
2022-09-28 22:52:12 +02:00
|
|
|
return [
|
|
|
|
LinkerEntry(
|
|
|
|
self,
|
2023-07-18 11:07:58 +02:00
|
|
|
[fs_dir / add_file_ext(name, linker=True) for name in self.files],
|
2022-09-28 22:52:12 +02:00
|
|
|
fs_dir.with_suffix(".dat"),
|
|
|
|
".data",
|
|
|
|
),
|
2021-04-13 09:47:52 +02:00
|
|
|
]
|