1
0
Fork 0
mirror of https://github.com/zeldaret/oot.git synced 2025-05-11 19:43:44 +00:00
oot/tools/assets/extract/extase_oot64/collision_resources.py
Dragorn421 cb93ca3981
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
2025-05-06 22:51:05 +02:00

766 lines
25 KiB
Python

# 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,
INDENT,
)
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
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("1")
if flags & 2:
flags &= ~2
flags_str_list.append("2")
if flags & 4:
flags &= ~4
flags_str_list.append("4")
elif i == 1:
if flags & 1:
flags &= ~1
flags_str_list.append("1")
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"{vtxId} | (({flags_str}) << 13), // {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
wctx.f.write(wctx.line_prefix)
wctx.f.write(f"COLPOLY_SNORMAL({nf})")
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"),
(0x00001F00, 8, "exitIndex"),
(0x0003E000, 13, "floorType"),
(0x001C0000, 18, "unk18"),
(0x03E00000, 21, "wallType"),
(0x3C000000, 26, "floorProperty"),
(0x40000000, 30, "isSoft"),
(0x80000000, 31, "isHorseBlocked"),
),
),
(
1,
(
(0x0000000F, 0, "material"),
(0x00000030, 4, "floorEffect"),
(0x000007C0, 6, "lightSetting"),
(0x0001F800, 11, "echo"),
(0x00020000, 17, "canHookshot"),
(0x001C0000, 18, "conveyorSpeed"),
(0x07E00000, 21, "conveyorDirection"),
(0x08000000, 27, "unk27"),
),
),
):
data_val = v[i_data]
f.write(wctx.line_prefix)
f.write(INDENT)
f.write("(\n")
has_prev = False
for mask, shift, name in bitfield_info:
val = (data_val & mask) >> shift
f.write(wctx.line_prefix)
f.write(INDENT * 2)
if has_prev:
f.write("| ")
else:
f.write(" ")
f.write(f"(({val} << {shift:2}) & 0x{mask:08X}) // {name}\n")
has_prev = True
f.write(wctx.line_prefix)
f.write(INDENT)
f.write(f"), // {i_data}\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_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):
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.u32), # TODO formatting
)
)
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_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",)