1
0
Fork 0
mirror of https://github.com/zeldaret/oot.git synced 2025-08-08 15:30:14 +00:00

New assets system (#2481)

* wip: New assets system tm

Builds gc-eu-mq-dbg OK from clean after
1) make setup
2) python3 -m tools.assets.extract -j
3) replace 0x80A8E610 with sShadowTex in extracted/gc-eu-mq-dbg/assets/overlays/ovl_En_Jsjutan/sShadowMaterialDL.inc.c
4) make various symbols in extracted data like sTex static

* use variables from config.yml for gMtxClear and sShadowTex addresses

* Write source with static for overlays using `HACK_IS_STATIC_ON` hack

* gc-eu-mq-dbg OK from clean with `make setup && make`

* implement more skeleton-related types, cleanups, fixups

* fix extracted data to no longer produce compilation warnings

* implement more of RoomShapeImage types

* yeet XmlPath from ExternalFile usage

* Implement PlayerAnimationDataResource (link_animetion data)

* fix csdis CS_TIME extra arg

* dmadata file names no longer hardcoded for gc-eu-mq-dbg

* ntsc-1.0 OK

* xml fixes

* slightly improve standard output

* rm extract_assets.py

* generate and use Limb enums (TODO: check Skin skels and implement for Curve skels)

* handle dependencies between xmls

* introduce RawPointers xml attribute to ignore specific pointers and keep them raw

* add tools/extract_assets.sh

* fixups

* only extract if xmls changed or if -f (force) is used

* fixups, gc-eu OK

* all versions OK

* check attributes of xml resources elements

* Implement legacy skelanime resources

* fix ASSET_FILES_BIN_EXTRACTED/COMMITTED: look for .u8.bin specifically instead of just .bin

* implement JFIFResource

* fix png/jpg wildcards: look specifically for .u64.png .u32.png .u64.jpg

* Makefile: Add rules to build .png, .bin and .jpg in assets/ too

* start writing actual docs

* extract sTransCircleDL and sTransWipeDL

* misc cleanup/fixes, pygfxd 1.0.3

* refactor CDataExt.set_write callback args to use a dataclass

* Move {} to in-source

* misc

* more progress on spec

* fix missing braces in n64dd_error_textures.c

* finish xml spec doc

* assets xmls fixes

* some cleanup, use `gNameTex_WIDTH/HEIGHT` macros in dlists

* handle hackmode_syotes_room, fix compile

* C build_from_png

* rm tools/assets/bin2c

* rm ZAPD

* format

* remove rule to generate dmadata_table.py

* CC0 license (and some import cleanup)

* dont try to build zapd (rmd)

* simplify palettes with single user (ci images with a non-shared palette)

* add docs on how images are handled

* bss

* allow -j N

* fix n64texconv python bindings memory management

* move -j at the end of calling extraction script

* with -j, update last_extracts.json as each job completes rather than only if all complete

* make interrupting less jank by making child processes ignore sigint

* use enum names in `SCENE_CMD_SKYBOX_SETTINGS`

* `multiprocessing.get_context("fork")`

* import rich, except ImportError s

* fix optional rich usage

* .bss

* .bss

* .bss

* assets extraction: -j -> -j$(N_THREADS)

* .bss

* change LIMB_NONE/MAX defaults to be FILE_OFFSET instead of SKELNAME

* 0XHEX -> 0xHEX

* fix bss

* Proper includes for assets

mostly proper, some includes like dlists resources always causing a sys_matrix.h include (when not every dlist references gIdentityMtx) could be done better

* rm z64.h

* rm z64.h take two

* bss

* Make .u64 suffix for pngs optional

* fixup: rm .u64 suffix from n64dd image paths

* Remove elemtype suffixes from .bin and .jpg files

* Update images.md

* some build_from_png cleanup, more error handling, comments

* Handle skybox textures

Introduce "sub-format" suffix for pngs, with sub-formats split_lo and split_hi being used for skybox textures

* fixup for older python

* improve collision output some

* fully use SURFACETYPE[01] macros in writing extracted surface types

* use WATERBOX_PROPERTIES in extracted waterboxes

* some SceneCommandsResource cleanup

* format EnvLightSettingsList output
This commit is contained in:
Dragorn421 2025-05-18 01:29:09 +02:00 committed by GitHub
parent 0c6c112cb9
commit 1e556e3a3d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
460 changed files with 14342 additions and 48656 deletions

View file

