mirror of
https://github.com/pmret/papermario.git
synced 2024-11-09 12:32:38 +01:00
ae66312d8c
* Add Python linter to github actions * wip * Add back splat_ext * Format files * C++ -> C * format 2 files * split workflow into separate file, line length 120, fix excludes * -l 120 in ci * update black locally and apply formatting changes * pyproject.toject --------- Co-authored-by: Ethan Roseman <ethteck@gmail.com>
175 lines
5.6 KiB
Python
Executable File
175 lines
5.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
from dataclasses import dataclass, field
|
|
from enum import Enum
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
import struct
|
|
from typing import List, Literal
|
|
|
|
|
|
class LightMode(Enum):
|
|
UNIFORM = 0
|
|
LINEAR = 4
|
|
QUADRATIC = 8
|
|
|
|
|
|
@dataclass
|
|
class Light:
|
|
flags: int
|
|
rgb: List[int]
|
|
pos: List[int]
|
|
falloff: float
|
|
|
|
def to_json(self):
|
|
d = self.__dict__
|
|
d["mode"] = LightMode(d["flags"] - 1).name.lower()
|
|
del d["flags"]
|
|
return d
|
|
|
|
|
|
@dataclass
|
|
class SpriteShadingProfile:
|
|
name: str
|
|
ambient: List[int]
|
|
power: int
|
|
lights: List[Light] = field(default_factory=lambda: [])
|
|
|
|
def to_json(self):
|
|
d = self.__dict__
|
|
d["lights"] = [light.to_json() for light in self.lights]
|
|
return d
|
|
|
|
|
|
@dataclass
|
|
class SpriteShadingGroup:
|
|
area: str
|
|
profiles: List[SpriteShadingProfile] = field(default_factory=lambda: [])
|
|
|
|
def to_json(self):
|
|
d = self.__dict__
|
|
d["profiles"] = [profile.to_json() for profile in self.profiles]
|
|
return d
|
|
|
|
|
|
def groups_from_json(data) -> List[SpriteShadingGroup]:
|
|
groups = []
|
|
for group in data:
|
|
profiles = []
|
|
for profile in group["profiles"]:
|
|
lights = []
|
|
for light in profile["lights"]:
|
|
enabled = light.get("enabled", True)
|
|
lights.append(
|
|
Light(
|
|
flags=LightMode[light["mode"].upper()].value + (1 if enabled else 0),
|
|
rgb=light["rgb"],
|
|
pos=light["pos"],
|
|
falloff=light["falloff"],
|
|
)
|
|
)
|
|
profiles.append(
|
|
SpriteShadingProfile(
|
|
name=profile["name"],
|
|
ambient=profile["ambient"],
|
|
power=profile["power"],
|
|
lights=lights,
|
|
)
|
|
)
|
|
groups.append(
|
|
SpriteShadingGroup(
|
|
area=group["area"],
|
|
profiles=profiles,
|
|
)
|
|
)
|
|
return groups
|
|
|
|
|
|
def build(
|
|
input: Path,
|
|
bin_out: Path,
|
|
header_out: Path,
|
|
endian: Literal["big", "little"] = "big",
|
|
matching: bool = True,
|
|
):
|
|
END = ">" if endian == "big" else "<"
|
|
|
|
with open(input, "r") as f:
|
|
json_data = json.load(f)
|
|
|
|
groups = groups_from_json(json_data)
|
|
|
|
# Header creation
|
|
with open(header_out, "w") as f:
|
|
f.write("#ifndef SHADING_PROFILES_H\n")
|
|
f.write("#define SHADING_PROFILES_H\n")
|
|
f.write(f"/* This file is auto-generated from {input.name}. Do not edit. */\n\n")
|
|
f.write("enum ShadingProfile {\n")
|
|
f.write(" SHADING_NONE = 0xFFFFFFFF,\n")
|
|
|
|
for g, group in enumerate(groups):
|
|
for i, profile in enumerate(group.profiles):
|
|
f.write(f" SHADING_{profile.name.upper()} = 0x{(g << 16) | i:08X},\n")
|
|
f.write("};\n")
|
|
f.write("#endif // SHADING_PROFILES_H\n")
|
|
|
|
data_offset = 0
|
|
profile_list_offset = len(groups) * 8
|
|
offsets_table = bytearray()
|
|
profile_lists = bytearray()
|
|
data_table = bytearray()
|
|
for g, group in enumerate(groups):
|
|
offsets_table.extend(struct.pack(END + "i", data_offset))
|
|
offsets_table.extend(struct.pack(END + "i", profile_list_offset))
|
|
|
|
group_data_offset = 0
|
|
|
|
for profile in group.profiles:
|
|
profile_lists.extend(struct.pack(END + "i", group_data_offset))
|
|
data_table.extend(struct.pack(END + "B", len(profile.lights)))
|
|
data_table.append(0)
|
|
data_table.extend(struct.pack(END + "B", profile.ambient[0]))
|
|
data_table.extend(struct.pack(END + "B", profile.ambient[1]))
|
|
data_table.extend(struct.pack(END + "B", profile.ambient[2]))
|
|
data_table.extend(struct.pack(END + "B", profile.power))
|
|
group_data_offset += 6
|
|
profile_list_offset += 4
|
|
|
|
for light in profile.lights:
|
|
data_table.extend(struct.pack(END + "B", light.flags))
|
|
data_table.extend(struct.pack(END + "B", light.rgb[0]))
|
|
data_table.extend(struct.pack(END + "B", light.rgb[1]))
|
|
data_table.extend(struct.pack(END + "B", light.rgb[2]))
|
|
data_table.extend(struct.pack(END + "h", light.pos[0]))
|
|
data_table.extend(struct.pack(END + "h", light.pos[1]))
|
|
data_table.extend(struct.pack(END + "h", light.pos[2]))
|
|
data_table.extend(struct.pack(END + "f", light.falloff))
|
|
data_table.append(0)
|
|
data_table.append(0)
|
|
group_data_offset += 0x10
|
|
data_offset += group_data_offset
|
|
|
|
offsets_table.extend(profile_lists)
|
|
if matching:
|
|
offsets_table += b"\0" * (0x1D0 - len(offsets_table)) # Pad to 0x1D0
|
|
|
|
final_data = offsets_table + data_table
|
|
if matching:
|
|
final_data += b"\0" * (0xE70 - len(final_data)) # Pad to 0xE70
|
|
|
|
with open(bin_out, "wb") as f:
|
|
f.write(final_data)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description="Sprite shading profiles")
|
|
parser.add_argument("input", type=Path, help="Input JSON file")
|
|
parser.add_argument("bin_out", type=Path, help="Output binary file path")
|
|
parser.add_argument("header_out", type=Path, help="Output header file path")
|
|
parser.add_argument("--endian", choices=["big", "little"], default="big", help="Output endianness")
|
|
parser.add_argument("--matching", help="Pad to matching size", default=True)
|
|
args = parser.parse_args()
|
|
|
|
build(args.input, args.bin_out, args.header_out, args.endian, args.matching)
|