From 66db26a999909f24aa31476dc8365f79419d5d25 Mon Sep 17 00:00:00 2001 From: Dragorn421 Date: Wed, 5 Feb 2025 23:17:05 +0100 Subject: [PATCH] implement more skeleton-related types, cleanups, fixups --- Makefile | 4 +- src/code/z_player_lib.c | 2 +- src/overlays/actors/ovl_En_Arrow/z_en_arrow.c | 2 +- src/overlays/actors/ovl_En_Horse/z_en_horse.c | 2 +- .../ovl_En_Horse_Ganon/z_en_horse_ganon.c | 2 +- .../z_en_horse_link_child.c | 2 +- .../ovl_En_Horse_Normal/z_en_horse_normal.c | 10 +- .../ovl_En_Horse_Zelda/z_en_horse_zelda.c | 2 +- .../actors/ovl_En_Torch2/z_en_torch2.c | 2 +- src/overlays/actors/ovl_En_fHG/z_en_fhg.c | 2 +- tools/assets/extract/extase/__init__.py | 14 +- .../assets/extract/extase/cdata_resources.py | 5 +- .../extract/extase_oot64/dlist_resources.py | 21 +- .../extase_oot64/skeleton_resources.py | 127 +++++---- .../extase_oot64/skeleton_skin_resources.py | 244 ++++++++++++++++++ tools/assets/extract/extract_xml_z64.py | 74 +++--- tools/assets/extract/oot64_data/misc_ids.py | 5 + tools/assets/extract/z64_resource_handlers.py | 67 +++-- 18 files changed, 459 insertions(+), 128 deletions(-) create mode 100644 tools/assets/extract/extase_oot64/skeleton_skin_resources.py diff --git a/Makefile b/Makefile index 6432583e5b..774c6e211b 100644 --- a/Makefile +++ b/Makefile @@ -790,7 +790,7 @@ setup: venv $(MAKE) -C tools $(PYTHON) tools/decompress_baserom.py $(VERSION) $(PYTHON) tools/extract_baserom.py $(BASEROM_DIR)/baserom-decompressed.z64 $(EXTRACTED_DIR)/baserom -v $(VERSION) - $(PYTHON) -m tools.assets.extract -j + $(PYTHON) -m tools.assets.extract -j $(EXTRACTED_DIR)/baserom $(EXTRACTED_DIR) $(PYTHON) tools/extract_incbins.py $(EXTRACTED_DIR)/baserom $(EXTRACTED_DIR)/incbin -v $(VERSION) $(PYTHON) tools/extract_text.py $(EXTRACTED_DIR)/baserom $(EXTRACTED_DIR)/text -v $(VERSION) $(PYTHON) tools/extract_audio.py -o $(EXTRACTED_DIR) -v $(VERSION) --read-xml @@ -985,7 +985,7 @@ $(BUILD_DIR)/src/overlays/%_reloc.o: $(BUILD_DIR)/spec $(AS) $(ASFLAGS) $(@:.o=.s) -o $@ $(BUILD_DIR)/assets/%.inc.c: assets/%.png - false # TODO + false # TODO duplicate extracted/ build rules for git-tracked assets/ too $(BUILD_DIR)/assets/%.u64.inc.c: $(EXTRACTED_DIR)/assets/%.u64.png $(PYTHON) tools/assets/build_from_png.py $< $(@:.inc.c=.bin) diff --git a/src/code/z_player_lib.c b/src/code/z_player_lib.c index e57fda3ca0..0e34ff571a 100644 --- a/src/code/z_player_lib.c +++ b/src/code/z_player_lib.c @@ -13,7 +13,7 @@ typedef struct BowSlingshotStringData { /* 0x04 */ Vec3f pos; } BowSlingshotStringData; // size = 0x10 -FlexSkeletonHeader* gPlayerSkelHeaders[] = { (void*)&gLinkAdultSkel, (void*)&gLinkChildSkel }; //! FIXME +FlexSkeletonHeader* gPlayerSkelHeaders[] = { &gLinkAdultSkel, &gLinkChildSkel }; s16 sBootData[PLAYER_BOOTS_MAX][17] = { // PLAYER_BOOTS_KOKIRI diff --git a/src/overlays/actors/ovl_En_Arrow/z_en_arrow.c b/src/overlays/actors/ovl_En_Arrow/z_en_arrow.c index 5dfa8942e2..669e5ed1fb 100644 --- a/src/overlays/actors/ovl_En_Arrow/z_en_arrow.c +++ b/src/overlays/actors/ovl_En_Arrow/z_en_arrow.c @@ -93,7 +93,7 @@ void EnArrow_Init(Actor* thisx, PlayState* play) { if (this->actor.params <= ARROW_SEED) { if (this->actor.params <= ARROW_0E) { - SkelAnime_Init(play, &this->skelAnime, (void*)&gArrowSkel, &gArrow2Anim, NULL, NULL, 0); + SkelAnime_Init(play, &this->skelAnime, &gArrowSkel, &gArrow2Anim, NULL, NULL, 0); } if (this->actor.params <= ARROW_NORMAL) { diff --git a/src/overlays/actors/ovl_En_Horse/z_en_horse.c b/src/overlays/actors/ovl_En_Horse/z_en_horse.c index da52400ddf..075facff4d 100644 --- a/src/overlays/actors/ovl_En_Horse/z_en_horse.c +++ b/src/overlays/actors/ovl_En_Horse/z_en_horse.c @@ -67,7 +67,7 @@ static AnimationHeader** sAnimationHeaders[] = { sEponaAnimHeaders, sHniAnimHead static f32 sPlaybackSpeeds[] = { 2.0f / 3.0f, 2.0f / 3.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f / 3.0f, 2.0f / 3.0f }; -static SkeletonHeader* sSkeletonHeaders[] = { (void*)&gEponaSkel, (void*)&gHorseIngoSkel }; +static SkeletonHeader* sSkeletonHeaders[] = { &gEponaSkel, &gHorseIngoSkel }; ActorProfile En_Horse_Profile = { /**/ ACTOR_EN_HORSE, diff --git a/src/overlays/actors/ovl_En_Horse_Ganon/z_en_horse_ganon.c b/src/overlays/actors/ovl_En_Horse_Ganon/z_en_horse_ganon.c index 7c861ba36f..0ce01f2d6e 100644 --- a/src/overlays/actors/ovl_En_Horse_Ganon/z_en_horse_ganon.c +++ b/src/overlays/actors/ovl_En_Horse_Ganon/z_en_horse_ganon.c @@ -177,7 +177,7 @@ void EnHorseGanon_Init(Actor* thisx, PlayState* play) { this->actor.focus.pos = this->actor.world.pos; this->action = 0; this->actor.focus.pos.y += 70.0f; - Skin_Init(play, &this->skin, (void*)&gHorseGanonSkel, &gHorseGanonIdleAnim); + Skin_Init(play, &this->skin, &gHorseGanonSkel, &gHorseGanonIdleAnim); this->currentAnimation = 0; Animation_PlayOnce(&this->skin.skelAnime, sAnimations[0]); diff --git a/src/overlays/actors/ovl_En_Horse_Link_Child/z_en_horse_link_child.c b/src/overlays/actors/ovl_En_Horse_Link_Child/z_en_horse_link_child.c index 8f1c796c37..755916a390 100644 --- a/src/overlays/actors/ovl_En_Horse_Link_Child/z_en_horse_link_child.c +++ b/src/overlays/actors/ovl_En_Horse_Link_Child/z_en_horse_link_child.c @@ -162,7 +162,7 @@ void EnHorseLinkChild_Init(Actor* thisx, PlayState* play) { this->action = 1; this->actor.focus.pos = this->actor.world.pos; this->actor.focus.pos.y += 70.0f; - Skin_Init(play, &this->skin, (void*)&gChildEponaSkel, &gChildEponaGallopingAnim); + Skin_Init(play, &this->skin, &gChildEponaSkel, &gChildEponaGallopingAnim); this->animationIdx = 0; Animation_PlayOnce(&this->skin.skelAnime, sAnimations[0]); Collider_InitCylinder(play, &this->bodyCollider); diff --git a/src/overlays/actors/ovl_En_Horse_Normal/z_en_horse_normal.c b/src/overlays/actors/ovl_En_Horse_Normal/z_en_horse_normal.c index fe5ee33320..c760dc54d7 100644 --- a/src/overlays/actors/ovl_En_Horse_Normal/z_en_horse_normal.c +++ b/src/overlays/actors/ovl_En_Horse_Normal/z_en_horse_normal.c @@ -226,7 +226,7 @@ void EnHorseNormal_Init(Actor* thisx, PlayState* play) { return; } this->actor.home.rot.z = this->actor.world.rot.z = this->actor.shape.rot.z = 0; - Skin_Init(play, &this->skin, (void*)&gHorseNormalSkel, &gHorseNormalIdleAnim); + Skin_Init(play, &this->skin, &gHorseNormalSkel, &gHorseNormalIdleAnim); Animation_PlayOnce(&this->skin.skelAnime, sAnimations[this->animationIdx]); if ((this->actor.world.pos.x == -730.0f && this->actor.world.pos.y == 0.0f && this->actor.world.pos.z == -1100.0f) || @@ -240,7 +240,7 @@ void EnHorseNormal_Init(Actor* thisx, PlayState* play) { Actor_Kill(&this->actor); return; } else { - Skin_Init(play, &this->skin, (void*)&gHorseNormalSkel, &gHorseNormalIdleAnim); + Skin_Init(play, &this->skin, &gHorseNormalSkel, &gHorseNormalIdleAnim); Animation_PlayOnce(&this->skin.skelAnime, sAnimations[this->animationIdx]); func_80A6C6B0(this); return; @@ -248,15 +248,15 @@ void EnHorseNormal_Init(Actor* thisx, PlayState* play) { } else if (play->sceneId == SCENE_GERUDOS_FORTRESS) { if (this->actor.world.pos.x == 3707.0f && this->actor.world.pos.y == 1413.0f && this->actor.world.pos.z == -665.0f) { - Skin_Init(play, &this->skin, (void*)&gHorseNormalSkel, &gHorseNormalIdleAnim); + Skin_Init(play, &this->skin, &gHorseNormalSkel, &gHorseNormalIdleAnim); Animation_PlayOnce(&this->skin.skelAnime, sAnimations[this->animationIdx]); func_80A6C4CC(this); return; } - Skin_Init(play, &this->skin, (void*)&gHorseNormalSkel, &gHorseNormalIdleAnim); + Skin_Init(play, &this->skin, &gHorseNormalSkel, &gHorseNormalIdleAnim); Animation_PlayOnce(&this->skin.skelAnime, sAnimations[this->animationIdx]); } else { - Skin_Init(play, &this->skin, (void*)&gHorseNormalSkel, &gHorseNormalIdleAnim); + Skin_Init(play, &this->skin, &gHorseNormalSkel, &gHorseNormalIdleAnim); Animation_PlayOnce(&this->skin.skelAnime, sAnimations[this->animationIdx]); } if (PARAMS_GET_NOSHIFT(this->actor.params, 4, 4) == 0x10 && PARAMS_GET_U(this->actor.params, 0, 4) != 0xF) { diff --git a/src/overlays/actors/ovl_En_Horse_Zelda/z_en_horse_zelda.c b/src/overlays/actors/ovl_En_Horse_Zelda/z_en_horse_zelda.c index b75f457725..04b7b19336 100644 --- a/src/overlays/actors/ovl_En_Horse_Zelda/z_en_horse_zelda.c +++ b/src/overlays/actors/ovl_En_Horse_Zelda/z_en_horse_zelda.c @@ -158,7 +158,7 @@ void EnHorseZelda_Init(Actor* thisx, PlayState* play) { this->actor.focus.pos = this->actor.world.pos; this->action = 0; this->actor.focus.pos.y += 70.0f; - Skin_Init(play, &this->skin, (void*)&gHorseZeldaSkel, &gHorseZeldaGallopingAnim); + Skin_Init(play, &this->skin, &gHorseZeldaSkel, &gHorseZeldaGallopingAnim); this->animationIndex = 0; Animation_PlayOnce(&this->skin.skelAnime, sAnimationHeaders[0]); Collider_InitCylinder(play, &this->colliderCylinder); diff --git a/src/overlays/actors/ovl_En_Torch2/z_en_torch2.c b/src/overlays/actors/ovl_En_Torch2/z_en_torch2.c index 66d0ded86c..8e69a1537f 100644 --- a/src/overlays/actors/ovl_En_Torch2/z_en_torch2.c +++ b/src/overlays/actors/ovl_En_Torch2/z_en_torch2.c @@ -102,7 +102,7 @@ void EnTorch2_Init(Actor* thisx, PlayState* play2) { this->currentShield = PLAYER_SHIELD_HYLIAN; this->heldItemAction = this->heldItemId = PLAYER_IA_SWORD_MASTER; Player_SetModelGroup(this, PLAYER_MODELGROUP_SWORD_AND_SHIELD); - play->playerInit(this, play, (void*)&gDarkLinkSkel); //! FIXME + play->playerInit(this, play, &gDarkLinkSkel); this->actor.naviEnemyId = NAVI_ENEMY_DARK_LINK; this->cylinder.base.acFlags = AC_ON | AC_TYPE_PLAYER; this->meleeWeaponQuads[0].base.atFlags = this->meleeWeaponQuads[1].base.atFlags = AT_ON | AT_TYPE_ENEMY; diff --git a/src/overlays/actors/ovl_En_fHG/z_en_fhg.c b/src/overlays/actors/ovl_En_fHG/z_en_fhg.c index b75844bc9f..bce528552b 100644 --- a/src/overlays/actors/ovl_En_fHG/z_en_fhg.c +++ b/src/overlays/actors/ovl_En_fHG/z_en_fhg.c @@ -80,7 +80,7 @@ void EnfHG_Init(Actor* thisx, PlayState* play2) { this->actor.speed = 0.0f; this->actor.focus.pos = this->actor.world.pos; this->actor.focus.pos.y += 70.0f; - Skin_Init(play, &this->skin, (void*)&gPhantomHorseSkel, &gPhantomHorseRunningAnim); + Skin_Init(play, &this->skin, &gPhantomHorseSkel, &gPhantomHorseRunningAnim); if (this->actor.params >= GND_FAKE_BOSS) { EnfHG_SetupApproach(this, play, this->actor.params - GND_FAKE_BOSS); diff --git a/tools/assets/extract/extase/__init__.py b/tools/assets/extract/extase/__init__.py index e860b6f1e0..7442594f02 100644 --- a/tools/assets/extract/extase/__init__.py +++ b/tools/assets/extract/extase/__init__.py @@ -687,6 +687,8 @@ class File: h.writelines(headers_includes) h.write("\n") + if not self._is_resources_sorted: + self.sort_resources() for resource in self._resources: if resource.write_c_definition(c): @@ -720,6 +722,14 @@ class File: + f"({self.name!r}, data is None={self.data is None}, size={self.size}, {self._resources!r})" ) + def __rich_repr__(self): + yield "name", self.name + yield "data is None", self.data is None + yield "size", self.size + yield "resources", self._resources + + __rich_repr__.angular = True + # # resources @@ -973,7 +983,7 @@ class Resource(abc.ABC): else "..." ) ), - f"file_name={self.file.name!r}", + f"file.name={self.file.name!r}", ) ) + ")" @@ -985,7 +995,7 @@ class Resource(abc.ABC): f"0x{self.range_start:08X}-" + (f"0x{self.range_end:08X}" if self.range_end is not None else "...") ) - yield "file_name", self.file.name + yield "file.name", self.file.name __rich_repr__.angular = True diff --git a/tools/assets/extract/extase/cdata_resources.py b/tools/assets/extract/extase/cdata_resources.py index 49391a35fa..acce03a70b 100644 --- a/tools/assets/extract/extase/cdata_resources.py +++ b/tools/assets/extract/extase/cdata_resources.py @@ -303,7 +303,10 @@ class CDataArrayResource(CDataResource): return super().try_parse_data(memory_context) def get_c_reference(self, resource_offset: int): - return self.symbol_name + if resource_offset == 0: + return self.symbol_name + else: + raise ValueError def get_c_expression_length(self, resource_offset: int): if resource_offset == 0: diff --git a/tools/assets/extract/extase_oot64/dlist_resources.py b/tools/assets/extract/extase_oot64/dlist_resources.py index b71e5038b6..980e5989cc 100644 --- a/tools/assets/extract/extase_oot64/dlist_resources.py +++ b/tools/assets/extract/extase_oot64/dlist_resources.py @@ -899,7 +899,7 @@ class ColorIndexedTexturesManager: tluts: dict[int, "ColorIndexedTexturesManager.Tlut"] texs: list["ColorIndexedTexturesManager.Tex"] - def __init__(self) -> None: + def __init__(self, *, HACK_late_SetTextureLUT=False): self.cur_tlut_mode: G_TT = None self.cur_tluts_count: int = None @@ -908,6 +908,11 @@ class ColorIndexedTexturesManager: self.ci_states: list[ColorIndexedTexturesManager.CIState] = [] + # Rarely, + # gsDPSetTextureLUT comes after gsDPLoadTextureBlock and gsDPLoadTLUT, + # instead of before + self.HACK_late_SetTextureLUT = HACK_late_SetTextureLUT + def ci_timg(self, timg, fmt: G_IM_FMT, siz: G_IM_SIZ, width, height, pal): if VERBOSE_ColorIndexedTexturesManager: print( @@ -920,7 +925,8 @@ class ColorIndexedTexturesManager: pal, ) assert fmt == G_IM_FMT.CI - assert self.cur_tlut_mode != G_TT.NONE + if not self.HACK_late_SetTextureLUT: + assert self.cur_tlut_mode != G_TT.NONE self.cur_texs.append( ColorIndexedTexturesManager.Tex(timg, fmt, siz, width, height, pal) @@ -933,7 +939,8 @@ class ColorIndexedTexturesManager: # HACK idx==-1 may be a libgfxd bug? assert count == 256 idx = 0 - assert self.cur_tlut_mode != G_TT.NONE + if not self.HACK_late_SetTextureLUT: + assert self.cur_tlut_mode != G_TT.NONE if self.cur_tluts_count != count: self.cur_tluts.clear() # TODO ? idk. (at worst it will cause errors) self.cur_tluts_count = count @@ -943,7 +950,8 @@ class ColorIndexedTexturesManager: if VERBOSE_ColorIndexedTexturesManager: print("ColorIndexedTexturesManager.tlut_mode", tt) if self.cur_tlut_mode != tt: - self.cur_tluts.clear() # TODO ? idk. (at worst it will cause errors) + if not self.HACK_late_SetTextureLUT: + self.cur_tluts.clear() # TODO ? idk. (at worst it will cause errors) self.cur_tlut_mode = tt def commit_state(self): @@ -1101,7 +1109,10 @@ class DListResource(Resource, can_size_be_unknown=True): ) return 0 - ci_tex_manager = ColorIndexedTexturesManager() + ci_tex_manager = ColorIndexedTexturesManager( + # TODO + HACK_late_SetTextureLUT=(self.name in {"gEponaHeadLimb_0600AC20_DL"}) + ) def timg_cb(timg, fmt, siz, width, height, pal): g_fmt = G_IM_FMT.by_i[fmt] diff --git a/tools/assets/extract/extase_oot64/skeleton_resources.py b/tools/assets/extract/extase_oot64/skeleton_resources.py index de211537c6..413c83a8b8 100644 --- a/tools/assets/extract/extase_oot64/skeleton_resources.py +++ b/tools/assets/extract/extase_oot64/skeleton_resources.py @@ -1,16 +1,12 @@ -from __future__ import annotations import io from typing import TYPE_CHECKING if TYPE_CHECKING: from ..extase.memorymap import MemoryContext -from ..extase import ( - File, - ResourceParseWaiting, -) from ..extase.cdata_resources import ( CDataResource, + CDataArrayResource, CDataExt_Value, CDataExt_Struct, CDataExt_Array, @@ -40,15 +36,38 @@ class StandardLimbResource(CDataResource): raise ValueError() -class LimbsArrayResource(CDataResource, can_size_be_unknown=True): +class LODLimbResource(CDataResource): + cdata_ext = CDataExt_Struct( + ( + ("jointPos", cdata_ext_Vec3s), + ("child", CDataExt_Value.u8), + ("sibling", CDataExt_Value.u8), + ("dLists", CDataExt_Array(dlist_resources.cdata_ext_gfx_segmented, 2)), + ) + ) + + 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() + + +class LimbsArrayResourceABC(CDataArrayResource): + limb_type: type[CDataResource] + c_limb_type: str + def report_limb_element(resource, memory_context: "MemoryContext", v): assert isinstance(v, int) address = v memory_context.report_resource_at_segmented( resource, address, - StandardLimbResource, - lambda file, offset: StandardLimbResource( + resource.limb_type, + lambda file, offset: resource.limb_type( file, offset, f"{resource.name}_{address:08X}" ), ) @@ -68,51 +87,39 @@ class LimbsArrayResource(CDataResource, can_size_be_unknown=True): .set_write(write_limb_element) ) - 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"void* {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_expression_length(self, resource_offset: int): - if resource_offset == 0: - return f"ARRAY_COUNT({self.symbol_name})" - else: - raise ValueError() + return f"{self.c_limb_type}* {self.symbol_name}[]" -class SkeletonNormalResource(CDataResource): +class StandardLimbsArrayResource(LimbsArrayResourceABC): + limb_type = StandardLimbResource + c_limb_type = "StandardLimb" + + +class LODLimbsArrayResource(LimbsArrayResourceABC): + limb_type = LODLimbResource + c_limb_type = "LodLimb" + + +class SkeletonResourceABC(CDataResource): + limbs_array_type: type[LimbsArrayResourceABC] + def report_segment(resource, memory_context: "MemoryContext", v): assert isinstance(v, int) address = v resource_limbs = memory_context.report_resource_at_segmented( resource, address, - LimbsArrayResource, - lambda file, offset: LimbsArrayResource( + resource.limbs_array_type, + lambda file, offset: resource.limbs_array_type( file, offset, f"{resource.name}_{address:08X}_Limbs", ), ) - resource_limbs.length = resource.get_skeleton_header_cdata_unpacked()[ - "limbCount" - ] + resource_limbs.set_length( + resource.get_skeleton_header_cdata_unpacked()["limbCount"] + ) def write_segment( resource, memory_context: "MemoryContext", v, f: io.TextIOBase, line_prefix @@ -162,18 +169,32 @@ class SkeletonNormalResource(CDataResource): return f"SkeletonHeader {self.symbol_name}" -class SkeletonFlexResource(CDataResource): - cdata_ext = CDataExt_Struct( - ( +class SkeletonNormalResource(SkeletonResourceABC): + limbs_array_type = StandardLimbsArrayResource + + +class SkeletonNormalLODResource(SkeletonResourceABC): + limbs_array_type = LODLimbsArrayResource + + +class SkeletonFlexResourceABC(CDataResource): + 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", - SkeletonNormalResource.cdata_ext, - ), # TODO FIXME this is bad, it ends up using StandardLimb s (or is it fine?) - ("dListCount", CDataExt_Value.u8), - ("pad9", CDataExt_Value.pad8), - ("pad10", CDataExt_Value.pad16), + ("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"] @@ -186,3 +207,11 @@ class SkeletonFlexResource(CDataResource): def get_c_declaration_base(self): return f"FlexSkeletonHeader {self.symbol_name}" + + +class SkeletonFlexResource(SkeletonFlexResourceABC): + skeleton_type = SkeletonNormalResource + + +class SkeletonFlexLODResource(SkeletonFlexResourceABC): + skeleton_type = SkeletonNormalLODResource diff --git a/tools/assets/extract/extase_oot64/skeleton_skin_resources.py b/tools/assets/extract/extase_oot64/skeleton_skin_resources.py new file mode 100644 index 0000000000..8ff66c8d3b --- /dev/null +++ b/tools/assets/extract/extase_oot64/skeleton_skin_resources.py @@ -0,0 +1,244 @@ +import io +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, + 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}[]" + + +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}[]" + + +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, f: io.TextIOBase, line_prefix + ): + assert isinstance(v, int) + address = v + f.write(line_prefix) + f.write(memory_context.get_c_reference_at_segmented(address)) + return True + + def write_limbTransformations( + resource, memory_context: "MemoryContext", v, f: io.TextIOBase, line_prefix + ): + assert isinstance(v, int) + address = v + f.write(line_prefix) + 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}[]" + + +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, f: io.TextIOBase, line_prefix + ): + assert isinstance(v, int) + address = v + f.write(line_prefix) + 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() + + +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, # TODO + 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, f: io.TextIOBase, line_prefix + ): + assert isinstance(v, int) + address = v + f.write(line_prefix) + if address == 0: + f.write("NULL") + else: + f.write(memory_context.get_c_reference_at_segmented(address)) + return True + + cdata_ext = CDataExt_Struct( + ( + ("jointPos", cdata_ext_Vec3s), + ("child", CDataExt_Value.u8), + ("sibling", CDataExt_Value.u8), + ( + "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 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() + + +class SkinLimbsArrayResource(skeleton_resources.LimbsArrayResourceABC): + limb_type = SkinLimbResource + c_limb_type = "SkinLimb" + + +class SkeletonSkinResource(skeleton_resources.SkeletonResourceABC): + limbs_array_type = SkinLimbsArrayResource diff --git a/tools/assets/extract/extract_xml_z64.py b/tools/assets/extract/extract_xml_z64.py index 8fe920e8ae..06ed6e4fb4 100644 --- a/tools/assets/extract/extract_xml_z64.py +++ b/tools/assets/extract/extract_xml_z64.py @@ -1,5 +1,4 @@ import dataclasses -import functools from pathlib import Path from pprint import pprint @@ -33,15 +32,16 @@ WRITE_EXTRACT = True from ..conf import WRITE_HINTS, I_D_OMEGALUL -OOT_VERSION = "gc-eu-mq-dbg" -BASEROM_PATH = Path("extracted") / OOT_VERSION / "baserom" -BUILD_PATH = Path("build") / OOT_VERSION -EXTRACTED_PATH = Path("extracted") / OOT_VERSION +@dataclasses.dataclass +class ExtractionContext: + oot_version: str + version_memctx_base: MemoryContext + baserom_path: Path + build_path: Path + extracted_path: Path - -@functools.lru_cache(maxsize=200) -def get_baserom_file_data(baserom_file_name: str): - return memoryview((BASEROM_PATH / baserom_file_name).read_bytes()) + def get_baserom_file_data(self, baserom_file_name: str): + return memoryview((self.baserom_path / baserom_file_name).read_bytes()) def create_file_resources(rescoll: ResourcesDescCollection, file: File): @@ -92,7 +92,7 @@ def create_file_resources(rescoll: ResourcesDescCollection, file: File): def process_pool( - version_memctx_base: MemoryContext, pool_desc: ResourcesDescCollectionsPool + extraction_ctx: ExtractionContext, pool_desc: ResourcesDescCollectionsPool ): if VERBOSE2: print("> process_pool") @@ -112,7 +112,7 @@ def process_pool( for rescoll in pool_desc.collections: if not isinstance(rescoll.backing_memory, BaseromFileBackingMemory): raise NotImplementedError(rescoll.backing_memory) - data = get_baserom_file_data(rescoll.backing_memory.name) + data = extraction_ctx.get_baserom_file_data(rescoll.backing_memory.name) if rescoll.backing_memory.range is not None: range_start, range_end = rescoll.backing_memory.range data = data[range_start:range_end] @@ -135,7 +135,7 @@ def process_pool( # 2) Build a MemoryContext for each File - memctx_base = version_memctx_base.copy() + memctx_base = extraction_ctx.version_memctx_base.copy() files_by_segment: dict[int, list[File]] = dict() for rescoll, file in file_by_rescoll.items(): @@ -228,31 +228,30 @@ def process_pool( # 5) - # TODO this looks jank for rescoll, file in file_by_rescoll.items(): file.set_source_path(Path("assets") / rescoll.out_path) file.set_resources_paths( - EXTRACTED_PATH, - BUILD_PATH, + extraction_ctx.extracted_path, + extraction_ctx.build_path, Path("assets") / rescoll.out_path, ) for file, file_memctx in memctx_by_file.items(): - # write to EXTRACTED_PATH + # write to extracted/ if WRITE_EXTRACT: file.write_resources_extracted(file_memctx) # "source" refers to the main .c and .h `#include`ing all the extracted resources if WRITE_SOURCE: # TODO fill referenced_files properly or something - file.referenced_files = set(memctx_by_file.keys()) + file.referenced_files = set(memctx_by_file.keys()) - {file} file.write_source() -def process_pool_wrapped(version_memctx_base, pd): +def process_pool_wrapped(extraction_ctx, pd): try: - process_pool(version_memctx_base, pd) + process_pool(extraction_ctx, pd) except Exception as e: import traceback import sys @@ -273,6 +272,16 @@ def main(): from tools import version_config parser = argparse.ArgumentParser() + parser.add_argument( + "baserom_segments_dir", + type=Path, + help="Directory of uncompressed ROM segments", + ) + parser.add_argument( + "output_dir", + type=Path, + help="Output directory to place files in", + ) parser.add_argument("-v", dest="oot_version", default="gc-eu-mq-dbg") parser.add_argument("-j", dest="use_multiprocessing", action="store_true") parser.add_argument("-s", dest="single", default=None) @@ -299,6 +308,14 @@ def main(): ) version_memctx_base.set_direct_file(vc.variables["sShadowTex"], file_sShadowTex) + extraction_ctx = ExtractionContext( + args.oot_version, + version_memctx_base, + args.baserom_segments_dir, + Path("build") / args.oot_version, + args.output_dir, + ) + z64_resource_handlers.register_resource_handlers() # TODO extract only when a pool xml was modified since last extract @@ -312,7 +329,7 @@ def main(): if coll.backing_memory.name == args.single: do_process_pool = True if do_process_pool: - process_pool(version_memctx_base, pool_desc) + process_pool(extraction_ctx, pool_desc) any_do_process_pool = True if any_do_process_pool: print("OK") @@ -320,7 +337,7 @@ def main(): print("Not found:", args.single) elif not args.use_multiprocessing: # everything on one process for pool_desc in pools_desc: - process_pool(version_memctx_base, pool_desc) + process_pool(extraction_ctx, pool_desc) print("all OK!!!") else: # multiprocessing import multiprocessing @@ -328,7 +345,7 @@ def main(): with multiprocessing.Pool() as pool: pool.starmap( process_pool_wrapped, - zip([version_memctx_base] * len(pools_desc), pools_desc), + zip([extraction_ctx] * len(pools_desc), pools_desc), ) print("all OK!?") except Exception as e: @@ -349,16 +366,3 @@ def main(): else: print("rich.pretty.pprint(e):") rich.pretty.pprint(e, indent_guides=False) - - # extract_xml(Path("objects/object_ydan_objects")) - # extract_xml(Path("objects/object_fd2")) # TODO xml needs TLUT fixing, see VERBOSE_BEST_EFFORT_TLUT_NO_REAL_USER - # extract_xml(Path("objects/object_am")) - # extract_xml(Path("scenes/indoors/hylia_labo")) - # extract_xml(Path("objects/gameplay_keep")) - # extract_xml(Path("overlays/ovl_En_Jsjutan")) # The only xml with ~~~~ a - # extract_xml(Path("scenes/dungeons/ddan")) # cutscene test - # extract_xml(Path("scenes/dungeons/ganontikasonogo")) # has a spawn not in the entrance table - - pprint(get_baserom_file_data.cache_info()) diff --git a/tools/assets/extract/oot64_data/misc_ids.py b/tools/assets/extract/oot64_data/misc_ids.py index c24f17dcda..910f046e65 100644 --- a/tools/assets/extract/oot64_data/misc_ids.py +++ b/tools/assets/extract/oot64_data/misc_ids.py @@ -101,3 +101,8 @@ CAMERA_SETTING_TYPES = { 0x40: "CAM_SET_PIVOT_FROM_SIDE", 0x41: "CAM_SET_NORMAL4", } + +SKIN_LIMB_TYPES = { + 4: "SKIN_LIMB_TYPE_ANIMATED", + 11: "SKIN_LIMB_TYPE_NORMAL", +} diff --git a/tools/assets/extract/z64_resource_handlers.py b/tools/assets/extract/z64_resource_handlers.py index 2b8570665e..a6ffa92c4f 100644 --- a/tools/assets/extract/z64_resource_handlers.py +++ b/tools/assets/extract/z64_resource_handlers.py @@ -14,6 +14,7 @@ from .extase.cdata_resources import Vec3sArrayResource, S16ArrayResource from .extase_oot64 import ( skeleton_resources, + skeleton_skin_resources, animation_resources, collision_resources, dlist_resources, @@ -82,16 +83,13 @@ def register_resource_handlers(): if resource_desc.limb_type == z64resources.LimbType.STANDARD: pass elif resource_desc.limb_type == z64resources.LimbType.LOD: - # TODO if resource_desc.type == z64resources.SkeletonType.NORMAL: - # } SkeletonHeader; // size = 0x8 - return BinaryBlobResource( - file, offset, offset + 0x8, resource_desc.symbol_name + return skeleton_resources.SkeletonNormalLODResource( + file, offset, resource_desc.symbol_name ) elif resource_desc.type == z64resources.SkeletonType.FLEX: - # } FlexSkeletonHeader; // size = 0xC - return BinaryBlobResource( - file, offset, offset + 0xC, resource_desc.symbol_name + return skeleton_resources.SkeletonFlexLODResource( + file, offset, resource_desc.symbol_name ) else: raise NotImplementedError( @@ -100,11 +98,9 @@ def register_resource_handlers(): resource_desc.type, ) elif resource_desc.limb_type == z64resources.LimbType.SKIN: - # TODO assert resource_desc.type == z64resources.SkeletonType.NORMAL - # } SkeletonHeader; // size = 0x8 - return BinaryBlobResource( - file, offset, offset + 0x8, resource_desc.symbol_name + return skeleton_skin_resources.SkeletonSkinResource( + file, offset, resource_desc.symbol_name ) elif resource_desc.limb_type == z64resources.LimbType.CURVE: assert resource_desc.type == z64resources.SkeletonType.CURVE @@ -147,16 +143,15 @@ def register_resource_handlers(): resource_desc.symbol_name, ) if resource_desc.limb_type == z64resources.LimbType.SKIN: - # } SkinLimb; // size = 0x10 - return BinaryBlobResource( - file, offset, offset + 0x10, resource_desc.symbol_name + return skeleton_skin_resources.SkinLimbResource( + file, offset, resource_desc.symbol_name ) if resource_desc.limb_type == z64resources.LimbType.LOD: - # } LodLimb; // size = 0x10 - return BinaryBlobResource( - file, offset, offset + 0x10, resource_desc.symbol_name + return skeleton_resources.LODLimbResource( + file, offset, resource_desc.symbol_name ) if resource_desc.limb_type == z64resources.LimbType.LEGACY: + # TODO LegacyLimbResource # } LegacyLimb; // size = 0x20 return BinaryBlobResource( file, offset, offset + 0x20, resource_desc.symbol_name @@ -171,6 +166,39 @@ def register_resource_handlers(): resource_desc.limb_type, ) + def limb_table_handler( + file: File, + resource_desc: z64resources.LimbTableResourceDesc, + ): + if resource_desc.limb_type == z64resources.LimbType.STANDARD: + resource = skeleton_resources.StandardLimbsArrayResource( + file, resource_desc.offset, resource_desc.symbol_name + ) + resource.set_length(resource_desc.count) + return resource + elif resource_desc.limb_type == z64resources.LimbType.SKIN: + resource = skeleton_skin_resources.SkinLimbsArrayResource( + file, resource_desc.offset, resource_desc.symbol_name + ) + resource.set_length(resource_desc.count) + return resource + elif resource_desc.limb_type == z64resources.LimbType.LOD: + resource = skeleton_resources.LODLimbsArrayResource( + file, resource_desc.offset, resource_desc.symbol_name + ) + resource.set_length(resource_desc.count) + return resource + elif resource_desc.limb_type == z64resources.LimbType.LEGACY: + # TODO LegacyLimbsArrayResource + return BinaryBlobResource( + file, + resource_desc.offset, + resource_desc.offset + 4, + resource_desc.symbol_name, + ) + else: + raise NotImplementedError("LimbTable of limb type", resource_desc.limb_type) + def animation_resource_handler( file: File, resource_desc: z64resources.AnimationResourceDesc, @@ -412,10 +440,7 @@ def register_resource_handlers(): z64resources.LegacyAnimationResourceDesc: get_fixed_size_resource_handler( 0xC ), # TODO - z64resources.LimbTableResourceDesc: get_fixed_size_resource_handler( - # idk, probably an array - 4 - ), # TODO + z64resources.LimbTableResourceDesc: limb_table_handler, z64resources.CurveAnimationResourceDesc: CurveAnimation_handler, z64resources.SceneResourceDesc: scene_resource_handler, z64resources.RoomResourceDesc: room_resource_handler,