@ -0,0 +1,205 @@
# SPDX-FileCopyrightText: © 2025 ZeldaRET
# SPDX-License-Identifier: CC0-1.0
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ..extase.memorymap import MemoryContext
from ..extase import (
RESOURCE_PARSE_SUCCESS,
ResourceParseWaiting,
File,
)
from ..extase.cdata_resources import (
CDataResource,
CDataExt_Value,
CDataExt_Struct,
CDataExt_Array,
CDataExtWriteContext,
)
class AnimationFrameDataResource(CDataResource, can_size_be_unknown=True):
def write_binang(resource, memory_context, v, wctx: CDataExtWriteContext):
wctx.f.write(wctx.line_prefix)
wctx.f.write(f" 0x{v:04X}" if v >= 0 else "-0x" + f"{v:04X}".removeprefix("-"))
return True
elem_cdata_ext = CDataExt_Value("h").set_write(write_binang)
def __init__(self, file: File, range_start: int, name: str):
super().__init__(file, range_start, name)
self.length = None
def try_parse_data(self, memory_context):
if self.length is not None:
self.cdata_ext = CDataExt_Array(self.elem_cdata_ext, self.length)
self.range_end = self.range_start + self.cdata_ext.size
return super().try_parse_data(memory_context)
else:
raise ResourceParseWaiting(waiting_for=["self.length"])
def get_c_declaration_base(self):
return f"s16 {self.symbol_name}[]"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return self.symbol_name
else:
raise ValueError()
def get_h_includes(self):
return ("ultra64.h",)
class AnimationJointIndicesResource(CDataResource, can_size_be_unknown=True):
elem_cdata_ext = CDataExt_Struct(
(
("x", CDataExt_Value.u16),
("y", CDataExt_Value.u16),
("z", CDataExt_Value.u16),
)
)
def __init__(self, file: File, range_start: int, name: str):
super().__init__(file, range_start, name)
self.length = None
def try_parse_data(self, memory_context):
if self.length is not None:
self.cdata_ext = CDataExt_Array(self.elem_cdata_ext, self.length)
self.range_end = self.range_start + self.cdata_ext.size
return super().try_parse_data(memory_context)
else:
raise ResourceParseWaiting(waiting_for=["self.length"])
def get_c_declaration_base(self):
return f"JointIndex {self.symbol_name}[]"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return self.symbol_name
else:
raise ValueError()
def get_h_includes(self):
return ("z64animation.h",)
class AnimationResource(CDataResource):
def write_frameData(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
def write_jointIndices(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
cdata_ext = CDataExt_Struct(
(
(
"common",
CDataExt_Struct((("frameCount", CDataExt_Value.s16),)),
),
("pad2", CDataExt_Value.pad16),
(
"frameData",
CDataExt_Value("I").set_write(write_frameData),
),
(
"jointIndices",
CDataExt_Value("I").set_write(write_jointIndices),
),
("staticIndexMax", CDataExt_Value.u16),
("padE", CDataExt_Value.pad16),
)
)
def try_parse_data(self, memory_context):
super().try_parse_data(memory_context)
frameData_address = self.cdata_unpacked["frameData"]
assert isinstance(frameData_address, int)
resource_frameData = memory_context.report_resource_at_segmented(
self,
frameData_address,
AnimationFrameDataResource,
lambda file, offset: AnimationFrameDataResource(
file,
offset,
f"{self.name}_{frameData_address:08X}_FrameData",
),
)
jointIndices_address = self.cdata_unpacked["jointIndices"]
assert isinstance(jointIndices_address, int)
resource_jointIndices = memory_context.report_resource_at_segmented(
self,
jointIndices_address,
AnimationJointIndicesResource,
lambda file, offset: AnimationJointIndicesResource(
file,
offset,
f"{self.name}_{jointIndices_address:08X}_JointIndices",
),
)
# The length of the frameData and jointIndices arrays is
# for now assumed to fill the space to the animation,
# at the very least before subtracting the offsets check that
# the offsets belong to the same file
# TODO better idea for computing this data's size
if not (resource_frameData.file == resource_jointIndices.file == self.file):
raise NotImplementedError(
"Expected frameData and jointIndices to be in the same file as the animation",
self.cdata_unpacked,
resource_frameData.file,
resource_jointIndices.file,
self.file,
)
if (
resource_frameData.range_start
< resource_jointIndices.range_start
< self.range_start
):
resource_frameData.length = (
resource_jointIndices.range_start - resource_frameData.range_start
) // AnimationFrameDataResource.elem_cdata_ext.size
resource_jointIndices.length = (
self.range_start - resource_jointIndices.range_start
) // AnimationJointIndicesResource.elem_cdata_ext.size
else:
raise NotImplementedError(
"Expected offsets of frameData, jointIndices, animation to be in order",
self.cdata_unpacked,
hex(resource_frameData.range_start),
hex(resource_jointIndices.range_start),
hex(self.range_start),
)
return RESOURCE_PARSE_SUCCESS
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return f"&{self.symbol_name}"
else:
raise ValueError()
def get_c_declaration_base(self):
return f"AnimationHeader {self.symbol_name}"
def get_h_includes(self):
return ("z64animation.h",)

View file

@ -0,0 +1,812 @@
# SPDX-FileCopyrightText: © 2025 ZeldaRET
# SPDX-License-Identifier: CC0-1.0
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
from ..extase.memorymap import MemoryContext
from ..extase import (
File,
RESOURCE_PARSE_SUCCESS,
ResourceParseInProgress,
ResourceParseWaiting,
)
from ..extase.cdata_resources import (
CDataResource,
CDataExt_Struct,
CDataExt_Array,
CDataExt_Value,
CDataExtWriteContext,
cdata_ext_Vec3s,
cdata_ext_Vec3s_aligned,
INDENT,
fmt_hex_u,
)
from .. import oot64_data
# TODO would be better for array resources to be of unknown size at instanciation
# and have their size set later, like LimbsArrayResource,
# which allows declaring them with offsets in xmls and have the data parsing
# fill in the length for both cases of it instantiating the array,
# and it being instantiated much earlier from the xml
class CollisionVtxListResource(CDataResource):
cdata_ext_elem = cdata_ext_Vec3s_aligned
def __init__(self, file: File, range_start: int, name: str, length: int):
self.cdata_ext = CDataExt_Array(self.cdata_ext_elem, length)
super().__init__(file, range_start, name)
def get_c_declaration_base(self):
if hasattr(self, "HACK_IS_STATIC_ON"):
return f"Vec3s {self.symbol_name}[{self.cdata_ext.length}]"
return f"Vec3s {self.symbol_name}[]"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return self.symbol_name
else:
raise ValueError()
def get_c_expression_length(self, resource_offset: int):
if resource_offset == 0:
return f"ARRAY_COUNT({self.symbol_name})"
else:
raise ValueError()
def get_h_includes(self):
return ("z64math.h",)
class CollisionPolyListResource(CDataResource):
def write_vtxData(
resource: "CollisionPolyListResource",
memory_context: "MemoryContext",
v,
wctx: CDataExtWriteContext,
):
assert isinstance(v, list)
assert len(v) == 3
vtxData = v
f = wctx.f
f.write(wctx.line_prefix)
f.write("{\n")
for i in range(3):
vI = vtxData[i]
vtxId = vI & 0x1FFF
flags = (vI & 0xE000) >> 13
flags_str_list = []
if i == 0:
if flags & 1:
flags &= ~1
flags_str_list.append("COLPOLY_IGNORE_CAMERA")
if flags & 2:
flags &= ~2
flags_str_list.append("COLPOLY_IGNORE_ENTITY")
if flags & 4:
flags &= ~4
flags_str_list.append("COLPOLY_IGNORE_PROJECTILES")
elif i == 1:
if flags & 1:
flags &= ~1
flags_str_list.append("COLPOLY_IS_FLOOR_CONVEYOR")
if flags != 0:
flags_str_list.append(f"0x{flags:X}")
if flags_str_list:
flags_str = " | ".join(flags_str_list)
else:
flags_str = "0"
f.write(wctx.line_prefix)
f.write(INDENT)
f.write(f"COLPOLY_VTX({vtxId}, {flags_str}), // {i}\n")
f.write(wctx.line_prefix)
f.write("}")
return True
def write_normal_component(
resource: "CollisionPolyListResource",
memory_context: "MemoryContext",
v,
wctx: CDataExtWriteContext,
):
assert isinstance(v, int)
nf = v / 0x7FFF
if int(round(nf, 5) * 0x7FFF) != v:
if v < 0:
nf -= 0.000_01
elif v > 0:
nf += 0.000_01
ns = f"{nf:.5f}"
while ns[-1] == "0" and ns[-2] != ".":
ns = ns[:-1]
wctx.f.write(wctx.line_prefix)
wctx.f.write(f"COLPOLY_SNORMAL({ns})")
return True
normal_component = CDataExt_Value("h").set_write(write_normal_component)
cdata_ext_elem = CDataExt_Struct(
(
("type", CDataExt_Value.u16),
("vtxData", CDataExt_Array(CDataExt_Value.u16, 3).set_write(write_vtxData)),
(
"normal",
CDataExt_Struct(
(
("x", normal_component),
("y", normal_component),
("z", normal_component),
)
),
),
("dist", CDataExt_Value.s16),
)
)
def __init__(self, file: File, range_start: int, name: str, length: int):
self.cdata_ext = CDataExt_Array(self.cdata_ext_elem, length)
super().__init__(file, range_start, name)
def try_parse_data(self, memory_context):
super().try_parse_data(memory_context)
self.max_surface_type_index = max(elem["type"] for elem in self.cdata_unpacked)
assert isinstance(self.max_surface_type_index, int)
return RESOURCE_PARSE_SUCCESS
def get_c_declaration_base(self):
if hasattr(self, "HACK_IS_STATIC_ON"):
return f"CollisionPoly {self.symbol_name}[{self.cdata_ext.length}]"
return f"CollisionPoly {self.symbol_name}[]"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return self.symbol_name
else:
raise ValueError()
def get_c_expression_length(self, resource_offset: int):
if resource_offset == 0:
return f"ARRAY_COUNT({self.symbol_name})"
else:
raise ValueError()
def get_h_includes(self):
return ("z64bgcheck.h",)
class CollisionSurfaceTypeListResource(CDataResource):
def write_data(
resource: "CollisionSurfaceTypeListResource",
memory_context: "MemoryContext",
v,
wctx: CDataExtWriteContext,
):
assert isinstance(v, list)
assert len(v) == 2
f = wctx.f
f.write(wctx.line_prefix)
f.write("{\n")
for i_data, bitfield_info in (
(
0,
(
(0x000000FF, 0, "bgCamIndex", int),
(0x00001F00, 8, "exitIndex", int),
(0x0003E000, 13, "floorType", oot64_data.misc_ids.FLOOR_TYPES),
(0x001C0000, 18, "unk18", int),
(0x03E00000, 21, "wallType", oot64_data.misc_ids.WALL_TYPES),
(
0x3C000000,
26,
"floorProperty",
oot64_data.misc_ids.FLOOR_PROPERTIES,
),
(0x40000000, 30, "isSoft", bool),
(0x80000000, 31, "isHorseBlocked", bool),
),
),
(
1,
(
(0x0000000F, 0, "material", oot64_data.misc_ids.SURFACE_MATERIALS),
(0x00000030, 4, "floorEffect", oot64_data.misc_ids.FLOOR_EFFECTS),
(0x000007C0, 6, "lightSetting", int),
(0x0001F800, 11, "echo", int),
(0x00020000, 17, "canHookshot", bool),
(
0x001C0000,
18,
"conveyorSpeed",
oot64_data.misc_ids.CONVEYOR_SPEEDS,
),
(
0x07E00000,
21,
"conveyorDirection",
lambda val: f"CONVEYOR_DIRECTION_FROM_BINANG({fmt_hex_u(val * (0x10000 // 64))})",
),
(0x08000000, 27, "unk27", bool),
),
),
):
data_val = v[i_data]
f.write(wctx.line_prefix)
f.write(INDENT)
f.write(f"SURFACETYPE{i_data}(\n")
lines = []
for mask, shift, name, fmt_info in bitfield_info:
val = (data_val & mask) >> shift
if fmt_info == int:
lines.append(f"/* {name} */ {val}")
elif fmt_info == bool:
assert val in {0, 1}
lines.append(f"/* {name} */ {'true' if val else 'false'}")
elif isinstance(fmt_info, dict):
lines.append(fmt_info[val])
elif callable(fmt_info):
lines.append(fmt_info(val))
else:
lines.append(f"/* {name} */ {val}")
f.write(",\n".join(f"{wctx.line_prefix}{INDENT * 2}{_l}" for _l in lines))
f.write("\n")
f.write(wctx.line_prefix)
f.write(INDENT)
f.write("),\n")
f.write(wctx.line_prefix)
f.write("}")
return True
cdata_ext_elem = CDataExt_Struct(
(("data", CDataExt_Array(CDataExt_Value.u32, 2).set_write(write_data)),)
)
def __init__(self, file: File, range_start: int, name: str, length: int):
self.cdata_ext = CDataExt_Array(self.cdata_ext_elem, length)
super().__init__(file, range_start, name)
def try_parse_data(self, memory_context):
super().try_parse_data(memory_context)
self.max_bgCamIndex = max(
elem["data"][0] & 0xFF for elem in self.cdata_unpacked
)
self.max_exitIndex = max(
(elem["data"][0] & 0x00001F00) >> 8 for elem in self.cdata_unpacked
)
return RESOURCE_PARSE_SUCCESS
def get_c_declaration_base(self):
if hasattr(self, "HACK_IS_STATIC_ON"):
return f"SurfaceType {self.symbol_name}[{self.cdata_ext.length}]"
return f"SurfaceType {self.symbol_name}[]"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return self.symbol_name
else:
raise ValueError()
def get_c_includes(self):
return ("stdbool.h",)
def get_h_includes(self):
return ("z64bgcheck.h",)
class BgCamFuncDataResource(CDataResource):
element_cdata_ext = cdata_ext_Vec3s
def __init__(self, file: File, range_start: int, range_end: int, name: str):
count = (range_end - range_start) // self.element_cdata_ext.size
self.cdata_ext = CDataExt_Array(self.element_cdata_ext, count)
super().__init__(file, range_start, name)
def get_c_declaration_base(self):
if hasattr(self, "HACK_IS_STATIC_ON"):
return f"Vec3s {self.symbol_name}[{self.cdata_ext.length}]"
return f"Vec3s {self.symbol_name}[]"
def get_c_reference(self, resource_offset: int):
if resource_offset % self.element_cdata_ext.size != 0:
raise ValueError(
"unaligned offset into Vec3s array (BgCamFuncData)",
hex(resource_offset),
self.element_cdata_ext.size,
)
index = resource_offset // self.element_cdata_ext.size
return f"&{self.symbol_name}[{index}]"
def get_h_includes(self):
return ("z64math.h",)
class CollisionBgCamListResource(CDataResource):
def write_bgCamFuncData(
resource: "CollisionSurfaceTypeListResource",
memory_context: "MemoryContext",
v,
wctx: CDataExtWriteContext,
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
if address != 0:
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
else:
wctx.f.write("NULL")
return True
cdata_ext_elem = CDataExt_Struct(
(
(
"setting",
CDataExt_Value("H").set_write_str_v(
lambda v: oot64_data.get_camera_setting_type_name(v)
),
),
("count", CDataExt_Value.s16),
(
"bgCamFuncData",
CDataExt_Value("I").set_write(write_bgCamFuncData),
), # Vec3s*
)
)
def __init__(self, file: File, range_start: int, name: str, length: int):
self.cdata_ext = CDataExt_Array(self.cdata_ext_elem, length)
super().__init__(file, range_start, name)
def try_parse_data(self, memory_context):
super().try_parse_data(memory_context)
# Note: operating directly on the segmented addresses here,
# so assuming from the start all bgCamFuncData use the same segment
bgCamFuncData_buffer_start = None
bgCamFuncData_buffer_end = None
for bgCamInfo in self.cdata_unpacked:
count = bgCamInfo["count"]
assert isinstance(count, int)
bgCamFuncData = bgCamInfo["bgCamFuncData"]
assert isinstance(bgCamFuncData, int)
if bgCamFuncData == 0:
continue
if bgCamFuncData_buffer_start is None:
bgCamFuncData_buffer_start = bgCamFuncData
bgCamFuncData_buffer_end = (
bgCamFuncData + count * BgCamFuncDataResource.element_cdata_ext.size
)
continue
assert bgCamFuncData_buffer_start is not None
assert bgCamFuncData_buffer_end is not None
if bgCamFuncData != bgCamFuncData_buffer_end:
raise NotImplementedError(
"bgCamFuncData buffer not used in the same order as its elements"
)
bgCamFuncData_buffer_end += (
count * BgCamFuncDataResource.element_cdata_ext.size
)
if bgCamFuncData_buffer_start is not None:
assert bgCamFuncData_buffer_end is not None
memory_context.report_resource_at_segmented(
self,
bgCamFuncData_buffer_start,
BgCamFuncDataResource,
lambda file, offset: BgCamFuncDataResource(
file,
offset,
offset + bgCamFuncData_buffer_end - bgCamFuncData_buffer_start,
f"{self.name}_{bgCamFuncData_buffer_start:08X}_BgCamFuncData",
),
)
return RESOURCE_PARSE_SUCCESS
def get_c_declaration_base(self):
if hasattr(self, "HACK_IS_STATIC_ON"):
return f"BgCamInfo {self.symbol_name}[{self.cdata_ext.length}]"
return f"BgCamInfo {self.symbol_name}[]"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return self.symbol_name
else:
raise ValueError()
def get_c_includes(self):
return ("z64camera.h",)
def get_h_includes(self):
return ("z64bgcheck.h",)
class CollisionWaterBoxesResource(CDataResource):
def write_properties(v):
bgCamIndex = (v >> 0) & 0xFF
lightIndex = (v >> 8) & 0x1F
room = (v >> 13) & 0x3F
setFlag19 = (v >> 19) & 1
return f"WATERBOX_PROPERTIES(/* bgCamIndex */ {bgCamIndex}, /* lightIndex */ {lightIndex}, /* room */ {room}, /* setFlag19 */ {'true' if setFlag19 else 'false'})"
elem_cdata_ext = CDataExt_Struct(
(
("xMin", CDataExt_Value.s16),
("ySurface", CDataExt_Value.s16),
("zMin", CDataExt_Value.s16),
("xLength", CDataExt_Value.s16),
("zLength", CDataExt_Value.s16),
("pad12", CDataExt_Value.pad16),
("properties", CDataExt_Value("I").set_write_str_v(write_properties)),
)
)
def __init__(self, file: File, range_start: int, name: str, length: int):
self.cdata_ext = CDataExt_Array(self.elem_cdata_ext, length)
super().__init__(file, range_start, name)
def get_c_declaration_base(self):
return f"WaterBox {self.symbol_name}[]"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return self.symbol_name
else:
raise ValueError
def get_c_expression_length(self, resource_offset: int):
if resource_offset == 0:
return f"ARRAY_COUNT({self.symbol_name})"
else:
raise ValueError
def get_c_includes(self):
return ("stdbool.h",)
def get_h_includes(self):
return ("z64bgcheck.h",)
def transfer_HACK_IS_STATIC_ON(source, dest):
if hasattr(source, "HACK_IS_STATIC_ON"):
dest.HACK_IS_STATIC_ON = source.HACK_IS_STATIC_ON
return dest
class CollisionResource(CDataResource):
def write_numVertices(
resource: "CollisionResource",
memory_context: "MemoryContext",
v,
wctx: CDataExtWriteContext,
):
wctx.f.write(wctx.line_prefix)
wctx.f.write(
memory_context.get_c_expression_length_at_segmented(
resource.cdata_unpacked["vtxList"]
)
)
return True
def report_vtxList(
resource: "CollisionResource", memory_context: "MemoryContext", v
):
assert isinstance(v, int)
address = v
memory_context.report_resource_at_segmented(
resource,
address,
CollisionVtxListResource,
lambda file, offset: transfer_HACK_IS_STATIC_ON(
resource,
CollisionVtxListResource(
file,
offset,
f"{resource.name}_{address:08X}_VtxList",
resource.cdata_unpacked["numVertices"],
),
),
)
def write_vtxList(
resource: "CollisionResource",
memory_context: "MemoryContext",
v,
wctx: CDataExtWriteContext,
):
assert isinstance(v, int)
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(v))
return True
def write_numPolygons(
resource: "CollisionResource",
memory_context: "MemoryContext",
v,
wctx: CDataExtWriteContext,
):
wctx.f.write(wctx.line_prefix)
wctx.f.write(
memory_context.get_c_expression_length_at_segmented(
resource.cdata_unpacked["polyList"]
)
)
return True
def report_polyList(
resource: "CollisionResource", memory_context: "MemoryContext", v
):
assert isinstance(v, int)
address = v
resource.resource_polyList = memory_context.report_resource_at_segmented(
resource,
address,
CollisionPolyListResource,
lambda file, offset: transfer_HACK_IS_STATIC_ON(
resource,
CollisionPolyListResource(
file,
offset,
f"{resource.name}_{address:08X}_PolyList",
resource.cdata_unpacked["numPolygons"],
),
),
)
def write_polyList(
resource: "CollisionResource",
memory_context: "MemoryContext",
v,
wctx: CDataExtWriteContext,
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
def write_numWaterBoxes(
resource: "CollisionResource",
memory_context: "MemoryContext",
v,
wctx: CDataExtWriteContext,
):
wctx.f.write(wctx.line_prefix)
length = resource.cdata_unpacked["numWaterBoxes"]
if length != 0:
wctx.f.write(
memory_context.get_c_expression_length_at_segmented(
resource.cdata_unpacked["waterBoxes"]
)
)
else:
wctx.f.write("0")
return True
def report_waterBoxes(
resource: "CollisionResource", memory_context: "MemoryContext", v
):
assert isinstance(v, int)
address = v
length = resource.cdata_unpacked["numWaterBoxes"]
if length != 0:
assert address != 0, address # should not be NULL
memory_context.report_resource_at_segmented(
resource,
address,
CollisionWaterBoxesResource,
lambda file, offset: transfer_HACK_IS_STATIC_ON(
resource,
CollisionWaterBoxesResource(
file,
offset,
f"{resource.name}_{address:08X}_WaterBoxes",
length,
),
),
)
def write_surfaceTypeList(
resource: "CollisionResource",
memory_context: "MemoryContext",
v,
wctx: CDataExtWriteContext,
):
assert isinstance(v, int)
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(v))
return True
def write_bgCamList(
resource: "CollisionResource",
memory_context: "MemoryContext",
v,
wctx: CDataExtWriteContext,
):
assert isinstance(v, int)
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(v))
return True
def write_waterBoxes(
resource: "CollisionResource",
memory_context: "MemoryContext",
v,
wctx: CDataExtWriteContext,
):
assert isinstance(v, int)
length = resource.cdata_unpacked["numWaterBoxes"]
f = wctx.f
f.write(wctx.line_prefix)
if length != 0:
f.write(memory_context.get_c_reference_at_segmented(v))
else:
if v == 0:
f.write("NULL")
else:
f.write(f"0x{v:08X}")
return True
cdata_ext = CDataExt_Struct(
(
("minBounds", cdata_ext_Vec3s),
("maxBounds", cdata_ext_Vec3s),
("numVertices", CDataExt_Value("H").set_write(write_numVertices)),
("pad14", CDataExt_Value.pad16),
(
"vtxList",
CDataExt_Value("I").set_report(report_vtxList).set_write(write_vtxList),
), # Vec3s*
("numPolygons", CDataExt_Value("H").set_write(write_numPolygons)),
("pad22", CDataExt_Value.pad16),
(
"polyList",
CDataExt_Value("I")
.set_report(report_polyList)
.set_write(write_polyList),
), # CollisionPoly*
(
"surfaceTypeList",
CDataExt_Value("I").set_write(write_surfaceTypeList),
), # SurfaceType*
("bgCamList", CDataExt_Value("I").set_write(write_bgCamList)), # BgCamInfo*
("numWaterBoxes", CDataExt_Value("H").set_write(write_numWaterBoxes)),
("pad38", CDataExt_Value.pad16),
(
"waterBoxes",
CDataExt_Value("I")
.set_report(report_waterBoxes)
.set_write(write_waterBoxes),
), # WaterBox*
)
)
def __init__(self, file: File, range_start: int, name: str):
super().__init__(file, range_start, name)
self.length_exitList: Optional[int] = None
self.resource_polyList: Optional[CollisionPolyListResource] = None
self.resource_surfaceTypeList: Optional[CollisionSurfaceTypeListResource] = None
self.resource_bgCamList: Optional[CollisionBgCamListResource] = None
def try_parse_data(self, memory_context):
super().try_parse_data(memory_context)
assert self.resource_polyList is not None
new_progress_done = []
waiting_for = []
# If the CollisionPolyListResource is parsed
if self.resource_polyList.is_data_parsed:
# report surfaceTypeList based on its length guessed from polyList data
length_surfaceTypeList = self.resource_polyList.max_surface_type_index + 1
surfaceTypeList_address = self.cdata_unpacked["surfaceTypeList"]
assert isinstance(surfaceTypeList_address, int)
self.resource_surfaceTypeList = memory_context.report_resource_at_segmented(
self,
surfaceTypeList_address,
CollisionSurfaceTypeListResource,
lambda file, offset: transfer_HACK_IS_STATIC_ON(
self,
CollisionSurfaceTypeListResource(
file,
offset,
f"{self.name}_{surfaceTypeList_address:08X}_SurfaceTypes",
length_surfaceTypeList, # TODO change CollisionSurfaceTypeListResource to a CDataArrayResource (same with more resources)
),
),
)
new_progress_done.append("reported CollisionSurfaceTypeListResource")
else:
waiting_for.append(
(
"waiting for CollisionPolyListResource"
" to be parsed to report CollisionSurfaceTypeListResource",
self.resource_polyList,
)
)
if self.resource_surfaceTypeList is not None:
# If the CollisionSurfaceTypeListResource is parsed
if self.resource_surfaceTypeList.is_data_parsed:
# report bgCamList based on its length guessed from surfaceTypeList data
length_bgCamList = self.resource_surfaceTypeList.max_bgCamIndex + 1
bgCamList_address = self.cdata_unpacked["bgCamList"]
assert isinstance(bgCamList_address, int)
self.resource_bgCamList = memory_context.report_resource_at_segmented(
self,
bgCamList_address,
CollisionBgCamListResource,
lambda file, offset: transfer_HACK_IS_STATIC_ON(
self,
CollisionBgCamListResource(
file,
offset,
f"{self.name}_{bgCamList_address:08X}_BgCamList",
length_bgCamList,
),
),
)
# exitIndex is 1-indexed, so e.g. if the max is 1 the list is of length 1.
self.length_exitList = self.resource_surfaceTypeList.max_exitIndex
new_progress_done.append("reported CollisionBgCamListResource")
else:
waiting_for.append(
(
"waiting for CollisionSurfaceTypeListResource"
" to be parsed to report CollisionBgCamListResource",
self.resource_surfaceTypeList,
)
)
else:
waiting_for.append("self.resource_surfaceTypeList")
if waiting_for:
if new_progress_done:
raise ResourceParseInProgress(
new_progress_done=new_progress_done, waiting_for=waiting_for
)
else:
raise ResourceParseWaiting(waiting_for=waiting_for)
assert (
self.resource_surfaceTypeList is not None
and self.resource_bgCamList is not None
and self.length_exitList is not None
)
return RESOURCE_PARSE_SUCCESS
def get_c_declaration_base(self):
return f"CollisionHeader {self.symbol_name}"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return f"&{self.symbol_name}"
else:
raise ValueError()
def get_c_includes(self):
return ("array_count.h",)
def get_h_includes(self):
return ("z64bgcheck.h",)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,63 @@
# SPDX-FileCopyrightText: © 2025 ZeldaRET
# SPDX-License-Identifier: CC0-1.0
import struct
from ..extase import (
RESOURCE_PARSE_SUCCESS,
File,
Resource,
)
from tools import csdis
class CutsceneResource(Resource, can_size_be_unknown=True):
def __init__(self, file: File, range_start: int, name: str):
super().__init__(file, range_start, None, name)
def try_parse_data(self, memory_context):
assert self.range_start % 4 == 0
data = self.file.data[self.range_start :]
num_bytes = len(data)
if num_bytes % 4 != 0:
data = data[: -(num_bytes % 4)]
data_words = [unpacked[0] for unpacked in struct.iter_unpack(">I", data)]
size_words, cs_source = csdis.disassemble_cutscene(data_words)
self.range_end = self.range_start + 4 * size_words
self.cs_source = cs_source
return RESOURCE_PARSE_SUCCESS
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return self.symbol_name
else:
raise ValueError
extracted_path_suffix = ".inc.c"
def get_filename_stem(self):
return f"{self.name}.csdata"
def write_extracted(self, memory_context):
with self.extract_to_path.open("w") as f:
if not self.braces_in_source:
f.write("{\n")
f.write(self.cs_source)
if not self.braces_in_source:
f.write("}\n")
def get_c_declaration_base(self):
return f"CutsceneData {self.symbol_name}[]"
def get_c_includes(self):
return (
"z64cutscene_commands.h",
# TODO these are not always needed:
"z64ocarina.h", # for OCARINA_ACTION_*
"z64player.h", # for PLAYER_CUEID_*
)
def get_h_includes(self):
return ("z64cutscene.h",)

View file

@ -0,0 +1,102 @@
# SPDX-FileCopyrightText: © 2025 ZeldaRET
# SPDX-License-Identifier: CC0-1.0
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ..extase import MemoryContext
from ..extase.cdata_resources import (
CDataResource,
CDataArrayResource,
CDataExt_Struct,
CDataExt_Value,
CDataExtWriteContext,
fmt_hex_s,
)
class PlayerAnimationDataResource(CDataArrayResource):
elem_cdata_ext = CDataExt_Value("h").set_write_str_v(lambda v: fmt_hex_s(v))
def __init__(self, file, range_start, name):
super().__init__(file, range_start, name)
self.frame_count_name = f"FRAMECOUNT_{self.symbol_name}"
def set_frame_count(self, frame_count: int):
self.set_length(frame_count * (22 * 3 + 1))
self.frame_count = frame_count
def write_c_declaration(self, h):
h.write(f"#define {self.frame_count_name} {self.frame_count}\n")
super().write_c_declaration(h)
def get_c_declaration_base(self):
return f"s16 {self.symbol_name}[{self.frame_count_name} * (PLAYER_LIMB_MAX * 3 + 1)]"
def get_h_includes(self):
return ("ultra64.h", "z64player.h")
class PlayerAnimationResource(CDataResource):
def report(resource, memory_context: "MemoryContext", v):
assert isinstance(v, dict)
segment = v["segment"]
assert isinstance(segment, int)
player_animation_data_res = memory_context.report_resource_at_segmented(
resource,
segment,
PlayerAnimationDataResource,
lambda file, offset: PlayerAnimationDataResource(
file, offset, f"{resource.name}_{segment:08X}_PlayerAnimData"
),
)
player_animation_data_res.set_frame_count(v["common"]["frameCount"])
def write_frameCount(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
address = resource.cdata_unpacked["segment"]
assert isinstance(address, int)
wctx.f.write(wctx.line_prefix)
wctx.f.write(
memory_context.resolve_segmented(address)
.get_resource(PlayerAnimationDataResource)
.frame_count_name
)
return True
def write_segment(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
cdata_ext = CDataExt_Struct(
(
(
"common",
CDataExt_Struct(
(("frameCount", CDataExt_Value("h").set_write(write_frameCount)),)
),
),
("pad2", CDataExt_Value.pad16),
("segment", CDataExt_Value("I").set_write(write_segment)),
)
).set_report(report)
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return f"&{self.symbol_name}"
else:
raise ValueError()
def get_c_declaration_base(self):
return f"LinkAnimationHeader {self.symbol_name}"
def get_h_includes(self):
return ("z64animation.h",)

View file

@ -0,0 +1,557 @@
# SPDX-FileCopyrightText: © 2025 ZeldaRET
# SPDX-License-Identifier: CC0-1.0
import enum
from typing import TYPE_CHECKING
from .. import oot64_data
if TYPE_CHECKING:
from ..extase.memorymap import MemoryContext
from ..extase import (
Resource,
File,
RESOURCE_PARSE_SUCCESS,
)
from ..extase.cdata_resources import (
CDataResource,
CDataArrayNamedLengthResource,
CDataExt_Struct,
CDataExt_Value,
CDataExtWriteContext,
cdata_ext_Vec3s,
)
from . import dlist_resources
class RoomShapeType(enum.Enum):
ROOM_SHAPE_TYPE_NORMAL = 0
ROOM_SHAPE_TYPE_IMAGE = enum.auto()
ROOM_SHAPE_TYPE_CULLABLE = enum.auto()
class RoomShapeImageAmountType(enum.Enum):
ROOM_SHAPE_IMAGE_AMOUNT_SINGLE = 1
ROOM_SHAPE_IMAGE_AMOUNT_MULTI = enum.auto()
def report_room_shape_at_segmented(
reporter: Resource, memory_context: "MemoryContext", address: int, name_base: str
):
def new_resource_pointed_to(file: File, offset: int):
resource_type = get_room_shape_resource_type(file, offset)
name_suffix = resource_type.__name__.removesuffix("Resource")
return resource_type(
file,
offset,
f"{name_base}_{address:08X}_{name_suffix}",
)
memory_context.report_resource_at_segmented(
reporter, address, Resource, new_resource_pointed_to
)
def get_room_shape_resource_type(file: File, offset: int):
room_shape_type_int = file.data[offset]
room_shape_type = RoomShapeType(room_shape_type_int)
resource_type = None
if room_shape_type == RoomShapeType.ROOM_SHAPE_TYPE_NORMAL:
resource_type = RoomShapeNormalResource
if room_shape_type == RoomShapeType.ROOM_SHAPE_TYPE_IMAGE:
room_shape_image_amount_type_int = file.data[offset + 1]
room_shape_image_amount_type = RoomShapeImageAmountType(
room_shape_image_amount_type_int
)
if (
room_shape_image_amount_type
== RoomShapeImageAmountType.ROOM_SHAPE_IMAGE_AMOUNT_SINGLE
):
resource_type = RoomShapeImageSingleResource
if (
room_shape_image_amount_type
== RoomShapeImageAmountType.ROOM_SHAPE_IMAGE_AMOUNT_MULTI
):
resource_type = RoomShapeImageMultiResource
if room_shape_type == RoomShapeType.ROOM_SHAPE_TYPE_CULLABLE:
resource_type = RoomShapeCullableResource
assert resource_type is not None
return resource_type
cdata_ext_RoomShapeBase = CDataExt_Struct(
(
(
"type",
CDataExt_Value("B").set_write_str_v(oot64_data.get_room_shape_type_name),
),
)
)
cdata_ext_RoomShapeDListsEntry = CDataExt_Struct(
(
("opa", dlist_resources.cdata_ext_gfx_segmented),
("xlu", dlist_resources.cdata_ext_gfx_segmented),
)
)
# TODO check if this even needs a named length
class RoomShapeNormalEntryArrayResource(CDataArrayNamedLengthResource):
elem_cdata_ext = cdata_ext_RoomShapeDListsEntry
def get_c_declaration_base(self):
return f"RoomShapeDListsEntry {self.symbol_name}[{self.length_name}]"
def get_h_includes(self):
return ("z64room.h",)
class RoomShapeNormalResource(CDataResource):
def write_numEntries(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
address = resource.cdata_unpacked["entries"]
assert isinstance(address, int)
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_expression_length_at_segmented(address))
return True
def report_entries(resource, memory_context: "MemoryContext", v):
assert isinstance(v, int)
address = v
entries_resource = memory_context.report_resource_at_segmented(
resource,
address,
RoomShapeNormalEntryArrayResource,
lambda file, offset: RoomShapeNormalEntryArrayResource(
file, offset, f"{resource.name}_{address:08X}_DListsEntries"
),
)
assert isinstance(entries_resource, RoomShapeNormalEntryArrayResource)
numEntries = resource.cdata_unpacked["numEntries"]
assert isinstance(numEntries, int)
entries_resource.set_length(numEntries)
def write_entries(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
def write_entriesEnd(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
address = resource.cdata_unpacked["entries"]
assert isinstance(address, int)
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
wctx.f.write(" + ")
wctx.f.write(memory_context.get_c_expression_length_at_segmented(address))
return True
cdata_ext = CDataExt_Struct(
(
("base", cdata_ext_RoomShapeBase),
("numEntries", CDataExt_Value("B").set_write(write_numEntries)),
("pad2", CDataExt_Value.pad16),
(
"entries",
CDataExt_Value("I").set_report(report_entries).set_write(write_entries),
),
(
"entriesEnd",
CDataExt_Value("I").set_write(write_entriesEnd),
),
)
)
def get_c_declaration_base(self):
return f"RoomShapeNormal {self.symbol_name}"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return f"&{self.symbol_name}"
else:
raise ValueError
def get_c_includes(self):
return ("array_count.h",)
def get_h_includes(self):
return ("z64room.h",)
class RoomShapeDListsEntryResource(CDataResource):
cdata_ext = cdata_ext_RoomShapeDListsEntry
def get_c_declaration_base(self):
return f"RoomShapeDListsEntry {self.symbol_name}"
def get_c_reference(self, resource_offset):
if resource_offset == 0:
return f"&{self.symbol_name}"
else:
raise ValueError
def get_h_includes(self):
return ("z64room.h",)
def report_RoomShapeImageBase_entry(resource, memory_context: "MemoryContext", v):
assert isinstance(v, int)
address = v
memory_context.report_resource_at_segmented(
resource,
address,
RoomShapeDListsEntryResource,
lambda file, offset: RoomShapeDListsEntryResource(
file, offset, f"{resource.name}_{address:08X}_RoomShapeDListsEntry"
),
)
def write_RoomShapeImageBase_entry(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
cdata_ext_RoomShapeImageBase = CDataExt_Struct(
(
("base", cdata_ext_RoomShapeBase),
(
"amountType",
CDataExt_Value("B").set_write_str_v(
oot64_data.get_room_shape_image_amount_type_name
),
),
("pad2", CDataExt_Value.pad16),
(
"entry",
(
CDataExt_Value("I")
.set_report(report_RoomShapeImageBase_entry)
.set_write(write_RoomShapeImageBase_entry)
),
), # RoomShapeDListsEntry*
)
)
class JFIFResource(Resource):
def __init__(self, file, range_start, name):
super().__init__(file, range_start, range_start + 320 * 240 * 2, name)
def try_parse_data(self, memory_context):
return RESOURCE_PARSE_SUCCESS
needs_build = True
extracted_path_suffix = ""
def get_filename_stem(self):
return f"{self.name}.jpg"
def write_extracted(self, memory_context):
# TODO trim zeros at the end of the data
self.extract_to_path.write_bytes(
self.file.data[self.range_start : self.range_end]
)
def get_c_declaration_base(self):
return f"u64 {self.symbol_name}[SCREEN_WIDTH * SCREEN_HEIGHT * G_IM_SIZ_16b_BYTES / sizeof(u64)]"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return f"&{self.symbol_name}"
else:
raise ValueError
def get_h_includes(self):
return (
"ultra64.h",
"gfx.h", # for SCREEN_WIDTH, SCREEN_HEIGHT
)
class RoomShapeImageSingleResource(CDataResource):
def report_source(resource, memory_context: "MemoryContext", v):
assert isinstance(v, int)
address = v
memory_context.report_resource_at_segmented(
resource,
address,
JFIFResource,
lambda file, offset: JFIFResource(
file,
offset,
f"{resource.name}_{address:08X}_JFIF",
),
)
def write_source(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
cdata_ext = CDataExt_Struct(
(
("base", cdata_ext_RoomShapeImageBase),
(
"source",
CDataExt_Value("I").set_report(report_source).set_write(write_source),
),
("unk_0C", CDataExt_Value.u32),
("tlut", CDataExt_Value.pointer), # TODO
("width", CDataExt_Value.u16),
("height", CDataExt_Value.u16),
("fmt", CDataExt_Value.u8), # TODO
("siz", CDataExt_Value.u8), # TODO
("tlutMode", CDataExt_Value.u16),
("tlutCount", CDataExt_Value.u16),
("pad1E", CDataExt_Value.pad16),
)
)
def get_c_declaration_base(self):
return f"RoomShapeImageSingle {self.symbol_name}"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return f"&{self.symbol_name}"
else:
raise ValueError
def get_h_includes(self):
return ("z64room.h",)
class RoomShapeImageMultiBgEntryArrayResource(CDataArrayNamedLengthResource):
def report_source(resource, memory_context: "MemoryContext", v):
assert isinstance(v, int)
address = v
memory_context.report_resource_at_segmented(
resource,
address,
JFIFResource,
lambda file, offset: JFIFResource(
file,
offset,
f"{resource.name}_{address:08X}_JFIF",
),
)
def write_source(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
elem_cdata_ext = CDataExt_Struct(
(
("unk_00", CDataExt_Value.u16),
("bgCamIndex", CDataExt_Value.u8),
("pad3", CDataExt_Value.pad8),
(
"source",
CDataExt_Value("I").set_report(report_source).set_write(write_source),
),
("unk_0C", CDataExt_Value.u32),
("tlut", CDataExt_Value.pointer), # TODO
("width", CDataExt_Value.u16),
("height", CDataExt_Value.u16),
("fmt", CDataExt_Value.u8), # TODO
("siz", CDataExt_Value.u8), # TODO
("tlutMode", CDataExt_Value.u16),
("tlutCount", CDataExt_Value.u16),
("pad1A", CDataExt_Value.pad16),
)
)
def get_c_declaration_base(self):
return f"RoomShapeImageMultiBgEntry {self.name}[{self.length_name}]"
def get_h_includes(self):
return ("z64room.h",)
class RoomShapeImageMultiResource(CDataResource):
def write_numBackgrounds(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
address = resource.cdata_unpacked["backgrounds"]
assert isinstance(address, int)
backgrounds_resource = memory_context.resolve_segmented(address).get_resource(
RoomShapeImageMultiBgEntryArrayResource
)
wctx.f.write(wctx.line_prefix)
wctx.f.write(backgrounds_resource.length_name)
return True
def report_backgrounds(resource, memory_context: "MemoryContext", v):
assert isinstance(v, int)
address = v
backgrounds_resource = memory_context.report_resource_at_segmented(
resource,
address,
RoomShapeImageMultiBgEntryArrayResource,
lambda file, offset: RoomShapeImageMultiBgEntryArrayResource(
file,
offset,
f"{resource.name}_{address:08X}_RoomShapeImageMultiBgEntries",
),
)
backgrounds_resource.set_length(resource.cdata_unpacked["numBackgrounds"])
def write_backgrounds(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
cdata_ext = CDataExt_Struct(
(
("base", cdata_ext_RoomShapeImageBase),
("numBackgrounds", CDataExt_Value("B").set_write(write_numBackgrounds)),
("pad9", CDataExt_Value.pad8),
("padA", CDataExt_Value.pad16),
(
"backgrounds",
(
CDataExt_Value("I")
.set_report(report_backgrounds)
.set_write(write_backgrounds)
),
), # RoomShapeImageMultiBgEntry*
)
)
def get_c_declaration_base(self):
return f"RoomShapeImageMulti {self.symbol_name}"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return f"&{self.symbol_name}"
else:
raise ValueError
def get_h_includes(self):
return ("z64room.h",)
class RoomShapeCullableEntryArrayResource(CDataArrayNamedLengthResource):
elem_cdata_ext = CDataExt_Struct(
(
("boundsSphereCenter", cdata_ext_Vec3s),
("boundsSphereRadius", CDataExt_Value.s16),
("opa", dlist_resources.cdata_ext_gfx_segmented),
("xlu", dlist_resources.cdata_ext_gfx_segmented),
)
)
def get_c_declaration_base(self):
return f"RoomShapeCullableEntry {self.symbol_name}[{self.length_name}]"
def get_h_includes(self):
return ("z64room.h",)
class RoomShapeCullableResource(CDataResource):
def write_numEntries(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
address = resource.cdata_unpacked["entries"]
assert isinstance(address, int)
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_expression_length_at_segmented(address))
return True
def report_entries(resource, memory_context: "MemoryContext", v):
assert isinstance(v, int)
address = v
entries_resource = memory_context.report_resource_at_segmented(
resource,
address,
RoomShapeCullableEntryArrayResource,
lambda file, offset: RoomShapeCullableEntryArrayResource(
file, offset, f"{resource.name}_{address:08X}_CullableEntries"
),
)
assert isinstance(entries_resource, RoomShapeCullableEntryArrayResource)
numEntries = resource.cdata_unpacked["numEntries"]
assert isinstance(numEntries, int)
entries_resource.set_length(numEntries)
def write_entries(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
def write_entriesEnd(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
address = resource.cdata_unpacked["entries"]
assert isinstance(address, int)
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
wctx.f.write(" + ")
wctx.f.write(memory_context.get_c_expression_length_at_segmented(address))
return True
cdata_ext = CDataExt_Struct(
(
("base", cdata_ext_RoomShapeBase),
("numEntries", CDataExt_Value("B").set_write(write_numEntries)),
("pad2", CDataExt_Value.pad16),
(
"entries",
CDataExt_Value("I").set_report(report_entries).set_write(write_entries),
),
(
"entriesEnd",
CDataExt_Value("I").set_write(write_entriesEnd),
),
)
)
def get_c_declaration_base(self):
return f"RoomShapeCullable {self.symbol_name}"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return f"&{self.symbol_name}"
else:
raise ValueError
def get_h_includes(self):
return ("z64room.h",)

View file

@ -0,0 +1,717 @@
# SPDX-FileCopyrightText: © 2025 ZeldaRET
# SPDX-License-Identifier: CC0-1.0
import enum
import struct
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ..extase.memorymap import MemoryContext
from ..extase import (
File,
Resource,
RESOURCE_PARSE_SUCCESS,
ResourceParseInProgress,
ResourceParseWaiting,
)
from ..extase.cdata_resources import (
CDataArrayResource,
CDataExt_Value,
CDataExtWriteContext,
)
from .. import oot64_data
from . import scene_rooms_resources
from . import collision_resources
from . import room_shape_resources
from . import misc_resources
def _SHIFTR(v: int, s: int, w: int):
assert isinstance(v, int)
assert isinstance(s, int)
assert isinstance(w, int)
assert v >= 0
assert s >= 0
assert w >= 1
return (v >> s) & ((1 << w) - 1)
VERBOSE_NOT_FULLY_PARSED_SCENECMD = False
class SceneCmdId(enum.Enum):
# keep the SCENE_CMD_ID_ prefix for grepability
SCENE_CMD_ID_SPAWN_LIST = 0
SCENE_CMD_ID_ACTOR_LIST = enum.auto()
SCENE_CMD_ID_UNUSED_2 = enum.auto()
SCENE_CMD_ID_COLLISION_HEADER = enum.auto()
SCENE_CMD_ID_ROOM_LIST = enum.auto()
SCENE_CMD_ID_WIND_SETTINGS = enum.auto()
SCENE_CMD_ID_ENTRANCE_LIST = enum.auto()
SCENE_CMD_ID_SPECIAL_FILES = enum.auto()
SCENE_CMD_ID_ROOM_BEHAVIOR = enum.auto()
SCENE_CMD_ID_UNDEFINED_9 = enum.auto()
SCENE_CMD_ID_ROOM_SHAPE = enum.auto()
SCENE_CMD_ID_OBJECT_LIST = enum.auto()
SCENE_CMD_ID_LIGHT_LIST = enum.auto()
SCENE_CMD_ID_PATH_LIST = enum.auto()
SCENE_CMD_ID_TRANSITION_ACTOR_LIST = enum.auto()
SCENE_CMD_ID_LIGHT_SETTINGS_LIST = enum.auto()
SCENE_CMD_ID_TIME_SETTINGS = enum.auto()
SCENE_CMD_ID_SKYBOX_SETTINGS = enum.auto()
SCENE_CMD_ID_SKYBOX_DISABLES = enum.auto()
SCENE_CMD_ID_EXIT_LIST = enum.auto()
SCENE_CMD_ID_END = enum.auto()
SCENE_CMD_ID_SOUND_SETTINGS = enum.auto()
SCENE_CMD_ID_ECHO_SETTINGS = enum.auto()
SCENE_CMD_ID_CUTSCENE_DATA = enum.auto()
SCENE_CMD_ID_ALTERNATE_HEADER_LIST = enum.auto()
SCENE_CMD_ID_MISC_SETTINGS = enum.auto()
scene_cmd_macro_name_by_cmd_id = {
SceneCmdId.SCENE_CMD_ID_SPAWN_LIST: "SCENE_CMD_SPAWN_LIST",
SceneCmdId.SCENE_CMD_ID_ACTOR_LIST: "SCENE_CMD_ACTOR_LIST",
SceneCmdId.SCENE_CMD_ID_UNUSED_2: "SCENE_CMD_UNUSED_02",
SceneCmdId.SCENE_CMD_ID_COLLISION_HEADER: "SCENE_CMD_COL_HEADER",
SceneCmdId.SCENE_CMD_ID_ROOM_LIST: "SCENE_CMD_ROOM_LIST",
SceneCmdId.SCENE_CMD_ID_WIND_SETTINGS: "SCENE_CMD_WIND_SETTINGS",
SceneCmdId.SCENE_CMD_ID_ENTRANCE_LIST: "SCENE_CMD_ENTRANCE_LIST",
SceneCmdId.SCENE_CMD_ID_SPECIAL_FILES: "SCENE_CMD_SPECIAL_FILES",
SceneCmdId.SCENE_CMD_ID_ROOM_BEHAVIOR: "SCENE_CMD_ROOM_BEHAVIOR",
SceneCmdId.SCENE_CMD_ID_UNDEFINED_9: "SCENE_CMD_UNK_09",
SceneCmdId.SCENE_CMD_ID_ROOM_SHAPE: "SCENE_CMD_ROOM_SHAPE",
SceneCmdId.SCENE_CMD_ID_OBJECT_LIST: "SCENE_CMD_OBJECT_LIST",
SceneCmdId.SCENE_CMD_ID_LIGHT_LIST: "SCENE_CMD_LIGHT_LIST",
SceneCmdId.SCENE_CMD_ID_PATH_LIST: "SCENE_CMD_PATH_LIST",
SceneCmdId.SCENE_CMD_ID_TRANSITION_ACTOR_LIST: "SCENE_CMD_TRANSITION_ACTOR_LIST",
SceneCmdId.SCENE_CMD_ID_LIGHT_SETTINGS_LIST: "SCENE_CMD_ENV_LIGHT_SETTINGS",
SceneCmdId.SCENE_CMD_ID_TIME_SETTINGS: "SCENE_CMD_TIME_SETTINGS",
SceneCmdId.SCENE_CMD_ID_SKYBOX_SETTINGS: "SCENE_CMD_SKYBOX_SETTINGS",
SceneCmdId.SCENE_CMD_ID_SKYBOX_DISABLES: "SCENE_CMD_SKYBOX_DISABLES",
SceneCmdId.SCENE_CMD_ID_EXIT_LIST: "SCENE_CMD_EXIT_LIST",
SceneCmdId.SCENE_CMD_ID_END: "SCENE_CMD_END",
SceneCmdId.SCENE_CMD_ID_SOUND_SETTINGS: "SCENE_CMD_SOUND_SETTINGS",
SceneCmdId.SCENE_CMD_ID_ECHO_SETTINGS: "SCENE_CMD_ECHO_SETTINGS",
SceneCmdId.SCENE_CMD_ID_CUTSCENE_DATA: "SCENE_CMD_CUTSCENE_DATA",
SceneCmdId.SCENE_CMD_ID_ALTERNATE_HEADER_LIST: "SCENE_CMD_ALTERNATE_HEADER_LIST",
SceneCmdId.SCENE_CMD_ID_MISC_SETTINGS: "SCENE_CMD_MISC_SETTINGS",
}
class SceneCommandsResource(Resource, can_size_be_unknown=True):
def __init__(self, file: File, range_start: int, name: str):
super().__init__(file, range_start, None, name)
self.parsed_commands: set[SceneCmdId] = set()
self.player_entry_list_length = None
self.room_list_length = None
self.exit_list_length = None
def try_parse_data(self, memory_context: "MemoryContext"):
data = self.file.data[self.range_start :]
new_progress_done = []
waiting_for = []
offset = 0
cmd_id = None
end_offset = None
found_commands: set[SceneCmdId] = set()
while offset + 8 <= len(data):
(cmd_id_int, data1, pad2, data2_I) = struct.unpack_from(
">BBHI", data, offset
)
(_, data2_H0, data2_H1) = struct.unpack_from(">IHH", data, offset)
(_, data2_B0, data2_B1, data2_B2, data2_B3) = struct.unpack_from(
">IBBBB", data, offset
)
offset += 8
cmd_id = SceneCmdId(cmd_id_int)
assert pad2 == 0
found_commands.add(cmd_id)
if cmd_id == SceneCmdId.SCENE_CMD_ID_END:
assert data1 == 0
assert data2_I == 0
end_offset = offset
self.parsed_commands.add(cmd_id)
break
if cmd_id in self.parsed_commands:
continue
if cmd_id == SceneCmdId.SCENE_CMD_ID_ACTOR_LIST:
resource = memory_context.report_resource_at_segmented(
self,
data2_I,
scene_rooms_resources.ActorEntryListResource,
lambda file, offset: scene_rooms_resources.ActorEntryListResource(
file, offset, f"{self.name}_{data2_I:08X}_ActorEntryList"
),
)
resource.set_length(data1)
self.parsed_commands.add(cmd_id)
new_progress_done.append(("reported ActorEntryListResource", cmd_id))
if cmd_id == SceneCmdId.SCENE_CMD_ID_OBJECT_LIST:
resource = memory_context.report_resource_at_segmented(
self,
data2_I,
scene_rooms_resources.ObjectListResource,
lambda file, offset: scene_rooms_resources.ObjectListResource(
file, offset, f"{self.name}_{data2_I:08X}_ObjectList"
),
)
resource.set_length(data1)
self.parsed_commands.add(cmd_id)
new_progress_done.append(("reported ObjectListResource", cmd_id))
if cmd_id == SceneCmdId.SCENE_CMD_ID_ROOM_SHAPE:
room_shape_resources.report_room_shape_at_segmented(
self, memory_context, data2_I, self.name
)
self.parsed_commands.add(cmd_id)
new_progress_done.append(("reported room shape resource", cmd_id))
if cmd_id == SceneCmdId.SCENE_CMD_ID_ROOM_LIST:
self.room_list_length = data1
resource = memory_context.report_resource_at_segmented(
self,
data2_I,
scene_rooms_resources.RoomListResource,
lambda file, offset: scene_rooms_resources.RoomListResource(
file, offset, f"{self.name}_{data2_I:08X}_RoomList"
),
)
resource.set_length(data1)
self.parsed_commands.add(cmd_id)
new_progress_done.append(("reported RoomListResource", cmd_id))
if cmd_id == SceneCmdId.SCENE_CMD_ID_COLLISION_HEADER:
assert data1 == 0
resource = memory_context.report_resource_at_segmented(
self,
data2_I,
collision_resources.CollisionResource,
lambda file, offset: collision_resources.CollisionResource(
file, offset, f"{self.name}_{data2_I:08X}_Col"
),
)
new_progress_done.append(("reported CollisionResource", cmd_id))
if resource.is_data_parsed:
self.exit_list_length = resource.length_exitList
self.parsed_commands.add(cmd_id)
new_progress_done.append(
("set self.exit_list_length from CollisionResource", cmd_id)
)
else:
waiting_for.append(
(
"CollisionResource to be parsed to set self.exit_list_length",
cmd_id,
resource,
)
)
if cmd_id == SceneCmdId.SCENE_CMD_ID_ENTRANCE_LIST:
assert data1 == 0
resource = memory_context.report_resource_at_segmented(
self,
data2_I,
scene_rooms_resources.SpawnListResource,
lambda file, offset: scene_rooms_resources.SpawnListResource(
file, offset, f"{self.name}_{data2_I:08X}_SpawnList"
),
)
new_progress_done.append(("reported SpawnListResource", cmd_id))
if (
self.player_entry_list_length is not None
and self.room_list_length is not None
):
resource.player_entry_list_length = self.player_entry_list_length
resource.room_list_length = self.room_list_length
self.parsed_commands.add(cmd_id)
new_progress_done.append(
("passed lengths to SpawnListResource", cmd_id)
)
else:
waiting_for.append(
(
"self.player_entry_list_length and self.room_list_length"
" to pass to SpawnListResource",
cmd_id,
)
)
if cmd_id == SceneCmdId.SCENE_CMD_ID_SPAWN_LIST:
self.player_entry_list_length = data1
resource = memory_context.report_resource_at_segmented(
self,
data2_I,
scene_rooms_resources.ActorEntryListResource,
lambda file, offset: scene_rooms_resources.ActorEntryListResource(
file, offset, f"{self.name}_{data2_I:08X}_PlayerEntryList"
),
)
resource.set_length(data1)
self.parsed_commands.add(cmd_id)
new_progress_done.append(("reported ActorEntryListResource", cmd_id))
if cmd_id == SceneCmdId.SCENE_CMD_ID_EXIT_LIST:
# TODO length from collision
assert data1 == 0
resource = memory_context.report_resource_at_segmented(
self,
data2_I,
scene_rooms_resources.ExitListResource,
lambda file, offset: scene_rooms_resources.ExitListResource(
file, offset, f"{self.name}_{data2_I:08X}_ExitList"
),
)
new_progress_done.append(("reported ExitListResource", cmd_id))
if self.exit_list_length is not None:
# TODO this doesnt work very well, eg need to trim to avoid overlaps
length = self.exit_list_length
# blindly align length to 2 (could/should check for zeros)
length = max(2, (length + 1) // 2 * 2)
# trim based on overlaps
while True:
_, other_resource = resource.file.get_resource_at(
resource.range_start
+ length * resource.elem_cdata_ext.size
- 1
)
if other_resource is resource:
break
length -= 2
assert length > 0
resource.set_length(length)
self.parsed_commands.add(cmd_id)
new_progress_done.append(
("passed length to ExitListResource", cmd_id, resource)
)
else:
waiting_for.append(
(
"self.exit_list_length to (guess a length to) pass to ExitListResource",
cmd_id,
)
)
if cmd_id == SceneCmdId.SCENE_CMD_ID_LIGHT_SETTINGS_LIST:
resource = memory_context.report_resource_at_segmented(
self,
data2_I,
scene_rooms_resources.EnvLightSettingsListResource,
lambda file, offset: scene_rooms_resources.EnvLightSettingsListResource(
file, offset, f"{self.name}_{data2_I:08X}_EnvLightSettingsList"
),
)
resource.set_length(data1)
self.parsed_commands.add(cmd_id)
new_progress_done.append(
("reported EnvLightSettingsListResource", cmd_id)
)
if cmd_id == SceneCmdId.SCENE_CMD_ID_TRANSITION_ACTOR_LIST:
resource = memory_context.report_resource_at_segmented(
self,
data2_I,
scene_rooms_resources.TransitionActorEntryListResource,
lambda file, offset: scene_rooms_resources.TransitionActorEntryListResource(
file,
offset,
f"{self.name}_{data2_I:08X}_TransitionActorEntryList",
),
)
resource.set_length(data1)
self.parsed_commands.add(cmd_id)
new_progress_done.append(
("reported TransitionActorEntryListResource", cmd_id)
)
if cmd_id == SceneCmdId.SCENE_CMD_ID_PATH_LIST:
assert data1 == 0
memory_context.report_resource_at_segmented(
self,
data2_I,
scene_rooms_resources.PathListResource,
lambda file, offset: scene_rooms_resources.PathListResource(
file, offset, f"{self.name}_{data2_I:08X}_PathList"
),
)
self.parsed_commands.add(cmd_id)
new_progress_done.append(("reported PathListResource", cmd_id))
if cmd_id == SceneCmdId.SCENE_CMD_ID_ALTERNATE_HEADER_LIST:
assert data1 == 0
memory_context.report_resource_at_segmented(
self,
data2_I,
AltHeadersResource,
lambda file, offset: AltHeadersResource(
file, offset, f"{self.name}_{data2_I:08X}_AltHeaders"
),
)
self.parsed_commands.add(cmd_id)
new_progress_done.append(("reported AltHeadersResource", cmd_id))
if cmd_id == SceneCmdId.SCENE_CMD_ID_CUTSCENE_DATA:
assert data1 == 0
memory_context.report_resource_at_segmented(
self,
data2_I,
misc_resources.CutsceneResource,
lambda file, offset: misc_resources.CutsceneResource(
file, offset, f"{self.name}_{data2_I:08X}_Cs"
),
)
self.parsed_commands.add(cmd_id)
new_progress_done.append(("reported CutsceneResource", cmd_id))
if cmd_id != SceneCmdId.SCENE_CMD_ID_END:
raise Exception("reached end of data without encountering end marker")
assert end_offset is not None
# Nothing to parse for these commands
found_commands.discard(SceneCmdId.SCENE_CMD_ID_SOUND_SETTINGS)
found_commands.discard(SceneCmdId.SCENE_CMD_ID_MISC_SETTINGS)
found_commands.discard(SceneCmdId.SCENE_CMD_ID_SPECIAL_FILES)
found_commands.discard(SceneCmdId.SCENE_CMD_ID_SKYBOX_SETTINGS)
found_commands.discard(SceneCmdId.SCENE_CMD_ID_TIME_SETTINGS)
found_commands.discard(SceneCmdId.SCENE_CMD_ID_ROOM_BEHAVIOR)
found_commands.discard(SceneCmdId.SCENE_CMD_ID_ECHO_SETTINGS)
found_commands.discard(SceneCmdId.SCENE_CMD_ID_SKYBOX_DISABLES)
found_commands.discard(SceneCmdId.SCENE_CMD_ID_UNDEFINED_9)
found_commands.discard(SceneCmdId.SCENE_CMD_ID_WIND_SETTINGS)
self.range_end = self.range_start + end_offset
assert self.parsed_commands.issubset(found_commands)
if found_commands - self.parsed_commands:
if VERBOSE_NOT_FULLY_PARSED_SCENECMD:
print(
"NOT FULLY PARSED:",
self,
"Found commands:",
found_commands,
"Parsed commands:",
self.parsed_commands,
"FOUND BUT NOT PARSED:",
found_commands - self.parsed_commands,
)
if waiting_for:
if new_progress_done:
raise ResourceParseInProgress(
new_progress_done=new_progress_done, waiting_for=waiting_for
)
else:
raise ResourceParseWaiting(waiting_for=waiting_for)
assert self.parsed_commands == found_commands
return RESOURCE_PARSE_SUCCESS
def get_c_declaration_base(self):
return f"SceneCmd {self.symbol_name}[]"
def write_extracted(self, memory_context):
data = self.file.data[self.range_start : self.range_end]
with self.extract_to_path.open("w") as f:
if not self.braces_in_source:
f.write("{\n")
for offset in range(0, len(data), 8):
(cmd_id_int, data1, pad2, data2_I) = struct.unpack_from(
">BBHI", data, offset
)
(_, data2_H0, data2_H1) = struct.unpack_from(">IHH", data, offset)
(_, data2_B0, data2_B1, data2_B2, data2_B3) = struct.unpack_from(
">IBBBB", data, offset
)
cmd_id = SceneCmdId(cmd_id_int)
f.write(" " * 4)
f.write(scene_cmd_macro_name_by_cmd_id[cmd_id])
f.write("(")
if cmd_id == SceneCmdId.SCENE_CMD_ID_SPAWN_LIST:
address = data2_I
f.write(
memory_context.get_c_expression_length_at_segmented(address)
)
f.write(", ")
f.write(memory_context.get_c_reference_at_segmented(address))
if cmd_id == SceneCmdId.SCENE_CMD_ID_ACTOR_LIST:
address = data2_I
f.write(
memory_context.get_c_expression_length_at_segmented(address)
)
f.write(", ")
f.write(memory_context.get_c_reference_at_segmented(address))
if cmd_id == SceneCmdId.SCENE_CMD_ID_UNUSED_2:
raise NotImplementedError(cmd_id)
if cmd_id == SceneCmdId.SCENE_CMD_ID_COLLISION_HEADER:
assert data1 == 0
address = data2_I
f.write(memory_context.get_c_reference_at_segmented(address))
if cmd_id == SceneCmdId.SCENE_CMD_ID_ROOM_LIST:
address = data2_I
f.write(
memory_context.get_c_expression_length_at_segmented(address)
)
f.write(", ")
f.write(memory_context.get_c_reference_at_segmented(address))
if cmd_id == SceneCmdId.SCENE_CMD_ID_WIND_SETTINGS:
assert data1 == 0
# TODO cast x,y,z to s8
xDir = data2_B0
yDir = data2_B1
zDir = data2_B2
strength = data2_B3
f.write(f"{xDir}, {yDir}, {zDir}, {strength}")
if cmd_id == SceneCmdId.SCENE_CMD_ID_ENTRANCE_LIST:
assert data1 == 0
address = data2_I
f.write(memory_context.get_c_reference_at_segmented(address))
if cmd_id == SceneCmdId.SCENE_CMD_ID_SPECIAL_FILES:
naviQuestHintFileId = data1
keepObjectId = data2_I
f.write(
f"{oot64_data.get_navi_quest_hint_file_id_name(naviQuestHintFileId)}, "
f"{oot64_data.get_object_id_name(keepObjectId)}"
)
if cmd_id == SceneCmdId.SCENE_CMD_ID_ROOM_BEHAVIOR:
gpFlags1 = data1
gpFlags2 = data2_I
behaviorType1 = gpFlags1
behaviorType2 = _SHIFTR(gpFlags2, 0, 8)
lensMode = _SHIFTR(gpFlags2, 8, 1)
disableWarpSongs = _SHIFTR(gpFlags2, 10, 1)
behaviorType1_name = oot64_data.get_room_behavior_type1_name(
behaviorType1
)
behaviorType2_name = oot64_data.get_room_behavior_type2_name(
behaviorType2
)
lensMode_name = oot64_data.get_lens_mode_name(lensMode)
disableWarpSongs_name = (
"true /* no warp songs */"
if disableWarpSongs
else "false /* warp songs enabled */"
)
f.write(
f"{behaviorType1_name}, {behaviorType2_name}, {lensMode_name}, {disableWarpSongs_name}"
)
if cmd_id == SceneCmdId.SCENE_CMD_ID_UNDEFINED_9:
assert data1 == 0
assert data2_I == 0
if cmd_id == SceneCmdId.SCENE_CMD_ID_ROOM_SHAPE:
assert data1 == 0 # TODO these asserts should be done on parse?
address = data2_I
f.write(memory_context.get_c_reference_at_segmented(address))
if cmd_id == SceneCmdId.SCENE_CMD_ID_OBJECT_LIST:
address = data2_I
f.write(
memory_context.get_c_expression_length_at_segmented(address)
)
f.write(", ")
f.write(memory_context.get_c_reference_at_segmented(address))
if cmd_id == SceneCmdId.SCENE_CMD_ID_LIGHT_LIST:
raise NotImplementedError(cmd_id)
if cmd_id == SceneCmdId.SCENE_CMD_ID_PATH_LIST:
assert data1 == 0
address = data2_I
f.write(memory_context.get_c_reference_at_segmented(address))
if cmd_id == SceneCmdId.SCENE_CMD_ID_TRANSITION_ACTOR_LIST:
address = data2_I
f.write(
memory_context.get_c_expression_length_at_segmented(address)
)
f.write(", ")
f.write(memory_context.get_c_reference_at_segmented(address))
if cmd_id == SceneCmdId.SCENE_CMD_ID_LIGHT_SETTINGS_LIST:
address = data2_I
f.write(
memory_context.get_c_expression_length_at_segmented(address)
)
f.write(", ")
f.write(memory_context.get_c_reference_at_segmented(address))
if cmd_id == SceneCmdId.SCENE_CMD_ID_TIME_SETTINGS:
assert data1 == 0
hour = data2_B0
min_ = data2_B1
timeSpeed = data2_B2
assert data2_B3 == 0
hour_str = "0xFF" if hour == 0xFF else str(hour)
min_str = "0xFF" if min_ == 0xFF else str(min_)
timeSpeed_str = "0xFF" if timeSpeed == 0xFF else str(timeSpeed)
if hour == 0xFF or min_ == 0xFF:
f.write("/* don't set time */ ")
f.write(f"{hour_str}, {min_str}, {timeSpeed_str}")
if timeSpeed == 0xFF or timeSpeed == 0:
f.write(" /* time doesn't move */")
if cmd_id == SceneCmdId.SCENE_CMD_ID_SKYBOX_SETTINGS:
assert data1 == 0
skyboxId = data2_B0
skyboxConfig = data2_B1
envLightMode = data2_B2
assert data2_B3 == 0
f.write(
f"{oot64_data.get_skybox_id(skyboxId)}, "
f"{skyboxConfig}, "
f"{oot64_data.get_light_mode(envLightMode)}"
)
if cmd_id == SceneCmdId.SCENE_CMD_ID_SKYBOX_DISABLES:
assert data1 == 0
skyboxDisabled = data2_B0
sunMoonDisabled = data2_B1
assert data2_B2 == data2_B3 == 0
skyboxDisabled_name = (
"true /* no skybox */"
if skyboxDisabled
else "false /* skybox enabled */"
)
sunMoonDisabled_name = (
"true /* no sun/moon */"
if sunMoonDisabled
else "false /* sun/moon enabled */"
)
f.write(f"{skyboxDisabled_name}, {sunMoonDisabled_name}")
if cmd_id == SceneCmdId.SCENE_CMD_ID_EXIT_LIST:
assert data1 == 0
address = data2_I
f.write(memory_context.get_c_reference_at_segmented(address))
if cmd_id == SceneCmdId.SCENE_CMD_ID_END:
assert data1 == 0
assert data2_I == 0
if cmd_id == SceneCmdId.SCENE_CMD_ID_SOUND_SETTINGS:
specId = data1
assert data2_B0 == 0
assert data2_B1 == 0
natureAmbienceId = data2_B2
seqId = data2_B3
natureAmbienceId_name = oot64_data.get_nature_ambience_id_name(
natureAmbienceId
)
seqId_name = oot64_data.get_sequence_id_name(seqId)
f.write(f"{specId}, {natureAmbienceId_name}, {seqId_name}")
if cmd_id == SceneCmdId.SCENE_CMD_ID_ECHO_SETTINGS:
assert data1 == 0
assert data2_B0 == data2_B1 == data2_B2 == 0
echo = data2_B3
f.write(f"{echo}")
if cmd_id == SceneCmdId.SCENE_CMD_ID_CUTSCENE_DATA:
assert data1 == 0
address = data2_I
f.write(memory_context.get_c_reference_at_segmented(address))
if cmd_id == SceneCmdId.SCENE_CMD_ID_ALTERNATE_HEADER_LIST:
assert data1 == 0
address = data2_I
f.write(memory_context.get_c_reference_at_segmented(address))
if cmd_id == SceneCmdId.SCENE_CMD_ID_MISC_SETTINGS:
sceneCamType = data1
worldMapLocation = data2_I
sceneCamType_name = oot64_data.get_scene_cam_type_name(sceneCamType)
f.write(f"{sceneCamType_name}, {worldMapLocation}")
f.write("),\n")
if not self.braces_in_source:
f.write("}\n")
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return self.symbol_name
else:
raise ValueError
def get_c_includes(self):
return (
"array_count.h",
"z64object.h", # for OBJECT_*
# TODO these are not always needed:
"sequence.h", # for NATURE_ID_* and NA_BGM_*
"z64skybox.h", # for SKYBOX_*
)
def get_h_includes(self):
return ("z64scene.h",)
class AltHeadersResource(CDataArrayResource):
def report_elem(resource, memory_context: "MemoryContext", v):
assert isinstance(v, int)
address = v
if address != 0:
memory_context.report_resource_at_segmented(
resource,
address,
SceneCommandsResource,
lambda file, offset: SceneCommandsResource(
file, offset, f"{resource.name}_{address:08X}_Cmds"
),
)
def write_elem(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
if address == 0:
wctx.f.write("NULL")
else:
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
elem_cdata_ext = (
CDataExt_Value("I").set_report(report_elem).set_write(write_elem)
) # SceneCmd*
def try_parse_data(self, memory_context):
length = 0
for i, (v,) in enumerate(
struct.iter_unpack(">I", self.file.data[self.range_start :])
):
if v == 0:
if i < 3:
length = i + 1
continue
else:
break
try:
resolve_result = memory_context.resolve_segmented(v)
except:
break
data_may_be_scenecmds = False
for j in range(
resolve_result.file_offset, len(resolve_result.file.data), 8
):
cmd_id_int = resolve_result.file.data[j]
try:
cmd_id = SceneCmdId(cmd_id_int)
except ValueError:
break
if cmd_id == SceneCmdId.SCENE_CMD_ID_END:
data_may_be_scenecmds = True
break
if not data_may_be_scenecmds:
break
length = i + 1
assert length > 0
self.set_length(length)
return super().try_parse_data(memory_context)
def get_c_declaration_base(self):
return f"SceneCmd* {self.symbol_name}[]"
def get_h_includes(self):
return ("z64scene.h",)

View file

@ -0,0 +1,457 @@
# SPDX-FileCopyrightText: © 2025 ZeldaRET
# SPDX-License-Identifier: CC0-1.0
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ..extase.memorymap import MemoryContext
from ..extase import (
ResourceParseWaiting,
)
from ..extase.cdata_resources import (
CDataArrayResource,
CDataArrayNamedLengthResource,
CDataExt_Struct,
CDataExt_Array,
CDataExt_Value,
CDataExtWriteContext,
cdata_ext_Vec3s,
INDENT,
Vec3sArrayResource,
fmt_hex_s,
)
from .. import oot64_data
VERBOSE_SPAWN_LIST_LENGTH_GUESSING = False
class ActorEntryListResource(CDataArrayNamedLengthResource):
def write_elem(resource, memory_context, v, wctx: CDataExtWriteContext):
assert isinstance(v, dict)
f = wctx.f
f.write(wctx.line_prefix)
f.write("{\n")
f.write(wctx.line_prefix + INDENT)
f.write(oot64_data.get_actor_id_name(v["id"]))
f.write(",\n")
f.write(wctx.line_prefix + INDENT)
f.write("{ ")
f.write(", ".join(f"{p:6}" for p in (v["pos"][axis] for axis in "xyz")))
f.write(" }, // pos\n")
f.write(wctx.line_prefix + INDENT)
f.write("{ ")
f.write(", ".join(fmt_hex_s(r, 4) for r in (v["rot"][axis] for axis in "xyz")))
f.write(" }, // rot\n")
f.write(wctx.line_prefix + INDENT)
params = v["params"]
f.write(fmt_hex_s(params, 4))
if params < 0:
params_u16 = params + 0x1_0000
f.write(f" /* 0x{params_u16:04X} */")
f.write(", // params\n")
f.write(wctx.line_prefix)
f.write("}")
return True
elem_cdata_ext = CDataExt_Struct(
(
("id", CDataExt_Value.s16),
("pos", cdata_ext_Vec3s),
("rot", cdata_ext_Vec3s),
("params", CDataExt_Value.s16),
)
).set_write(write_elem)
def get_c_declaration_base(self):
return f"ActorEntry {self.symbol_name}[{self.length_name}]"
def get_c_includes(self):
return ("z64actor.h",)
def get_h_includes(self):
return ("z64scene.h",)
class ObjectListResource(CDataArrayNamedLengthResource):
elem_cdata_ext = CDataExt_Value("h").set_write_str_v(
lambda v: oot64_data.get_object_id_name(v)
)
def get_c_declaration_base(self):
return f"s16 {self.symbol_name}[{self.length_name}]"
def get_c_includes(self):
return ("z64object.h",)
def get_h_includes(self):
return ("ultra64.h",)
def write_RomFile(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, dict)
vromStart = v["vromStart"]
vromEnd = v["vromEnd"]
rom_file_name = memory_context.get_dmadata_table_rom_file_name_from_vrom(
vromStart, vromEnd
)
wctx.f.write(wctx.line_prefix)
wctx.f.write(f"ROM_FILE({rom_file_name})")
return True
cdata_ext_RomFile = CDataExt_Struct(
(
("vromStart", CDataExt_Value.u32),
("vromEnd", CDataExt_Value.u32),
)
).set_write(write_RomFile)
class RoomListResource(CDataArrayNamedLengthResource):
elem_cdata_ext = cdata_ext_RomFile
def get_c_declaration_base(self):
return f"RomFile {self.symbol_name}[{self.length_name}]"
def get_c_includes(self):
# TODO use DECLARE_ROM_SEGMENT to declare rooms rom files
return ("segment_symbols.h",)
def get_h_includes(self):
return ("romfile.h",)
class SpawnListResource(CDataArrayResource):
elem_cdata_ext = CDataExt_Struct(
(
("playerEntryIndex", CDataExt_Value.u8),
("room", CDataExt_Value.u8),
)
)
# (eventually) set by SceneCommandsResource
player_entry_list_length = None
room_list_length = None
def try_parse_data(self, memory_context):
if self.player_entry_list_length is None or self.room_list_length is None:
raise ResourceParseWaiting(
waiting_for=[
msg
for is_waiting, msg in (
(
self.player_entry_list_length is None,
"self.player_entry_list_length",
),
(
self.room_list_length is None,
"self.room_list_length",
),
)
if is_waiting
]
)
# File.name comes from the Name attribute on a <File> element,
# which is also used to make the path to the baserom file to read from.
# So File.name is the name of the baserom file.
# This may change in the future though ¯\_(ツ)_/¯
rom_file_name = self.file.name
# This way we can get the scene ID of the file
scene_id = oot64_data.get_scene_id_from_rom_file_name(rom_file_name)
scene_id_name = oot64_data.get_scene_id_name(scene_id)
# Get all the spawns used by all entrances using the scene
entrance_ids = oot64_data.get_entrance_ids_from_scene_id_name(scene_id_name)
all_used_spawns = set()
for entrance_id in entrance_ids:
entrance_spawn = oot64_data.get_entrance_spawn(entrance_id)
all_used_spawns.add(entrance_spawn)
num_spawns = max(all_used_spawns) + 1
# Handle the cases where the entrance table references spawn outside the spawn list,
# by checking if the indices in the last spawn in the list make sense.
# This is required for a few scenes in practice, otherwise the spawn list and exit list overlap.
while True:
last_spawn_unpacked = self.elem_cdata_ext.unpack_from(
self.file.data,
self.range_start + (num_spawns - 1) * self.elem_cdata_ext.size,
)
if (
last_spawn_unpacked["playerEntryIndex"] < self.player_entry_list_length
and last_spawn_unpacked["room"] < self.room_list_length
):
break
if VERBOSE_SPAWN_LIST_LENGTH_GUESSING:
print(
self,
"Removing one spawn because the last spawn of the list has bad indices",
last_spawn_unpacked,
num_spawns,
"->",
num_spawns - 1,
)
num_spawns -= 1
assert num_spawns > 0
# Handle the case where there may be an unused spawn, in the place of
# what would otherwise be padding.
if oot64_data.I_D_OMEGALUL:
assert self.elem_cdata_ext.size == 2
if num_spawns % 2 == 1:
data_to_next_4align = self.file.data[
self.range_start + num_spawns * 2 :
][:2]
if data_to_next_4align != b"\x00\x00":
if VERBOSE_SPAWN_LIST_LENGTH_GUESSING:
print(
self,
"Adding one spawn because the next supposedly-padding"
" two bytes are not padding (not zero)",
bytes(data_to_next_4align),
num_spawns,
"->",
num_spawns + 1,
)
num_spawns += 1
# Trim the list to avoid overlaps
# TODO this may be linked to headers for cutscene layers not having the spawns the entrance table expects
# for example spot00 hyrule field seems to always have a single 0,0 spawn for cutscene layers?
# (so the above approach using the entrance table does not generalize to cutscene layers)
# so it may be relevant to have the layer type when parsing the spawn list
# Alternatively, somehow trim the variable-length resources when overlapping
while True:
range_end = self.range_start + num_spawns * self.elem_cdata_ext.size
result, resource = self.file.get_resource_at(range_end - 1)
if resource is self:
break
if VERBOSE_SPAWN_LIST_LENGTH_GUESSING:
print(
self,
"Removing one spawn because the last spawn of the list overlaps with another resource",
resource,
num_spawns,
"->",
num_spawns - 1,
)
num_spawns -= 1
assert num_spawns > 0
self.set_length(num_spawns)
return super().try_parse_data(memory_context)
def get_c_declaration_base(self):
return f"Spawn {self.symbol_name}[]"
def get_h_includes(self):
return ("z64scene.h",)
class ExitListResource(CDataArrayResource):
elem_cdata_ext = CDataExt_Value("h").set_write_str_v(
lambda v: oot64_data.get_entrance_id_name(v)
)
# length set by SceneCommandsResource.try_parse_data
def get_c_declaration_base(self):
return f"s16 {self.symbol_name}[]"
def get_h_includes(self):
return ("ultra64.h",)
class EnvLightSettingsListResource(CDataArrayNamedLengthResource):
def write_color(resource, memory_context, v, wctx: CDataExtWriteContext):
assert not wctx.inhibit_top_braces
wctx.f.write(wctx.line_prefix)
wctx.f.write("{ " + f"{v[0]:3}, {v[1]:3}, {v[2]:3}" + " }")
return True
color_cdata_ext = CDataExt_Array(CDataExt_Value.u8, 3).set_write(write_color)
def write_dir(resource, memory_context, v, wctx: CDataExtWriteContext):
assert not wctx.inhibit_top_braces
wctx.f.write(wctx.line_prefix)
wctx.f.write("{ " + f"{v[0]:4}, {v[1]:4}, {v[2]:4}" + " }")
return True
dir_cdata_ext = CDataExt_Array(CDataExt_Value.s8, 3).set_write(write_dir)
def write_blendRateAndFogNear(v):
blendRate = (v >> 10) * 4
fogNear = v & 0x3FF
return f"(({blendRate} / 4) << 10) | {fogNear}"
elem_cdata_ext = CDataExt_Struct(
(
("ambientColor", color_cdata_ext),
("light1Dir", dir_cdata_ext),
("light1Color", color_cdata_ext),
("light2Dir", dir_cdata_ext),
("light2Color", color_cdata_ext),
("fogColor", color_cdata_ext),
(
"blendRateAndFogNear",
CDataExt_Value("h").set_write_str_v(write_blendRateAndFogNear),
),
("zFar", CDataExt_Value.s16),
)
)
def get_c_declaration_base(self):
return f"EnvLightSettings {self.symbol_name}[{self.length_name}]"
def get_h_includes(self):
return ("z64environment.h",)
class TransitionActorEntryListResource(CDataArrayNamedLengthResource):
def write_elem(resource, memory_context, v, wctx: CDataExtWriteContext):
assert isinstance(v, dict)
f = wctx.f
f.write(wctx.line_prefix)
f.write("{\n")
f.write(wctx.line_prefix + INDENT)
f.write("{\n")
f.write(wctx.line_prefix + 2 * INDENT)
f.write("// { room, bgCamIndex }\n")
for side_i in range(2):
side = v["sides"][side_i]
room = side["room"]
bgCamIndex = side["bgCamIndex"]
f.write(wctx.line_prefix + 2 * INDENT)
f.write("{ ")
f.write(f"{room}, {bgCamIndex}")
f.write(" },\n")
f.write(wctx.line_prefix + INDENT)
f.write("}, // sides\n")
f.write(wctx.line_prefix + INDENT)
f.write(oot64_data.get_actor_id_name(v["id"]))
f.write(",\n")
f.write(wctx.line_prefix + INDENT)
f.write("{ ")
f.write(", ".join(f"{p:6}" for p in (v["pos"][axis] for axis in "xyz")))
f.write(" }, // pos\n")
f.write(wctx.line_prefix + INDENT)
f.write(fmt_hex_s(v["rotY"], 4))
f.write(", // rotY\n")
f.write(wctx.line_prefix + INDENT)
params = v["params"]
f.write(fmt_hex_s(params, 4))
if params < 0:
params_u16 = params + 0x1_0000
f.write(f" /* 0x{params_u16:04X} */")
f.write(", // params\n")
f.write(wctx.line_prefix)
f.write("}")
return True
elem_cdata_ext = CDataExt_Struct(
(
(
"sides",
CDataExt_Array(
CDataExt_Struct(
(
("room", CDataExt_Value.s8),
("bgCamIndex", CDataExt_Value.s8),
)
),
2,
),
),
("id", CDataExt_Value.s16),
("pos", cdata_ext_Vec3s),
("rotY", CDataExt_Value.s16),
("params", CDataExt_Value.s16),
)
).set_write(write_elem)
def get_c_declaration_base(self):
return f"TransitionActorEntry {self.symbol_name}[{self.length_name}]"
def get_c_includes(self):
return ("z64actor.h",)
def get_h_includes(self):
return ("z64scene.h",)
class PathListResource(CDataArrayResource):
def report_elem(resource, memory_context: "MemoryContext", v):
assert isinstance(v, dict)
count = v["count"]
assert isinstance(count, int)
points = v["points"]
assert isinstance(points, int)
memory_context.report_resource_at_segmented(
resource,
points,
Vec3sArrayResource,
lambda file, offset: Vec3sArrayResource(
file, offset, f"{resource.name}_{points:08X}_Points", count
),
)
def write_elem(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, dict)
count = v["count"]
assert isinstance(count, int)
points = v["points"]
assert isinstance(points, int)
f = wctx.f
f.write(wctx.line_prefix)
f.write("{ ")
f.write(memory_context.get_c_expression_length_at_segmented(points))
f.write(", ")
f.write(memory_context.get_c_reference_at_segmented(points))
f.write(" }")
return True
elem_cdata_ext = (
CDataExt_Struct(
(
("count", CDataExt_Value.u8),
("pad1", CDataExt_Value.pad8),
("pad2", CDataExt_Value.pad16),
("points", CDataExt_Value("I")), # Vec3s*
)
)
.set_report(report_elem)
.set_write(write_elem)
)
def try_parse_data(self, memory_context):
if self._length is None:
# TODO guess
self.set_length(1)
return super().try_parse_data(memory_context)
def get_c_declaration_base(self):
return f"Path {self.symbol_name}[]"
def get_h_includes(self):
return ("z64path.h",)

View file

@ -0,0 +1,218 @@
# SPDX-FileCopyrightText: © 2025 ZeldaRET
# SPDX-License-Identifier: CC0-1.0
from typing import TYPE_CHECKING
from ..extase import RESOURCE_PARSE_SUCCESS
if TYPE_CHECKING:
from ..extase.memorymap import MemoryContext
from ..extase.cdata_resources import (
CDataResource,
CDataArrayResource,
CDataExt_Struct,
CDataExtWriteContext,
cdata_ext_Vec3f,
cdata_ext_Vec3s,
CDataExt_Value,
)
from . import animation_resources
from . import dlist_resources
from . import skeleton_resources
class LegacyLimbResource(CDataResource):
def report_limb(resource, memory_context: "MemoryContext", v):
assert isinstance(v, int)
address = v
if address != 0:
memory_context.report_resource_at_segmented(
resource,
address,
LegacyLimbResource,
lambda file, offset: LegacyLimbResource(
file,
offset,
f"{resource.name}_{address:08X}_LegacyLimb",
),
)
def write_limb(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
if address == 0:
wctx.f.write("NULL")
else:
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
cdata_ext = CDataExt_Struct(
(
("dList", dlist_resources.cdata_ext_gfx_segmented),
("trans", cdata_ext_Vec3f),
("rot", cdata_ext_Vec3s),
("pad16", CDataExt_Value.pad16),
(
"sibling",
CDataExt_Value("I").set_report(report_limb).set_write(write_limb),
), # LegacyLimb*
(
"child",
CDataExt_Value("I").set_report(report_limb).set_write(write_limb),
), # LegacyLimb*
)
)
def get_c_declaration_base(self):
return f"LegacyLimb {self.symbol_name}"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return f"&{self.symbol_name}"
else:
raise ValueError()
def get_h_includes(self):
return ("z64animation_legacy.h",)
class LegacyJointKeyArrayResource(CDataArrayResource):
elem_cdata_ext = CDataExt_Struct(
(
("xMax", CDataExt_Value.s16),
("x", CDataExt_Value.s16),
("yMax", CDataExt_Value.s16),
("y", CDataExt_Value.s16),
("zMax", CDataExt_Value.s16),
("z", CDataExt_Value.s16),
)
)
def get_c_declaration_base(self):
return f"LegacyJointKey {self.symbol_name}[]"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return f"{self.symbol_name}"
else:
raise ValueError()
def get_h_includes(self):
return ("z64animation_legacy.h",)
class LegacyAnimationResource(CDataResource):
def write_frameData(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
def write_jointKey(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
cdata_ext = CDataExt_Struct(
(
("frameCount", CDataExt_Value.s16),
("limbCount", CDataExt_Value.s16),
("frameData", CDataExt_Value("I").set_write(write_frameData)), # s16*
(
"jointKey",
CDataExt_Value("I").set_write(write_jointKey),
), # LegacyJointKey*
)
)
def try_parse_data(self, memory_context):
super().try_parse_data(memory_context)
address_frameData = self.cdata_unpacked["frameData"]
assert isinstance(address_frameData, int)
resource_frameData = memory_context.report_resource_at_segmented(
self,
address_frameData,
animation_resources.AnimationFrameDataResource,
lambda file, offset: animation_resources.AnimationFrameDataResource(
file,
offset,
f"{self.name}_{address_frameData:08X}_FrameData",
),
)
address_jointKey = self.cdata_unpacked["jointKey"]
assert isinstance(address_jointKey, int)
resource_jointKey = memory_context.report_resource_at_segmented(
self,
address_jointKey,
LegacyJointKeyArrayResource,
lambda file, offset: LegacyJointKeyArrayResource(
file,
offset,
f"{self.name}_{address_jointKey:08X}_JointKeys",
),
)
resource_jointKey.set_length(self.cdata_unpacked["limbCount"] + 1)
# The length of the frameData array is
# for now assumed to fill the space to the jointKey data,
# at the very least before subtracting the offsets check that
# the offsets belong to the same file
# TODO better idea for computing this data's size
if not (resource_frameData.file == resource_jointKey.file):
raise NotImplementedError(
"Expected frameData and jointIndices to be in the same file",
self.cdata_unpacked,
resource_frameData.file,
resource_jointKey.file,
)
if resource_frameData.range_start < resource_jointKey.range_start:
resource_frameData.length = (
resource_jointKey.range_start - resource_frameData.range_start
) // animation_resources.AnimationFrameDataResource.elem_cdata_ext.size
else:
raise NotImplementedError(
"Expected offsets of frameData, jointKey to be in order",
self.cdata_unpacked,
hex(resource_frameData.range_start),
hex(resource_jointKey.range_start),
)
return RESOURCE_PARSE_SUCCESS
def get_c_declaration_base(self):
return f"LegacyAnimationHeader {self.symbol_name}"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return f"&{self.symbol_name}"
else:
raise ValueError()
def get_h_includes(self):
return ("z64animation_legacy.h",)
class LegacyLimbsArrayResource(skeleton_resources.LimbsArrayResourceABC):
limb_type = LegacyLimbResource
c_limb_type = "LegacyLimb"
def get_h_includes(self):
return ("z64animation_legacy.h",)

View file

@ -0,0 +1,378 @@
# SPDX-FileCopyrightText: © 2025 ZeldaRET
# SPDX-License-Identifier: CC0-1.0
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ..extase.memorymap import MemoryContext
from ..extase import (
File,
RESOURCE_PARSE_SUCCESS,
ResourceParseWaiting,
)
from ..extase.cdata_resources import (
CDataResource,
CDataExt_Struct,
CDataExt_Value,
CDataExt_Array,
CDataExtWriteContext,
)
from . import dlist_resources
class KnotCountsArrayResource(CDataResource, can_size_be_unknown=True):
elem_cdata_ext = CDataExt_Value.u8
def __init__(self, file: File, range_start: int, name: str):
super().__init__(file, range_start, name)
self.length = None
def try_parse_data(self, memory_context: "MemoryContext"):
if self.length is not None:
self.cdata_ext = CDataExt_Array(self.elem_cdata_ext, self.length)
self.range_end = self.range_start + self.cdata_ext.size
return super().try_parse_data(memory_context)
else:
raise ResourceParseWaiting(waiting_for=["self.length"])
def get_c_declaration_base(self):
return f"u8 {self.symbol_name}[]"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return self.symbol_name
else:
raise ValueError()
def get_h_includes(self):
return ("ultra64.h",)
class CurveInterpKnotArrayResource(CDataResource, can_size_be_unknown=True):
elem_cdata_ext = CDataExt_Struct(
(
("flags", CDataExt_Value.u16),
("abscissa", CDataExt_Value.s16),
("leftGradient", CDataExt_Value.s16),
("rightGradient", CDataExt_Value.s16),
("ordinate", CDataExt_Value.f32),
)
)
def __init__(self, file: File, range_start: int, name: str):
super().__init__(file, range_start, name)
self.length = None
def try_parse_data(self, memory_context: "MemoryContext"):
if self.length is not None:
self.cdata_ext = CDataExt_Array(self.elem_cdata_ext, self.length)
self.range_end = self.range_start + self.cdata_ext.size
return super().try_parse_data(memory_context)
else:
raise ResourceParseWaiting(waiting_for=["self.length"])
def get_c_declaration_base(self):
return f"CurveInterpKnot {self.symbol_name}[]"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return self.symbol_name
else:
raise ValueError()
def get_h_includes(self):
return ("z64curve.h",)
class ConstantDataArrayResource(CDataResource, can_size_be_unknown=True):
elem_cdata_ext = CDataExt_Value.s16
def __init__(self, file: File, range_start: int, name: str):
super().__init__(file, range_start, name)
self.length = None
def try_parse_data(self, memory_context: "MemoryContext"):
if self.length is not None:
self.cdata_ext = CDataExt_Array(self.elem_cdata_ext, self.length)
self.range_end = self.range_start + self.cdata_ext.size
return super().try_parse_data(memory_context)
else:
raise ResourceParseWaiting(waiting_for=["self.length"])
def get_c_declaration_base(self):
return f"s16 {self.symbol_name}[]"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return self.symbol_name
else:
raise ValueError()
def get_h_includes(self):
return ("ultra64.h",)
class CurveAnimationHeaderResource(CDataResource):
def report_knotCounts(resource, memory_context: "MemoryContext", v):
assert isinstance(v, int)
address = v
memory_context.report_resource_at_segmented(
resource,
address,
KnotCountsArrayResource,
lambda file, offset: KnotCountsArrayResource(
file, offset, f"{resource.name}_{address:08X}_KnotCounts"
),
)
def write_knotCounts(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
def report_interpolationData(resource, memory_context: "MemoryContext", v):
assert isinstance(v, int)
address = v
memory_context.report_resource_at_segmented(
resource,
address,
CurveInterpKnotArrayResource,
lambda file, offset: CurveInterpKnotArrayResource(
file, offset, f"{resource.name}_{address:08X}_InterpolationData"
),
)
def write_interpolationData(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
def report_constantData(resource, memory_context: "MemoryContext", v):
assert isinstance(v, int)
address = v
memory_context.report_resource_at_segmented(
resource,
address,
ConstantDataArrayResource,
lambda file, offset: ConstantDataArrayResource(
file, offset, f"{resource.name}_{address:08X}_ConstantData"
),
)
def write_constantData(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
cdata_ext = CDataExt_Struct(
(
(
"knotCounts",
CDataExt_Value("I")
.set_report(report_knotCounts)
.set_write(write_knotCounts),
), # u8*
(
"interpolationData",
CDataExt_Value("I")
.set_report(report_interpolationData)
.set_write(write_interpolationData),
), # CurveInterpKnot*
(
"constantData",
CDataExt_Value("I")
.set_report(report_constantData)
.set_write(write_constantData),
), # s16*
("unk_0C", CDataExt_Value.s16),
("frameCount", CDataExt_Value.s16),
)
)
def try_parse_data(self, memory_context):
super().try_parse_data(memory_context)
knotCounts = self.cdata_unpacked["knotCounts"]
interpolationData = self.cdata_unpacked["interpolationData"]
constantData = self.cdata_unpacked["constantData"]
resource_knotCounts = memory_context.resolve_segmented(knotCounts).get_resource(
KnotCountsArrayResource
)
resource_interpolationData = memory_context.resolve_segmented(
interpolationData
).get_resource(CurveInterpKnotArrayResource)
resource_constantData = memory_context.resolve_segmented(
constantData
).get_resource(ConstantDataArrayResource)
if (
resource_knotCounts.file
== resource_interpolationData.file
== resource_constantData.file
== self.file
):
knotCounts_offset = resource_knotCounts.range_start
constantData_offset = resource_constantData.range_start
interpolationData_offset = resource_interpolationData.range_start
animHeader_offset = self.range_start
assert (
knotCounts_offset
< constantData_offset
< interpolationData_offset
< animHeader_offset
)
resource_knotCounts.length = (
constantData_offset - knotCounts_offset
) // resource_knotCounts.elem_cdata_ext.size
resource_constantData.length = (
interpolationData_offset - constantData_offset
) // resource_constantData.elem_cdata_ext.size
resource_interpolationData.length = (
animHeader_offset - interpolationData_offset
) // resource_interpolationData.elem_cdata_ext.size
return RESOURCE_PARSE_SUCCESS
else:
raise NotImplementedError
def get_c_declaration_base(self):
return f"CurveAnimationHeader {self.symbol_name}"
def get_c_reference(self, resource_offset: int):
raise ValueError()
def get_h_includes(self):
return ("z64curve.h",)
class SkelCurveLimbResource(CDataResource):
cdata_ext = CDataExt_Struct(
(
("child", CDataExt_Value.u8),
("sibling", CDataExt_Value.u8),
("pad2", CDataExt_Value.pad16),
(
"dList",
CDataExt_Array(
dlist_resources.cdata_ext_gfx_segmented, # Gfx*
2,
),
),
)
)
def get_c_declaration_base(self):
return f"SkelCurveLimb {self.symbol_name}"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return f"&{self.symbol_name}"
else:
raise ValueError()
def get_h_includes(self):
return ("z64curve.h",)
class SkelCurveLimbArrayResource(CDataResource):
def report_limb_element(resource, memory_context: "MemoryContext", v):
assert isinstance(v, int)
address = v
memory_context.report_resource_at_segmented(
resource,
address,
SkelCurveLimbResource,
lambda file, offset: SkelCurveLimbResource(
file, offset, f"{resource.name}_{address:08X}"
),
)
def write_limb_element(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
elem_cdata_ext = (
CDataExt_Value("I")
.set_report(report_limb_element)
.set_write(write_limb_element)
)
def __init__(self, file: File, range_start: int, name: str, length: int):
self.cdata_ext = CDataExt_Array(self.elem_cdata_ext, length)
super().__init__(file, range_start, name)
def get_c_declaration_base(self):
return f"SkelCurveLimb* {self.symbol_name}[]"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return self.symbol_name
else:
raise ValueError()
def get_h_includes(self):
return ("z64curve.h",)
class CurveSkeletonHeaderResource(CDataResource):
def report_limbs(resource, memory_context: "MemoryContext", v):
assert isinstance(v, int)
address = v
memory_context.report_resource_at_segmented(
resource,
address,
SkelCurveLimbArrayResource,
lambda file, offset: SkelCurveLimbArrayResource(
file,
offset,
f"{resource.name}_Limbs_",
resource.cdata_unpacked["limbCount"],
),
)
def write_limbs(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
cdata_ext = CDataExt_Struct(
(
(
"limbs",
CDataExt_Value("I").set_report(report_limbs).set_write(write_limbs),
), # SkelCurveLimb**
("limbCount", CDataExt_Value.u8),
("pad5", CDataExt_Value.pad8),
("pad6", CDataExt_Value.pad16),
)
)
def get_c_declaration_base(self):
return f"CurveSkeletonHeader {self.symbol_name}"
def get_c_reference(self, resource_offset: int):
raise ValueError()
def get_h_includes(self):
return ("z64curve.h",)

View file

@ -0,0 +1,356 @@
# SPDX-FileCopyrightText: © 2025 ZeldaRET
# SPDX-License-Identifier: CC0-1.0
import abc
from typing import TYPE_CHECKING
from ..extase import (
RESOURCE_PARSE_SUCCESS,
ResourceParseInProgress,
ResourceParseWaiting,
)
if TYPE_CHECKING:
from ..extase.memorymap import MemoryContext
from ..extase.cdata_resources import (
CDataResource,
CDataArrayResource,
CDataExt_Value,
CDataExt_Struct,
CDataExt_Array,
CDataExtWriteContext,
cdata_ext_Vec3s,
)
from . import dlist_resources
class StandardLimbResource(CDataResource):
def write_limb_index(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
f = wctx.f
f.write(wctx.line_prefix)
if resource.skeleton_resource is None:
f.write(f"{v}")
else:
f.write(f"/* {v} */ ")
if v == 0xFF:
f.write("LIMB_DONE")
else:
f.write(
resource.skeleton_resource.limbs_array_resource.limbs[
v
].enum_member_name
)
f.write(" - 1")
return True
cdata_ext = CDataExt_Struct(
(
("jointPos", cdata_ext_Vec3s),
("child", CDataExt_Value("B").set_write(write_limb_index)),
("sibling", CDataExt_Value("B").set_write(write_limb_index)),
("dList", dlist_resources.cdata_ext_gfx_segmented),
)
)
def __init__(self, file, range_start, name):
super().__init__(file, range_start, name)
self.enum_member_name = f"LIMB_{file.name.upper()}_{range_start:06X}"
self.skeleton_resource: "SkeletonResourceBaseABC | None" = None
def set_enum_member_name(self, enum_member_name: str):
self.enum_member_name = enum_member_name
def get_c_declaration_base(self):
return f"StandardLimb {self.symbol_name}"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return f"&{self.symbol_name}"
else:
raise ValueError()
def get_h_includes(self):
return ("z64animation.h",)
class LODLimbResource(CDataResource):
cdata_ext = CDataExt_Struct(
(
("jointPos", cdata_ext_Vec3s),
(
"child",
CDataExt_Value("B").set_write(StandardLimbResource.write_limb_index),
),
(
"sibling",
CDataExt_Value("B").set_write(StandardLimbResource.write_limb_index),
),
("dLists", CDataExt_Array(dlist_resources.cdata_ext_gfx_segmented, 2)),
)
)
def __init__(self, file, range_start, name):
super().__init__(file, range_start, name)
self.enum_member_name = f"LIMB_{file.name.upper()}_{range_start:06X}"
def set_enum_member_name(self, enum_member_name: str):
self.enum_member_name = enum_member_name
def get_c_declaration_base(self):
return f"LodLimb {self.symbol_name}"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return f"&{self.symbol_name}"
else:
raise ValueError()
def get_h_includes(self):
return ("z64animation.h",)
class LimbsArrayResourceABC(CDataArrayResource):
limb_type: type[CDataResource]
c_limb_type: str
def write_limb_element(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
elem_cdata_ext = CDataExt_Value("I").set_write(write_limb_element)
def __init__(self, file, range_start, name):
super().__init__(file, range_start, name)
self.limbs = None
def try_parse_data(self, memory_context):
ret = super().try_parse_data(memory_context)
assert ret == RESOURCE_PARSE_SUCCESS
self.limbs: list[self.limb_type] = []
for address in self.cdata_unpacked:
limb = memory_context.report_resource_at_segmented(
self,
address,
self.limb_type,
lambda file, offset: self.limb_type(
file,
offset,
f"{self.name}_{address:08X}_{self.c_limb_type}",
),
)
self.limbs.append(limb)
return RESOURCE_PARSE_SUCCESS
def get_c_declaration_base(self):
return f"void* {self.symbol_name}[]"
class StandardLimbsArrayResource(LimbsArrayResourceABC):
limb_type = StandardLimbResource
c_limb_type = "StandardLimb"
class LODLimbsArrayResource(LimbsArrayResourceABC):
limb_type = LODLimbResource
c_limb_type = "LodLimb"
class SkeletonResourceBaseABC(CDataResource):
limbs_array_type: type[LimbsArrayResourceABC]
def __init__(self, file, range_start, name):
super().__init__(file, range_start, name)
self.enum_name = f"{self.symbol_name}Limb"
self.enum_member_name_none = f"LIMB_{file.name.upper()}_{range_start:06X}_NONE"
self.enum_member_name_max = f"LIMB_{file.name.upper()}_{range_start:06X}_MAX"
self.limbs_array_resource = None
def set_enum_name(self, enum_name: str):
self.enum_name = enum_name
def set_enum_member_name_none(self, enum_member_name_none: str):
self.enum_member_name_none = enum_member_name_none
def set_enum_member_name_max(self, enum_member_name_max: str):
self.enum_member_name_max = enum_member_name_max
def try_parse_data(self, memory_context):
if self.limbs_array_resource is None:
ret = super().try_parse_data(memory_context)
assert ret == RESOURCE_PARSE_SUCCESS
data = self.get_skeleton_header_cdata_unpacked()
segment_resolve_result = memory_context.resolve_segmented(data["segment"])
self.limbs_array_resource = segment_resolve_result.get_resource(
self.limbs_array_type
)
if self.limbs_array_resource.limbs is None:
raise ResourceParseInProgress(
new_progress_done=["reported limbs array"],
waiting_for=["self.limbs_array_resource.limbs"],
)
else:
if self.limbs_array_resource.limbs is None:
raise ResourceParseWaiting(
waiting_for=["self.limbs_array_resource.limbs"],
)
for limb in self.limbs_array_resource.limbs:
limb.skeleton_resource = self
return RESOURCE_PARSE_SUCCESS
def write_c_declaration(self, h):
h.write(f"typedef enum {self.enum_name} {{\n")
limb_enum_members = (
self.enum_member_name_none,
*(limb.enum_member_name for limb in self.limbs_array_resource.limbs),
self.enum_member_name_max,
)
h.write(
",\n".join(
f" /* {i:2} */ {enum_member}"
for i, enum_member in enumerate(limb_enum_members)
)
+ "\n"
)
h.write(f"}} {self.enum_name};\n")
super().write_c_declaration(h)
return True
@abc.abstractmethod
def get_skeleton_header_cdata_unpacked(self) -> dict: ...
class SkeletonResourceABC(SkeletonResourceBaseABC):
def report_segment(resource, memory_context: "MemoryContext", v):
assert isinstance(v, int)
address = v
resource_limbs = memory_context.report_resource_at_segmented(
resource,
address,
resource.limbs_array_type,
lambda file, offset: resource.limbs_array_type(
file,
offset,
f"{resource.name}_{address:08X}_Limbs",
),
)
resource_limbs.set_length(
resource.get_skeleton_header_cdata_unpacked()["limbCount"]
)
def write_segment(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
def write_limbCount(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
wctx.f.write(wctx.line_prefix)
wctx.f.write(
memory_context.get_c_expression_length_at_segmented(
resource.get_skeleton_header_cdata_unpacked()["segment"]
)
)
return True
cdata_ext = CDataExt_Struct(
(
(
"segment",
CDataExt_Value("I").set_report(report_segment).set_write(write_segment),
),
(
"limbCount",
CDataExt_Value("B").set_write(write_limbCount),
),
("pad5", CDataExt_Value.pad8),
("pad6", CDataExt_Value.pad16),
)
)
def get_skeleton_header_cdata_unpacked(self):
return self.cdata_unpacked
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return f"&{self.symbol_name}"
else:
raise ValueError()
def get_c_declaration_base(self):
return f"SkeletonHeader {self.symbol_name}"
def get_c_includes(self):
return ("array_count.h",)
def get_h_includes(self):
return ("z64animation.h",)
class SkeletonNormalResource(SkeletonResourceABC):
limbs_array_type = StandardLimbsArrayResource
class SkeletonNormalLODResource(SkeletonResourceABC):
limbs_array_type = LODLimbsArrayResource
class SkeletonFlexResourceABC(SkeletonResourceBaseABC):
skeleton_type: type[SkeletonResourceABC]
# For SkeletonResourceABC.report_segment
@property
def limbs_array_type(self):
return self.skeleton_type.limbs_array_type
def __init__(self, file, range_start, name):
self.cdata_ext = CDataExt_Struct(
(
("sh", self.skeleton_type.cdata_ext),
("dListCount", CDataExt_Value.u8),
("pad9", CDataExt_Value.pad8),
("pad10", CDataExt_Value.pad16),
)
)
super().__init__(file, range_start, name)
def get_skeleton_header_cdata_unpacked(self):
return self.cdata_unpacked["sh"]
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return f"&{self.symbol_name}"
else:
raise ValueError()
def get_c_declaration_base(self):
return f"FlexSkeletonHeader {self.symbol_name}"
def get_c_includes(self):
return ("array_count.h",)
def get_h_includes(self):
return ("z64animation.h",)
class SkeletonFlexResource(SkeletonFlexResourceABC):
skeleton_type = SkeletonNormalResource
class SkeletonFlexLODResource(SkeletonFlexResourceABC):
skeleton_type = SkeletonNormalLODResource

View file

@ -0,0 +1,279 @@
# SPDX-FileCopyrightText: © 2025 ZeldaRET
# SPDX-License-Identifier: CC0-1.0
from typing import TYPE_CHECKING
from ..oot64_data.misc_ids import SKIN_LIMB_TYPES
if TYPE_CHECKING:
from ..extase.memorymap import MemoryContext
from ..extase.cdata_resources import (
CDataResource,
CDataArrayResource,
CDataExt_Value,
CDataExt_Struct,
CDataExtWriteContext,
cdata_ext_Vec3s,
)
from . import dlist_resources
from . import skeleton_resources
class SkinVertexArrayResource(CDataArrayResource):
elem_cdata_ext = CDataExt_Struct(
(
("index", CDataExt_Value.u16),
("s", CDataExt_Value.s16),
("t", CDataExt_Value.s16),
("normX", CDataExt_Value.s8),
("normY", CDataExt_Value.s8),
("normZ", CDataExt_Value.s8),
("alpha", CDataExt_Value.u8),
)
)
def get_c_declaration_base(self):
return f"SkinVertex {self.symbol_name}[]"
def get_h_includes(self):
return ("z64skin.h",)
class SkinTransformationArrayResource(CDataArrayResource):
elem_cdata_ext = CDataExt_Struct(
(
("limbIndex", CDataExt_Value.u8),
("pad1", CDataExt_Value.pad8),
("x", CDataExt_Value.s16),
("y", CDataExt_Value.s16),
("z", CDataExt_Value.s16),
("scale", CDataExt_Value.u8),
("pad9", CDataExt_Value.pad8),
)
)
def get_c_declaration_base(self):
return f"SkinTransformation {self.symbol_name}[]"
def get_h_includes(self):
return ("z64skin.h",)
class SkinLimbModifArrayResource(CDataArrayResource):
def report_elem(resource, memory_context: "MemoryContext", v):
assert isinstance(v, dict)
address = v["skinVertices"]
assert isinstance(address, int)
skin_vertices_res = memory_context.report_resource_at_segmented(
resource,
address,
SkinVertexArrayResource,
lambda file, offset: SkinVertexArrayResource(
file,
offset,
f"{resource.name}_{offset:08X}_SkinVertices",
),
)
skin_vertices_res.set_length(v["vtxCount"])
address = v["limbTransformations"]
assert isinstance(address, int)
skin_vertices_res = memory_context.report_resource_at_segmented(
resource,
address,
SkinTransformationArrayResource,
lambda file, offset: SkinTransformationArrayResource(
file,
offset,
f"{resource.name}_{offset:08X}_SkinTransforms",
),
)
skin_vertices_res.set_length(v["transformCount"])
def write_skinVertices(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
def write_limbTransformations(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
elem_cdata_ext = CDataExt_Struct(
(
("vtxCount", CDataExt_Value.u16),
("transformCount", CDataExt_Value.u16),
("unk_4", CDataExt_Value.u16),
("pad6", CDataExt_Value.pad16),
(
"skinVertices",
(CDataExt_Value("I").set_write(write_skinVertices)),
), # SkinVertex*
(
"limbTransformations",
(CDataExt_Value("I").set_write(write_limbTransformations)),
), # SkinTransformation*
)
).set_report(report_elem)
def get_c_declaration_base(self):
return f"SkinLimbModif {self.symbol_name}[]"
def get_h_includes(self):
return ("z64skin.h",)
class SkinAnimatedLimbDataResource(CDataResource):
def report_limbModifications(resource, memory_context: "MemoryContext", v):
assert isinstance(v, int)
address = v
skin_vertices_res = memory_context.report_resource_at_segmented(
resource,
address,
SkinLimbModifArrayResource,
lambda file, offset: SkinLimbModifArrayResource(
file,
offset,
f"{resource.name}_{offset:08X}_SkinLimbModifs",
),
)
skin_vertices_res.set_length(resource.cdata_unpacked["limbModifCount"])
def write_limbModifications(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
cdata_ext = CDataExt_Struct(
(
("totalVtxCount", CDataExt_Value.u16),
("limbModifCount", CDataExt_Value.u16),
(
"limbModifications",
(
CDataExt_Value("I")
.set_report(report_limbModifications)
.set_write(write_limbModifications)
),
), # SkinLimbModif*
("dlist", dlist_resources.cdata_ext_gfx_segmented),
)
)
def get_c_declaration_base(self):
return f"SkinAnimatedLimbData {self.symbol_name}"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return f"&{self.symbol_name}"
else:
raise ValueError()
def get_h_includes(self):
return ("z64skin.h",)
class SkinLimbResource(CDataResource):
def report_segment(resource, memory_context: "MemoryContext", v):
assert isinstance(v, int)
address = v
segmentType = resource.cdata_unpacked["segmentType"]
if segmentType == 4: # SKIN_LIMB_TYPE_ANIMATED
# segment is SkinAnimatedLimbData*
assert address != 0
memory_context.report_resource_at_segmented(
resource,
address,
SkinAnimatedLimbDataResource,
lambda file, offset: SkinAnimatedLimbDataResource(
file, offset, f"{resource.name}_{address:08X}_SkinAnimatedLimbData"
),
)
elif segmentType == 11: # SKIN_LIMB_TYPE_NORMAL
# segment is Gfx*
assert address != 0
dlist_resources.report_gfx_segmented(resource, memory_context, address)
def write_segment(
resource, memory_context: "MemoryContext", v, wctx: CDataExtWriteContext
):
assert isinstance(v, int)
address = v
wctx.f.write(wctx.line_prefix)
if address == 0:
wctx.f.write("NULL")
else:
wctx.f.write(memory_context.get_c_reference_at_segmented(address))
return True
cdata_ext = CDataExt_Struct(
(
("jointPos", cdata_ext_Vec3s),
(
"child",
CDataExt_Value("B").set_write(
skeleton_resources.StandardLimbResource.write_limb_index
),
),
(
"sibling",
CDataExt_Value("B").set_write(
skeleton_resources.StandardLimbResource.write_limb_index
),
),
(
"segmentType",
CDataExt_Value("i").set_write_str_v(
lambda v: SKIN_LIMB_TYPES.get(v, f"{v}")
),
),
(
"segment",
CDataExt_Value("I").set_report(report_segment).set_write(write_segment),
),
)
)
def __init__(self, file, range_start, name):
super().__init__(file, range_start, name)
self.enum_member_name = f"LIMB_{file.name.upper()}_{range_start:06X}"
def set_enum_member_name(self, enum_member_name: str):
self.enum_member_name = enum_member_name
def get_c_declaration_base(self):
return f"SkinLimb {self.symbol_name}"
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return f"&{self.symbol_name}"
else:
raise ValueError()
def get_h_includes(self):
return ("z64skin.h",)
class SkinLimbsArrayResource(skeleton_resources.LimbsArrayResourceABC):
limb_type = SkinLimbResource
c_limb_type = "SkinLimb"
class SkeletonSkinResource(skeleton_resources.SkeletonResourceABC):
limbs_array_type = SkinLimbsArrayResource