mirror of https://github.com/zeldaret/oot.git
460 lines
15 KiB
Python
460 lines
15 KiB
Python
# 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 ("actor.h",)
|
|
|
|
def get_h_includes(self):
|
|
return ("scene.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 ("object.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 ("scene.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
|
|
if blendRate < 0:
|
|
blendRate += 0x100
|
|
fogNear = v & 0x3FF
|
|
return f"BLEND_RATE_AND_FOG_NEAR({blendRate}, {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 ("environment.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 ("actor.h",)
|
|
|
|
def get_h_includes(self):
|
|
return ("scene.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 ("path.h",)
|