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:
parent
0c6c112cb9
commit
1e556e3a3d
460 changed files with 14342 additions and 48656 deletions
205
tools/assets/extract/extase_oot64/animation_resources.py
Normal file
205
tools/assets/extract/extase_oot64/animation_resources.py
Normal 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",)
|
812
tools/assets/extract/extase_oot64/collision_resources.py
Normal file
812
tools/assets/extract/extase_oot64/collision_resources.py
Normal 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",)
|
1579
tools/assets/extract/extase_oot64/dlist_resources.py
Normal file
1579
tools/assets/extract/extase_oot64/dlist_resources.py
Normal file
File diff suppressed because it is too large
Load diff
63
tools/assets/extract/extase_oot64/misc_resources.py
Normal file
63
tools/assets/extract/extase_oot64/misc_resources.py
Normal 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",)
|
102
tools/assets/extract/extase_oot64/playeranim_resources.py
Normal file
102
tools/assets/extract/extase_oot64/playeranim_resources.py
Normal 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",)
|
557
tools/assets/extract/extase_oot64/room_shape_resources.py
Normal file
557
tools/assets/extract/extase_oot64/room_shape_resources.py
Normal 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",)
|
717
tools/assets/extract/extase_oot64/scene_commands_resource.py
Normal file
717
tools/assets/extract/extase_oot64/scene_commands_resource.py
Normal 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",)
|
457
tools/assets/extract/extase_oot64/scene_rooms_resources.py
Normal file
457
tools/assets/extract/extase_oot64/scene_rooms_resources.py
Normal 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",)
|
218
tools/assets/extract/extase_oot64/skelanime_legacy_resources.py
Normal file
218
tools/assets/extract/extase_oot64/skelanime_legacy_resources.py
Normal 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",)
|
378
tools/assets/extract/extase_oot64/skelcurve_resources.py
Normal file
378
tools/assets/extract/extase_oot64/skelcurve_resources.py
Normal 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",)
|
356
tools/assets/extract/extase_oot64/skeleton_resources.py
Normal file
356
tools/assets/extract/extase_oot64/skeleton_resources.py
Normal 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
|
279
tools/assets/extract/extase_oot64/skeleton_skin_resources.py
Normal file
279
tools/assets/extract/extase_oot64/skeleton_skin_resources.py
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue