1
0
Fork 0
mirror of https://github.com/zeldaret/oot.git synced 2025-07-13 11:24:40 +00:00

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
This commit is contained in:
Dragorn421 2025-02-05 16:31:29 +01:00
parent 748859595a
commit 8411c34b38
No known key found for this signature in database
GPG key ID: 381AEBAF3D429335
80 changed files with 535882 additions and 112 deletions

View file

@ -489,8 +489,8 @@ ASSET_BIN_DIRS := $(ASSET_BIN_DIRS_EXTRACTED) $(ASSET_BIN_DIRS_COMMITTED)
ASSET_FILES_BIN_EXTRACTED := $(foreach dir,$(ASSET_BIN_DIRS_EXTRACTED),$(wildcard $(dir)/*.bin))
ASSET_FILES_BIN_COMMITTED := $(foreach dir,$(ASSET_BIN_DIRS_COMMITTED),$(wildcard $(dir)/*.bin))
ASSET_FILES_OUT := $(foreach f,$(ASSET_FILES_BIN_EXTRACTED:.bin=.bin.inc.c),$(f:$(EXTRACTED_DIR)/%=$(BUILD_DIR)/%)) \
$(foreach f,$(ASSET_FILES_BIN_COMMITTED:.bin=.bin.inc.c),$(BUILD_DIR)/$f)
ASSET_FILES_OUT := $(foreach f,$(ASSET_FILES_BIN_EXTRACTED:.bin=.inc.c),$(f:$(EXTRACTED_DIR)/%=$(BUILD_DIR)/%)) \
$(foreach f,$(ASSET_FILES_BIN_COMMITTED:.bin=.inc.c),$(BUILD_DIR)/$f)
# Find all .o files included in the spec
SPEC_O_FILES := $(shell $(CPP) $(CPPFLAGS) $(SPEC) | $(BUILD_DIR_REPLACE) | sed -n -E 's/^[ \t]*include[ \t]*"([a-zA-Z0-9/_.-]+\.o)"/\1/p')
@ -791,7 +791,6 @@ setup: venv
$(PYTHON) tools/extract_baserom.py $(BASEROM_DIR)/baserom-decompressed.z64 $(EXTRACTED_DIR)/baserom -v $(VERSION)
$(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_assets.py $(EXTRACTED_DIR)/baserom $(EXTRACTED_DIR)/assets -v $(VERSION) -j$(N_THREADS)
$(PYTHON) tools/extract_audio.py -o $(EXTRACTED_DIR) -v $(VERSION) --read-xml
disasm:
@ -969,16 +968,22 @@ $(BUILD_DIR)/src/overlays/%_reloc.o: $(BUILD_DIR)/$(SPEC)
$(AS) $(ASFLAGS) $(@:.o=.s) -o $@
$(BUILD_DIR)/assets/%.inc.c: assets/%.png
$(ZAPD) btex -eh -tt $(subst .,,$(suffix $*)) -i $< -o $@
false # TODO
$(BUILD_DIR)/assets/%.inc.c: $(EXTRACTED_DIR)/assets/%.png
$(ZAPD) btex -eh -tt $(subst .,,$(suffix $*)) -i $< -o $@
$(BUILD_DIR)/assets/%.u64.inc.c: $(EXTRACTED_DIR)/assets/%.u64.png
$(PYTHON) tools/assets/build_from_png.py $< $(@:.inc.c=.bin)
@echo // From file://`realpath $<` >$@
tools/assets/bin2c/bin2c u64 <$(@:.inc.c=.bin) >>$@
$(BUILD_DIR)/assets/%.bin.inc.c: assets/%.bin
$(ZAPD) bblb -eh -i $< -o $@
# same as above rule but u32
$(BUILD_DIR)/assets/%.u32.inc.c: $(EXTRACTED_DIR)/assets/%.u32.png
$(PYTHON) tools/assets/build_from_png.py $< $(@:.inc.c=.bin)
@echo // From file://`realpath $<` >$@
tools/assets/bin2c/bin2c u32 <$(@:.inc.c=.bin) >>$@
$(BUILD_DIR)/assets/%.bin.inc.c: $(EXTRACTED_DIR)/assets/%.bin
$(ZAPD) bblb -eh -i $< -o $@
$(BUILD_DIR)/assets/%.u8.inc.c: $(EXTRACTED_DIR)/assets/%.u8.bin
@echo // From file://`realpath $<` >$@
tools/assets/bin2c/bin2c u8 <$< >>$@
$(BUILD_DIR)/assets/%.jpg.inc.c: assets/%.jpg
$(ZAPD) bren -eh -i $< -o $@

View file

@ -58,7 +58,7 @@
<Texture Name="gGerudoPurpleEarTex" OutName="gerudo_purple_ear" Format="ci8" Width="8" Height="16" Offset="0x4EF8" TlutOffset="0x4D08"/>
<Texture Name="gGerudoPurpleDarkFabricTex" OutName="gerudo_purple_dark_fabric" Format="ci8" Width="8" Height="8" Offset="0x49C8" TlutOffset="0x4788"/>
<Texture Name="gGerudoPurpleFabricFoldTex" OutName="gerudo_purple_fabric_fold" Format="i8" Width="16" Height="16" Offset="0x4C08"/>
<Texture Name="gGerudoPurpleGlaiveGuard" OutName="gerudo_purple_glaive_guard" Format="i8" Width="8" Height="8" Offset="0x5378"/>
<Texture Name="gGerudoPurpleGlaiveGuard" OutName="gerudo_purple_glaive_guard" Format="ci8" Width="8" Height="8" Offset="0x5378" TlutOffset="0x4D08"/>
<Texture Name="gGerudoPurpleGlaiveBladeFabricPatternTex" OutName="gerudo_purple_fabric_pattern" Format="i8" Width="16" Height="16" Offset="0x53B8"/>
<Texture Name="gGerudoPurpleShoeUpperTex" OutName="gerudo_purple_shoe_upper" Format="ci8" Width="8" Height="16" Offset="0x54B8" TlutOffset="0x4D08"/>
<Texture Name="gGerudoPurpleGlaiveHaftShoeSoleTex" OutName="gerudo_purple_glaive_haft_shoe_upper" Format="ci8" Width="8" Height="8" Offset="0x5538" TlutOffset="0x4D08"/>

View file

@ -59,10 +59,10 @@
<Texture Name="gMegami1Tex" OutName="megami_tex_1" Format="ci4" Width="64" Height="64" Offset="0x5CE8" TlutOffset="0x5C80"/>
<Texture Name="gMegami2TLUT" OutName="megami_tlut_2" Format="rgba16" Width="4" Height="4" Offset="0xAC50"/>
<Texture Name="gMegami2Tex" OutName="megami_tex_2" Format="ci4" Width="64" Height="64" Offset="0xACB8" TlutOffset="0xAC50"/>
<Texture Name="gMegami3TLUT" OutName="megami_tlut_3" Format="rgba16" Width="4" Height="64" Offset="0x6CE8"/>
<Texture Name="gMegami3TLUT" OutName="megami_tlut_3" Format="ci8" Width="32" Height="32" Offset="0x6CE8" TlutOffset="0x5CA0"/>
<Texture Name="gMegami3Tex" OutName="megami_tex_3" Format="ci4" Width="64" Height="64" Offset="0x64E8" TlutOffset="0x5C80"/>
<Texture Name="gMegami4TLUT" OutName="megami_tlut_4" Format="rgba16" Width="4" Height="4" Offset="0x5CA0"/>
<Texture Name="gMegami5TLUT" OutName="megami_tlut_5" Format="rgba16" Width="4" Height="4" Offset="0xAC70"/>
<Texture Name="gMegami4TLUT" OutName="megami_tlut_4" Format="rgba16" Width="36" Height="1" Offset="0x5CA0"/>
<Texture Name="gMegami5TLUT" OutName="megami_tlut_5" Format="rgba16" Width="36" Height="1" Offset="0xAC70"/>
<Texture Name="gMegamiCrumbleTLUT" OutName="megami_crumble_tlut" Format="rgba16" Width="4" Height="4" Offset="0x4E0"/>
<Texture Name="gMegamiRightCrumble1Tex" OutName="megami_right_crumble_1" Format="ci4" Width="64" Height="64" Offset="0xD00" TlutOffset="0x4E0"/>
<Texture Name="gMegamiRightCrumble2Tex" OutName="megami_right_crumble_2" Format="ci4" Width="64" Height="64" Offset="0x1D00" TlutOffset="0x4E0"/>

View file

@ -125,6 +125,14 @@
<DList Name="gLinkAdultBrokenGiantsKnifeBladeDL" Offset="0x2BA38"/> <!-- Used for when Giants Knife shatters -->
<Texture Name="gLinkAdultTLUT1" Format="rgba16" Width="16" Height="16" Offset="0xCB40"/>
<Texture Name="gLinkAdultTLUT2" Format="rgba16" Width="16" Height="16" Offset="0xCD48"/>
<Texture Name="gLinkAdultTLUT3" Format="rgba16" Width="12" Height="12" Offset="0xCF50"/>
<Texture Name="gLinkAdultTLUT4" Format="rgba16" Width="8" Height="4" Offset="0xD078"/>
<Texture Name="gLinkAdultTLUT7" Format="rgba16" Width="16" Height="16" Offset="0x5400"/>
<Texture Name="gLinkAdultTLUT5" Format="rgba16" Width="16" Height="16" Offset="0x5800"/>
<Texture Name="gLinkAdultTLUT6" Format="rgba16" Width="8" Height="11" Offset="0x5A00"/>
<Texture Name="gLinkAdultShieldHandleTex" OutName="shield_handle" Format="ci8" Width="8" Height="16" Offset="0xD4B8" TlutOffset="0xCD48"/>
<Texture Name="gLinkAdultHylianShieldBackTex" OutName="hylian_shield_back" Format="ci8" Width="16" Height="32" Offset="0xD938" TlutOffset="0xCB40"/>
<Texture Name="gLinkAdultSheathBandTex" OutName="sheath_band" Format="ci8" Width="32" Height="16" Offset="0xE838" TlutOffset="0xCB40"/>

View file

@ -74,9 +74,5 @@
<Vtx/>
</Array>
<Array Name="gMorphaVtx_007BB8" Count="4" Offset="0x7BB8">
<Vtx/>
</Array>
</File>
</Root>

View file

@ -4,6 +4,7 @@
<Animation Name="gDampeWalkAnim" Offset="0x1FA8"/>
<Animation Name="gDampeRestAnim" Offset="0x2F84"/>
<Animation Name="gDampeFloatAnim" Offset="0x3768"/>
<Texture Name="gDampeUnkTLUT" Format="rgba16" Width="102" Height="2" Offset="0x3780"/>
<Texture Name="gDampeEyeOpenTex" OutName="dampe_eye_open" Format="rgba16" Width="32" Height="32" Offset="0x3B40"/>
<Texture Name="gDampeEyeHalfTex" OutName="dampe_eye_half_open" Format="rgba16" Width="32" Height="32" Offset="0x4340"/>
<Texture Name="gDampeEyeClosedTex" OutName="dampe_eye_closed" Format="rgba16" Width="32" Height="32" Offset="0x4B40"/>

View file

@ -38,5 +38,7 @@
<Texture Name="gZelda2_7TLUT" OutName="zelda_2_7_tlut" Format="rgba16" Width="6" Height="4" Offset="0x9708"/>
<Texture Name="gZelda2TriforceTex" OutName="zelda_2_triforce" Format="rgba16" Width="16" Height="16" Offset="0x2900"/>
<Texture Name="gZelda2Tex_003A08" OutName="tex_003A08" Format="ci8" Width="8" Height="8" Offset="0x3A08" TlutOffset="0x2F50"/>
</File>
</Root>

View file

@ -16,9 +16,6 @@
<Vtx/>
</Array>
<!-- D_80A8E610. Variable declared in `.bss` of EnJsjutan. Used by sShadowMaterialDL -->
<Symbol Name="sShadowTex" Type="u8" TypeSize="1" Count="0x800" Offset="0x3B78"/>
<!-- D_80A8D598. Draws the carpet's texture. -->
<DList Name="sCarpetMaterialDL" Offset="0x2B00"/>
<!-- D_80A8D618. Draws the carpet's shadow texture. -->

View file

@ -1,5 +1,6 @@
<Root>
<File Name="ddan_scene" Segment="2">
<Texture Name="gDCSceneTLUT" Format="rgba16" Width="16" Height="16" Offset="0x11D70"/>
<Texture Name="gDCDayEntranceTex" OutName="day_entrance" Format="ci8" Width="32" Height="64" Offset="0x12378" TlutOffset="0x11D70"/>
<Texture Name="gDCNightEntranceTex" OutName="night_entrance" Format="ci8" Width="32" Height="64" Offset="0x13378" TlutOffset="0x11D70"/>
@ -18,6 +19,7 @@
</File>
<File Name="ddan_room_0" Segment="3">
<Room Name="ddan_room_0" Offset="0x0"/>
<Texture Name="gDCRoom0TLUT" Format="rgba16" Width="16" Height="16" Offset="0x11290"/>
</File>
<File Name="ddan_room_1" Segment="3">
<Room Name="ddan_room_1" Offset="0x0"/>

View file

@ -1,6 +1,9 @@
<Root>
<File Name="ganon_boss_scene" Segment="2">
<Scene Name="ganon_boss_scene" Offset="0x0"/>
<Texture Name="ganon_boss_sceneTLUT_001680" Width="16" Height="16" Format="rgba16" Offset="0x1680"/>
<Texture Name="ganon_boss_sceneTex_006C18" Width="32" Height="64" Format="ci8" Offset="0x6C18" TlutOffset="0x1680"/>
<Texture Name="ganon_boss_sceneTex_007418" Width="32" Height="64" Format="ci8" Offset="0x7418" TlutOffset="0x1680"/>
</File>
<File Name="ganon_boss_room_0" Segment="3">
<Room Name="ganon_boss_room_0" Offset="0x0"/>

View file

@ -1,72 +1,74 @@
<Root>
<File Name="map_48x85_static" Segment="12">
<Texture Name="gDekuTreePauseScreenMapFloor3LeftTex" OutName="deku_tree_pause_screen_map_floor_3_left" Format="ci4" Width="48" Height="85" Offset="0x0"/>
<Texture Name="gDekuTreePauseScreenMapFloor3RightTex" OutName="deku_tree_pause_screen_map_floor_3_right" Format="ci4" Width="48" Height="85" Offset="0x07F8"/>
<Texture Name="gDekuTreePauseScreenMapFloor2LeftTex" OutName="deku_tree_pause_screen_map_floor_2_left" Format="ci4" Width="48" Height="85" Offset="0x0FF0"/>
<Texture Name="gDekuTreePauseScreenMapFloor2RightTex" OutName="deku_tree_pause_screen_map_floor_2_right" Format="ci4" Width="48" Height="85" Offset="0x17E8"/>
<Texture Name="gDekuTreePauseScreenMapFloor1LeftTex" OutName="deku_tree_pause_screen_map_floor_1_left" Format="ci4" Width="48" Height="85" Offset="0x1FE0"/>
<Texture Name="gDekuTreePauseScreenMapFloor1RightTex" OutName="deku_tree_pause_screen_map_floor_1_right" Format="ci4" Width="48" Height="85" Offset="0x27D8"/>
<Texture Name="gDekuTreePauseScreenMapBasement1LeftTex" OutName="deku_tree_pause_screen_map_basement_1_left" Format="ci4" Width="48" Height="85" Offset="0x2FD0"/>
<Texture Name="gDekuTreePauseScreenMapBasement1RightTex" OutName="deku_tree_pause_screen_map_basement_1_right" Format="ci4" Width="48" Height="85" Offset="0x37C8"/>
<Texture Name="gDekuTreePauseScreenMapBasement2LeftTex" OutName="deku_tree_pause_screen_map_basement_2_left" Format="ci4" Width="48" Height="85" Offset="0x3FC0"/>
<Texture Name="gDekuTreePauseScreenMapBasement2RightTex" OutName="deku_tree_pause_screen_map_basement_2_right" Format="ci4" Width="48" Height="85" Offset="0x47B8"/>
<Texture Name="gDodongosCavernPauseScreenMapFloor2LeftTex" OutName="dodongos_cavern_pause_screen_map_floor_2_left" Format="ci4" Width="48" Height="85" Offset="0x4FB0"/>
<Texture Name="gDodongosCavernPauseScreenMapFloor2RightTex" OutName="dodongos_cavern_pause_screen_map_floor_2_right" Format="ci4" Width="48" Height="85" Offset="0x57A8"/>
<Texture Name="gDodongosCavernPauseScreenMapFloor1LeftTex" OutName="dodongos_cavern_pause_screen_map_floor_1_left" Format="ci4" Width="48" Height="85" Offset="0x5FA0"/>
<Texture Name="gDodongosCavernPauseScreenMapFloor1RightTex" OutName="dodongos_cavern_pause_screen_map_floor_1_right" Format="ci4" Width="48" Height="85" Offset="0x6798"/>
<Texture Name="gJabuPauseScreenMapFloor1LeftTex" OutName="jabu_pause_screen_map_floor_1_left" Format="ci4" Width="48" Height="85" Offset="0x6F90"/>
<Texture Name="gJabuPauseScreenMapFloor1RightTex" OutName="jabu_pause_screen_map_floor_1_right" Format="ci4" Width="48" Height="85" Offset="0x7788"/>
<Texture Name="gJabuPauseScreenMapBasement1LeftTex" OutName="jabu_pause_screen_map_basement_1_left" Format="ci4" Width="48" Height="85" Offset="0x7F80"/>
<Texture Name="gJabuPauseScreenMapBasement1RightTex" OutName="jabu_pause_screen_map_basement_1_right" Format="ci4" Width="48" Height="85" Offset="0x8778"/>
<Texture Name="gForestTemplePauseScreenMapFloor2LeftTex" OutName="forest_temple_pause_screen_map_floor_2_left" Format="ci4" Width="48" Height="85" Offset="0x8F70"/>
<Texture Name="gForestTemplePauseScreenMapFloor2RightTex" OutName="forest_temple_pause_screen_map_floor_2_right" Format="ci4" Width="48" Height="85" Offset="0x9768"/>
<Texture Name="gForestTemplePauseScreenMapFloor1LeftTex" OutName="forest_temple_pause_screen_map_floor_1_left" Format="ci4" Width="48" Height="85" Offset="0x9F60"/>
<Texture Name="gForestTemplePauseScreenMapFloor1RightTex" OutName="forest_temple_pause_screen_map_floor_1_right" Format="ci4" Width="48" Height="85" Offset="0xA758"/>
<Texture Name="gForestTemplePauseScreenMapBasement1LeftTex" OutName="forest_temple_pause_screen_map_basement_1_left" Format="ci4" Width="48" Height="85" Offset="0xAF50"/>
<Texture Name="gForestTemplePauseScreenMapBasement1RightTex" OutName="forest_temple_pause_screen_map_basement_1_right" Format="ci4" Width="48" Height="85" Offset="0xB748"/>
<Texture Name="gForestTemplePauseScreenMapBasement2LeftTex" OutName="forest_temple_pause_screen_map_basement_2_left" Format="ci4" Width="48" Height="85" Offset="0xBF40"/>
<Texture Name="gForestTemplePauseScreenMapBasement2RightTex" OutName="forest_temple_pause_screen_map_basement_2_right" Format="ci4" Width="48" Height="85" Offset="0xC738"/>
<Texture Name="gFireTemplePauseScreenMapFloor5LeftTex" OutName="fire_temple_pause_screen_map_floor_5_left" Format="ci4" Width="48" Height="85" Offset="0xCF30"/>
<Texture Name="gFireTemplePauseScreenMapFloor5RightTex" OutName="fire_temple_pause_screen_map_floor_5_right" Format="ci4" Width="48" Height="85" Offset="0xD728"/>
<Texture Name="gFireTemplePauseScreenMapFloor4LeftTex" OutName="fire_temple_pause_screen_map_floor_4_left" Format="ci4" Width="48" Height="85" Offset="0xDF20"/>
<Texture Name="gFireTemplePauseScreenMapFloor4RightTex" OutName="fire_temple_pause_screen_map_floor_4_right" Format="ci4" Width="48" Height="85" Offset="0xE718"/>
<Texture Name="gFireTemplePauseScreenMapFloor3LeftTex" OutName="fire_temple_pause_screen_map_floor_3_left" Format="ci4" Width="48" Height="85" Offset="0xEF10"/>
<Texture Name="gFireTemplePauseScreenMapFloor3RightTex" OutName="fire_temple_pause_screen_map_floor_3_right" Format="ci4" Width="48" Height="85" Offset="0xF708"/>
<Texture Name="gFireTemplePauseScreenMapFloor2LeftTex" OutName="fire_temple_pause_screen_map_floor_2_left" Format="ci4" Width="48" Height="85" Offset="0xFF00"/>
<Texture Name="gFireTemplePauseScreenMapFloor2RightTex" OutName="fire_temple_pause_screen_map_floor_2_right" Format="ci4" Width="48" Height="85" Offset="0x106F8"/>
<Texture Name="gFireTemplePauseScreenMapFloor1LeftTex" OutName="fire_temple_pause_screen_map_floor_1_left" Format="ci4" Width="48" Height="85" Offset="0x10EF0"/>
<Texture Name="gFireTemplePauseScreenMapFloor1RightTex" OutName="fire_temple_pause_screen_map_floor_1_right" Format="ci4" Width="48" Height="85" Offset="0x116E8"/>
<Texture Name="gWaterTemplePauseScreenMapFloor3LeftTex" OutName="water_temple_pause_screen_map_floor_3_left" Format="ci4" Width="48" Height="85" Offset="0x11EE0"/>
<Texture Name="gWaterTemplePauseScreenMapFloor3RightTex" OutName="water_temple_pause_screen_map_floor_3_right" Format="ci4" Width="48" Height="85" Offset="0x126D8"/>
<Texture Name="gWaterTemplePauseScreenMapFloor2LeftTex" OutName="water_temple_pause_screen_map_floor_2_left" Format="ci4" Width="48" Height="85" Offset="0x12ED0"/>
<Texture Name="gWaterTemplePauseScreenMapFloor2RightTex" OutName="water_temple_pause_screen_map_floor_2_right" Format="ci4" Width="48" Height="85" Offset="0x136C8"/>
<Texture Name="gWaterTemplePauseScreenMapFloor1LeftTex" OutName="water_temple_pause_screen_map_floor_1_left" Format="ci4" Width="48" Height="85" Offset="0x13EC0"/>
<Texture Name="gWaterTemplePauseScreenMapFloor1RightTex" OutName="water_temple_pause_screen_map_floor_1_right" Format="ci4" Width="48" Height="85" Offset="0x146B8"/>
<Texture Name="gWaterTemplePauseScreenMapBasement1LeftTex" OutName="water_temple_pause_screen_map_basement_1_left" Format="ci4" Width="48" Height="85" Offset="0x14EB0"/>
<Texture Name="gWaterTemplePauseScreenMapBasement1RightTex" OutName="water_temple_pause_screen_map_basement_1_right" Format="ci4" Width="48" Height="85" Offset="0x156A8"/>
<Texture Name="gSpiritTemplePauseScreenMapFloor4LeftTex" OutName="spirit_temple_pause_screen_map_floor_4_left" Format="ci4" Width="48" Height="85" Offset="0x15EA0"/>
<Texture Name="gSpiritTemplePauseScreenMapFloor4RightTex" OutName="spirit_temple_pause_screen_map_floor_4_right" Format="ci4" Width="48" Height="85" Offset="0x16698"/>
<Texture Name="gSpiritTemplePauseScreenMapFloor3LeftTex" OutName="spirit_temple_pause_screen_map_floor_3_left" Format="ci4" Width="48" Height="85" Offset="0x16E90"/>
<Texture Name="gSpiritTemplePauseScreenMapFloor3RightTex" OutName="spirit_temple_pause_screen_map_floor_3_right" Format="ci4" Width="48" Height="85" Offset="0x17688"/>
<Texture Name="gSpiritTemplePauseScreenMapFloor2LeftTex" OutName="spirit_temple_pause_screen_map_floor_2_left" Format="ci4" Width="48" Height="85" Offset="0x17E80"/>
<Texture Name="gSpiritTemplePauseScreenMapFloor2RightTex" OutName="spirit_temple_pause_screen_map_floor_2_right" Format="ci4" Width="48" Height="85" Offset="0x18678"/>
<Texture Name="gSpiritTemplePauseScreenMapFloor1LeftTex" OutName="spirit_temple_pause_screen_map_floor_1_left" Format="ci4" Width="48" Height="85" Offset="0x18E70"/>
<Texture Name="gSpiritTemplePauseScreenMapFloor1RightTex" OutName="spirit_temple_pause_screen_map_floor_1_right" Format="ci4" Width="48" Height="85" Offset="0x19668"/>
<Texture Name="gShadowTemplePauseScreenMapBasement1LeftTex" OutName="shadow_temple_pause_screen_map_basement_1_left" Format="ci4" Width="48" Height="85" Offset="0x19E60"/>
<Texture Name="gShadowTemplePauseScreenMapBasement1RightTex" OutName="shadow_temple_pause_screen_map_basement_1_right" Format="ci4" Width="48" Height="85" Offset="0x1A658"/>
<Texture Name="gShadowTemplePauseScreenMapBasement2LeftTex" OutName="shadow_temple_pause_screen_map_basement_2_left" Format="ci4" Width="48" Height="85" Offset="0x1AE50"/>
<Texture Name="gShadowTemplePauseScreenMapBasement2RightTex" OutName="shadow_temple_pause_screen_map_basement_2_right" Format="ci4" Width="48" Height="85" Offset="0x1B648"/>
<Texture Name="gShadowTemplePauseScreenMapBasement3LeftTex" OutName="shadow_temple_pause_screen_map_basement_3_left" Format="ci4" Width="48" Height="85" Offset="0x1BE40"/>
<Texture Name="gShadowTemplePauseScreenMapBasement3RightTex" OutName="shadow_temple_pause_screen_map_basement_3_right" Format="ci4" Width="48" Height="85" Offset="0x1C638"/>
<Texture Name="gShadowTemplePauseScreenMapBasement4LeftTex" OutName="shadow_temple_pause_screen_map_basement_4_left" Format="ci4" Width="48" Height="85" Offset="0x1CE30"/>
<Texture Name="gShadowTemplePauseScreenMapBasement4RightTex" OutName="shadow_temple_pause_screen_map_basement_4_right" Format="ci4" Width="48" Height="85" Offset="0x1D628"/>
<Texture Name="gBottomOfTheWellPauseScreenMapBasement1LeftTex" OutName="bottom_of_the_well_pause_screen_map_basement_1_left" Format="ci4" Width="48" Height="85" Offset="0x1DE20"/>
<Texture Name="gBottomOfTheWellPauseScreenMapBasement1RightTex" OutName="bottom_of_the_well_pause_screen_map_basement_1_right" Format="ci4" Width="48" Height="85" Offset="0x1E618"/>
<Texture Name="gBottomOfTheWellPauseScreenMapBasement2LeftTex" OutName="bottom_of_the_well_pause_screen_map_basement_2_left" Format="ci4" Width="48" Height="85" Offset="0x1EE10"/>
<Texture Name="gBottomOfTheWellPauseScreenMapBasement2RightTex" OutName="bottom_of_the_well_pause_screen_map_basement_2_right" Format="ci4" Width="48" Height="85" Offset="0x1F608"/>
<Texture Name="gBottomOfTheWellPauseScreenMapBasement3LeftTex" OutName="bottom_of_the_well_pause_screen_map_basement_3_left" Format="ci4" Width="48" Height="85" Offset="0x1FE00"/>
<Texture Name="gBottomOfTheWellPauseScreenMapBasement3RightTex" OutName="bottom_of_the_well_pause_screen_map_basement_3_right" Format="ci4" Width="48" Height="85" Offset="0x205F8"/>
<Texture Name="gIceCavernPauseScreenMapFloor1LeftTex" OutName="ice_cavern_pause_screen_map_floor_1_left" Format="ci4" Width="48" Height="85" Offset="0x20DF0"/>
<Texture Name="gIceCavernPauseScreenMapFloor1RightTex" OutName="ice_cavern_pause_screen_map_floor_1_right" Format="ci4" Width="48" Height="85" Offset="0x215E8"/>
<!-- All these textures are used as ci4 with a dynamically generated palette.
Since there is no "real" palette, they are extracted as i4. -->
<Texture Name="gDekuTreePauseScreenMapFloor3LeftTex" OutName="deku_tree_pause_screen_map_floor_3_left" Format="i4" Width="48" Height="85" Offset="0x0"/>
<Texture Name="gDekuTreePauseScreenMapFloor3RightTex" OutName="deku_tree_pause_screen_map_floor_3_right" Format="i4" Width="48" Height="85" Offset="0x07F8"/>
<Texture Name="gDekuTreePauseScreenMapFloor2LeftTex" OutName="deku_tree_pause_screen_map_floor_2_left" Format="i4" Width="48" Height="85" Offset="0x0FF0"/>
<Texture Name="gDekuTreePauseScreenMapFloor2RightTex" OutName="deku_tree_pause_screen_map_floor_2_right" Format="i4" Width="48" Height="85" Offset="0x17E8"/>
<Texture Name="gDekuTreePauseScreenMapFloor1LeftTex" OutName="deku_tree_pause_screen_map_floor_1_left" Format="i4" Width="48" Height="85" Offset="0x1FE0"/>
<Texture Name="gDekuTreePauseScreenMapFloor1RightTex" OutName="deku_tree_pause_screen_map_floor_1_right" Format="i4" Width="48" Height="85" Offset="0x27D8"/>
<Texture Name="gDekuTreePauseScreenMapBasement1LeftTex" OutName="deku_tree_pause_screen_map_basement_1_left" Format="i4" Width="48" Height="85" Offset="0x2FD0"/>
<Texture Name="gDekuTreePauseScreenMapBasement1RightTex" OutName="deku_tree_pause_screen_map_basement_1_right" Format="i4" Width="48" Height="85" Offset="0x37C8"/>
<Texture Name="gDekuTreePauseScreenMapBasement2LeftTex" OutName="deku_tree_pause_screen_map_basement_2_left" Format="i4" Width="48" Height="85" Offset="0x3FC0"/>
<Texture Name="gDekuTreePauseScreenMapBasement2RightTex" OutName="deku_tree_pause_screen_map_basement_2_right" Format="i4" Width="48" Height="85" Offset="0x47B8"/>
<Texture Name="gDodongosCavernPauseScreenMapFloor2LeftTex" OutName="dodongos_cavern_pause_screen_map_floor_2_left" Format="i4" Width="48" Height="85" Offset="0x4FB0"/>
<Texture Name="gDodongosCavernPauseScreenMapFloor2RightTex" OutName="dodongos_cavern_pause_screen_map_floor_2_right" Format="i4" Width="48" Height="85" Offset="0x57A8"/>
<Texture Name="gDodongosCavernPauseScreenMapFloor1LeftTex" OutName="dodongos_cavern_pause_screen_map_floor_1_left" Format="i4" Width="48" Height="85" Offset="0x5FA0"/>
<Texture Name="gDodongosCavernPauseScreenMapFloor1RightTex" OutName="dodongos_cavern_pause_screen_map_floor_1_right" Format="i4" Width="48" Height="85" Offset="0x6798"/>
<Texture Name="gJabuPauseScreenMapFloor1LeftTex" OutName="jabu_pause_screen_map_floor_1_left" Format="i4" Width="48" Height="85" Offset="0x6F90"/>
<Texture Name="gJabuPauseScreenMapFloor1RightTex" OutName="jabu_pause_screen_map_floor_1_right" Format="i4" Width="48" Height="85" Offset="0x7788"/>
<Texture Name="gJabuPauseScreenMapBasement1LeftTex" OutName="jabu_pause_screen_map_basement_1_left" Format="i4" Width="48" Height="85" Offset="0x7F80"/>
<Texture Name="gJabuPauseScreenMapBasement1RightTex" OutName="jabu_pause_screen_map_basement_1_right" Format="i4" Width="48" Height="85" Offset="0x8778"/>
<Texture Name="gForestTemplePauseScreenMapFloor2LeftTex" OutName="forest_temple_pause_screen_map_floor_2_left" Format="i4" Width="48" Height="85" Offset="0x8F70"/>
<Texture Name="gForestTemplePauseScreenMapFloor2RightTex" OutName="forest_temple_pause_screen_map_floor_2_right" Format="i4" Width="48" Height="85" Offset="0x9768"/>
<Texture Name="gForestTemplePauseScreenMapFloor1LeftTex" OutName="forest_temple_pause_screen_map_floor_1_left" Format="i4" Width="48" Height="85" Offset="0x9F60"/>
<Texture Name="gForestTemplePauseScreenMapFloor1RightTex" OutName="forest_temple_pause_screen_map_floor_1_right" Format="i4" Width="48" Height="85" Offset="0xA758"/>
<Texture Name="gForestTemplePauseScreenMapBasement1LeftTex" OutName="forest_temple_pause_screen_map_basement_1_left" Format="i4" Width="48" Height="85" Offset="0xAF50"/>
<Texture Name="gForestTemplePauseScreenMapBasement1RightTex" OutName="forest_temple_pause_screen_map_basement_1_right" Format="i4" Width="48" Height="85" Offset="0xB748"/>
<Texture Name="gForestTemplePauseScreenMapBasement2LeftTex" OutName="forest_temple_pause_screen_map_basement_2_left" Format="i4" Width="48" Height="85" Offset="0xBF40"/>
<Texture Name="gForestTemplePauseScreenMapBasement2RightTex" OutName="forest_temple_pause_screen_map_basement_2_right" Format="i4" Width="48" Height="85" Offset="0xC738"/>
<Texture Name="gFireTemplePauseScreenMapFloor5LeftTex" OutName="fire_temple_pause_screen_map_floor_5_left" Format="i4" Width="48" Height="85" Offset="0xCF30"/>
<Texture Name="gFireTemplePauseScreenMapFloor5RightTex" OutName="fire_temple_pause_screen_map_floor_5_right" Format="i4" Width="48" Height="85" Offset="0xD728"/>
<Texture Name="gFireTemplePauseScreenMapFloor4LeftTex" OutName="fire_temple_pause_screen_map_floor_4_left" Format="i4" Width="48" Height="85" Offset="0xDF20"/>
<Texture Name="gFireTemplePauseScreenMapFloor4RightTex" OutName="fire_temple_pause_screen_map_floor_4_right" Format="i4" Width="48" Height="85" Offset="0xE718"/>
<Texture Name="gFireTemplePauseScreenMapFloor3LeftTex" OutName="fire_temple_pause_screen_map_floor_3_left" Format="i4" Width="48" Height="85" Offset="0xEF10"/>
<Texture Name="gFireTemplePauseScreenMapFloor3RightTex" OutName="fire_temple_pause_screen_map_floor_3_right" Format="i4" Width="48" Height="85" Offset="0xF708"/>
<Texture Name="gFireTemplePauseScreenMapFloor2LeftTex" OutName="fire_temple_pause_screen_map_floor_2_left" Format="i4" Width="48" Height="85" Offset="0xFF00"/>
<Texture Name="gFireTemplePauseScreenMapFloor2RightTex" OutName="fire_temple_pause_screen_map_floor_2_right" Format="i4" Width="48" Height="85" Offset="0x106F8"/>
<Texture Name="gFireTemplePauseScreenMapFloor1LeftTex" OutName="fire_temple_pause_screen_map_floor_1_left" Format="i4" Width="48" Height="85" Offset="0x10EF0"/>
<Texture Name="gFireTemplePauseScreenMapFloor1RightTex" OutName="fire_temple_pause_screen_map_floor_1_right" Format="i4" Width="48" Height="85" Offset="0x116E8"/>
<Texture Name="gWaterTemplePauseScreenMapFloor3LeftTex" OutName="water_temple_pause_screen_map_floor_3_left" Format="i4" Width="48" Height="85" Offset="0x11EE0"/>
<Texture Name="gWaterTemplePauseScreenMapFloor3RightTex" OutName="water_temple_pause_screen_map_floor_3_right" Format="i4" Width="48" Height="85" Offset="0x126D8"/>
<Texture Name="gWaterTemplePauseScreenMapFloor2LeftTex" OutName="water_temple_pause_screen_map_floor_2_left" Format="i4" Width="48" Height="85" Offset="0x12ED0"/>
<Texture Name="gWaterTemplePauseScreenMapFloor2RightTex" OutName="water_temple_pause_screen_map_floor_2_right" Format="i4" Width="48" Height="85" Offset="0x136C8"/>
<Texture Name="gWaterTemplePauseScreenMapFloor1LeftTex" OutName="water_temple_pause_screen_map_floor_1_left" Format="i4" Width="48" Height="85" Offset="0x13EC0"/>
<Texture Name="gWaterTemplePauseScreenMapFloor1RightTex" OutName="water_temple_pause_screen_map_floor_1_right" Format="i4" Width="48" Height="85" Offset="0x146B8"/>
<Texture Name="gWaterTemplePauseScreenMapBasement1LeftTex" OutName="water_temple_pause_screen_map_basement_1_left" Format="i4" Width="48" Height="85" Offset="0x14EB0"/>
<Texture Name="gWaterTemplePauseScreenMapBasement1RightTex" OutName="water_temple_pause_screen_map_basement_1_right" Format="i4" Width="48" Height="85" Offset="0x156A8"/>
<Texture Name="gSpiritTemplePauseScreenMapFloor4LeftTex" OutName="spirit_temple_pause_screen_map_floor_4_left" Format="i4" Width="48" Height="85" Offset="0x15EA0"/>
<Texture Name="gSpiritTemplePauseScreenMapFloor4RightTex" OutName="spirit_temple_pause_screen_map_floor_4_right" Format="i4" Width="48" Height="85" Offset="0x16698"/>
<Texture Name="gSpiritTemplePauseScreenMapFloor3LeftTex" OutName="spirit_temple_pause_screen_map_floor_3_left" Format="i4" Width="48" Height="85" Offset="0x16E90"/>
<Texture Name="gSpiritTemplePauseScreenMapFloor3RightTex" OutName="spirit_temple_pause_screen_map_floor_3_right" Format="i4" Width="48" Height="85" Offset="0x17688"/>
<Texture Name="gSpiritTemplePauseScreenMapFloor2LeftTex" OutName="spirit_temple_pause_screen_map_floor_2_left" Format="i4" Width="48" Height="85" Offset="0x17E80"/>
<Texture Name="gSpiritTemplePauseScreenMapFloor2RightTex" OutName="spirit_temple_pause_screen_map_floor_2_right" Format="i4" Width="48" Height="85" Offset="0x18678"/>
<Texture Name="gSpiritTemplePauseScreenMapFloor1LeftTex" OutName="spirit_temple_pause_screen_map_floor_1_left" Format="i4" Width="48" Height="85" Offset="0x18E70"/>
<Texture Name="gSpiritTemplePauseScreenMapFloor1RightTex" OutName="spirit_temple_pause_screen_map_floor_1_right" Format="i4" Width="48" Height="85" Offset="0x19668"/>
<Texture Name="gShadowTemplePauseScreenMapBasement1LeftTex" OutName="shadow_temple_pause_screen_map_basement_1_left" Format="i4" Width="48" Height="85" Offset="0x19E60"/>
<Texture Name="gShadowTemplePauseScreenMapBasement1RightTex" OutName="shadow_temple_pause_screen_map_basement_1_right" Format="i4" Width="48" Height="85" Offset="0x1A658"/>
<Texture Name="gShadowTemplePauseScreenMapBasement2LeftTex" OutName="shadow_temple_pause_screen_map_basement_2_left" Format="i4" Width="48" Height="85" Offset="0x1AE50"/>
<Texture Name="gShadowTemplePauseScreenMapBasement2RightTex" OutName="shadow_temple_pause_screen_map_basement_2_right" Format="i4" Width="48" Height="85" Offset="0x1B648"/>
<Texture Name="gShadowTemplePauseScreenMapBasement3LeftTex" OutName="shadow_temple_pause_screen_map_basement_3_left" Format="i4" Width="48" Height="85" Offset="0x1BE40"/>
<Texture Name="gShadowTemplePauseScreenMapBasement3RightTex" OutName="shadow_temple_pause_screen_map_basement_3_right" Format="i4" Width="48" Height="85" Offset="0x1C638"/>
<Texture Name="gShadowTemplePauseScreenMapBasement4LeftTex" OutName="shadow_temple_pause_screen_map_basement_4_left" Format="i4" Width="48" Height="85" Offset="0x1CE30"/>
<Texture Name="gShadowTemplePauseScreenMapBasement4RightTex" OutName="shadow_temple_pause_screen_map_basement_4_right" Format="i4" Width="48" Height="85" Offset="0x1D628"/>
<Texture Name="gBottomOfTheWellPauseScreenMapBasement1LeftTex" OutName="bottom_of_the_well_pause_screen_map_basement_1_left" Format="i4" Width="48" Height="85" Offset="0x1DE20"/>
<Texture Name="gBottomOfTheWellPauseScreenMapBasement1RightTex" OutName="bottom_of_the_well_pause_screen_map_basement_1_right" Format="i4" Width="48" Height="85" Offset="0x1E618"/>
<Texture Name="gBottomOfTheWellPauseScreenMapBasement2LeftTex" OutName="bottom_of_the_well_pause_screen_map_basement_2_left" Format="i4" Width="48" Height="85" Offset="0x1EE10"/>
<Texture Name="gBottomOfTheWellPauseScreenMapBasement2RightTex" OutName="bottom_of_the_well_pause_screen_map_basement_2_right" Format="i4" Width="48" Height="85" Offset="0x1F608"/>
<Texture Name="gBottomOfTheWellPauseScreenMapBasement3LeftTex" OutName="bottom_of_the_well_pause_screen_map_basement_3_left" Format="i4" Width="48" Height="85" Offset="0x1FE00"/>
<Texture Name="gBottomOfTheWellPauseScreenMapBasement3RightTex" OutName="bottom_of_the_well_pause_screen_map_basement_3_right" Format="i4" Width="48" Height="85" Offset="0x205F8"/>
<Texture Name="gIceCavernPauseScreenMapFloor1LeftTex" OutName="ice_cavern_pause_screen_map_floor_1_left" Format="i4" Width="48" Height="85" Offset="0x20DF0"/>
<Texture Name="gIceCavernPauseScreenMapFloor1RightTex" OutName="ice_cavern_pause_screen_map_floor_1_right" Format="i4" Width="48" Height="85" Offset="0x215E8"/>
</File>
</Root>

View file

@ -10,7 +10,7 @@ Gfx sTransCircleEmptyDL[] = {
gsSPEndDisplayList(),
};
#include "assets/code/fbdemo_circle/z_fbdemo_circle.c"
#include "assets/code/fbdemo_circle/code.c"
Gfx sTransCircleDL[] = {
gsDPPipeSync(),

View file

@ -1,6 +1,6 @@
#include "global.h"
#include "assets/code/fbdemo_triforce/z_fbdemo_triforce.c"
#include "assets/code/fbdemo_triforce/code.c"
void TransitionTriforce_Start(void* thisx) {
TransitionTriforce* this = (TransitionTriforce*)thisx;

View file

@ -5,7 +5,7 @@ typedef enum TransitionWipeDirection {
/* 1 */ TRANS_WIPE_DIR_OUT
} TransitionWipeDirection;
#include "assets/code/fbdemo_wipe1/z_fbdemo_wipe1.c"
#include "assets/code/fbdemo_wipe1/code.c"
Gfx sTransWipeDL[] = {
gsDPPipeSync(),

View file

@ -13,7 +13,7 @@ typedef struct BowSlingshotStringData {
/* 0x04 */ Vec3f pos;
} BowSlingshotStringData; // size = 0x10
FlexSkeletonHeader* gPlayerSkelHeaders[] = { &gLinkAdultSkel, &gLinkChildSkel };
FlexSkeletonHeader* gPlayerSkelHeaders[] = { (void*)&gLinkAdultSkel, (void*)&gLinkChildSkel }; //! FIXME
s16 sBootData[PLAYER_BOOTS_MAX][17] = {
// PLAYER_BOOTS_KOKIRI

View file

@ -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, &gArrowSkel, &gArrow2Anim, NULL, NULL, 0);
SkelAnime_Init(play, &this->skelAnime, (void*)&gArrowSkel, &gArrow2Anim, NULL, NULL, 0);
}
if (this->actor.params <= ARROW_NORMAL) {

View file

@ -5,6 +5,26 @@
#include "global.h"
#include "assets/objects/object_cow/object_cow.h"
typedef enum CowLimb {
/* 0x00 */ COW_LIMB_NONE,
/* 0x01 */ COW_LIMB_ROOT,
/* 0x02 */ COW_LIMB_HEAD,
/* 0x03 */ COW_LIMB_JAW,
/* 0x04 */ COW_LIMB_NOSE,
/* 0x05 */ COW_LIMB_NOSE_RING,
/* 0x06 */ COW_LIMB_MAX
} CowLimb;
typedef enum CowTailLimb {
/* 0x00 */ COW_TAIL_LIMB_NONE,
/* 0x01 */ COW_TAIL_LIMB_ROOT,
/* 0x02 */ COW_TAIL_LIMB_UPPER,
/* 0x03 */ COW_TAIL_LIMB_MIDDLE,
/* 0x04 */ COW_TAIL_LIMB_LOWER,
/* 0x05 */ COW_TAIL_LIMB_END,
/* 0x06 */ COW_TAIL_LIMB_MAX
} CowTailLimb;
#define COW_FLAG_PLAYER_NEARBY (1 << 1)
#define COW_FLAG_FAILED_TO_GIVE_MILK (1 << 2)

View file

@ -5,6 +5,28 @@
#include "global.h"
#include "assets/objects/object_shopnuts/object_shopnuts.h"
typedef enum BusinessScrubLimb {
/* 0x00 */ BUSINESS_SCRUB_LIMB_NONE,
/* 0x01 */ BUSINESS_SCRUB_LIMB_ROOT,
/* 0x02 */ BUSINESS_SCRUB_LIMB_LEFT_THIGH,
/* 0x03 */ BUSINESS_SCRUB_LIMB_LEFT_SHIN,
/* 0x04 */ BUSINESS_SCRUB_LIMB_LEFT_FOOT,
/* 0x05 */ BUSINESS_SCRUB_LIMB_RIGHT_THIGH,
/* 0x06 */ BUSINESS_SCRUB_LIMB_RIGHT_SHIN,
/* 0x07 */ BUSINESS_SCRUB_LIMB_RIGHT_FOOT,
/* 0x08 */ BUSINESS_SCRUB_LIMB_TOP_LEAF,
/* 0x09 */ BUSINESS_SCRUB_LIMB_NOSE,
/* 0x0A */ BUSINESS_SCRUB_LIMB_LEFT_UPPER_ARM,
/* 0x0B */ BUSINESS_SCRUB_LIMB_LEFT_FOREARM,
/* 0x0C */ BUSINESS_SCRUB_LIMB_LEFT_HAND,
/* 0x0D */ BUSINESS_SCRUB_LIMB_RIGHT_UPPER_ARM,
/* 0x0E */ BUSINESS_SCRUB_LIMB_RIGHT_FOREARM,
/* 0x0F */ BUSINESS_SCRUB_LIMB_RIGHT_HAND,
/* 0x10 */ BUSINESS_SCRUB_LIMB_EYES,
/* 0x11 */ BUSINESS_SCRUB_LIMB_BODY,
/* 0x12 */ BUSINESS_SCRUB_LIMB_MAX
} BusinessScrubLimb;
#define DNS_GET_TYPE(thisx) ((thisx)->params)
typedef enum EnDnsType {

View file

@ -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[] = { &gEponaSkel, &gHorseIngoSkel };
static SkeletonHeader* sSkeletonHeaders[] = { (void*)&gEponaSkel, (void*)&gHorseIngoSkel };
ActorProfile En_Horse_Profile = {
/**/ ACTOR_EN_HORSE,

View file

@ -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, &gHorseGanonSkel, &gHorseGanonIdleAnim);
Skin_Init(play, &this->skin, (void*)&gHorseGanonSkel, &gHorseGanonIdleAnim);
this->currentAnimation = 0;
Animation_PlayOnce(&this->skin.skelAnime, sAnimations[0]);

View file

@ -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, &gChildEponaSkel, &gChildEponaGallopingAnim);
Skin_Init(play, &this->skin, (void*)&gChildEponaSkel, &gChildEponaGallopingAnim);
this->animationIdx = 0;
Animation_PlayOnce(&this->skin.skelAnime, sAnimations[0]);
Collider_InitCylinder(play, &this->bodyCollider);

View file

@ -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, &gHorseNormalSkel, &gHorseNormalIdleAnim);
Skin_Init(play, &this->skin, (void*)&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, &gHorseNormalSkel, &gHorseNormalIdleAnim);
Skin_Init(play, &this->skin, (void*)&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, &gHorseNormalSkel, &gHorseNormalIdleAnim);
Skin_Init(play, &this->skin, (void*)&gHorseNormalSkel, &gHorseNormalIdleAnim);
Animation_PlayOnce(&this->skin.skelAnime, sAnimations[this->animationIdx]);
func_80A6C4CC(this);
return;
}
Skin_Init(play, &this->skin, &gHorseNormalSkel, &gHorseNormalIdleAnim);
Skin_Init(play, &this->skin, (void*)&gHorseNormalSkel, &gHorseNormalIdleAnim);
Animation_PlayOnce(&this->skin.skelAnime, sAnimations[this->animationIdx]);
} else {
Skin_Init(play, &this->skin, &gHorseNormalSkel, &gHorseNormalIdleAnim);
Skin_Init(play, &this->skin, (void*)&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) {

View file

@ -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, &gHorseZeldaSkel, &gHorseZeldaGallopingAnim);
Skin_Init(play, &this->skin, (void*)&gHorseZeldaSkel, &gHorseZeldaGallopingAnim);
this->animationIndex = 0;
Animation_PlayOnce(&this->skin.skelAnime, sAnimationHeaders[0]);
Collider_InitCylinder(play, &this->colliderCylinder);

View file

@ -7,6 +7,29 @@
#include "z_en_ma1.h"
#include "assets/objects/object_ma1/object_ma1.h"
typedef enum ChildMalonLimb {
/* 0x00 */ CHILD_MALON_LIMB_NONE,
/* 0x01 */ CHILD_MALON_ROOT,
/* 0x02 */ CHILD_MALON_LIMB_LEFT_THIGH,
/* 0x03 */ CHILD_MALON_LIMB_LEFT_SHIN,
/* 0x04 */ CHILD_MALON_LIMB_LEFT_FOOT,
/* 0x05 */ CHILD_MALON_LIMB_RIGHT_THIGH,
/* 0x06 */ CHILD_MALON_LIMB_RIGHT_SHIN,
/* 0x07 */ CHILD_MALON_LIMB_RIGHT_FOOT,
/* 0x08 */ CHILD_MALON_LIMB_CHEST,
/* 0x09 */ CHILD_MALON_LIMB_LEFT_SHOULDER,
/* 0x0A */ CHILD_MALON_LIMB_LEFT_ARM,
/* 0x0B */ CHILD_MALON_LIMB_LEFT_HAND,
/* 0x0C */ CHILD_MALON_LIMB_RIGHT_SHOULDER,
/* 0x0D */ CHILD_MALON_LIMB_RIGHT_ARM,
/* 0x0E */ CHILD_MALON_LIMB_RIGHT_HAND,
/* 0x0F */ CHILD_MALON_LIMB_HEAD,
/* 0x10 */ CHILD_MALON_LIMB_DRESS_UPPER,
/* 0x11 */ CHILD_MALON_LIMB_DRESS_MIDDLE,
/* 0x12 */ CHILD_MALON_LIMB_DRESS_LOWER,
/* 0x13 */ CHILD_MALON_LIMB_MAX
} ChildMalonLimb;
#define FLAGS \
(ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \
ACTOR_FLAG_DRAW_CULLING_DISABLED | ACTOR_FLAG_UPDATE_DURING_OCARINA)

View file

@ -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, &gDarkLinkSkel);
play->playerInit(this, play, (void*)&gDarkLinkSkel); //! FIXME
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;

View file

@ -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, &gPhantomHorseSkel, &gPhantomHorseRunningAnim);
Skin_Init(play, &this->skin, (void*)&gPhantomHorseSkel, &gPhantomHorseRunningAnim);
if (this->actor.params >= GND_FAKE_BOSS) {
EnfHG_SetupApproach(this, play, this->actor.params - GND_FAKE_BOSS);

View file

@ -38,6 +38,7 @@ all: $(PROGRAMS) $(IDO_RECOMP_5_3_DIR) $(IDO_RECOMP_7_1_DIR) $(EGCS_DIR)
$(MAKE) -C ZAPD
$(MAKE) -C fado
$(MAKE) -C audio
$(MAKE) -C assets
clean:
$(RM) $(PROGRAMS) $(addsuffix .exe,$(PROGRAMS))
@ -45,6 +46,7 @@ clean:
$(MAKE) -C ZAPD clean
$(MAKE) -C fado clean
$(MAKE) -C audio clean
$(MAKE) -C assets clean
distclean: clean
$(MAKE) -C audio distclean

9
tools/assets/Makefile Normal file
View file

@ -0,0 +1,9 @@
all:
$(MAKE) -C bin2c
$(MAKE) -C png2raw
clean:
$(MAKE) -C bin2c clean
$(MAKE) -C png2raw clean
.PHONY: all clean

1
tools/assets/bin2c/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
bin2c

View file

@ -0,0 +1,9 @@
CFLAGS := -Wall
bin2c: bin2c.c
$(CC) $(CFLAGS) -o $@ $<
clean:
$(RM) bin2c
.PHONY: clean

View file

@ -0,0 +1,86 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
int main(int argc, char** argv) {
if (argc != 2) {
usage:
fprintf(stderr, "%s <u8|u32|u64>\n", argv[0]);
return EXIT_FAILURE;
}
char* bytes_per_elem_str = argv[1];
struct {
size_t num;
char* str;
} bytes_per_elem_arg_info[] = {
{ 1, "u8" },
{ 4, "u32" },
{ 8, "u64" },
};
size_t bytes_per_elem = 0;
for (int i = 0; i < 3; i++) {
if (strcmp(bytes_per_elem_arg_info[i].str, bytes_per_elem_str) == 0) {
bytes_per_elem = bytes_per_elem_arg_info[i].num;
}
}
if (bytes_per_elem == 0) {
goto usage;
}
FILE* in_bin = stdin;
FILE* out_c = stdout;
fprintf(out_c, "{\n");
int cur_line_nelems = 0;
while (true) {
uint8_t buffer[bytes_per_elem];
size_t nread = fread(buffer, 1, bytes_per_elem, in_bin);
if (nread == 0 && feof(in_bin)) {
break;
}
if (nread != bytes_per_elem) {
if (feof(in_bin)) {
fprintf(stderr, "Input has unaligned size\n");
} else {
fprintf(stderr, "Error reading from input\n");
}
return EXIT_FAILURE;
}
if (cur_line_nelems == 0) {
fprintf(out_c, " ");
}
fprintf(out_c, "0x");
for (size_t i = 0; i < bytes_per_elem; i++) {
fprintf(out_c, "%02X", buffer[i]);
}
fprintf(out_c, ",");
cur_line_nelems++;
int bytes_per_line = bytes_per_elem == 1 ? 0x10 : 0x20;
if (cur_line_nelems * bytes_per_elem >= bytes_per_line) {
fprintf(out_c, "\n");
cur_line_nelems = 0;
} else {
fprintf(out_c, " ");
}
}
if (cur_line_nelems != 0) {
fprintf(out_c, "\n");
}
fprintf(out_c, "}\n");
return EXIT_SUCCESS;
}

109
tools/assets/build_from_png.py Executable file
View file

@ -0,0 +1,109 @@
#!/usr/bin/env python3
import sys
from pathlib import Path
VERBOSE = False
from n64 import G_IM_FMT, G_IM_SIZ
import n64yatc
from png2raw import png2raw
def main():
png_path = Path(sys.argv[1])
out_bin_path = Path(sys.argv[2])
suffixes = png_path.suffixes
assert len(suffixes) >= 2
assert suffixes[-1] == ".png"
suffixes.pop()
if suffixes[-1] in {".u64", ".u32"}:
suffixes.pop()
assert len(suffixes) > 0
if suffixes[-1].startswith(".tlut_"):
tlut_info = suffixes.pop().removeprefix(".")
else:
tlut_info = None
assert len(suffixes) > 0
fmtsiz_str = suffixes[-1].removeprefix(".")
fmt, siz = None, None
for candidate_fmt in G_IM_FMT:
for candidate_siz in G_IM_SIZ:
candidate_fmtsiz_str = f"{candidate_fmt.name.lower()}{candidate_siz.bpp}"
if candidate_fmtsiz_str == fmtsiz_str:
fmt = candidate_fmt
siz = candidate_siz
assert fmt is not None and siz is not None, fmtsiz_str
if fmt != G_IM_FMT.CI:
with png2raw.Instance(png_path) as png:
data_rgba32 = png.read_to_rgba32()
tex_bin = n64yatc.convert(data_rgba32, G_IM_FMT.RGBA, G_IM_SIZ._32b, fmt, siz)
# print(len(tex_bin), tex_bin[:0x10], tex_bin[-0x10:], file=sys.stderr)
# sys.stdout.buffer.write(tex_bin) # for some reason the *string* "None." is also written to stdout???
out_bin_path.write_bytes(tex_bin)
else:
# TODO probably move tlut_info and overall tex file suffix construction/parsing to its own library
if tlut_info is None:
tlut_elem_type = "u64"
tlut_out_bin_path_base_str = str(out_bin_path)
tlut_out_bin_path_base_str = tlut_out_bin_path_base_str.removesuffix(".bin")
if tlut_out_bin_path_base_str.endswith(".u64"):
tlut_out_bin_path_base_str = tlut_out_bin_path_base_str.removesuffix(
".u64"
)
all_pngs_using_tlut = [png_path]
else:
tlut_elem_type = "u64"
if tlut_info.endswith("_u64"):
tlut_elem_type = "u64"
tlut_name = tlut_info.removeprefix("tlut_").removesuffix("_u64")
elif tlut_info.endswith("_u32"):
tlut_elem_type = "u32"
tlut_name = tlut_info.removeprefix("tlut_").removesuffix("_u32")
else:
tlut_name = tlut_info.removeprefix("tlut_")
tlut_out_bin_path_base_str = str(out_bin_path.parent / tlut_name)
# TODO this is far from perfect.
# what if a tlut_name is included in another
# what if not in the same folder (just don't support that)
# does the same png get built several times
all_pngs_using_tlut = list(png_path.parent.glob(f"*.tlut_{tlut_name}*.png"))
assert png_path in all_pngs_using_tlut
tlut_out_bin_path = Path(
f"{tlut_out_bin_path_base_str}.tlut.rgba16.{tlut_elem_type}.bin"
)
if VERBOSE:
print(all_pngs_using_tlut)
with png2raw.Instance(png_path) as png:
palette_rgba32 = png.get_palette_rgba32()
data_ci8 = png.read_palette_indices()
tex_bin = n64yatc.convert(data_ci8, G_IM_FMT.CI, G_IM_SIZ._8b, fmt, siz)
tlut_bin = n64yatc.convert(
palette_rgba32, G_IM_FMT.RGBA, G_IM_SIZ._32b, G_IM_FMT.RGBA, G_IM_SIZ._16b
)
out_bin_path.write_bytes(tex_bin)
tlut_out_bin_path.write_bytes(tlut_bin)
import subprocess
# HACK since the makefile doesn't know the tlut file should be built (bin2c'd), build it here
cmd = [
"tools/assets/bin2c/bin2c",
tlut_elem_type,
]
with tlut_out_bin_path.open("rb") as fin:
with tlut_out_bin_path.with_suffix(".inc.c").open("w") as fout:
subprocess.check_call(cmd, stdin=fin, stdout=fout)
if __name__ == "__main__":
main()

2
tools/assets/conf.py Normal file
View file

@ -0,0 +1,2 @@
WRITE_HINTS = True
I_D_OMEGALUL = True # IDO specific things that may be otherwise different

View file

@ -0,0 +1,23 @@
from tools import version_config
from . import base
def main():
vc = version_config.load_version_config("gc-eu-mq-dbg")
pools = base.get_resources_desc(vc)
from pprint import pprint
if 0:
with open(
"/home/dragorn421/Documents/oot/tools/assets/descriptor/resources.txt",
"w",
) as f:
for i, pool in enumerate(pools):
print(round(i / len(pools) * 100, 2), "%", end="\r")
pprint(pool, f)
main()

View file

@ -0,0 +1,307 @@
import abc
import dataclasses
from functools import cache
from pathlib import Path
from typing import Callable, Optional
from xml.etree import ElementTree
from tools import version_config
class BackingMemory(abc.ABC):
pass
@dataclasses.dataclass
class BaseromFileBackingMemory(BackingMemory):
name: str
range: Optional[tuple[int, int]]
"""If set, consider file_data[range[0]:range[1]] instead of the full file"""
@dataclasses.dataclass
class NoBackingMemory(BackingMemory):
pass
# eq=False so this uses id-based equality and hashing
# Subclasses must also be made to use id-based equality and hashing
@dataclasses.dataclass(eq=False)
class ResourceDesc(abc.ABC):
"""A resource is a data unit.
For example, a symbol's data such as a DList or a texture."""
symbol_name: str
offset: int
"""How many bytes into the backing memory the resource is located at"""
collection: "ResourcesDescCollection" = dataclasses.field(repr=False)
origin: object
"""opaque object with data about where this resource comes from (for debugging)"""
hack_modes: set[str] = dataclasses.field(init=False, default_factory=set)
class StartAddress(abc.ABC):
pass
@dataclasses.dataclass
class VRAMStartAddress(StartAddress):
vram: int
@dataclasses.dataclass
class SegmentStartAddress(StartAddress):
segment: int
@dataclasses.dataclass(eq=False)
class ResourcesDescCollection:
"""A collection is a list of resources backed by the same memory."""
out_path: Path
backing_memory: BackingMemory
start_address: Optional[StartAddress]
resources: list[ResourceDesc]
last_modified_time: float
@dataclasses.dataclass(eq=False)
class ResourcesDescCollectionsPool:
"""A pool contains a minimal set of interconnected collections.
For example, gkeep and all files using gkeep,
or more simply a single collection with no connection."""
collections: list[ResourcesDescCollection]
ResourceHandlerPass2Callback = Callable[[ResourcesDescCollectionsPool], None]
@dataclasses.dataclass
class ResourceHandlerNeedsPass2Exception(Exception):
resource: ResourceDesc
pass2_callback: ResourceHandlerPass2Callback
# eq=False so this uses id-based equality and hashing
@dataclasses.dataclass(eq=False)
class AssetConfigPiece:
ac: version_config.AssetConfig
last_modified_time: float = None
etree: ElementTree.ElementTree = None
def get_resources_desc(vc: version_config.VersionConfig):
# Wrap AssetConfig objects in AssetConfigPiece for hashability and to collect data
acps = [AssetConfigPiece(ac) for ac in vc.assets]
# Parse xmls
for acp in acps:
acp.last_modified_time = acp.ac.xml_path.stat().st_mtime
try:
with acp.ac.xml_path.open(encoding="UTF-8") as f:
etree = ElementTree.parse(f)
acp.etree = etree
except Exception as e:
raise Exception(f"Error when parsing XML for {acp}") from e
# Resolve pools
acp_by_name = {acp.ac.name: acp for acp in acps}
pools = {acp: {acp} for acp in acps}
for acp in acps:
try:
rootelem = acp.etree.getroot()
assert rootelem.tag == "Root", rootelem.tag
for fileelem in rootelem:
assert fileelem.tag in {"ExternalFile", "File"}, fileelem.tag
if fileelem.tag == "ExternalFile":
externalfile_name = str(
Path(fileelem.attrib["OutPath"]).relative_to("assets")
)
assert externalfile_name in acp_by_name, externalfile_name
externalfile_acp = acp_by_name[externalfile_name]
acp_pool = pools[acp]
externalfile_acp_pool = pools[externalfile_acp]
merged_pool = acp_pool | externalfile_acp_pool
for merged_pool_acp in merged_pool:
pools[merged_pool_acp] = merged_pool
except Exception as e:
raise Exception(f"Error while resolving pools with {acp}") from e
if 0:
import pprint
with Path(
"/home/dragorn421/Documents/oot/tools/assets/descriptor/pools.txt"
).open("w") as f:
pprint.pprint(pools, stream=f)
# List unique pools
pools_unique: list[set[AssetConfigPiece]] = []
while pools:
pool = next(iter(pools.values()))
pools_unique.append(pool)
for acp in pool:
del pools[acp]
# Build resources for all pools
pools: list[ResourcesDescCollectionsPool] = []
for pool in pools_unique:
try:
all_needs_pass2_exceptions: list[ResourceHandlerNeedsPass2Exception] = []
rescolls: list[ResourcesDescCollection] = []
# Pass 1: create Resource objects
for acp in pool:
try:
rootelem = acp.etree.getroot()
for fileelem in rootelem:
if fileelem.tag == "File":
rc, needs_pass2_exceptions = (
_get_resources_fileelem_to_resourcescollection_pass1(
vc, pool, acp, fileelem
)
)
rescolls.append(rc)
all_needs_pass2_exceptions.extend(needs_pass2_exceptions)
except Exception as e:
raise Exception(f"Error with {acp}") from e
rcpool = ResourcesDescCollectionsPool(rescolls)
# Pass 2: execute callbacks
for needs_pass2_exc in all_needs_pass2_exceptions:
try:
needs_pass2_exc.pass2_callback(rcpool)
except Exception as e:
raise Exception(
f"Error with pass 2 callback for {needs_pass2_exc.resource}"
) from e
pools.append(rcpool)
except Exception as e:
raise Exception(f"Error with pool {pool}") from e
return pools
def _get_resources_fileelem_to_resourcescollection_pass1(
vc: version_config.VersionConfig,
pool: list[AssetConfigPiece],
acp: AssetConfigPiece,
fileelem: ElementTree.Element,
):
# Determine backing_memory
if acp.ac.start_offset is None:
assert acp.ac.end_offset is None
baserom_file_range = None
else:
assert acp.ac.end_offset is not None
baserom_file_range = (acp.ac.start_offset, acp.ac.end_offset)
backing_memory = BaseromFileBackingMemory(
name=fileelem.attrib["Name"],
range=baserom_file_range,
)
# Determine start_address
if any(
acp.ac.name.startswith(_prefix) for _prefix in ("code/", "n64dd/", "overlays/")
):
# File start address is vram
assert "Segment" not in fileelem.attrib
assert acp.ac.start_offset is not None and acp.ac.end_offset is not None, (
"Unsupported combination: "
f"start/end offset not in config for vram asset {acp.ac.name}"
)
if acp.ac.name.startswith("overlays/"):
overlay_name = acp.ac.name.split("/")[1]
start_address = VRAMStartAddress(
vc.dmadata_segments[overlay_name].vram + acp.ac.start_offset
)
else:
file_name = acp.ac.name.split("/")[0] # "code" or "n64dd"
start_address = VRAMStartAddress(
vc.dmadata_segments[file_name].vram + acp.ac.start_offset
)
elif "Segment" in fileelem.attrib:
# File start address is a segmented address
assert acp.ac.start_offset is None and acp.ac.end_offset is None, (
"Unsupported combination: "
"start/end offset in config and file starts at a segmented address"
)
start_address = SegmentStartAddress(int(fileelem.attrib["Segment"]))
else:
# File does not have a start address
start_address = None
# resources
resources: list[ResourceDesc] = []
collection = ResourcesDescCollection(
Path(acp.ac.name),
backing_memory,
start_address,
resources,
acp.last_modified_time,
)
needs_pass2_exceptions: list[ResourceHandlerNeedsPass2Exception] = []
for reselem in fileelem:
try:
symbol_name = reselem.attrib["Name"]
offset = int(reselem.attrib["Offset"], 16)
res_handler = _get_resource_handler(reselem.tag)
try:
res = res_handler(symbol_name, offset, collection, reselem)
except ResourceHandlerNeedsPass2Exception as needs_pass2_exc:
res = needs_pass2_exc.resource
needs_pass2_exceptions.append(needs_pass2_exc)
assert isinstance(res, ResourceDesc)
resources.append(res)
except Exception as e:
raise Exception(
"Error with resource element:\n"
+ ElementTree.tostring(reselem, encoding="unicode")
) from e
return collection, needs_pass2_exceptions
ResourceHandler = Callable[
[str, int, ResourcesDescCollection, ElementTree.Element],
ResourceDesc,
]
@cache
def _get_resource_handler(tag: str) -> ResourceHandler:
from . import n64resources
from . import z64resources
resource_handlers = {
"DList": n64resources.handler_DList,
"Blob": n64resources.handler_Blob,
"Mtx": n64resources.handler_Mtx,
"Array": n64resources.handler_Array,
"Texture": n64resources.handler_Texture,
"Collision": z64resources.handler_Collision,
"Animation": z64resources.handler_Animation,
"PlayerAnimation": z64resources.handler_PlayerAnimation,
"LegacyAnimation": z64resources.handler_LegacyAnimation,
"Cutscene": z64resources.handler_Cutscene,
"Scene": z64resources.handler_Scene,
"Room": z64resources.handler_Room,
"PlayerAnimationData": z64resources.handler_PlayerAnimationData,
"Path": z64resources.handler_PathList,
"Skeleton": z64resources.handler_Skeleton,
"Limb": z64resources.handler_Limb,
"CurveAnimation": z64resources.handler_CurveAnimation,
"LimbTable": z64resources.handler_LimbTable,
}
rh = resource_handlers.get(tag)
if rh is None:
raise Exception(f"Unknown resource tag {tag}")
else:
return rh

View file

@ -0,0 +1,176 @@
import dataclasses
import enum
from xml.etree.ElementTree import Element
from ..n64 import G_IM_FMT, G_IM_SIZ
from .base import (
ResourceDesc,
ResourcesDescCollection,
ResourcesDescCollectionsPool,
ResourceHandlerNeedsPass2Exception,
BaseromFileBackingMemory,
)
class GfxMicroCode(enum.Enum):
F3DEX = enum.auto()
F3DEX2 = enum.auto()
@dataclasses.dataclass(eq=False)
class DListResourceDesc(ResourceDesc):
ucode: GfxMicroCode
def handler_DList(symbol_name, offset, collection, reselem: Element):
if "Ucode" in reselem.attrib:
ucode = GfxMicroCode[reselem.attrib["Ucode"].upper()]
else:
ucode = GfxMicroCode.F3DEX2
return DListResourceDesc(symbol_name, offset, collection, reselem, ucode)
@dataclasses.dataclass(eq=False)
class BlobResourceDesc(ResourceDesc):
size: int
def handler_Blob(symbol_name, offset, collection, reselem: Element):
size = int(reselem.attrib["Size"], 16)
return BlobResourceDesc(symbol_name, offset, collection, reselem, size)
@dataclasses.dataclass(eq=False)
class MtxResourceDesc(ResourceDesc):
pass
def handler_Mtx(symbol_name, offset, collection, reselem: Element):
return MtxResourceDesc(symbol_name, offset, collection, reselem)
@dataclasses.dataclass(eq=False)
class S16ArrayResourceDesc(ResourceDesc):
count: int
@dataclasses.dataclass(eq=False)
class Vec3sArrayResourceDesc(ResourceDesc):
count: int
@dataclasses.dataclass(eq=False)
class VtxArrayResourceDesc(ResourceDesc):
count: int
def handler_Array(symbol_name, offset, collection, reselem: Element):
count = int(reselem.attrib["Count"])
assert len(reselem) == 1, "Expected exactly one child of Array node"
array_elem = reselem[0]
if array_elem.tag == "Vtx":
array_resource_type = VtxArrayResourceDesc
elif (
array_elem.tag == "Vector"
and array_elem.attrib["Type"] == "s16"
and int(array_elem.attrib["Dimensions"]) == 3
):
array_resource_type = Vec3sArrayResourceDesc
elif array_elem.tag == "Scalar" and array_elem.attrib["Type"] == "s16":
array_resource_type = S16ArrayResourceDesc
else:
raise NotImplementedError(f"Array of {array_elem.tag}")
return array_resource_type(symbol_name, offset, collection, reselem, count)
class TextureFormat(enum.Enum):
RGBA16 = (G_IM_FMT.RGBA, G_IM_SIZ._16b)
RGBA32 = (G_IM_FMT.RGBA, G_IM_SIZ._32b)
CI4 = (G_IM_FMT.CI, G_IM_SIZ._4b)
CI8 = (G_IM_FMT.CI, G_IM_SIZ._8b)
I4 = (G_IM_FMT.I, G_IM_SIZ._4b)
I8 = (G_IM_FMT.I, G_IM_SIZ._8b)
IA4 = (G_IM_FMT.IA, G_IM_SIZ._4b)
IA8 = (G_IM_FMT.IA, G_IM_SIZ._8b)
IA16 = (G_IM_FMT.IA, G_IM_SIZ._16b)
def __init__(self, fmt: G_IM_FMT, siz: G_IM_SIZ):
self.fmt = fmt
self.siz = siz
@dataclasses.dataclass(eq=False)
class TextureResourceDesc(ResourceDesc):
format: TextureFormat
width: int
height: int
@dataclasses.dataclass(eq=False)
class CITextureResourceDesc(TextureResourceDesc):
tlut: TextureResourceDesc
def handler_Texture(
symbol_name, offset, collection: ResourcesDescCollection, reselem: Element
):
format = TextureFormat[reselem.attrib["Format"].upper()]
width = int(reselem.attrib["Width"])
height = int(reselem.attrib["Height"])
if format.fmt == G_IM_FMT.CI:
res = CITextureResourceDesc(
symbol_name, offset, collection, reselem, format, width, height, None
)
if reselem.attrib.get("SplitTlut") == "true":
res.hack_modes.add("hackmode_split_tlut")
assert (
"TlutOffset" in reselem.attrib or "ExternalTlutOffset" in reselem.attrib
), f"CI texture {symbol_name} is missing a tlut offset"
if "TlutOffset" in reselem.attrib:
tlut_offset = int(reselem.attrib["TlutOffset"], 16)
def pass2_callback(pool: ResourcesDescCollectionsPool):
matching_tlut_resources = [
res for res in collection.resources if res.offset == tlut_offset
]
assert len(matching_tlut_resources) == 1, (
f"Found {len(matching_tlut_resources)} resources at TlutOffset "
f"0x{tlut_offset:X} instead of exactly one"
)
assert isinstance(
matching_tlut_resources[0], TextureResourceDesc
), matching_tlut_resources[0]
res.tlut = matching_tlut_resources[0]
else:
external_tlut_file = reselem.attrib["ExternalTlut"]
external_tlut_offset = int(reselem.attrib["ExternalTlutOffset"], 16)
def pass2_callback(pool: ResourcesDescCollectionsPool):
matching_collections = [
coll
for coll in pool.collections
if isinstance(coll.backing_memory, BaseromFileBackingMemory)
and coll.backing_memory.name == external_tlut_file
]
assert len(matching_collections) == 1
matching_tlut_resources = [
res
for res in matching_collections[0].resources
if res.offset == external_tlut_offset
]
assert len(matching_tlut_resources) == 1, matching_tlut_resources
assert isinstance(
matching_tlut_resources[0], TextureResourceDesc
), matching_tlut_resources[0]
res.tlut = matching_tlut_resources[0]
raise ResourceHandlerNeedsPass2Exception(res, pass2_callback)
else:
return TextureResourceDesc(
symbol_name, offset, collection, reselem, format, width, height
)

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,14 @@
## `ExternalFile`
For example, if config.yml contains
```yml
assets:
- name: objects/gameplay_keep
xml_path: assets/xml/objects/gameplay_keep_pal.xml
```
then `<ExternalFile OutPath="assets/objects/gameplay_keep/"/>` refers to that gameplay_keep entry.
----------

View file

@ -0,0 +1,171 @@
import dataclasses
import enum
from xml.etree.ElementTree import Element
from .base import (
ResourceDesc,
ResourcesDescCollection,
ResourceHandlerNeedsPass2Exception,
)
@dataclasses.dataclass(eq=False)
class CollisionResourceDesc(ResourceDesc):
pass
handler_Collision = CollisionResourceDesc
@dataclasses.dataclass(eq=False)
class AnimationResourceDesc(ResourceDesc):
pass
handler_Animation = AnimationResourceDesc
@dataclasses.dataclass(eq=False)
class PlayerAnimationResourceDesc(ResourceDesc):
pass
handler_PlayerAnimation = PlayerAnimationResourceDesc
@dataclasses.dataclass(eq=False)
class LegacyAnimationResourceDesc(ResourceDesc):
pass
handler_LegacyAnimation = LegacyAnimationResourceDesc
@dataclasses.dataclass(eq=False)
class CutsceneResourceDesc(ResourceDesc):
pass
handler_Cutscene = CutsceneResourceDesc
@dataclasses.dataclass(eq=False)
class SceneResourceDesc(ResourceDesc):
pass
handler_Scene = SceneResourceDesc
@dataclasses.dataclass(eq=False)
class RoomResourceDesc(ResourceDesc):
pass
def handler_Room(symbol_name, offset, collection, reselem: Element):
res = RoomResourceDesc(symbol_name, offset, collection, reselem)
if reselem.attrib.get("HackMode") == "syotes_room":
res.hack_modes.add("hackmode_syotes_room")
return res
@dataclasses.dataclass(eq=False)
class PlayerAnimationDataResourceDesc(ResourceDesc):
frame_count: int
def handler_PlayerAnimationData(symbol_name, offset, collection, reselem: Element):
frame_count = int(reselem.attrib["FrameCount"])
return PlayerAnimationDataResourceDesc(
symbol_name, offset, collection, reselem, frame_count
)
@dataclasses.dataclass(eq=False)
class PathListResourceDesc(ResourceDesc):
num_paths: int
def handler_PathList(symbol_name, offset, collection, reselem: Element):
num_paths = int(reselem.attrib["NumPaths"])
return PathListResourceDesc(symbol_name, offset, collection, reselem, num_paths)
class SkeletonType(enum.Enum):
NORMAL = enum.auto()
FLEX = enum.auto()
CURVE = enum.auto()
class LimbType(enum.Enum):
STANDARD = enum.auto()
LOD = enum.auto()
SKIN = enum.auto()
CURVE = enum.auto()
LEGACY = enum.auto()
@dataclasses.dataclass(eq=False)
class SkeletonResourceDesc(ResourceDesc):
type: SkeletonType
limb_type: LimbType
def handler_Skeleton(symbol_name, offset, collection, reselem: Element):
skel_type = SkeletonType[reselem.attrib["Type"].upper()]
limb_type = LimbType[reselem.attrib["LimbType"].upper()]
return SkeletonResourceDesc(
symbol_name, offset, collection, reselem, skel_type, limb_type
)
@dataclasses.dataclass(eq=False)
class LimbResourceDesc(ResourceDesc):
limb_type: LimbType
def handler_Limb(symbol_name, offset, collection, reselem: Element):
limb_type = LimbType[reselem.attrib["LimbType"].upper()]
return LimbResourceDesc(symbol_name, offset, collection, reselem, limb_type)
@dataclasses.dataclass(eq=False)
class LimbTableResourceDesc(ResourceDesc):
limb_type: LimbType
count: int
def handler_LimbTable(symbol_name, offset, collection, reselem: Element):
limb_type = LimbType[reselem.attrib["LimbType"].upper()]
count = int(reselem.attrib["Count"])
return LimbTableResourceDesc(
symbol_name, offset, collection, reselem, limb_type, count
)
@dataclasses.dataclass(eq=False)
class CurveAnimationResourceDesc(ResourceDesc):
skeleton: SkeletonResourceDesc
def handler_CurveAnimation(
symbol_name, offset, collection: ResourcesDescCollection, reselem: Element
):
res = CurveAnimationResourceDesc(symbol_name, offset, collection, reselem, None)
skel_offset = int(reselem.attrib["SkelOffset"], 16)
def pass2_callback(pool):
matching_tlut_resources = [
res for res in collection.resources if res.offset == skel_offset
]
assert len(matching_tlut_resources) == 1, (
f"Found {len(matching_tlut_resources)} resources at SkelOffset "
f"0x{skel_offset:X} instead of exactly one"
)
assert isinstance(
matching_tlut_resources[0], SkeletonResourceDesc
), matching_tlut_resources[0]
res.skeleton = matching_tlut_resources[0]
raise ResourceHandlerNeedsPass2Exception(res, pass2_callback)

View file

@ -0,0 +1,10 @@
from . import extract_xml_z64
if __name__ == "__main__":
profile = True
if profile:
import cProfile
cProfile.run("extract_xml_z64.main()", "cprof_assets421.txt")
else:
extract_xml_z64.main()

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,391 @@
from __future__ import annotations
import abc
import io
from typing import TYPE_CHECKING, Callable, Any, Sequence, Union
if TYPE_CHECKING:
from .memorymap import MemoryContext
from . import (
RESOURCE_PARSE_SUCCESS,
Resource,
File,
ResourceParseWaiting,
)
from .repr_c_struct import (
CData,
CData_Value,
CData_Struct,
CData_Array,
)
class CDataExt(CData, abc.ABC):
report_f = None
write_f = None
# TODO not sure what to name this, it doesn't have to be used for pointer reporting,
# more generic "callback" may be better idk yet
def set_report(
self, report_f: Callable[["CDataResource", "MemoryContext", Any], None]
):
self.report_f = report_f
return self
def set_write(
self,
write_f: Callable[
["CDataResource", "MemoryContext", Any, io.TextIOBase, str],
bool,
],
):
"""
write_f should return True if it wrote anything
"""
self.write_f = write_f
return self
def freeze(self):
self.set_report = None
self.set_write = None
return self
@abc.abstractmethod
def write_default(
self,
resource: "CDataResource",
memory_context: "MemoryContext",
v: Any,
f: io.TextIOBase,
line_prefix: str,
) -> bool: ...
def report(
self,
resource: "CDataResource",
memory_context: "MemoryContext",
v: Any,
):
if self.report_f:
try:
self.report_f(resource, memory_context, v)
except:
print("Error reporting data", self, self.report_f, resource, v)
raise
def write(
self,
resource: "CDataResource",
memory_context: "MemoryContext",
v: Any,
f: io.TextIOBase,
line_prefix: str,
) -> bool:
"""
Returns True if something has been written
(typically, False will be returned if this data is struct padding)
"""
if self.write_f:
ret = self.write_f(resource, memory_context, v, f, line_prefix)
# This assert is meant to ensure the function returns a value at all,
# since it's easy to forget to return a value (typically True)
assert isinstance(ret, bool), ("must return a bool", self.write_f)
else:
ret = self.write_default(resource, memory_context, v, f, line_prefix)
assert isinstance(ret, bool), self
return ret
class CDataExt_Value(CData_Value, CDataExt):
is_padding = False
def padding(self):
self.is_padding = True
return self
def freeze(self):
self.padding = None
return super().freeze()
def set_write_str_v(self, str_v: Callable[[Any], str]):
"""Utility wrapper for set_write, writes the value as stringified by str_v."""
def write_f(
resource: "CDataResource",
memory_context: "MemoryContext",
v: Any,
f: io.TextIOBase,
line_prefix: str,
):
f.write(line_prefix)
f.write(str_v(v))
return True
self.set_write(write_f)
return self
def report(self, resource, memory_context, v):
super().report(resource, memory_context, v)
if self.is_padding:
if v != 0:
raise Exception("non-0 padding")
def write_default(self, resource, memory_context, v, f, line_prefix):
if not self.is_padding:
f.write(line_prefix)
f.write(str(v))
return True
else:
return False
CDataExt_Value.s8 = CDataExt_Value("b").freeze()
CDataExt_Value.u8 = CDataExt_Value("B").freeze()
CDataExt_Value.s16 = CDataExt_Value("h").freeze()
CDataExt_Value.u16 = CDataExt_Value("H").freeze()
CDataExt_Value.s32 = CDataExt_Value("i").freeze()
CDataExt_Value.u32 = CDataExt_Value("I").freeze()
CDataExt_Value.f32 = CDataExt_Value("f").freeze()
CDataExt_Value.f64 = CDataExt_Value("d").freeze()
CDataExt_Value.pointer = CDataExt_Value("I").freeze()
CDataExt_Value.pad8 = CDataExt_Value("b").padding().freeze()
CDataExt_Value.pad16 = CDataExt_Value("h").padding().freeze()
CDataExt_Value.pad32 = CDataExt_Value("i").padding().freeze()
INDENT = " " * 4
class CDataExt_Array(CData_Array, CDataExt):
def __init__(self, element_cdata_ext: CDataExt, length: int):
super().__init__(element_cdata_ext, length)
self.element_cdata_ext = element_cdata_ext
def report(self, resource, memory_context, v):
assert isinstance(v, list)
super().report(resource, memory_context, v)
for elem in v:
self.element_cdata_ext.report(resource, memory_context, elem)
def write_default(self, resource, memory_context, v, f, line_prefix):
assert isinstance(v, list)
f.write(line_prefix)
f.write("{\n")
for i, elem in enumerate(v):
ret = self.element_cdata_ext.write(
resource, memory_context, elem, f, line_prefix + INDENT
)
assert ret
f.write(f", // {i}\n")
f.write(line_prefix)
f.write("}")
return True
class CDataExt_Struct(CData_Struct, CDataExt):
def __init__(self, members: Sequence[tuple[str, CDataExt]]):
super().__init__(members)
self.members_ext = members
def report(self, resource, memory_context, v):
assert isinstance(v, dict)
super().report(resource, memory_context, v)
for member_name, member_cdata_ext in self.members_ext:
member_cdata_ext.report(resource, memory_context, v[member_name])
def write_default(self, resource, memory_context, v, f, line_prefix):
assert isinstance(v, dict)
f.write(line_prefix)
f.write("{\n")
for member_name, member_cdata_ext in self.members_ext:
if member_cdata_ext.write(
resource, memory_context, v[member_name], f, line_prefix + INDENT
):
f.write(f", // {member_name}\n")
f.write(line_prefix)
f.write("}")
return True
class CDataResource(Resource):
# Set by child classes
cdata_ext: CDataExt
# Resource implementation
def __init__(self, file: File, range_start: int, name: str):
if not self.can_size_be_unknown:
assert hasattr(self, "cdata_ext"), self.__class__
assert self.cdata_ext is not None
range_end = range_start + self.cdata_ext.size
else:
if hasattr(self, "cdata_ext") and self.cdata_ext is not None:
range_end = range_start + self.cdata_ext.size
else:
range_end = None
super().__init__(file, range_start, range_end, name)
self._is_cdata_processed = False
def try_parse_data(self, memory_context: "MemoryContext"):
if self.can_size_be_unknown:
assert hasattr(self, "cdata_ext") and self.cdata_ext is not None, (
"Subclasses with can_size_be_unknown=True should redefine try_parse_data"
" and call the superclass definition (CDataResource.try_parse_data)"
" only once cdata_ext has been set",
self.__class__,
)
assert (
self.range_end is not None
), "Subclasses with can_size_be_unknown=True should also set range_end once the size is known"
assert hasattr(self, "cdata_ext")
assert self.cdata_ext is not None
# In case the subclass does more involved processing, the self.is_data_parsed
# bool wouldn't necessarily reflect the state of the cdata.
# Use own bool self._is_cdata_processed to remember if cdata has been unpacked and
# reported already.
if not self._is_cdata_processed:
self.cdata_unpacked = self.cdata_ext.unpack_from(
self.file.data, self.range_start
)
self.cdata_ext.report(self, memory_context, self.cdata_unpacked)
self._is_cdata_processed = True
return RESOURCE_PARSE_SUCCESS
def write_extracted(self, memory_context):
with self.extract_to_path.open("w") as f:
self.cdata_ext.write(self, memory_context, self.cdata_unpacked, f, "")
f.write("\n")
class CDataArrayResource(CDataResource):
"""Helper for variable-length array resources.
The length is unknown at object creation, and must be set eventually
with set_length (for example by another resource).
The length being set then allows this resource to be parsed.
For static-length array resources, just use CDataResource.
"""
def __init_subclass__(cls, /, **kwargs):
super().__init_subclass__(can_size_be_unknown=True, **kwargs)
elem_cdata_ext: CDataExt
def __init__(self, file: File, range_start: int, name: str):
super().__init__(file, range_start, name)
self._length: Union[None, int] = None
def set_length(self, length: int):
if self._length is not None:
if self._length != length:
raise Exception(
"length already set and is different", self._length, length
)
assert length > 0
self._length = length
def try_parse_data(self, memory_context: "MemoryContext"):
if self._length is None:
raise ResourceParseWaiting(waiting_for=["self._length"])
assert isinstance(self.elem_cdata_ext, CDataExt), (self.__class__, self)
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)
def get_c_reference(self, resource_offset: int):
return self.symbol_name
def get_c_expression_length(self, resource_offset: int):
if resource_offset == 0:
return f"ARRAY_COUNT({self.symbol_name})"
else:
raise ValueError
class CDataArrayNamedLengthResource(CDataArrayResource):
"""CDataArrayResource and with a macro (define) for its length.
This is useful for arrays that have a length that should be referenced somewhere,
but cannot due to the order the definitions are in.
This writes a macro to the .h for the length, along the symbol declaration,
to be used in the declaration base (! by the subclass, in get_c_declaration_base)
"""
def __init__(self, file: File, range_start: int, name: str):
super().__init__(file, range_start, name)
self.length_name = f"LENGTH_{self.symbol_name}"
def write_c_declaration(self, h: io.TextIOBase):
h.write(f"#define {self.length_name} {self._length}\n")
super().write_c_declaration(h)
cdata_ext_Vec3s = CDataExt_Struct(
(
("x", CDataExt_Value.s16),
("y", CDataExt_Value.s16),
("z", CDataExt_Value.s16),
)
)
# TODO move to z64 ?
class Vec3sArrayResource(CDataResource):
elem_cdata_ext = cdata_ext_Vec3s
def __init__(self, file: File, range_start: int, name: str, length: int):
assert length > 0
self.cdata_ext = CDataExt_Array(self.elem_cdata_ext, length)
super().__init__(file, range_start, name)
def get_c_declaration_base(self):
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()
class S16ArrayResource(CDataResource):
elem_cdata_ext = CDataExt_Value.s16
def __init__(self, file: File, range_start: int, name: str, length: int):
assert length > 0
self.cdata_ext = CDataExt_Array(self.elem_cdata_ext, length)
super().__init__(file, range_start, name)
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_c_expression_length(self, resource_offset: int):
if resource_offset == 0:
return f"ARRAY_COUNT({self.symbol_name})"
else:
raise ValueError()

View file

@ -0,0 +1,408 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Callable, TypeVar, Generic
from . import Resource, File, GetResourceAtResult
# when failing to resolve an address,
# (try to) keep going by creating "fake" files/resources
# or defaulting to poor choices (e.g. raw addresses)
BEST_EFFORT = True
if BEST_EFFORT:
VERBOSE_BEST_EFFORT = False
# RangeMap
RangeMapValueT = TypeVar("RangeMapValueT")
class RangeMap(Generic[RangeMapValueT]):
def __init__(self):
self.values_by_range: dict[tuple[int, int], RangeMapValueT] = dict()
def set(self, range_start: int, range_end: int, value: RangeMapValueT):
assert range_start < range_end
current_values_in_range = self.get_all_in_range(range_start, range_end)
if current_values_in_range:
raise Exception(
"Range already used (at least partially)",
hex(range_start),
hex(range_end),
current_values_in_range,
)
self.values_by_range[(range_start, range_end)] = value
def get_all_by_predicate(self, predicate: Callable[[int, int], bool]):
"""Return all values associated to a range for which the predicate returns True"""
values: dict[tuple[int, int], RangeMapValueT] = dict()
for ((range_start, range_end), value) in self.values_by_range.items():
if predicate(range_start, range_end):
values[(range_start, range_end)] = value
return values
def get_all_in_range(self, range_start: int, range_end: int):
"""Return all values associated to a range intersecting with the given range"""
assert range_start < range_end
def check_intersect(value_range_start, value_range_end):
assert value_range_start < value_range_end
if range_end <= value_range_start:
return False
if value_range_end <= range_start:
return False
return True
return self.get_all_by_predicate(check_intersect)
def get(self, offset) -> RangeMapValueT:
"""Return the value associated to the range the given offset is in,
if any, or raise IndexError"""
def check_belong(value_range_start, value_range_end):
assert value_range_start < value_range_end
return value_range_start <= offset < value_range_end
values = self.get_all_by_predicate(check_belong)
assert len(values) <= 1, values
if values:
return next(iter(values.values()))
else:
raise IndexError(offset)
def copy(self):
"""Returns a shallow copy"""
other = RangeMap()
other.values_by_range = self.values_by_range.copy()
return other
class NoResourceError(Exception):
"""There is no resource at the requested address"""
pass
class UnexpectedResourceTypeError(Exception):
"""There is a resource at the requested address, but of the wrong type"""
pass
class UnmappedAddressError(Exception):
"""Indicates an address could not be resolved because nothing was found for the address."""
pass
AttributeValueT = TypeVar("AttributeValueT")
@dataclass(frozen=True)
class Attribute(Generic[AttributeValueT]):
name: str # Uniquely identifies the attribute
value_type: type[AttributeValueT]
def __eq__(self, other):
if isinstance(other, Attribute):
return self.name == other.name
else:
return False
def __hash__(self):
return hash(self.name)
class Attributes:
c_reference = Attribute("c_reference", str)
c_expression_length = Attribute("c_expression_length", str)
ResourceT = TypeVar("ResourceT", bound="Resource")
class AddressResolveResult:
def __init__(self, original_address: int, file: File, file_offset: int):
self.original_address = original_address
"""Original address that was resolved to this result (for debugging purposes)"""
self.file = file
self.file_offset = file_offset
def get_resource(self, resource_type: type[ResourceT]) -> ResourceT:
result, resource = self.file.get_resource_at(self.file_offset)
if result != GetResourceAtResult.DEFINITIVE:
raise NoResourceError("No definitive resource", result)
assert resource is not None
if resource.range_start != self.file_offset:
raise NoResourceError(
"No resource at (exactly) the requested address", resource
)
if not isinstance(resource, resource_type):
raise UnexpectedResourceTypeError(resource, resource_type)
return resource
def get_attribute(self, attribute: Attribute[AttributeValueT]) -> AttributeValueT:
result, resource = self.file.get_resource_at(self.file_offset)
if result != GetResourceAtResult.DEFINITIVE:
raise Exception("No definitive resource", result)
assert resource is not None
resource_offset = self.file_offset - resource.range_start
if attribute == Attributes.c_reference:
value = resource.get_c_reference(resource_offset)
elif attribute == Attributes.c_expression_length:
value = resource.get_c_expression_length(resource_offset)
else:
raise NotImplementedError(attribute)
if not isinstance(value, attribute.value_type):
raise Exception(
"Resource gave an attribute value of unexpected type",
resource,
attribute,
value,
type(value),
)
return value
def __repr__(self):
return (
"AddressResolveResult("
f"original_address=0x{self.original_address:08X}, "
f"file_name={self.file.name!r}, "
f"file_offset=0x{self.file_offset:X})"
)
def address_resolver(original_address, address_offset):
return AddressResolveResult(original_address)
AddressResolver = Callable[[int, int], AddressResolveResult]
class MemoryMap:
def __init__(self):
self.direct = RangeMap[AddressResolver]()
self.segments: dict[int, RangeMap[AddressResolver]] = {
segment_num: RangeMap[AddressResolver]() for segment_num in range(1, 16)
}
def copy(self):
"""Returns a copy that is independently mutable
(only the mappings are copied, the underlying AddressResolver s are the same)
"""
other = MemoryMap()
other.direct = self.direct.copy()
other.segments = {
segment_num: segment_range_map.copy()
for segment_num, segment_range_map in self.segments.items()
}
return other
def get_segment_num(address: int):
return (address & 0x0F00_0000) >> 24
class MemoryContext:
"""
handles segmented addresses, pointers, external symbols (eg gMtxClear)
maps offsets to data
"""
def __init__(self):
self.memory_map = MemoryMap()
# FIXME HACK
# TODO config for this
from ..extase_oot64.dlist_resources import MtxResource
file_gMtxClear = File("sys_matrix__gMtxClear", size=0x40)
resource_gMtxClear = MtxResource(file_gMtxClear, 0, "gMtxClear")
file_gMtxClear.add_resource(resource_gMtxClear)
self.set_direct_file(0x12DB20, file_gMtxClear)
def copy(self):
other = MemoryContext()
other.memory_map = self.memory_map.copy()
return other
def _direct_address_to_offset(self, address: int):
segment_num = get_segment_num(address)
if segment_num != 0:
raise ValueError("Address is segmented, not direct", hex(address))
# The 0xF000_0000 bits are ignored. Not 100% correct but simplest
offset = address & 0x00FF_FFFF
return offset
def set_direct_file(self, address: int, target_file: File):
direct_file_offset_start = self._direct_address_to_offset(address)
direct_file_offset_end = direct_file_offset_start + target_file.size
def file_direct_address_resolver(original_address, address_offset):
file_offset = address_offset - direct_file_offset_start
return AddressResolveResult(original_address, target_file, file_offset)
# TODO should memory_map members be directly accessed,
# or should MemoryMap have functions (applies elsewhere in MemoryContext)
self.memory_map.direct.set(
direct_file_offset_start,
direct_file_offset_end,
file_direct_address_resolver,
)
def set_segment_file(self, segment_num: int, target_file: File):
if not (1 <= segment_num < 16):
raise ValueError(
"Segment number must be between 1 and 15 (inclusive)", segment_num
)
def file_segment_address_resolver(original_address, address_offset):
file_offset = address_offset
return AddressResolveResult(original_address, target_file, file_offset)
self.memory_map.segments[segment_num].set(
0, 0x0100_0000, file_segment_address_resolver
)
def resolve_direct(self, address: int):
offset = self._direct_address_to_offset(address)
try:
address_resolver = self.memory_map.direct.get(offset)
except IndexError as e:
raise UnmappedAddressError(
"direct address is not mapped", hex(address)
) from e
return address_resolver(address, offset)
def resolve_segmented(self, address: int):
segment_num = get_segment_num(address)
if segment_num == 0:
return self.resolve_direct(address)
else:
assert address & 0xF000_0000 == 0
offset = address & 0x00FF_FFFF
try:
address_resolver = self.memory_map.segments[segment_num].get(offset)
except IndexError as e:
raise UnmappedAddressError(
"segment address is not mapped", hex(address)
) from e
return address_resolver(address, offset)
def report_resource_at_segmented(
self,
reporter: Resource,
address: int,
resource_type: type[ResourceT],
new_resource_pointed_to: Callable[[File, int], ResourceT],
) -> ResourceT:
try:
resolve_result = self.resolve_segmented(address)
except UnmappedAddressError as e:
if BEST_EFFORT:
fake_file = File(f"besteffort_fakefile_{address:08X}", size=0x0100_0000)
fake_resource = new_resource_pointed_to(fake_file, 0)
fake_resource.reporters.add(reporter)
fake_file.add_resource(fake_resource)
if VERBOSE_BEST_EFFORT:
print(
"BEST_EFFORT: ignored error e=",
repr(e),
"on resource report by reporter=",
reporter,
"at address=",
hex(address),
"and created fake_file=",
fake_file,
"and fake_resource=",
fake_resource,
)
fake_file.FAKE_FOR_BEST_EFFORT = True
fake_resource.FAKE_FOR_BEST_EFFORT = True
return fake_resource
raise
try:
resource = resolve_result.get_resource(resource_type)
except NoResourceError:
resource = None
except UnexpectedResourceTypeError:
print("Could not resolve segment address for reporting", resolve_result)
raise
else:
assert resource is not None
if resource is None:
resource = new_resource_pointed_to(
resolve_result.file,
resolve_result.file_offset,
)
resolve_result.file.add_resource(resource)
resource.reporters.add(reporter)
return resource
def mark_resource_buffer_at_segmented(
self,
reporter: Resource,
resource_type: type[Resource],
name: str,
address_start: int,
address_end: int,
):
# Note: this function assumes the whole address_start-address_end range resolves the same way.
# It not being the case would be very weird, but it's not checked here
try:
resolve_result = self.resolve_segmented(address_start)
except UnmappedAddressError as e:
if BEST_EFFORT:
if VERBOSE_BEST_EFFORT:
print(
"BEST_EFFORT: ignored error e=",
repr(e),
"and skipping marking resource buffer for reporter=",
reporter,
"resource_type=",
resource_type,
"address_start=",
hex(address_start),
"address_end=",
hex(address_end),
)
return
raise
file_start = resolve_result.file_offset
file_end = file_start + address_end - address_start
resolve_result.file.mark_resource_buffer(
reporter, resource_type, name, file_start, file_end
)
def get_attribute_at_segmented(
self, address: int, attribute: Attribute[AttributeValueT]
):
return self.resolve_segmented(address).get_attribute(attribute)
def get_c_reference_at_segmented(self, address: int):
try:
return self.get_attribute_at_segmented(address, Attributes.c_reference)
except UnmappedAddressError as e:
if BEST_EFFORT:
if VERBOSE_BEST_EFFORT:
print(
"BEST_EFFORT: ignored error e=",
repr(e),
"and returning raw address =",
f"0x{address:08X}",
)
return f"0x{address:08X}"
raise
def get_c_expression_length_at_segmented(self, address: int):
return self.get_attribute_at_segmented(address, Attributes.c_expression_length)

View file

@ -0,0 +1,223 @@
from __future__ import annotations
import struct
import abc
from typing import Sequence, Any
# NOTE: this system does NOT handle struct alignment/padding automatically, it should be made explicit
# this system voluntarily does not handle variable length arrays. which is not a valid "type" in C anyway (?)
# having variable-sized data is too messy to handle, because it needs a size at some point anyway
# This choice allows the root CData ABC to have a size as a guaranteed attribute
# BOSA = "Byte Order, Size, and Alignment" for the struct module
# Big Endian
STRUCT_BOSA_CHAR = ">"
class CData(abc.ABC):
@abc.abstractmethod
def __init__(self, size: int):
self.size = size
# Unpack
@abc.abstractmethod
def unpack_from(self, data: memoryview, offset: int = 0) -> Any:
...
class CData_Value(CData):
def __init__(self, format_char: str):
assert format_char in set("bBhHiIfd")
self.unpack_struct = struct.Struct(STRUCT_BOSA_CHAR + format_char)
super().__init__(self.unpack_struct.size)
def unpack_from(self, data: memoryview, offset: int = 0):
return self.unpack_struct.unpack_from(data, offset)[0]
CData_Value.s8 = CData_Value("b")
CData_Value.u8 = CData_Value("B")
CData_Value.s16 = CData_Value("h")
CData_Value.u16 = CData_Value("H")
CData_Value.s32 = CData_Value("i")
CData_Value.u32 = CData_Value("I")
CData_Value.f32 = CData_Value("f")
CData_Value.f64 = CData_Value("d")
CData_Value.pointer = CData_Value("I")
class CData_Array(CData):
def __init__(self, element_cdata: CData, length: int):
assert length > 0
self.element_cdata = element_cdata
self.length = length
super().__init__(element_cdata.size * length)
def unpack_from(self, data: memoryview, offset: int = 0):
array_unpacked = []
for i in range(self.length):
unpacked = self.element_cdata.unpack_from(data, offset)
array_unpacked.append(unpacked)
offset += self.element_cdata.size
assert len(array_unpacked) == self.length
return array_unpacked
class CData_Struct(CData):
def __init__(self, members: Sequence[tuple[str, CData]]):
# assert all members have different names
assert len(members) == len(
set(member_name for member_name, member_cdata in members)
), members
self.members = members
super().__init__(
sum(member_cdata.size for member_name, member_cdata in members)
)
if __debug__:
# Check alignment
# This may mess up with CData instances other than CData_Value, Array and Struct
def get_required_alignment(cdata: CData):
if isinstance(cdata, CData_Struct):
return max(
get_required_alignment(cdata_member_cdata)
for cdata_member_name, cdata_member_cdata in cdata.members
)
elif isinstance(cdata, CData_Array):
return get_required_alignment(cdata.element_cdata)
else:
# Assume the alignment requirement corresponds to the size
# (e.g. this is correct for CData_Value)
return cdata.size
# Check alignment of the members of the struct
offset = 0
for member_name, member_cdata in members:
alignment = get_required_alignment(member_cdata)
assert offset % alignment == 0, (member_name, offset, alignment)
offset += member_cdata.size
# Check alignment of the struct size
alignment = get_required_alignment(self)
assert self.size % alignment == 0, (self.size, alignment)
def unpack_from(self, data: memoryview, offset: int = 0):
struct_unpacked = dict()
for member_name, member_cdata in self.members:
member_unpacked = member_cdata.unpack_from(data, offset)
struct_unpacked[member_name] = member_unpacked
offset += member_cdata.size
return struct_unpacked
def try_stuff():
"""
struct {
s8 fun;
// u8 pad;
s16 games;
} array[] = { { 1, 2 }, { 3, 4 } };
u8 varLenArray[] = { 1, 2, 3 };
struct {
u8* ptr;
u16 len;
struct {
s32 secret1;
u32 secret2;
} mySubStruct;
} data = { varLenArray, 3, { 421, 0x01020304 } };
"""
array_bytes = bytes(
[
1,
0,
*(0, 2),
3,
0,
*(0, 4),
]
)
varLenArray_bytes = bytes([1, 2, 3])
data_bytes = bytes(
[
*(0x12, 0x34, 0x56, 0x78),
*(0, 3),
0,
0,
*(0, 0, 421 >> 8, 421 & 0xFF),
*(1, 2, 3, 4),
]
)
arrayElem_CData_Struct = CData_Struct(
(
("fun", CData_Value.s8),
("pad1", CData_Value.s8),
("games", CData_Value.s16),
)
)
array_CData_Array = CData_Array(arrayElem_CData_Struct, 2)
print(array_CData_Array.unpack_from(array_bytes))
mySubStruct_CData_Struct = CData_Struct(
(
("secret1", CData_Value.s32),
("secret2", CData_Value.u32),
)
)
data_CData_Struct = CData_Struct(
(
("ptr", CData_Value.pointer),
("len", CData_Value.u16),
("pad_6", CData_Value.s16),
("mySubStruct", mySubStruct_CData_Struct),
)
)
data_unpacked = data_CData_Struct.unpack_from(data_bytes)
print(data_unpacked)
varLenArray_CData_Array = CData_Array(CData_Value.u8, data_unpacked["len"])
print(varLenArray_CData_Array.unpack_from(varLenArray_bytes))
data_integratedSubStruct_CData_Struct = CData_Struct(
(
("ptr", CData_Value.pointer),
("len", CData_Value.u16),
("pad_6", CData_Value.s16),
(
"mySubStruct",
CData_Struct(
(
("secret1", CData_Value.s32),
("secret2", CData_Value.u32),
)
),
),
)
)
data_unpacked = data_integratedSubStruct_CData_Struct.unpack_from(data_bytes)
print(data_unpacked)
if __name__ == "__main__":
try_stuff()

View file

@ -0,0 +1,194 @@
from __future__ import annotations
import io
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,
)
class AnimationFrameDataResource(CDataResource, can_size_be_unknown=True):
def write_binang(resource, memory_context, v, f: io.TextIOBase, line_prefix):
f.write(line_prefix)
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()
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()
class AnimationResource(CDataResource):
def write_frameData(
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_jointIndices(
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(
(
(
"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}"

View file

@ -0,0 +1,748 @@
from __future__ import annotations
import io
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,
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()
class CollisionPolyListResource(CDataResource):
def write_vtxData(
resource: "CollisionPolyListResource",
memory_context: "MemoryContext",
v,
f: io.TextIOBase,
line_prefix,
):
assert isinstance(v, list)
assert len(v) == 3
vtxData = v
f.write(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(line_prefix)
f.write(INDENT)
f.write(f"{vtxId} | (({flags_str}) << 13), // {i}\n")
f.write(line_prefix)
f.write("}")
return True
def write_normal_component(
resource: "CollisionPolyListResource",
memory_context: "MemoryContext",
v,
f: io.TextIOBase,
line_prefix,
):
assert isinstance(v, int)
nf = v / 0x7FFF
f.write(line_prefix)
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()
class CollisionSurfaceTypeListResource(CDataResource):
def write_data(
resource: "CollisionSurfaceTypeListResource",
memory_context: "MemoryContext",
v,
f: io.TextIOBase,
line_prefix,
):
assert isinstance(v, list)
assert len(v) == 2
f.write(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(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(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(line_prefix)
f.write(INDENT)
f.write(f"), // {i_data}\n")
f.write(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()
class BgCamFuncDataResource(CDataResource):
element_cdata_ext = cdata_ext_Vec3s
def __init__(self, file: File, range_start: int, range_end: int, name: str):
# TODO see VtxArrayResource
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}]"
class CollisionBgCamListResource(CDataResource):
def write_bgCamFuncData(
resource: "CollisionSurfaceTypeListResource",
memory_context: "MemoryContext",
v,
f: io.TextIOBase,
line_prefix,
):
assert isinstance(v, int)
address = v
f.write(line_prefix)
if address != 0:
f.write(memory_context.get_c_reference_at_segmented(address))
else:
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()
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 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,
f: io.TextIOBase,
line_prefix,
):
f.write(line_prefix)
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,
f: io.TextIOBase,
line_prefix,
):
assert isinstance(v, int)
f.write(line_prefix)
f.write(memory_context.get_c_reference_at_segmented(v))
return True
def write_numPolygons(
resource: "CollisionResource",
memory_context: "MemoryContext",
v,
f: io.TextIOBase,
line_prefix,
):
f.write(line_prefix)
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,
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_numWaterBoxes(
resource: "CollisionResource",
memory_context: "MemoryContext",
v,
f: io.TextIOBase,
line_prefix,
):
f.write(line_prefix)
length = resource.cdata_unpacked["numWaterBoxes"]
if length != 0:
f.write(
memory_context.get_c_expression_length_at_segmented(
resource.cdata_unpacked["waterBoxes"]
)
)
else:
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,
f: io.TextIOBase,
line_prefix,
):
assert isinstance(v, int)
f.write(line_prefix)
f.write(memory_context.get_c_reference_at_segmented(v))
return True
def write_bgCamList(
resource: "CollisionResource",
memory_context: "MemoryContext",
v,
f: io.TextIOBase,
line_prefix,
):
assert isinstance(v, int)
f.write(line_prefix)
f.write(memory_context.get_c_reference_at_segmented(v))
return True
def write_waterBoxes(
resource: "CollisionResource",
memory_context: "MemoryContext",
v,
f: io.TextIOBase,
line_prefix,
):
assert isinstance(v, int)
length = resource.cdata_unpacked["numWaterBoxes"]
f.write(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()

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,49 @@
from __future__ import annotations
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:
f.write('#include "z64cutscene_commands.h"\n')
f.write("{\n")
f.write(self.cs_source)
f.write("}\n")
def get_c_declaration_base(self):
return f"CutsceneData {self.symbol_name}[]"

View file

@ -0,0 +1,28 @@
from __future__ import annotations
from ..extase.cdata_resources import (
CDataResource,
CDataExt_Struct,
CDataExt_Value,
)
class PlayerAnimationResource(CDataResource):
cdata_ext = CDataExt_Struct(
(
(
"common",
CDataExt_Struct((("frameCount", CDataExt_Value.s16),)),
),
("pad2", CDataExt_Value.pad16),
("segment", CDataExt_Value.pointer),
)
)
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}"

View file

@ -0,0 +1,341 @@
from __future__ import annotations
import enum
import io
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ..extase.memorymap import MemoryContext
from ..extase import (
Resource,
File,
BinaryBlobResource,
)
from ..extase.cdata_resources import (
CDataResource,
CDataArrayNamedLengthResource,
CDataExt_Struct,
CDataExt_Value,
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.u8),))
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}]"
class RoomShapeNormalResource(CDataResource):
def write_numEntries(
resource, memory_context: "MemoryContext", v, f: io.TextIOBase, line_prefix: str
):
address = resource.cdata_unpacked["entries"]
assert isinstance(address, int)
f.write(line_prefix)
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, f: io.TextIOBase, line_prefix: str
):
assert isinstance(v, int)
address = v
f.write(line_prefix)
f.write(memory_context.get_c_reference_at_segmented(address))
return True
def report_unk_8(resource, memory_context: "MemoryContext", v):
assert isinstance(v, int)
address = v
memory_context.report_resource_at_segmented(
resource,
address,
BinaryBlobResource,
lambda file, offset: BinaryBlobResource(
file, offset, offset + 1, f"{resource.name}_{address:08X}_Unk8"
),
)
def write_unk_8(
resource, memory_context: "MemoryContext", v, f: io.TextIOBase, line_prefix: str
):
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(
(
("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),
),
(
"unk_8",
CDataExt_Value("I").set_report(report_unk_8).set_write(write_unk_8),
), # "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
cdata_ext_RoomShapeImageBase = CDataExt_Struct(
(
("base", cdata_ext_RoomShapeBase),
("amountType", CDataExt_Value.u8),
("pad2", CDataExt_Value.pad16),
("entry", CDataExt_Value.pointer), # RoomShapeDListsEntry*
)
)
# TODO dummy
class RoomShapeImageSingleResource(CDataResource):
cdata_ext = CDataExt_Struct(
(
("base", cdata_ext_RoomShapeImageBase),
("source", CDataExt_Value.pointer),
("unk_0C", CDataExt_Value.u32),
("tlut", CDataExt_Value.pointer),
("width", CDataExt_Value.u16),
("height", CDataExt_Value.u16),
("fmt", CDataExt_Value.u8),
("siz", CDataExt_Value.u8),
("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
# TODO dummy
class RoomShapeImageMultiResource(CDataResource):
cdata_ext = CDataExt_Struct(
(
("base", cdata_ext_RoomShapeImageBase),
("numBackgrounds", CDataExt_Value.u8),
("pad9", CDataExt_Value.pad8),
("padA", CDataExt_Value.pad16),
("backgrounds", CDataExt_Value.pointer), # 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
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}]"
class RoomShapeCullableResource(CDataResource):
def write_numEntries(
resource, memory_context: "MemoryContext", v, f: io.TextIOBase, line_prefix: str
):
address = resource.cdata_unpacked["entries"]
assert isinstance(address, int)
f.write(line_prefix)
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, f: io.TextIOBase, line_prefix: str
):
assert isinstance(v, int)
address = v
f.write(line_prefix)
f.write(memory_context.get_c_reference_at_segmented(address))
return True
def report_unk_8(resource, memory_context: "MemoryContext", v):
assert isinstance(v, int)
address = v
memory_context.report_resource_at_segmented(
resource,
address,
BinaryBlobResource,
lambda file, offset: BinaryBlobResource(
file, offset, offset + 1, f"{resource.name}_{address:08X}_Unk8"
),
)
def write_unk_8(
resource, memory_context: "MemoryContext", v, f: io.TextIOBase, line_prefix: str
):
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(
(
("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),
),
(
"unk_8",
CDataExt_Value("I").set_report(report_unk_8).set_write(write_unk_8),
), # "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

View file

@ -0,0 +1,692 @@
from __future__ import annotations
import enum
import struct
import io
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,
)
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:
# TODO guess length, no other way I think
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:
# TODO guess length, no other way I think
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
# TODO hack until I have a clearer view of stuff once all cmds are loosely implemented
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:
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"{naviQuestHintFileId}, {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"{skyboxId}, {skyboxConfig}, {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")
f.write("}\n")
def get_c_reference(self, resource_offset: int):
if resource_offset == 0:
return self.symbol_name
else:
raise ValueError
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, f: io.TextIOBase, line_prefix: str
):
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
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}[]"

View file

@ -0,0 +1,426 @@
from __future__ import annotations
import io
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ..extase.memorymap import MemoryContext
from ..extase import (
RESOURCE_PARSE_SUCCESS,
ResourceParseWaiting,
ResourceParseInProgress,
)
from ..extase.cdata_resources import (
CDataArrayResource,
CDataArrayNamedLengthResource,
CDataExt_Struct,
CDataExt_Array,
CDataExt_Value,
cdata_ext_Vec3s,
INDENT,
Vec3sArrayResource,
)
from .. import oot64_data
VERBOSE_SPAWN_LIST_LENGTH_GUESSING = False
def fmt_hex_s(v: int, nibbles: int = 0):
"""Format v to 0x-prefixed uppercase hexadecimal, using (at least) the specified amount of nibbles.
Meant for signed values (_s suffix),
adds a space in place of where the - sign would be for positive values.
Note compared to this,
- f"{v:#X}" would produce an uppercase 0X (1 -> 0X1)
- f"0x{v:X}" doesn't work with negative values (-1 -> 0x-1)
"""
v_str = f"{v:0{nibbles}X}"
if v < 0:
v_str = v_str.removeprefix("-")
return f"-0x{v_str}"
else:
return f" 0x{v_str}"
def fmt_hex_u(v: int, nibbles: int = 0):
"""Format v to 0x-prefixed uppercase hexadecimal, using (at least) the specified amount of nibbles.
Meant for unsigned values (_u suffix),
but won't fail for negative values.
See: fmt_hex_s
"""
v_str = f"{v:0{nibbles}X}"
if v < 0:
# Also handle v being negative just in case,
# it will only mean the output isn't aligned as expected
v_str = v_str.removeprefix("-")
return f"-0x{v_str}"
else:
return f"0x{v_str}"
class ActorEntryListResource(CDataArrayNamedLengthResource):
def write_elem(resource, memory_context, v, f: io.TextIOBase, line_prefix: str):
assert isinstance(v, dict)
f.write(line_prefix)
f.write("{\n")
f.write(line_prefix + INDENT)
f.write(oot64_data.get_actor_id_name(v["id"]))
f.write(",\n")
f.write(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(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(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(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}]"
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 write_RomFile(resource, memory_context, v, f: io.TextIOBase, line_prefix: str):
assert isinstance(v, dict)
vromStart = v["vromStart"]
vromEnd = v["vromEnd"]
rom_file_name = oot64_data.get_dmadata_table_rom_file_name_from_vrom(
vromStart, vromEnd
)
f.write(line_prefix)
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}]"
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}[]"
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}[]"
class EnvLightSettingsListResource(CDataArrayNamedLengthResource):
# TODO formatting
elem_cdata_ext = CDataExt_Struct(
(
("ambientColor", CDataExt_Array(CDataExt_Value.u8, 3)),
("light1Dir", CDataExt_Array(CDataExt_Value.s8, 3)),
("light1Color", CDataExt_Array(CDataExt_Value.u8, 3)),
("light2Dir", CDataExt_Array(CDataExt_Value.s8, 3)),
("light2Color", CDataExt_Array(CDataExt_Value.u8, 3)),
("fogColor", CDataExt_Array(CDataExt_Value.u8, 3)),
("blendRateAndFogNear", CDataExt_Value.s16),
("zFar", CDataExt_Value.s16),
)
)
def get_c_declaration_base(self):
return f"EnvLightSettings {self.symbol_name}[{self.length_name}]"
class TransitionActorEntryListResource(CDataArrayNamedLengthResource):
def write_elem(resource, memory_context, v, f: io.TextIOBase, line_prefix: str):
assert isinstance(v, dict)
f.write(line_prefix)
f.write("{\n")
f.write(line_prefix + INDENT)
f.write("{\n")
f.write(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(line_prefix + 2 * INDENT)
f.write("{ ")
f.write(f"{room}, {bgCamIndex}")
f.write(" },\n")
f.write(line_prefix + INDENT)
f.write("}, // sides\n")
f.write(line_prefix + INDENT)
f.write(oot64_data.get_actor_id_name(v["id"]))
f.write(",\n")
f.write(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(line_prefix + INDENT)
f.write(fmt_hex_s(v["rotY"], 4))
f.write(", // rotY\n")
f.write(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(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}]"
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, f: io.TextIOBase, line_prefix: str
):
assert isinstance(v, dict)
count = v["count"]
assert isinstance(count, int)
points = v["points"]
assert isinstance(points, int)
f.write(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}[]"

View file

@ -0,0 +1,355 @@
from __future__ import annotations
import io
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,
)
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()
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()
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()
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, 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 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, 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 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, 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(
(
(
"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()
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()
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, 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_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()
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, 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(
(
(
"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()

View file

@ -0,0 +1,188 @@
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,
CDataExt_Value,
CDataExt_Struct,
CDataExt_Array,
cdata_ext_Vec3s,
)
from . import dlist_resources
class StandardLimbResource(CDataResource):
cdata_ext = CDataExt_Struct(
(
("jointPos", cdata_ext_Vec3s),
("child", CDataExt_Value.u8),
("sibling", CDataExt_Value.u8),
("dList", dlist_resources.cdata_ext_gfx_segmented),
)
)
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()
class LimbsArrayResource(CDataResource, can_size_be_unknown=True):
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(
file, offset, f"{resource.name}_{address:08X}"
),
)
def write_limb_element(
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_Value("I")
.set_report(report_limb_element)
.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()
class SkeletonNormalResource(CDataResource):
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(
file,
offset,
f"{resource.name}_{address:08X}_Limbs",
),
)
resource_limbs.length = resource.get_skeleton_header_cdata_unpacked()[
"limbCount"
]
def write_segment(
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_limbCount(
resource, memory_context: "MemoryContext", v, f: io.TextIOBase, line_prefix
):
f.write(line_prefix)
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}"
class SkeletonFlexResource(CDataResource):
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),
)
)
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}"

View file

@ -0,0 +1,334 @@
from pathlib import Path
import functools
from pprint import pprint
from ..descriptor.base import (
BaseromFileBackingMemory,
get_resources_desc,
ResourceDesc,
ResourcesDescCollection,
ResourcesDescCollectionsPool,
SegmentStartAddress,
VRAMStartAddress,
)
from .extase import File, Resource
from .extase.memorymap import MemoryContext
from . import z64_resource_handlers
#
# main
#
VERBOSE1 = False
VERBOSE2 = True
# "options"
RM_SOURCE = True
WRITE_SOURCE = True
RM_EXTRACT = True
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
@functools.lru_cache(maxsize=200)
def get_baserom_file_data(baserom_file_name: str):
return memoryview((BASEROM_PATH / baserom_file_name).read_bytes())
def create_file_resources(rescoll: ResourcesDescCollection, file: File):
"""Collect resources described by the collection into the file object.
Does not do any processing of the data itself: no parsing, no discovering more resources.
"""
file_resources_by_desc: dict[ResourceDesc, Resource] = dict()
list_ResourceNeedsPostProcessWithPoolResourcesException: list[
z64_resource_handlers.ResourceNeedsPostProcessWithPoolResourcesException
] = []
for resource_desc in rescoll.resources:
try:
resource = z64_resource_handlers.get_resource_from_desc(file, resource_desc)
except (
z64_resource_handlers.ResourceNeedsPostProcessWithPoolResourcesException
) as e:
resource = e.resource
list_ResourceNeedsPostProcessWithPoolResourcesException.append(e)
file.add_resource(resource)
file_resources_by_desc[resource_desc] = resource
if VERBOSE1:
print(file)
print(file.name, file._resources)
# Check if described resources overlap
file.sort_resources()
file.check_overlapping_resources()
return (
file_resources_by_desc,
list_ResourceNeedsPostProcessWithPoolResourcesException,
)
def process_pool(pool_desc: ResourcesDescCollectionsPool):
if VERBOSE2:
print("> process_pool")
print(
"pool_desc collections:",
[str(rescoll.out_path) for rescoll in pool_desc.collections],
)
file_by_rescoll: dict[ResourcesDescCollection, File] = dict()
# 1) Create File and Resource objects
pool_resources_by_desc: dict[ResourceDesc, Resource] = dict()
list_all_ResourceNeedsPostProcessWithPoolResourcesException: list[
z64_resource_handlers.ResourceNeedsPostProcessWithPoolResourcesException
] = []
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)
if rescoll.backing_memory.range is not None:
range_start, range_end = rescoll.backing_memory.range
data = data[range_start:range_end]
# TODO File.name
file = File(rescoll.backing_memory.name, data=data)
file_by_rescoll[rescoll] = file
(
file_resources_by_desc,
list_ResourceNeedsPostProcessWithPoolResourcesException,
) = create_file_resources(rescoll, file)
pool_resources_by_desc |= file_resources_by_desc
list_all_ResourceNeedsPostProcessWithPoolResourcesException.extend(
list_ResourceNeedsPostProcessWithPoolResourcesException
)
for e in list_all_ResourceNeedsPostProcessWithPoolResourcesException:
e.callback(pool_resources_by_desc)
# 2) Build a MemoryContext for each File
memctx_base = MemoryContext()
files_by_segment: dict[int, list[File]] = dict()
for rescoll, file in file_by_rescoll.items():
if rescoll.start_address is None:
pass
elif isinstance(rescoll.start_address, SegmentStartAddress):
files_by_segment.setdefault(rescoll.start_address.segment, []).append(file)
elif isinstance(rescoll.start_address, VRAMStartAddress):
memctx_base.set_direct_file(rescoll.start_address.vram, file)
else:
raise NotImplementedError(rescoll.start_address)
disputed_segments = []
for segment, files in files_by_segment.items():
if len(files) == 1:
memctx_base.set_segment_file(segment, files[0])
else:
disputed_segments.append(segment)
if VERBOSE2:
print(f"{disputed_segments=}")
memctx_by_file: dict[File, MemoryContext] = dict()
for rescoll, file in file_by_rescoll.items():
file_memctx = memctx_base.copy()
if (
isinstance(rescoll.start_address, SegmentStartAddress)
and rescoll.start_address.segment in disputed_segments
):
if VERBOSE2:
print(
"disputed segment",
rescoll.start_address.segment,
"set to own file for file's memctx",
file.name,
)
file_memctx.set_segment_file(rescoll.start_address.segment, file)
memctx_by_file[file] = file_memctx
# 3) parse: iteratively discover and parse data
# (discover = add resources, parse = make friendlier than binary)
def parse_all_files():
while True:
any_progress = False
for file, file_memctx in memctx_by_file.items():
if VERBOSE2:
print(file.name, ".try_parse_resources_data()")
if file.try_parse_resources_data(file_memctx):
any_progress = True
if not any_progress:
break
for file in memctx_by_file.keys():
file.check_non_parsed_resources()
if VERBOSE2:
print("parse_all_files() ...")
parse_all_files()
for file in memctx_by_file.keys():
file.commit_resource_buffers()
if VERBOSE2:
print("parse new resources (resource buffers) ...")
parse_all_files() # parse new resources (resource buffers)
for file in memctx_by_file.keys():
file.sort_resources()
file.check_overlapping_resources()
# 4) add dummy (binary) resources for the unaccounted gaps
if VERBOSE2:
print("unaccounted...")
for file in memctx_by_file.keys():
file.add_unaccounted_resources(I_D_OMEGALUL=I_D_OMEGALUL)
parse_all_files() # FIXME this is to set is_data_parsed=True on binary blob unaccounteds, handle better
for file in memctx_by_file.keys():
file.sort_resources()
assert not file.get_overlapping_resources()
# At this point all files are completely mapped with resources
# 5)
# TODO this looks jank
for rescoll, file in file_by_rescoll.items():
file.set_source_path(Path("assets") / rescoll.out_path)
(Path("assets") / rescoll.out_path).mkdir(parents=True, exist_ok=True)
file.set_resources_paths(
EXTRACTED_PATH,
BUILD_PATH,
Path("assets") / rescoll.out_path,
)
(EXTRACTED_PATH / rescoll.out_path).mkdir(parents=True, exist_ok=True)
for file, file_memctx in memctx_by_file.items():
# write to EXTRACTED_PATH
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.write_source()
def process_pool_wrapped(pd):
try:
process_pool(pd)
except Exception as e:
import traceback
import sys
# Some exceptions can't be pickled for passing back to the main process
# so print them now as well
traceback.print_exc(sys.stdout)
raise Exception() from e
def main():
import argparse
# TODO cleanup, command-line argument for version
from tools import version_config
parser = argparse.ArgumentParser()
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)
args = parser.parse_args()
vc = version_config.load_version_config(args.oot_version)
pools_desc = get_resources_desc(vc)
z64_resource_handlers.register_resource_handlers()
# TODO single pool extract cli arg
# TODO extract only when a pool xml was modified since last extract
try:
if args.single is not None:
any_do_process_pool = False
for pool_desc in pools_desc:
do_process_pool = False
for coll in pool_desc.collections:
if isinstance(coll.backing_memory, BaseromFileBackingMemory):
if coll.backing_memory.name == args.single:
do_process_pool = True
if do_process_pool:
process_pool(pool_desc)
any_do_process_pool = True
if any_do_process_pool:
print("OK")
else:
print("Not found:", args.single)
elif not args.use_multiprocessing: # everything on one process
for pool_desc in pools_desc:
process_pool(pool_desc)
print("all OK!!!")
else: # multiprocessing
import multiprocessing
with multiprocessing.Pool() as pool:
pool.map(process_pool_wrapped, pools_desc)
print("all OK!?")
except Exception as e:
import traceback
import sys
traceback.print_exc(file=sys.stdout)
try:
import rich.pretty
except:
print("Install rich for prettier output (`pip install rich`)")
else:
# TODO implement more __rich_repr__
if e.__class__ in (Exception, AssertionError, NotImplementedError):
print("rich.pretty.pprint(e.args):")
rich.pretty.pprint(e.args, indent_guides=False)
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 ~~<Symbol>~~ a <File Extract="False"
# extract_xml(Path("overlays/ovl_Magic_Wind")) # SkelCurve
# extract_xml(Path("objects/object_link_child")) # The only xml with <Mtx>
# 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())

View file

@ -0,0 +1,62 @@
ROOT := ../../../../
DATA_FILES := actor_ids.py object_ids.py entrance_table_mini.py scene_table_mini.py dmadata_table.py
default:
@echo 'Run `make all` or with the appropriate target to (re)build data files'
all: $(DATA_FILES)
distclean:
$(RM) $(DATA_FILES)
.PHONY: default all distclean
actor_ids.py: $(ROOT)/include/tables/actor_table.h
echo '# This file was generated from $<' > $@
echo >> $@
echo 'DATA = (' >> $@
cpp -P \
-D'DEFINE_ACTOR(_0,enumValue,_2,_3)=#enumValue,' \
-D'DEFINE_ACTOR_UNSET(enumValue)=#enumValue,' \
-D'DEFINE_ACTOR_INTERNAL=DEFINE_ACTOR' \
$< >> $@
echo ')' >> $@
object_ids.py: $(ROOT)/include/tables/object_table.h
echo '# This file was generated from $<' > $@
echo >> $@
echo 'DATA = (' >> $@
cpp -P \
-D'DEFINE_OBJECT(_0,enumValue)=#enumValue,' \
-D'DEFINE_OBJECT_UNSET(enumValue)=#enumValue,' \
-D'DEFINE_OBJECT_NULL(_0,enumValue)=#enumValue,' \
$< >> $@
echo ')' >> $@
entrance_table_mini.py: $(ROOT)/include/tables/entrance_table.h
echo '# This file was generated from $<' > $@
echo >> $@
echo 'DATA = (' >> $@
cpp -P \
-D'DEFINE_ENTRANCE(enumValue, sceneId, spawn, _3, _4, _5, _6)=(#enumValue, #sceneId, spawn),' \
$< >> $@
echo ')' >> $@
scene_table_mini.py: $(ROOT)/include/tables/scene_table.h
echo '# This file was generated from $<' > $@
echo >> $@
echo 'DATA = (' >> $@
cpp -P \
-D'DEFINE_SCENE(name, _1, enumValue, _3, _4, _5)=(#name, #enumValue),' \
$< >> $@
echo ')' >> $@
dmadata_table.py: $(ROOT)/include/tables/dmadata_table_mqdbg.h
echo '# This file was generated from $<' > $@
echo >> $@
echo 'DATA = (' >> $@
cpp -P \
-D'DEFINE_DMA_ENTRY(name, _1)=#name,' \
$< >> $@
echo ')' >> $@

View file

@ -0,0 +1,137 @@
from __future__ import annotations
from typing import Sequence
I_D_OMEGALUL = True
from . import actor_ids
def get_actor_id_name(actor_id: int) -> str:
return actor_ids.DATA[actor_id]
from . import object_ids
def get_object_id_name(object_id: int) -> str:
return object_ids.DATA[object_id]
from . import entrance_table_mini
from . import entrance_ids_special
def get_entrance_id_name(entrance_id: int) -> str:
if entrance_id in entrance_ids_special.DATA:
return entrance_ids_special.DATA[entrance_id]
return entrance_table_mini.DATA[entrance_id][0]
def get_entrance_scene_id_name(entrance_id: int) -> str:
return entrance_table_mini.DATA[entrance_id][1]
def get_entrance_spawn(entrance_id: int) -> int:
return entrance_table_mini.DATA[entrance_id][2]
entrance_ids_by_scene_id_name: dict[str, list[int]] = dict()
for (
entrance_id,
(
entrance_id_name,
entrance_scene_id_name,
entrance_spawn,
),
) in enumerate(entrance_table_mini.DATA):
entrance_ids_by_scene_id_name.setdefault(entrance_scene_id_name, []).append(
entrance_id
)
def get_entrance_ids_from_scene_id_name(scene_id_name: str) -> Sequence[int]:
return entrance_ids_by_scene_id_name[scene_id_name]
from . import scene_table_mini
def get_scene_rom_file_name(scene_id: int) -> str:
return scene_table_mini.DATA[scene_id][0]
def get_scene_id_name(scene_id: int) -> str:
return scene_table_mini.DATA[scene_id][1]
scene_id_by_rom_file_name = {
rom_file_name: scene_id
for scene_id, (rom_file_name, scene_id_name) in enumerate(scene_table_mini.DATA)
}
def get_scene_id_from_rom_file_name(rom_file_name: str) -> int:
return scene_id_by_rom_file_name[rom_file_name]
from . import dmadata_table
def get_dmadata_table_rom_file_names() -> Sequence[str]:
return dmadata_table.DATA
dmadata = None
def get_dmadata_table_rom_file_name_from_vrom(vromStart: int, vromEnd: int) -> str:
global dmadata
if dmadata is None:
import struct
# FIXME handle multiversion
with open("extracted/gc-eu-mq-dbg/baserom/dmadata", "rb") as f:
bytes = f.read()
dmadata = list(struct.iter_unpack(">IIII", bytes))
for i, (e_vromStart, e_vromEnd, e_romStart, e_romEnd) in enumerate(dmadata):
if e_vromStart == vromStart and e_vromEnd == vromEnd:
break
if i >= len(dmadata_table.DATA):
raise ValueError("Can't find rom file", hex(vromStart), hex(vromEnd))
return dmadata_table.DATA[i]
from . import audio_ids
def get_sequence_id_name(sequence_id: int) -> str:
return audio_ids.SEQ_IDS[sequence_id]
def get_nature_ambience_id_name(nature_ambience_id: int) -> str:
return audio_ids.NATURE_AMBIENCE_IDS[nature_ambience_id]
from . import misc_ids
def get_scene_cam_type_name(scene_cam_type: int) -> str:
return misc_ids.SCENE_CAM_TYPES[scene_cam_type]
def get_room_behavior_type1_name(behavior_type1: int) -> str:
return misc_ids.ROOM_BEHAVIOR_TYPE1_NAMES[behavior_type1]
def get_room_behavior_type2_name(behavior_type2: int) -> str:
return misc_ids.ROOM_BEHAVIOR_TYPE2_NAMES[behavior_type2]
def get_lens_mode_name(lens_mode: int) -> str:
return misc_ids.LENS_MODES[lens_mode]
def get_camera_setting_type_name(camera_setting_type: int) -> str:
return misc_ids.CAMERA_SETTING_TYPES[camera_setting_type]

View file

@ -0,0 +1,475 @@
# This file was generated from ../../../..//include/tables/actor_table.h
DATA = (
"ACTOR_PLAYER",
"ACTOR_UNSET_1",
"ACTOR_EN_TEST",
"ACTOR_UNSET_3",
"ACTOR_EN_GIRLA",
"ACTOR_UNSET_5",
"ACTOR_UNSET_6",
"ACTOR_EN_PART",
"ACTOR_EN_LIGHT",
"ACTOR_EN_DOOR",
"ACTOR_EN_BOX",
"ACTOR_BG_DY_YOSEIZO",
"ACTOR_BG_HIDAN_FIREWALL",
"ACTOR_EN_POH",
"ACTOR_EN_OKUTA",
"ACTOR_BG_YDAN_SP",
"ACTOR_EN_BOM",
"ACTOR_EN_WALLMAS",
"ACTOR_EN_DODONGO",
"ACTOR_EN_FIREFLY",
"ACTOR_EN_HORSE",
"ACTOR_EN_ITEM00",
"ACTOR_EN_ARROW",
"ACTOR_UNSET_17",
"ACTOR_EN_ELF",
"ACTOR_EN_NIW",
"ACTOR_UNSET_1A",
"ACTOR_EN_TITE",
"ACTOR_EN_REEBA",
"ACTOR_EN_PEEHAT",
"ACTOR_EN_BUTTE",
"ACTOR_UNSET_1F",
"ACTOR_EN_INSECT",
"ACTOR_EN_FISH",
"ACTOR_UNSET_22",
"ACTOR_EN_HOLL",
"ACTOR_EN_SCENE_CHANGE",
"ACTOR_EN_ZF",
"ACTOR_EN_HATA",
"ACTOR_BOSS_DODONGO",
"ACTOR_BOSS_GOMA",
"ACTOR_EN_ZL1",
"ACTOR_EN_VIEWER",
"ACTOR_EN_GOMA",
"ACTOR_BG_PUSHBOX",
"ACTOR_EN_BUBBLE",
"ACTOR_DOOR_SHUTTER",
"ACTOR_EN_DODOJR",
"ACTOR_EN_BDFIRE",
"ACTOR_UNSET_31",
"ACTOR_EN_BOOM",
"ACTOR_EN_TORCH2",
"ACTOR_EN_BILI",
"ACTOR_EN_TP",
"ACTOR_UNSET_36",
"ACTOR_EN_ST",
"ACTOR_EN_BW",
"ACTOR_EN_A_OBJ",
"ACTOR_EN_EIYER",
"ACTOR_EN_RIVER_SOUND",
"ACTOR_EN_HORSE_NORMAL",
"ACTOR_EN_OSSAN",
"ACTOR_BG_TREEMOUTH",
"ACTOR_BG_DODOAGO",
"ACTOR_BG_HIDAN_DALM",
"ACTOR_BG_HIDAN_HROCK",
"ACTOR_EN_HORSE_GANON",
"ACTOR_BG_HIDAN_ROCK",
"ACTOR_BG_HIDAN_RSEKIZOU",
"ACTOR_BG_HIDAN_SEKIZOU",
"ACTOR_BG_HIDAN_SIMA",
"ACTOR_BG_HIDAN_SYOKU",
"ACTOR_EN_XC",
"ACTOR_BG_HIDAN_CURTAIN",
"ACTOR_BG_SPOT00_HANEBASI",
"ACTOR_EN_MB",
"ACTOR_EN_BOMBF",
"ACTOR_EN_ZL2",
"ACTOR_BG_HIDAN_FSLIFT",
"ACTOR_EN_OE2",
"ACTOR_BG_YDAN_HASI",
"ACTOR_BG_YDAN_MARUTA",
"ACTOR_BOSS_GANONDROF",
"ACTOR_UNSET_53",
"ACTOR_EN_AM",
"ACTOR_EN_DEKUBABA",
"ACTOR_EN_M_FIRE1",
"ACTOR_EN_M_THUNDER",
"ACTOR_BG_DDAN_JD",
"ACTOR_BG_BREAKWALL",
"ACTOR_EN_JJ",
"ACTOR_EN_HORSE_ZELDA",
"ACTOR_BG_DDAN_KD",
"ACTOR_DOOR_WARP1",
"ACTOR_OBJ_SYOKUDAI",
"ACTOR_ITEM_B_HEART",
"ACTOR_EN_DEKUNUTS",
"ACTOR_BG_MENKURI_KAITEN",
"ACTOR_BG_MENKURI_EYE",
"ACTOR_EN_VALI",
"ACTOR_BG_MIZU_MOVEBG",
"ACTOR_BG_MIZU_WATER",
"ACTOR_ARMS_HOOK",
"ACTOR_EN_FHG",
"ACTOR_BG_MORI_HINERI",
"ACTOR_EN_BB",
"ACTOR_BG_TOKI_HIKARI",
"ACTOR_EN_YUKABYUN",
"ACTOR_BG_TOKI_SWD",
"ACTOR_EN_FHG_FIRE",
"ACTOR_BG_MJIN",
"ACTOR_BG_HIDAN_KOUSI",
"ACTOR_DOOR_TOKI",
"ACTOR_BG_HIDAN_HAMSTEP",
"ACTOR_EN_BIRD",
"ACTOR_UNSET_73",
"ACTOR_UNSET_74",
"ACTOR_UNSET_75",
"ACTOR_UNSET_76",
"ACTOR_EN_WOOD02",
"ACTOR_UNSET_78",
"ACTOR_UNSET_79",
"ACTOR_UNSET_7A",
"ACTOR_UNSET_7B",
"ACTOR_EN_LIGHTBOX",
"ACTOR_EN_PU_BOX",
"ACTOR_UNSET_7E",
"ACTOR_UNSET_7F",
"ACTOR_EN_TRAP",
"ACTOR_EN_AROW_TRAP",
"ACTOR_EN_VASE",
"ACTOR_UNSET_83",
"ACTOR_EN_TA",
"ACTOR_EN_TK",
"ACTOR_BG_MORI_BIGST",
"ACTOR_BG_MORI_ELEVATOR",
"ACTOR_BG_MORI_KAITENKABE",
"ACTOR_BG_MORI_RAKKATENJO",
"ACTOR_EN_VM",
"ACTOR_DEMO_EFFECT",
"ACTOR_DEMO_KANKYO",
"ACTOR_BG_HIDAN_FWBIG",
"ACTOR_EN_FLOORMAS",
"ACTOR_EN_HEISHI1",
"ACTOR_EN_RD",
"ACTOR_EN_PO_SISTERS",
"ACTOR_BG_HEAVY_BLOCK",
"ACTOR_BG_PO_EVENT",
"ACTOR_OBJ_MURE",
"ACTOR_EN_SW",
"ACTOR_BOSS_FD",
"ACTOR_OBJECT_KANKYO",
"ACTOR_EN_DU",
"ACTOR_EN_FD",
"ACTOR_EN_HORSE_LINK_CHILD",
"ACTOR_DOOR_ANA",
"ACTOR_BG_SPOT02_OBJECTS",
"ACTOR_BG_HAKA",
"ACTOR_MAGIC_WIND",
"ACTOR_MAGIC_FIRE",
"ACTOR_UNSET_A0",
"ACTOR_EN_RU1",
"ACTOR_BOSS_FD2",
"ACTOR_EN_FD_FIRE",
"ACTOR_EN_DH",
"ACTOR_EN_DHA",
"ACTOR_EN_RL",
"ACTOR_EN_ENCOUNT1",
"ACTOR_DEMO_DU",
"ACTOR_DEMO_IM",
"ACTOR_DEMO_TRE_LGT",
"ACTOR_EN_FW",
"ACTOR_BG_VB_SIMA",
"ACTOR_EN_VB_BALL",
"ACTOR_BG_HAKA_MEGANE",
"ACTOR_BG_HAKA_MEGANEBG",
"ACTOR_BG_HAKA_SHIP",
"ACTOR_BG_HAKA_SGAMI",
"ACTOR_UNSET_B2",
"ACTOR_EN_HEISHI2",
"ACTOR_EN_ENCOUNT2",
"ACTOR_EN_FIRE_ROCK",
"ACTOR_EN_BROB",
"ACTOR_MIR_RAY",
"ACTOR_BG_SPOT09_OBJ",
"ACTOR_BG_SPOT18_OBJ",
"ACTOR_BOSS_VA",
"ACTOR_BG_HAKA_TUBO",
"ACTOR_BG_HAKA_TRAP",
"ACTOR_BG_HAKA_HUTA",
"ACTOR_BG_HAKA_ZOU",
"ACTOR_BG_SPOT17_FUNEN",
"ACTOR_EN_SYATEKI_ITM",
"ACTOR_EN_SYATEKI_MAN",
"ACTOR_EN_TANA",
"ACTOR_EN_NB",
"ACTOR_BOSS_MO",
"ACTOR_EN_SB",
"ACTOR_EN_BIGOKUTA",
"ACTOR_EN_KAREBABA",
"ACTOR_BG_BDAN_OBJECTS",
"ACTOR_DEMO_SA",
"ACTOR_DEMO_GO",
"ACTOR_EN_IN",
"ACTOR_EN_TR",
"ACTOR_BG_SPOT16_BOMBSTONE",
"ACTOR_UNSET_CE",
"ACTOR_BG_HIDAN_KOWARERUKABE",
"ACTOR_BG_BOMBWALL",
"ACTOR_BG_SPOT08_ICEBLOCK",
"ACTOR_EN_RU2",
"ACTOR_OBJ_DEKUJR",
"ACTOR_BG_MIZU_UZU",
"ACTOR_BG_SPOT06_OBJECTS",
"ACTOR_BG_ICE_OBJECTS",
"ACTOR_BG_HAKA_WATER",
"ACTOR_UNSET_D8",
"ACTOR_EN_MA2",
"ACTOR_EN_BOM_CHU",
"ACTOR_EN_HORSE_GAME_CHECK",
"ACTOR_BOSS_TW",
"ACTOR_EN_RR",
"ACTOR_EN_BA",
"ACTOR_EN_BX",
"ACTOR_EN_ANUBICE",
"ACTOR_EN_ANUBICE_FIRE",
"ACTOR_BG_MORI_HASHIGO",
"ACTOR_BG_MORI_HASHIRA4",
"ACTOR_BG_MORI_IDOMIZU",
"ACTOR_BG_SPOT16_DOUGHNUT",
"ACTOR_BG_BDAN_SWITCH",
"ACTOR_EN_MA1",
"ACTOR_BOSS_GANON",
"ACTOR_BOSS_SST",
"ACTOR_UNSET_EA",
"ACTOR_UNSET_EB",
"ACTOR_EN_NY",
"ACTOR_EN_FR",
"ACTOR_ITEM_SHIELD",
"ACTOR_BG_ICE_SHELTER",
"ACTOR_EN_ICE_HONO",
"ACTOR_ITEM_OCARINA",
"ACTOR_UNSET_F2",
"ACTOR_UNSET_F3",
"ACTOR_MAGIC_DARK",
"ACTOR_DEMO_6K",
"ACTOR_EN_ANUBICE_TAG",
"ACTOR_BG_HAKA_GATE",
"ACTOR_BG_SPOT15_SAKU",
"ACTOR_BG_JYA_GOROIWA",
"ACTOR_BG_JYA_ZURERUKABE",
"ACTOR_UNSET_FB",
"ACTOR_BG_JYA_COBRA",
"ACTOR_BG_JYA_KANAAMI",
"ACTOR_FISHING",
"ACTOR_OBJ_OSHIHIKI",
"ACTOR_BG_GATE_SHUTTER",
"ACTOR_EFF_DUST",
"ACTOR_BG_SPOT01_FUSYA",
"ACTOR_BG_SPOT01_IDOHASHIRA",
"ACTOR_BG_SPOT01_IDOMIZU",
"ACTOR_BG_PO_SYOKUDAI",
"ACTOR_BG_GANON_OTYUKA",
"ACTOR_BG_SPOT15_RRBOX",
"ACTOR_BG_UMAJUMP",
"ACTOR_UNSET_109",
"ACTOR_ARROW_FIRE",
"ACTOR_ARROW_ICE",
"ACTOR_ARROW_LIGHT",
"ACTOR_UNSET_10D",
"ACTOR_UNSET_10E",
"ACTOR_ITEM_ETCETERA",
"ACTOR_OBJ_KIBAKO",
"ACTOR_OBJ_TSUBO",
"ACTOR_EN_WONDER_ITEM",
"ACTOR_EN_IK",
"ACTOR_DEMO_IK",
"ACTOR_EN_SKJ",
"ACTOR_EN_SKJNEEDLE",
"ACTOR_EN_G_SWITCH",
"ACTOR_DEMO_EXT",
"ACTOR_DEMO_SHD",
"ACTOR_EN_DNS",
"ACTOR_ELF_MSG",
"ACTOR_EN_HONOTRAP",
"ACTOR_EN_TUBO_TRAP",
"ACTOR_OBJ_ICE_POLY",
"ACTOR_BG_SPOT03_TAKI",
"ACTOR_BG_SPOT07_TAKI",
"ACTOR_EN_FZ",
"ACTOR_EN_PO_RELAY",
"ACTOR_BG_RELAY_OBJECTS",
"ACTOR_EN_DIVING_GAME",
"ACTOR_EN_KUSA",
"ACTOR_OBJ_BEAN",
"ACTOR_OBJ_BOMBIWA",
"ACTOR_UNSET_128",
"ACTOR_UNSET_129",
"ACTOR_OBJ_SWITCH",
"ACTOR_OBJ_ELEVATOR",
"ACTOR_OBJ_LIFT",
"ACTOR_OBJ_HSBLOCK",
"ACTOR_EN_OKARINA_TAG",
"ACTOR_EN_YABUSAME_MARK",
"ACTOR_EN_GOROIWA",
"ACTOR_EN_EX_RUPPY",
"ACTOR_EN_TORYO",
"ACTOR_EN_DAIKU",
"ACTOR_UNSET_134",
"ACTOR_EN_NWC",
"ACTOR_EN_BLKOBJ",
"ACTOR_ITEM_INBOX",
"ACTOR_EN_GE1",
"ACTOR_OBJ_BLOCKSTOP",
"ACTOR_EN_SDA",
"ACTOR_EN_CLEAR_TAG",
"ACTOR_EN_NIW_LADY",
"ACTOR_EN_GM",
"ACTOR_EN_MS",
"ACTOR_EN_HS",
"ACTOR_BG_INGATE",
"ACTOR_EN_KANBAN",
"ACTOR_EN_HEISHI3",
"ACTOR_EN_SYATEKI_NIW",
"ACTOR_EN_ATTACK_NIW",
"ACTOR_BG_SPOT01_IDOSOKO",
"ACTOR_EN_SA",
"ACTOR_EN_WONDER_TALK",
"ACTOR_BG_GJYO_BRIDGE",
"ACTOR_EN_DS",
"ACTOR_EN_MK",
"ACTOR_EN_BOM_BOWL_MAN",
"ACTOR_EN_BOM_BOWL_PIT",
"ACTOR_EN_OWL",
"ACTOR_EN_ISHI",
"ACTOR_OBJ_HANA",
"ACTOR_OBJ_LIGHTSWITCH",
"ACTOR_OBJ_MURE2",
"ACTOR_EN_GO",
"ACTOR_EN_FU",
"ACTOR_UNSET_154",
"ACTOR_EN_CHANGER",
"ACTOR_BG_JYA_MEGAMI",
"ACTOR_BG_JYA_LIFT",
"ACTOR_BG_JYA_BIGMIRROR",
"ACTOR_BG_JYA_BOMBCHUIWA",
"ACTOR_BG_JYA_AMISHUTTER",
"ACTOR_BG_JYA_BOMBIWA",
"ACTOR_BG_SPOT18_BASKET",
"ACTOR_UNSET_15D",
"ACTOR_EN_GANON_ORGAN",
"ACTOR_EN_SIOFUKI",
"ACTOR_EN_STREAM",
"ACTOR_UNSET_161",
"ACTOR_EN_MM",
"ACTOR_EN_KO",
"ACTOR_EN_KZ",
"ACTOR_EN_WEATHER_TAG",
"ACTOR_BG_SST_FLOOR",
"ACTOR_EN_ANI",
"ACTOR_EN_EX_ITEM",
"ACTOR_BG_JYA_IRONOBJ",
"ACTOR_EN_JS",
"ACTOR_EN_JSJUTAN",
"ACTOR_EN_CS",
"ACTOR_EN_MD",
"ACTOR_EN_HY",
"ACTOR_EN_GANON_MANT",
"ACTOR_EN_OKARINA_EFFECT",
"ACTOR_EN_MAG",
"ACTOR_DOOR_GERUDO",
"ACTOR_ELF_MSG2",
"ACTOR_DEMO_GT",
"ACTOR_EN_PO_FIELD",
"ACTOR_EFC_ERUPC",
"ACTOR_BG_ZG",
"ACTOR_EN_HEISHI4",
"ACTOR_EN_ZL3",
"ACTOR_BOSS_GANON2",
"ACTOR_EN_KAKASI",
"ACTOR_EN_TAKARA_MAN",
"ACTOR_OBJ_MAKEOSHIHIKI",
"ACTOR_OCEFF_SPOT",
"ACTOR_END_TITLE",
"ACTOR_UNSET_180",
"ACTOR_EN_TORCH",
"ACTOR_DEMO_EC",
"ACTOR_SHOT_SUN",
"ACTOR_EN_DY_EXTRA",
"ACTOR_EN_WONDER_TALK2",
"ACTOR_EN_GE2",
"ACTOR_OBJ_ROOMTIMER",
"ACTOR_EN_SSH",
"ACTOR_EN_STH",
"ACTOR_OCEFF_WIPE",
"ACTOR_OCEFF_STORM",
"ACTOR_EN_WEIYER",
"ACTOR_BG_SPOT05_SOKO",
"ACTOR_BG_JYA_1FLIFT",
"ACTOR_BG_JYA_HAHENIRON",
"ACTOR_BG_SPOT12_GATE",
"ACTOR_BG_SPOT12_SAKU",
"ACTOR_EN_HINTNUTS",
"ACTOR_EN_NUTSBALL",
"ACTOR_BG_SPOT00_BREAK",
"ACTOR_EN_SHOPNUTS",
"ACTOR_EN_IT",
"ACTOR_EN_GELDB",
"ACTOR_OCEFF_WIPE2",
"ACTOR_OCEFF_WIPE3",
"ACTOR_EN_NIW_GIRL",
"ACTOR_EN_DOG",
"ACTOR_EN_SI",
"ACTOR_BG_SPOT01_OBJECTS2",
"ACTOR_OBJ_COMB",
"ACTOR_BG_SPOT11_BAKUDANKABE",
"ACTOR_OBJ_KIBAKO2",
"ACTOR_EN_DNT_DEMO",
"ACTOR_EN_DNT_JIJI",
"ACTOR_EN_DNT_NOMAL",
"ACTOR_EN_GUEST",
"ACTOR_BG_BOM_GUARD",
"ACTOR_EN_HS2",
"ACTOR_DEMO_KEKKAI",
"ACTOR_BG_SPOT08_BAKUDANKABE",
"ACTOR_BG_SPOT17_BAKUDANKABE",
"ACTOR_UNSET_1AA",
"ACTOR_OBJ_MURE3",
"ACTOR_EN_TG",
"ACTOR_EN_MU",
"ACTOR_EN_GO2",
"ACTOR_EN_WF",
"ACTOR_EN_SKB",
"ACTOR_DEMO_GJ",
"ACTOR_DEMO_GEFF",
"ACTOR_BG_GND_FIREMEIRO",
"ACTOR_BG_GND_DARKMEIRO",
"ACTOR_BG_GND_SOULMEIRO",
"ACTOR_BG_GND_NISEKABE",
"ACTOR_BG_GND_ICEBLOCK",
"ACTOR_EN_GB",
"ACTOR_EN_GS",
"ACTOR_BG_MIZU_BWALL",
"ACTOR_BG_MIZU_SHUTTER",
"ACTOR_EN_DAIKU_KAKARIKO",
"ACTOR_BG_BOWL_WALL",
"ACTOR_EN_WALL_TUBO",
"ACTOR_EN_PO_DESERT",
"ACTOR_EN_CROW",
"ACTOR_DOOR_KILLER",
"ACTOR_BG_SPOT11_OASIS",
"ACTOR_BG_SPOT18_FUTA",
"ACTOR_BG_SPOT18_SHUTTER",
"ACTOR_EN_MA3",
"ACTOR_EN_COW",
"ACTOR_BG_ICE_TURARA",
"ACTOR_BG_ICE_SHUTTER",
"ACTOR_EN_KAKASI2",
"ACTOR_EN_KAKASI3",
"ACTOR_OCEFF_WIPE4",
"ACTOR_EN_EG",
"ACTOR_BG_MENKURI_NISEKABE",
"ACTOR_EN_ZO",
"ACTOR_OBJ_MAKEKINSUTA",
"ACTOR_EN_GE3",
"ACTOR_OBJ_TIMEBLOCK",
"ACTOR_OBJ_HAMISHI",
"ACTOR_EN_ZL4",
"ACTOR_EN_MM2",
"ACTOR_BG_JYA_BLOCK",
"ACTOR_OBJ_WARP2BLOCK",
)

View file

@ -0,0 +1,141 @@
# This file was made manually
SEQ_IDS = {
0x00: "NA_BGM_GENERAL_SFX",
0x01: "NA_BGM_NATURE_AMBIENCE",
0x02: "NA_BGM_FIELD_LOGIC",
0x03: "NA_BGM_FIELD_INIT",
0x04: "NA_BGM_FIELD_DEFAULT_1",
0x05: "NA_BGM_FIELD_DEFAULT_2",
0x06: "NA_BGM_FIELD_DEFAULT_3",
0x07: "NA_BGM_FIELD_DEFAULT_4",
0x08: "NA_BGM_FIELD_DEFAULT_5",
0x09: "NA_BGM_FIELD_DEFAULT_6",
0x0A: "NA_BGM_FIELD_DEFAULT_7",
0x0B: "NA_BGM_FIELD_DEFAULT_8",
0x0C: "NA_BGM_FIELD_DEFAULT_9",
0x0D: "NA_BGM_FIELD_DEFAULT_A",
0x0E: "NA_BGM_FIELD_DEFAULT_B",
0x0F: "NA_BGM_FIELD_ENEMY_INIT",
0x10: "NA_BGM_FIELD_ENEMY_1",
0x11: "NA_BGM_FIELD_ENEMY_2",
0x12: "NA_BGM_FIELD_ENEMY_3",
0x13: "NA_BGM_FIELD_ENEMY_4",
0x14: "NA_BGM_FIELD_STILL_1",
0x15: "NA_BGM_FIELD_STILL_2",
0x16: "NA_BGM_FIELD_STILL_3",
0x17: "NA_BGM_FIELD_STILL_4",
0x18: "NA_BGM_DUNGEON",
0x19: "NA_BGM_KAKARIKO_ADULT",
0x1A: "NA_BGM_ENEMY",
0x1B: "NA_BGM_BOSS",
0x1C: "NA_BGM_INSIDE_DEKU_TREE",
0x1D: "NA_BGM_MARKET",
0x1E: "NA_BGM_TITLE",
0x1F: "NA_BGM_LINK_HOUSE",
0x20: "NA_BGM_GAME_OVER",
0x21: "NA_BGM_BOSS_CLEAR",
0x22: "NA_BGM_ITEM_GET",
0x23: "NA_BGM_OPENING_GANON",
0x24: "NA_BGM_HEART_GET",
0x25: "NA_BGM_OCA_LIGHT",
0x26: "NA_BGM_JABU_JABU",
0x27: "NA_BGM_KAKARIKO_KID",
0x28: "NA_BGM_GREAT_FAIRY",
0x29: "NA_BGM_ZELDA_THEME",
0x2A: "NA_BGM_FIRE_TEMPLE",
0x2B: "NA_BGM_OPEN_TRE_BOX",
0x2C: "NA_BGM_FOREST_TEMPLE",
0x2D: "NA_BGM_COURTYARD",
0x2E: "NA_BGM_GANON_TOWER",
0x2F: "NA_BGM_LONLON",
0x30: "NA_BGM_GORON_CITY",
0x31: "NA_BGM_FIELD_MORNING",
0x32: "NA_BGM_SPIRITUAL_STONE",
0x33: "NA_BGM_OCA_BOLERO",
0x34: "NA_BGM_OCA_MINUET",
0x35: "NA_BGM_OCA_SERENADE",
0x36: "NA_BGM_OCA_REQUIEM",
0x37: "NA_BGM_OCA_NOCTURNE",
0x38: "NA_BGM_MINI_BOSS",
0x39: "NA_BGM_SMALL_ITEM_GET",
0x3A: "NA_BGM_TEMPLE_OF_TIME",
0x3B: "NA_BGM_EVENT_CLEAR",
0x3C: "NA_BGM_KOKIRI",
0x3D: "NA_BGM_OCA_FAIRY_GET",
0x3E: "NA_BGM_SARIA_THEME",
0x3F: "NA_BGM_SPIRIT_TEMPLE",
0x40: "NA_BGM_HORSE",
0x41: "NA_BGM_HORSE_GOAL",
0x42: "NA_BGM_INGO",
0x43: "NA_BGM_MEDALLION_GET",
0x44: "NA_BGM_OCA_SARIA",
0x45: "NA_BGM_OCA_EPONA",
0x46: "NA_BGM_OCA_ZELDA",
0x47: "NA_BGM_OCA_SUNS",
0x48: "NA_BGM_OCA_TIME",
0x49: "NA_BGM_OCA_STORM",
0x4A: "NA_BGM_NAVI_OPENING",
0x4B: "NA_BGM_DEKU_TREE_CS",
0x4C: "NA_BGM_WINDMILL",
0x4D: "NA_BGM_HYRULE_CS",
0x4E: "NA_BGM_MINI_GAME",
0x4F: "NA_BGM_SHEIK",
0x50: "NA_BGM_ZORA_DOMAIN",
0x51: "NA_BGM_APPEAR",
0x52: "NA_BGM_ADULT_LINK",
0x53: "NA_BGM_MASTER_SWORD",
0x54: "NA_BGM_INTRO_GANON",
0x55: "NA_BGM_SHOP",
0x56: "NA_BGM_CHAMBER_OF_SAGES",
0x57: "NA_BGM_FILE_SELECT",
0x58: "NA_BGM_ICE_CAVERN",
0x59: "NA_BGM_DOOR_OF_TIME",
0x5A: "NA_BGM_OWL",
0x5B: "NA_BGM_SHADOW_TEMPLE",
0x5C: "NA_BGM_WATER_TEMPLE",
0x5D: "NA_BGM_BRIDGE_TO_GANONS",
0x5E: "NA_BGM_OCARINA_OF_TIME",
0x5F: "NA_BGM_GERUDO_VALLEY",
0x60: "NA_BGM_POTION_SHOP",
0x61: "NA_BGM_KOTAKE_KOUME",
0x62: "NA_BGM_ESCAPE",
0x63: "NA_BGM_UNDERGROUND",
0x64: "NA_BGM_GANONDORF_BOSS",
0x65: "NA_BGM_GANON_BOSS",
0x66: "NA_BGM_END_DEMO",
0x67: "NA_BGM_STAFF_1",
0x68: "NA_BGM_STAFF_2",
0x69: "NA_BGM_STAFF_3",
0x6A: "NA_BGM_STAFF_4",
0x6B: "NA_BGM_FIRE_BOSS",
0x6C: "NA_BGM_TIMED_MINI_GAME",
0x6D: "NA_BGM_CUTSCENE_EFFECTS",
0x7F: "NA_BGM_NO_MUSIC",
0x80: "NA_BGM_NATURE_SFX_RAIN",
0xFFFF: "NA_BGM_DISABLED",
}
NATURE_AMBIENCE_IDS = {
0x00: "NATURE_ID_GENERAL_NIGHT",
0x01: "NATURE_ID_MARKET_ENTRANCE",
0x02: "NATURE_ID_KAKARIKO_REGION",
0x03: "NATURE_ID_MARKET_RUINS",
0x04: "NATURE_ID_KOKIRI_REGION",
0x05: "NATURE_ID_MARKET_NIGHT",
0x06: "NATURE_ID_06",
0x07: "NATURE_ID_GANONS_LAIR",
0x08: "NATURE_ID_08",
0x09: "NATURE_ID_09",
0x0A: "NATURE_ID_WASTELAND",
0x0B: "NATURE_ID_COLOSSUS",
0x0C: "NATURE_ID_DEATH_MOUNTAIN_TRAIL",
0x0D: "NATURE_ID_0D",
0x0E: "NATURE_ID_0E",
0x0F: "NATURE_ID_0F",
0x10: "NATURE_ID_10",
0x11: "NATURE_ID_11",
0x12: "NATURE_ID_12",
0x13: "NATURE_ID_NONE",
0xFF: "NATURE_ID_DISABLED",
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,11 @@
# This file was made manually
DATA = {
0x7FF9: "ENTR_RETURN_GREAT_FAIRYS_FOUNTAIN_SPELLS",
0x7FFA: "ENTR_RETURN_SHOOTING_GALLERY",
0x7FFB: "ENTR_RETURN_2",
0x7FFC: "ENTR_RETURN_BAZAAR",
0x7FFD: "ENTR_RETURN_4",
0x7FFE: "ENTR_RETURN_GREAT_FAIRYS_FOUNTAIN_MAGIC",
0x7FFF: "ENTR_RETURN_GROTTO",
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,103 @@
# This file was made manually
SCENE_CAM_TYPES = {
0: "SCENE_CAM_TYPE_DEFAULT",
0x10: "SCENE_CAM_TYPE_FIXED_SHOP_VIEWPOINT",
0x20: "SCENE_CAM_TYPE_FIXED_TOGGLE_VIEWPOINT",
0x30: "SCENE_CAM_TYPE_FIXED",
0x40: "SCENE_CAM_TYPE_FIXED_MARKET",
0x50: "SCENE_CAM_TYPE_SHOOTING_GALLERY",
}
ROOM_BEHAVIOR_TYPE1_NAMES = {
0: "ROOM_TYPE_NORMAL",
1: "ROOM_TYPE_DUNGEON",
2: "ROOM_TYPE_INDOORS",
3: "ROOM_TYPE_3",
4: "ROOM_TYPE_4",
5: "ROOM_TYPE_BOSS",
}
ROOM_BEHAVIOR_TYPE2_NAMES = {
0: "ROOM_ENV_DEFAULT",
1: "ROOM_ENV_COLD",
2: "ROOM_ENV_WARM",
3: "ROOM_ENV_HOT",
4: "ROOM_ENV_UNK_STRETCH_1",
5: "ROOM_ENV_UNK_STRETCH_2",
6: "ROOM_ENV_UNK_STRETCH_3",
}
LENS_MODES = {
0: "LENS_MODE_SHOW_ACTORS",
1: "LENS_MODE_HIDE_ACTORS",
}
CAMERA_SETTING_TYPES = {
0x00: "CAM_SET_NONE",
0x01: "CAM_SET_NORMAL0",
0x02: "CAM_SET_NORMAL1",
0x03: "CAM_SET_DUNGEON0",
0x04: "CAM_SET_DUNGEON1",
0x05: "CAM_SET_NORMAL3",
0x06: "CAM_SET_HORSE",
0x07: "CAM_SET_BOSS_GOHMA",
0x08: "CAM_SET_BOSS_DODONGO",
0x09: "CAM_SET_BOSS_BARINADE",
0x0A: "CAM_SET_BOSS_PHANTOM_GANON",
0x0B: "CAM_SET_BOSS_VOLVAGIA",
0x0C: "CAM_SET_BOSS_BONGO",
0x0D: "CAM_SET_BOSS_MORPHA",
0x0E: "CAM_SET_BOSS_TWINROVA_PLATFORM",
0x0F: "CAM_SET_BOSS_TWINROVA_FLOOR",
0x10: "CAM_SET_BOSS_GANONDORF",
0x11: "CAM_SET_BOSS_GANON",
0x12: "CAM_SET_TOWER_CLIMB",
0x13: "CAM_SET_TOWER_UNUSED",
0x14: "CAM_SET_MARKET_BALCONY",
0x15: "CAM_SET_CHU_BOWLING",
0x16: "CAM_SET_PIVOT_CRAWLSPACE",
0x17: "CAM_SET_PIVOT_SHOP_BROWSING",
0x18: "CAM_SET_PIVOT_IN_FRONT",
0x19: "CAM_SET_PREREND_FIXED",
0x1A: "CAM_SET_PREREND_PIVOT",
0x1B: "CAM_SET_PREREND_SIDE_SCROLL",
0x1C: "CAM_SET_DOOR0",
0x1D: "CAM_SET_DOORC",
0x1E: "CAM_SET_CRAWLSPACE",
0x1F: "CAM_SET_START0",
0x20: "CAM_SET_START1",
0x21: "CAM_SET_FREE0",
0x22: "CAM_SET_FREE2",
0x23: "CAM_SET_PIVOT_CORNER",
0x24: "CAM_SET_PIVOT_WATER_SURFACE",
0x25: "CAM_SET_CS_0",
0x26: "CAM_SET_CS_TWISTED_HALLWAY",
0x27: "CAM_SET_FOREST_BIRDS_EYE",
0x28: "CAM_SET_SLOW_CHEST_CS",
0x29: "CAM_SET_ITEM_UNUSED",
0x2A: "CAM_SET_CS_3",
0x2B: "CAM_SET_CS_ATTENTION",
0x2C: "CAM_SET_BEAN_GENERIC",
0x2D: "CAM_SET_BEAN_LOST_WOODS",
0x2E: "CAM_SET_SCENE_UNUSED",
0x2F: "CAM_SET_SCENE_TRANSITION",
0x30: "CAM_SET_ELEVATOR_PLATFORM",
0x31: "CAM_SET_FIRE_STAIRCASE",
0x32: "CAM_SET_FOREST_UNUSED",
0x33: "CAM_SET_FOREST_DEFEAT_POE",
0x34: "CAM_SET_BIG_OCTO",
0x35: "CAM_SET_MEADOW_BIRDS_EYE",
0x36: "CAM_SET_MEADOW_UNUSED",
0x37: "CAM_SET_FIRE_BIRDS_EYE",
0x38: "CAM_SET_TURN_AROUND",
0x39: "CAM_SET_PIVOT_VERTICAL",
0x3A: "CAM_SET_NORMAL2",
0x3B: "CAM_SET_FISHING",
0x3C: "CAM_SET_CS_C",
0x3D: "CAM_SET_JABU_TENTACLE",
0x3E: "CAM_SET_DUNGEON2",
0x3F: "CAM_SET_DIRECTED_YAW",
0x40: "CAM_SET_PIVOT_FROM_SIDE",
0x41: "CAM_SET_NORMAL4",
}

View file

@ -0,0 +1,406 @@
# This file was generated from ../../../..//include/tables/object_table.h
DATA = (
"OBJECT_INVALID",
"OBJECT_GAMEPLAY_KEEP",
"OBJECT_GAMEPLAY_FIELD_KEEP",
"OBJECT_GAMEPLAY_DANGEON_KEEP",
"OBJECT_UNSET_4",
"OBJECT_UNSET_5",
"OBJECT_HUMAN",
"OBJECT_OKUTA",
"OBJECT_CROW",
"OBJECT_POH",
"OBJECT_DY_OBJ",
"OBJECT_WALLMASTER",
"OBJECT_DODONGO",
"OBJECT_FIREFLY",
"OBJECT_BOX",
"OBJECT_FIRE",
"OBJECT_UNSET_10",
"OBJECT_UNSET_11",
"OBJECT_BUBBLE",
"OBJECT_NIW",
"OBJECT_LINK_BOY",
"OBJECT_LINK_CHILD",
"OBJECT_TITE",
"OBJECT_REEBA",
"OBJECT_PEEHAT",
"OBJECT_KINGDODONGO",
"OBJECT_HORSE",
"OBJECT_ZF",
"OBJECT_GOMA",
"OBJECT_ZL1",
"OBJECT_GOL",
"OBJECT_DODOJR",
"OBJECT_TORCH2",
"OBJECT_BL",
"OBJECT_TP",
"OBJECT_OA1",
"OBJECT_ST",
"OBJECT_BW",
"OBJECT_EI",
"OBJECT_HORSE_NORMAL",
"OBJECT_OB1",
"OBJECT_O_ANIME",
"OBJECT_SPOT04_OBJECTS",
"OBJECT_DDAN_OBJECTS",
"OBJECT_HIDAN_OBJECTS",
"OBJECT_HORSE_GANON",
"OBJECT_OA2",
"OBJECT_SPOT00_OBJECTS",
"OBJECT_MB",
"OBJECT_BOMBF",
"OBJECT_SK2",
"OBJECT_OE1",
"OBJECT_OE_ANIME",
"OBJECT_OE2",
"OBJECT_YDAN_OBJECTS",
"OBJECT_GND",
"OBJECT_AM",
"OBJECT_DEKUBABA",
"OBJECT_UNSET_3A",
"OBJECT_OA3",
"OBJECT_OA4",
"OBJECT_OA5",
"OBJECT_OA6",
"OBJECT_OA7",
"OBJECT_JJ",
"OBJECT_OA8",
"OBJECT_OA9",
"OBJECT_OB2",
"OBJECT_OB3",
"OBJECT_OB4",
"OBJECT_HORSE_ZELDA",
"OBJECT_OPENING_DEMO1",
"OBJECT_WARP1",
"OBJECT_B_HEART",
"OBJECT_DEKUNUTS",
"OBJECT_OE3",
"OBJECT_OE4",
"OBJECT_MENKURI_OBJECTS",
"OBJECT_OE5",
"OBJECT_OE6",
"OBJECT_OE7",
"OBJECT_OE8",
"OBJECT_OE9",
"OBJECT_OE10",
"OBJECT_OE11",
"OBJECT_OE12",
"OBJECT_VALI",
"OBJECT_OA10",
"OBJECT_OA11",
"OBJECT_MIZU_OBJECTS",
"OBJECT_FHG",
"OBJECT_OSSAN",
"OBJECT_MORI_HINERI1",
"OBJECT_BB",
"OBJECT_TOKI_OBJECTS",
"OBJECT_YUKABYUN",
"OBJECT_ZL2",
"OBJECT_MJIN",
"OBJECT_MJIN_FLASH",
"OBJECT_MJIN_DARK",
"OBJECT_MJIN_FLAME",
"OBJECT_MJIN_ICE",
"OBJECT_MJIN_SOUL",
"OBJECT_MJIN_WIND",
"OBJECT_MJIN_OKA",
"OBJECT_HAKA_OBJECTS",
"OBJECT_SPOT06_OBJECTS",
"OBJECT_ICE_OBJECTS",
"OBJECT_RELAY_OBJECTS",
"OBJECT_PO_FIELD",
"OBJECT_PO_COMPOSER",
"OBJECT_MORI_HINERI1A",
"OBJECT_MORI_HINERI2",
"OBJECT_MORI_HINERI2A",
"OBJECT_MORI_OBJECTS",
"OBJECT_MORI_TEX",
"OBJECT_SPOT08_OBJ",
"OBJECT_WARP2",
"OBJECT_HATA",
"OBJECT_BIRD",
"OBJECT_UNSET_78",
"OBJECT_UNSET_79",
"OBJECT_UNSET_7A",
"OBJECT_UNSET_7B",
"OBJECT_WOOD02",
"OBJECT_UNSET_7D",
"OBJECT_UNSET_7E",
"OBJECT_UNSET_7F",
"OBJECT_UNSET_80",
"OBJECT_LIGHTBOX",
"OBJECT_PU_BOX",
"OBJECT_UNSET_83",
"OBJECT_UNSET_84",
"OBJECT_TRAP",
"OBJECT_VASE",
"OBJECT_IM",
"OBJECT_TA",
"OBJECT_TK",
"OBJECT_XC",
"OBJECT_VM",
"OBJECT_BV",
"OBJECT_HAKACH_OBJECTS",
"OBJECT_EFC_CRYSTAL_LIGHT",
"OBJECT_EFC_FIRE_BALL",
"OBJECT_EFC_FLASH",
"OBJECT_EFC_LGT_SHOWER",
"OBJECT_EFC_STAR_FIELD",
"OBJECT_GOD_LGT",
"OBJECT_LIGHT_RING",
"OBJECT_TRIFORCE_SPOT",
"OBJECT_BDAN_OBJECTS",
"OBJECT_SD",
"OBJECT_RD",
"OBJECT_PO_SISTERS",
"OBJECT_HEAVY_OBJECT",
"OBJECT_GNDD",
"OBJECT_FD",
"OBJECT_DU",
"OBJECT_FW",
"OBJECT_MEDAL",
"OBJECT_HORSE_LINK_CHILD",
"OBJECT_SPOT02_OBJECTS",
"OBJECT_HAKA",
"OBJECT_RU1",
"OBJECT_SYOKUDAI",
"OBJECT_FD2",
"OBJECT_DH",
"OBJECT_RL",
"OBJECT_EFC_TW",
"OBJECT_DEMO_TRE_LGT",
"OBJECT_GI_KEY",
"OBJECT_MIR_RAY",
"OBJECT_BROB",
"OBJECT_GI_JEWEL",
"OBJECT_SPOT09_OBJ",
"OBJECT_SPOT18_OBJ",
"OBJECT_BDOOR",
"OBJECT_SPOT17_OBJ",
"OBJECT_SHOP_DUNGEN",
"OBJECT_NB",
"OBJECT_MO",
"OBJECT_SB",
"OBJECT_GI_MELODY",
"OBJECT_GI_HEART",
"OBJECT_GI_COMPASS",
"OBJECT_GI_BOSSKEY",
"OBJECT_GI_MEDAL",
"OBJECT_GI_NUTS",
"OBJECT_SA",
"OBJECT_GI_HEARTS",
"OBJECT_GI_ARROWCASE",
"OBJECT_GI_BOMBPOUCH",
"OBJECT_IN",
"OBJECT_TR",
"OBJECT_SPOT16_OBJ",
"OBJECT_OE1S",
"OBJECT_OE4S",
"OBJECT_OS_ANIME",
"OBJECT_GI_BOTTLE",
"OBJECT_GI_STICK",
"OBJECT_GI_MAP",
"OBJECT_OF1D_MAP",
"OBJECT_RU2",
"OBJECT_GI_SHIELD_1",
"OBJECT_DEKUJR",
"OBJECT_GI_MAGICPOT",
"OBJECT_GI_BOMB_1",
"OBJECT_OF1S",
"OBJECT_MA2",
"OBJECT_GI_PURSE",
"OBJECT_HNI",
"OBJECT_TW",
"OBJECT_RR",
"OBJECT_BXA",
"OBJECT_ANUBICE",
"OBJECT_GI_GERUDO",
"OBJECT_GI_ARROW",
"OBJECT_GI_BOMB_2",
"OBJECT_GI_EGG",
"OBJECT_GI_SCALE",
"OBJECT_GI_SHIELD_2",
"OBJECT_GI_HOOKSHOT",
"OBJECT_GI_OCARINA",
"OBJECT_GI_MILK",
"OBJECT_MA1",
"OBJECT_GANON",
"OBJECT_SST",
"OBJECT_NY_UNUSED",
"OBJECT_UNSET_E4",
"OBJECT_NY",
"OBJECT_FR",
"OBJECT_GI_PACHINKO",
"OBJECT_GI_BOOMERANG",
"OBJECT_GI_BOW",
"OBJECT_GI_GLASSES",
"OBJECT_GI_LIQUID",
"OBJECT_ANI",
"OBJECT_DEMO_6K",
"OBJECT_GI_SHIELD_3",
"OBJECT_GI_LETTER",
"OBJECT_SPOT15_OBJ",
"OBJECT_JYA_OBJ",
"OBJECT_GI_CLOTHES",
"OBJECT_GI_BEAN",
"OBJECT_GI_FISH",
"OBJECT_GI_SAW",
"OBJECT_GI_HAMMER",
"OBJECT_GI_GRASS",
"OBJECT_GI_LONGSWORD",
"OBJECT_SPOT01_OBJECTS",
"OBJECT_MD_UNUSED",
"OBJECT_MD",
"OBJECT_KM1",
"OBJECT_KW1",
"OBJECT_ZO",
"OBJECT_KZ",
"OBJECT_UMAJUMP",
"OBJECT_MASTERKOKIRI",
"OBJECT_MASTERKOKIRIHEAD",
"OBJECT_MASTERGOLON",
"OBJECT_MASTERZOORA",
"OBJECT_AOB",
"OBJECT_IK",
"OBJECT_AHG",
"OBJECT_CNE",
"OBJECT_GI_NIWATORI",
"OBJECT_SKJ",
"OBJECT_GI_BOTTLE_LETTER",
"OBJECT_BJI",
"OBJECT_BBA",
"OBJECT_GI_OCARINA_0",
"OBJECT_DS",
"OBJECT_ANE",
"OBJECT_BOJ",
"OBJECT_SPOT03_OBJECT",
"OBJECT_SPOT07_OBJECT",
"OBJECT_FZ",
"OBJECT_BOB",
"OBJECT_GE1",
"OBJECT_YABUSAME_POINT",
"OBJECT_GI_BOOTS_2",
"OBJECT_GI_SEED",
"OBJECT_GND_MAGIC",
"OBJECT_D_ELEVATOR",
"OBJECT_D_HSBLOCK",
"OBJECT_D_LIFT",
"OBJECT_MAMENOKI",
"OBJECT_GOROIWA",
"OBJECT_UNSET_120",
"OBJECT_TORYO",
"OBJECT_DAIKU",
"OBJECT_UNSET_123",
"OBJECT_NWC",
"OBJECT_BLKOBJ",
"OBJECT_GM",
"OBJECT_MS",
"OBJECT_HS",
"OBJECT_INGATE",
"OBJECT_LIGHTSWITCH",
"OBJECT_KUSA",
"OBJECT_TSUBO",
"OBJECT_GI_GLOVES",
"OBJECT_GI_COIN",
"OBJECT_KANBAN",
"OBJECT_GJYO_OBJECTS",
"OBJECT_OWL",
"OBJECT_MK",
"OBJECT_FU",
"OBJECT_GI_KI_TAN_MASK",
"OBJECT_GI_REDEAD_MASK",
"OBJECT_GI_SKJ_MASK",
"OBJECT_GI_RABIT_MASK",
"OBJECT_GI_TRUTH_MASK",
"OBJECT_GANON_OBJECTS",
"OBJECT_SIOFUKI",
"OBJECT_STREAM",
"OBJECT_MM",
"OBJECT_FA",
"OBJECT_OS",
"OBJECT_GI_EYE_LOTION",
"OBJECT_GI_POWDER",
"OBJECT_GI_MUSHROOM",
"OBJECT_GI_TICKETSTONE",
"OBJECT_GI_BROKENSWORD",
"OBJECT_JS",
"OBJECT_CS",
"OBJECT_GI_PRESCRIPTION",
"OBJECT_GI_BRACELET",
"OBJECT_GI_SOLDOUT",
"OBJECT_GI_FROG",
"OBJECT_MAG",
"OBJECT_DOOR_GERUDO",
"OBJECT_GT",
"OBJECT_EFC_ERUPC",
"OBJECT_ZL2_ANIME1",
"OBJECT_ZL2_ANIME2",
"OBJECT_GI_GOLONMASK",
"OBJECT_GI_ZORAMASK",
"OBJECT_GI_GERUDOMASK",
"OBJECT_GANON2",
"OBJECT_KA",
"OBJECT_TS",
"OBJECT_ZG",
"OBJECT_GI_HOVERBOOTS",
"OBJECT_GI_M_ARROW",
"OBJECT_DS2",
"OBJECT_EC",
"OBJECT_FISH",
"OBJECT_GI_SUTARU",
"OBJECT_GI_GODDESS",
"OBJECT_SSH",
"OBJECT_BIGOKUTA",
"OBJECT_BG",
"OBJECT_SPOT05_OBJECTS",
"OBJECT_SPOT12_OBJ",
"OBJECT_BOMBIWA",
"OBJECT_HINTNUTS",
"OBJECT_RS",
"OBJECT_SPOT00_BREAK",
"OBJECT_GLA",
"OBJECT_SHOPNUTS",
"OBJECT_GELDB",
"OBJECT_GR",
"OBJECT_DOG",
"OBJECT_JYA_IRON",
"OBJECT_JYA_DOOR",
"OBJECT_UNSET_16E",
"OBJECT_SPOT11_OBJ",
"OBJECT_KIBAKO2",
"OBJECT_DNS",
"OBJECT_DNK",
"OBJECT_GI_FIRE",
"OBJECT_GI_INSECT",
"OBJECT_GI_BUTTERFLY",
"OBJECT_GI_GHOST",
"OBJECT_GI_SOUL",
"OBJECT_BOWL",
"OBJECT_DEMO_KEKKAI",
"OBJECT_EFC_DOUGHNUT",
"OBJECT_GI_DEKUPOUCH",
"OBJECT_GANON_ANIME1",
"OBJECT_GANON_ANIME2",
"OBJECT_GANON_ANIME3",
"OBJECT_GI_RUPY",
"OBJECT_SPOT01_MATOYA",
"OBJECT_SPOT01_MATOYAB",
"OBJECT_MU",
"OBJECT_WF",
"OBJECT_SKB",
"OBJECT_GJ",
"OBJECT_GEFF",
"OBJECT_HAKA_DOOR",
"OBJECT_GS",
"OBJECT_PS",
"OBJECT_BWALL",
"OBJECT_COW",
"OBJECT_COB",
"OBJECT_GI_SWORD_1",
"OBJECT_DOOR_KILLER",
"OBJECT_OUKE_HAKA",
"OBJECT_TIMEBLOCK",
"OBJECT_ZL4",
)

View file

@ -0,0 +1,114 @@
# This file was generated from ../../../..//include/tables/scene_table.h
DATA = (
("ydan_scene", "SCENE_DEKU_TREE"),
("ddan_scene", "SCENE_DODONGOS_CAVERN"),
("bdan_scene", "SCENE_JABU_JABU"),
("Bmori1_scene", "SCENE_FOREST_TEMPLE"),
("HIDAN_scene", "SCENE_FIRE_TEMPLE"),
("MIZUsin_scene", "SCENE_WATER_TEMPLE"),
("jyasinzou_scene", "SCENE_SPIRIT_TEMPLE"),
("HAKAdan_scene", "SCENE_SHADOW_TEMPLE"),
("HAKAdanCH_scene", "SCENE_BOTTOM_OF_THE_WELL"),
("ice_doukutu_scene", "SCENE_ICE_CAVERN"),
("ganon_scene", "SCENE_GANONS_TOWER"),
("men_scene", "SCENE_GERUDO_TRAINING_GROUND"),
("gerudoway_scene", "SCENE_THIEVES_HIDEOUT"),
("ganontika_scene", "SCENE_INSIDE_GANONS_CASTLE"),
("ganon_sonogo_scene", "SCENE_GANONS_TOWER_COLLAPSE_INTERIOR"),
("ganontikasonogo_scene", "SCENE_INSIDE_GANONS_CASTLE_COLLAPSE"),
("takaraya_scene", "SCENE_TREASURE_BOX_SHOP"),
("ydan_boss_scene", "SCENE_DEKU_TREE_BOSS"),
("ddan_boss_scene", "SCENE_DODONGOS_CAVERN_BOSS"),
("bdan_boss_scene", "SCENE_JABU_JABU_BOSS"),
("moribossroom_scene", "SCENE_FOREST_TEMPLE_BOSS"),
("FIRE_bs_scene", "SCENE_FIRE_TEMPLE_BOSS"),
("MIZUsin_bs_scene", "SCENE_WATER_TEMPLE_BOSS"),
("jyasinboss_scene", "SCENE_SPIRIT_TEMPLE_BOSS"),
("HAKAdan_bs_scene", "SCENE_SHADOW_TEMPLE_BOSS"),
("ganon_boss_scene", "SCENE_GANONDORF_BOSS"),
("ganon_final_scene", "SCENE_GANONS_TOWER_COLLAPSE_EXTERIOR"),
("entra_scene", "SCENE_MARKET_ENTRANCE_DAY"),
("entra_n_scene", "SCENE_MARKET_ENTRANCE_NIGHT"),
("enrui_scene", "SCENE_MARKET_ENTRANCE_RUINS"),
("market_alley_scene", "SCENE_BACK_ALLEY_DAY"),
("market_alley_n_scene", "SCENE_BACK_ALLEY_NIGHT"),
("market_day_scene", "SCENE_MARKET_DAY"),
("market_night_scene", "SCENE_MARKET_NIGHT"),
("market_ruins_scene", "SCENE_MARKET_RUINS"),
("shrine_scene", "SCENE_TEMPLE_OF_TIME_EXTERIOR_DAY"),
("shrine_n_scene", "SCENE_TEMPLE_OF_TIME_EXTERIOR_NIGHT"),
("shrine_r_scene", "SCENE_TEMPLE_OF_TIME_EXTERIOR_RUINS"),
("kokiri_home_scene", "SCENE_KNOW_IT_ALL_BROS_HOUSE"),
("kokiri_home3_scene", "SCENE_TWINS_HOUSE"),
("kokiri_home4_scene", "SCENE_MIDOS_HOUSE"),
("kokiri_home5_scene", "SCENE_SARIAS_HOUSE"),
("kakariko_scene", "SCENE_KAKARIKO_CENTER_GUEST_HOUSE"),
("kakariko3_scene", "SCENE_BACK_ALLEY_HOUSE"),
("shop1_scene", "SCENE_BAZAAR"),
("kokiri_shop_scene", "SCENE_KOKIRI_SHOP"),
("golon_scene", "SCENE_GORON_SHOP"),
("zoora_scene", "SCENE_ZORA_SHOP"),
("drag_scene", "SCENE_POTION_SHOP_KAKARIKO"),
("alley_shop_scene", "SCENE_POTION_SHOP_MARKET"),
("night_shop_scene", "SCENE_BOMBCHU_SHOP"),
("face_shop_scene", "SCENE_HAPPY_MASK_SHOP"),
("link_home_scene", "SCENE_LINKS_HOUSE"),
("impa_scene", "SCENE_DOG_LADY_HOUSE"),
("malon_stable_scene", "SCENE_STABLE"),
("labo_scene", "SCENE_IMPAS_HOUSE"),
("hylia_labo_scene", "SCENE_LAKESIDE_LABORATORY"),
("tent_scene", "SCENE_CARPENTERS_TENT"),
("hut_scene", "SCENE_GRAVEKEEPERS_HUT"),
("daiyousei_izumi_scene", "SCENE_GREAT_FAIRYS_FOUNTAIN_MAGIC"),
("yousei_izumi_tate_scene", "SCENE_FAIRYS_FOUNTAIN"),
("yousei_izumi_yoko_scene", "SCENE_GREAT_FAIRYS_FOUNTAIN_SPELLS"),
("kakusiana_scene", "SCENE_GROTTOS"),
("hakaana_scene", "SCENE_REDEAD_GRAVE"),
("hakaana2_scene", "SCENE_GRAVE_WITH_FAIRYS_FOUNTAIN"),
("hakaana_ouke_scene", "SCENE_ROYAL_FAMILYS_TOMB"),
("syatekijyou_scene", "SCENE_SHOOTING_GALLERY"),
("tokinoma_scene", "SCENE_TEMPLE_OF_TIME"),
("kenjyanoma_scene", "SCENE_CHAMBER_OF_THE_SAGES"),
("hairal_niwa_scene", "SCENE_CASTLE_COURTYARD_GUARDS_DAY"),
("hairal_niwa_n_scene", "SCENE_CASTLE_COURTYARD_GUARDS_NIGHT"),
("hiral_demo_scene", "SCENE_CUTSCENE_MAP"),
("hakasitarelay_scene", "SCENE_WINDMILL_AND_DAMPES_GRAVE"),
("turibori_scene", "SCENE_FISHING_POND"),
("nakaniwa_scene", "SCENE_CASTLE_COURTYARD_ZELDA"),
("bowling_scene", "SCENE_BOMBCHU_BOWLING_ALLEY"),
("souko_scene", "SCENE_LON_LON_BUILDINGS"),
("miharigoya_scene", "SCENE_MARKET_GUARD_HOUSE"),
("mahouya_scene", "SCENE_POTION_SHOP_GRANNY"),
("ganon_demo_scene", "SCENE_GANON_BOSS"),
("kinsuta_scene", "SCENE_HOUSE_OF_SKULLTULA"),
("spot00_scene", "SCENE_HYRULE_FIELD"),
("spot01_scene", "SCENE_KAKARIKO_VILLAGE"),
("spot02_scene", "SCENE_GRAVEYARD"),
("spot03_scene", "SCENE_ZORAS_RIVER"),
("spot04_scene", "SCENE_KOKIRI_FOREST"),
("spot05_scene", "SCENE_SACRED_FOREST_MEADOW"),
("spot06_scene", "SCENE_LAKE_HYLIA"),
("spot07_scene", "SCENE_ZORAS_DOMAIN"),
("spot08_scene", "SCENE_ZORAS_FOUNTAIN"),
("spot09_scene", "SCENE_GERUDO_VALLEY"),
("spot10_scene", "SCENE_LOST_WOODS"),
("spot11_scene", "SCENE_DESERT_COLOSSUS"),
("spot12_scene", "SCENE_GERUDOS_FORTRESS"),
("spot13_scene", "SCENE_HAUNTED_WASTELAND"),
("spot15_scene", "SCENE_HYRULE_CASTLE"),
("spot16_scene", "SCENE_DEATH_MOUNTAIN_TRAIL"),
("spot17_scene", "SCENE_DEATH_MOUNTAIN_CRATER"),
("spot18_scene", "SCENE_GORON_CITY"),
("spot20_scene", "SCENE_LON_LON_RANCH"),
("ganon_tou_scene", "SCENE_OUTSIDE_GANONS_CASTLE"),
("test01_scene", "SCENE_TEST01"),
("besitu_scene", "SCENE_BESITU"),
("depth_test_scene", "SCENE_DEPTH_TEST"),
("syotes_scene", "SCENE_SYOTES"),
("syotes2_scene", "SCENE_SYOTES2"),
("sutaru_scene", "SCENE_SUTARU"),
("hairal_niwa2_scene", "SCENE_HAIRAL_NIWA2"),
("sasatest_scene", "SCENE_SASATEST"),
("testroom_scene", "SCENE_TESTROOM"),
)

View file

@ -0,0 +1,445 @@
from typing import Callable
from ..descriptor.base import ResourceDesc
from ..descriptor import n64resources
from ..descriptor import z64resources
from .extase import (
GetResourceAtResult,
File,
Resource,
BinaryBlobResource,
)
from .extase.cdata_resources import Vec3sArrayResource, S16ArrayResource
from .extase_oot64 import (
skeleton_resources,
animation_resources,
collision_resources,
dlist_resources,
playeranim_resources,
skelcurve_resources,
misc_resources,
scene_rooms_resources,
scene_commands_resource,
)
#
# resource handlers
#
class ResourceHandlerException(Exception): ...
class ResourceNeedsPostProcessWithPoolResourcesException(ResourceHandlerException):
def __init__(
self,
*args,
resource: Resource,
callback: Callable[[dict[ResourceDesc, Resource]], None],
):
super().__init__(*args)
self.resource = resource
self.callback = callback
def __repr__(self) -> str:
return (
super().__repr__().removesuffix(")")
+ f", resource={self.resource!r}"
+ f", callback={self.callback!r})"
)
ResourceHandler = Callable[[File, ResourceDesc], Resource]
# Returns a dummy resource_handler that produces a `BinaryBlobResource` of the given size
# This is meant as a "placeholder resource" until a resource is properly implemented
def get_fixed_size_resource_handler(size) -> ResourceHandler:
def resource_handler(
file: File,
resource_desc: ResourceDesc,
):
return BinaryBlobResource(
file,
resource_desc.offset,
resource_desc.offset + size,
resource_desc.symbol_name,
)
return resource_handler
def register_resource_handlers():
def skeleton_resource_handler(
file: File,
resource_desc: z64resources.SkeletonResourceDesc,
):
offset = resource_desc.offset
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
)
elif resource_desc.type == z64resources.SkeletonType.FLEX:
# } FlexSkeletonHeader; // size = 0xC
return BinaryBlobResource(
file, offset, offset + 0xC, resource_desc.symbol_name
)
else:
raise NotImplementedError(
"LimbType=LOD",
"unimplemented SkeletonType",
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
)
elif resource_desc.limb_type == z64resources.LimbType.CURVE:
assert resource_desc.type == z64resources.SkeletonType.CURVE
return skelcurve_resources.CurveSkeletonHeaderResource(
file, offset, resource_desc.symbol_name
)
else:
raise NotImplementedError(
"unimplemented Skeleton LimbType",
resource_desc.limb_type,
)
if resource_desc.type == z64resources.SkeletonType.NORMAL:
return skeleton_resources.SkeletonNormalResource(
file,
offset,
resource_desc.symbol_name,
)
elif resource_desc.type == z64resources.SkeletonType.FLEX:
return skeleton_resources.SkeletonFlexResource(
file,
offset,
resource_desc.symbol_name,
)
else:
raise NotImplementedError(
"unimplemented SkeletonType",
resource_desc.type,
)
def limb_resource_handler(
file: File,
resource_desc: z64resources.LimbResourceDesc,
):
offset = resource_desc.offset
if resource_desc.limb_type == z64resources.LimbType.STANDARD:
return skeleton_resources.StandardLimbResource(
file,
offset,
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
)
if resource_desc.limb_type == z64resources.LimbType.LOD:
# } LodLimb; // size = 0x10
return BinaryBlobResource(
file, offset, offset + 0x10, resource_desc.symbol_name
)
if resource_desc.limb_type == z64resources.LimbType.LEGACY:
# } LegacyLimb; // size = 0x20
return BinaryBlobResource(
file, offset, offset + 0x20, resource_desc.symbol_name
)
if resource_desc.limb_type == z64resources.LimbType.CURVE:
return skelcurve_resources.SkelCurveLimbResource(
file, offset, resource_desc.symbol_name
)
else:
raise NotImplementedError(
"unimplemented LimbType",
resource_desc.limb_type,
)
def animation_resource_handler(
file: File,
resource_desc: z64resources.AnimationResourceDesc,
):
return animation_resources.AnimationResource(
file,
resource_desc.offset,
resource_desc.symbol_name,
)
def collision_resource_handler(
file: File,
resource_desc: z64resources.CollisionResourceDesc,
):
return collision_resources.CollisionResource(
file,
resource_desc.offset,
resource_desc.symbol_name,
)
def dlist_resource_handler(
file: File,
resource_desc: n64resources.DListResourceDesc,
):
return dlist_resources.DListResource(
file,
resource_desc.offset,
resource_desc.symbol_name,
target_ucode={
n64resources.GfxMicroCode.F3DEX: dlist_resources.Ucode.f3dex,
n64resources.GfxMicroCode.F3DEX2: dlist_resources.Ucode.f3dex2,
}[resource_desc.ucode],
)
def texture_resource_handler(
file: File, resource_desc: n64resources.TextureResourceDesc
):
return dlist_resources.TextureResource(
file,
resource_desc.offset,
resource_desc.symbol_name,
resource_desc.format.fmt,
resource_desc.format.siz,
resource_desc.width,
resource_desc.height,
)
def ci_texture_resource_handler(
file: File, resource_desc: n64resources.CITextureResourceDesc
):
if "hackmode_split_tlut" in resource_desc.hack_modes:
# TODO implement SplitTlut="true"
return BinaryBlobResource(
file,
resource_desc.offset,
resource_desc.offset
+ (
resource_desc.format.siz.bpp
* resource_desc.width
* resource_desc.height
// 8
),
resource_desc.symbol_name,
)
resource = dlist_resources.TextureResource(
file,
resource_desc.offset,
resource_desc.symbol_name,
resource_desc.format.fmt,
resource_desc.format.siz,
resource_desc.width,
resource_desc.height,
)
def callback_set_tlut(pool_resources_by_desc):
resource_tlut_desc = resource_desc.tlut
resource_tlut = pool_resources_by_desc[resource_tlut_desc]
resource.set_tlut(resource_tlut)
raise ResourceNeedsPostProcessWithPoolResourcesException(
resource=resource, callback=callback_set_tlut
)
def PlayerAnimationData_handler(
file: File,
resource_desc: z64resources.PlayerAnimationDataResourceDesc,
):
size = resource_desc.frame_count * (22 * 3 + 1) * 2
return BinaryBlobResource(
file,
resource_desc.offset,
resource_desc.offset + size,
resource_desc.symbol_name,
)
# TODO
return skeleton_resources.PlayerAnimationDataResource(
file,
resource_desc.offset,
resource_desc.symbol_name,
resource_desc.frame_count,
)
def PlayerAnimation_handler(
file: File,
resource_desc: z64resources.PlayerAnimationResourceDesc,
):
return playeranim_resources.PlayerAnimationResource(
file,
resource_desc.offset,
resource_desc.symbol_name,
)
def vec3s_array_resource_handler(
file: File,
resource_desc: n64resources.Vec3sArrayResourceDesc,
):
return Vec3sArrayResource(
file,
resource_desc.offset,
resource_desc.symbol_name,
resource_desc.count,
)
def s16_array_resource_handler(
file: File,
resource_desc: n64resources.S16ArrayResourceDesc,
):
return S16ArrayResource(
file,
resource_desc.offset,
resource_desc.symbol_name,
resource_desc.count,
)
def vtx_array_resource_handler(
file: File,
resource_desc: n64resources.VtxArrayResourceDesc,
):
return dlist_resources.VtxArrayResource(
file,
resource_desc.offset,
(
resource_desc.offset
+ (
resource_desc.count
* dlist_resources.VtxArrayResource.element_cdata_ext.size
)
),
resource_desc.symbol_name,
)
def binary_blob_resource_handler(
file: File,
resource_desc: n64resources.BlobResourceDesc,
):
return BinaryBlobResource(
file,
resource_desc.offset,
resource_desc.offset + resource_desc.size,
resource_desc.symbol_name,
)
def CurveAnimation_handler(
file: File,
resource_desc: z64resources.CurveAnimationResourceDesc,
):
# TODO use resource_desc.skeleton
return skelcurve_resources.CurveAnimationHeaderResource(
file, resource_desc.offset, resource_desc.symbol_name
)
def Mtx_handler(
file: File,
resource_desc: n64resources.MtxResourceDesc,
):
return dlist_resources.MtxResource(
file, resource_desc.offset, resource_desc.symbol_name
)
def cutscene_resource_handler(
file: File,
resource_desc: z64resources.CutsceneResourceDesc,
):
return misc_resources.CutsceneResource(
file, resource_desc.offset, resource_desc.symbol_name
)
def scene_resource_handler(
file: File,
resource_desc: z64resources.SceneResourceDesc,
):
return scene_commands_resource.SceneCommandsResource(
file, resource_desc.offset, resource_desc.symbol_name
)
def room_resource_handler(
file: File,
resource_desc: z64resources.RoomResourceDesc,
):
if "hackmode_syotes_room" in resource_desc.hack_modes:
# TODO
return BinaryBlobResource(
file,
resource_desc.offset,
resource_desc.offset + 4,
resource_desc.symbol_name,
)
return scene_commands_resource.SceneCommandsResource(
file, resource_desc.offset, resource_desc.symbol_name
)
def path_list_resource_handler(
file: File,
resource_desc: z64resources.PathListResourceDesc,
):
resource = scene_rooms_resources.PathListResource(
file, resource_desc.offset, resource_desc.symbol_name
)
resource.set_length(resource_desc.num_paths)
return resource
RESOURCE_HANDLERS.update(
{
z64resources.SkeletonResourceDesc: skeleton_resource_handler,
z64resources.LimbResourceDesc: limb_resource_handler,
z64resources.AnimationResourceDesc: animation_resource_handler,
z64resources.CollisionResourceDesc: collision_resource_handler,
n64resources.DListResourceDesc: dlist_resource_handler,
n64resources.TextureResourceDesc: texture_resource_handler,
n64resources.CITextureResourceDesc: ci_texture_resource_handler,
z64resources.PlayerAnimationDataResourceDesc: PlayerAnimationData_handler,
n64resources.Vec3sArrayResourceDesc: vec3s_array_resource_handler,
n64resources.S16ArrayResourceDesc: s16_array_resource_handler,
n64resources.VtxArrayResourceDesc: vtx_array_resource_handler,
z64resources.PlayerAnimationResourceDesc: PlayerAnimation_handler,
n64resources.BlobResourceDesc: binary_blob_resource_handler,
n64resources.MtxResourceDesc: Mtx_handler,
z64resources.LegacyAnimationResourceDesc: get_fixed_size_resource_handler(
0xC
), # TODO
z64resources.LimbTableResourceDesc: get_fixed_size_resource_handler(
# idk, probably an array
4
), # TODO
z64resources.CurveAnimationResourceDesc: CurveAnimation_handler,
z64resources.SceneResourceDesc: scene_resource_handler,
z64resources.RoomResourceDesc: room_resource_handler,
z64resources.PathListResourceDesc: path_list_resource_handler,
z64resources.CutsceneResourceDesc: cutscene_resource_handler,
}
)
RESOURCE_HANDLERS: dict[str, ResourceHandler] = {}
def get_resource_from_desc(
file: File,
resource_desc: ResourceDesc,
) -> Resource:
resource_handler = RESOURCE_HANDLERS.get(type(resource_desc))
if resource_handler is None:
raise Exception("Unknown resource descriptor type", resource_desc)
try:
resource = resource_handler(file, resource_desc)
except ResourceHandlerException:
raise
return resource

50
tools/assets/n64.py Normal file
View file

@ -0,0 +1,50 @@
import enum
class G_IM_FMT(enum.Enum):
RGBA = 0
YUV = 1
CI = 2
IA = 3
I = 4
def __init__(self, i: int):
self.i = i
by_i: dict[int, "G_IM_FMT"]
G_IM_FMT.by_i = {fmt.i: fmt for fmt in G_IM_FMT}
class G_IM_SIZ(enum.Enum):
_4b = (0, 4)
_8b = (1, 8)
_16b = (2, 16)
_32b = (3, 32)
def __init__(self, i: int, bpp: int):
self.i = i
self.bpp = bpp
by_i: dict[int, "G_IM_SIZ"]
G_IM_SIZ.by_i = {siz.i: siz for siz in G_IM_SIZ}
G_MDSFT_TEXTLUT = 14
class G_TT(enum.Enum):
NONE = 0b00 << G_MDSFT_TEXTLUT
RGBA16 = 0b10 << G_MDSFT_TEXTLUT
IA16 = 0b11 << G_MDSFT_TEXTLUT
def __init__(self, i: int):
self.i = i
by_i: dict[int, "G_TT"]
G_TT.by_i = {tt.i: tt for tt in G_TT}

203
tools/assets/n64yatc.py Normal file
View file

@ -0,0 +1,203 @@
# n64 yet another texture converter
# TODO replace with a C implementation
# FIXME use all tools as packages or idk
try:
from .n64 import G_IM_FMT, G_IM_SIZ
except ImportError:
from n64 import G_IM_FMT, G_IM_SIZ
def convert(
data: bytes,
from_fmt: G_IM_FMT,
from_siz: G_IM_SIZ,
to_fmt: G_IM_FMT,
to_siz: G_IM_SIZ,
):
if from_fmt == to_fmt == G_IM_FMT.CI:
if from_siz == to_siz:
return bytes(data)
elif from_siz == G_IM_SIZ._4b and to_siz == G_IM_SIZ._8b:
data_ci4 = data
data_ci8 = bytearray(len(data_ci4) * 2)
i = 0
for d in data_ci4:
data_ci8[i] = d >> 4
i += 1
data_ci8[i] = d & 0xF
i += 1
return data_ci8
elif from_siz == G_IM_SIZ._8b and to_siz == G_IM_SIZ._4b:
data_ci8 = data
assert len(data_ci8) % 2 == 0
data_ci4 = bytearray(len(data_ci8) // 2)
i = 0
while i < len(data_ci8):
d1, d2 = data_ci8[i : i + 2]
assert d1 < 16
assert d2 < 16
data_ci4[i // 2] = (d1 << 4) | d2
i += 2
return data_ci4
else:
raise NotImplementedError("ci", from_siz, to_siz)
else:
assert G_IM_FMT.CI not in (
from_fmt,
to_fmt,
), "Can't convert between a CI format and a non-CI format"
assert (
len(data) * 8 % from_siz.bpp == 0
), "data size is not a multiple of pixel size"
n_pixels = len(data) * 8 // from_siz.bpp
data_rgba32 = bytearray(4 * n_pixels)
_i = [0]
def push():
i = _i[0]
data_rgba32[4 * i : 4 * i + 4] = (r, g, b, a)
i += 1
_i[0] = i
f = (from_fmt, from_siz)
if f == (G_IM_FMT.RGBA, G_IM_SIZ._32b):
data_rgba32[:] = data
elif f == (G_IM_FMT.RGBA, G_IM_SIZ._16b):
while (i := _i[0]) < n_pixels:
d1, d2 = data[i * 2 : i * 2 + 2]
r = (d1 & 0b1111_1000) >> 3
g = ((d1 & 0b0000_0111) << 2) | ((d2 & 0b1100_0000) >> 6)
b = (d2 & 0b0011_1110) >> 1
a = d2 & 1
r = (r << 3) | (r >> 2)
g = (g << 3) | (g >> 2)
b = (b << 3) | (b >> 2)
a = a * 255
push()
elif f == (G_IM_FMT.IA, G_IM_SIZ._16b):
while (i := _i[0]) < n_pixels:
d1, d2 = data[i * 2 : i * 2 + 2]
i = d1
a = d2
r = i
g = i
b = i
a = a
push()
elif f == (G_IM_FMT.IA, G_IM_SIZ._8b):
while (i := _i[0]) < n_pixels:
d = data[i]
i = d >> 4
a = d & 0x0F
i = (i << 4) | i
a = (a << 4) | a
r = i
g = i
b = i
a = a
push()
elif f == (G_IM_FMT.IA, G_IM_SIZ._4b):
while (i := _i[0]) < n_pixels:
assert i % 2 == 0
d = data[i // 2]
for dh in (d >> 4, d & 0xF):
i = dh >> 1
a = dh & 1
i = (i << 5) | (i << 2) | (i >> 1)
a = a * 255
r = i
g = i
b = i
a = a
push()
elif f == (G_IM_FMT.I, G_IM_SIZ._8b):
while (i := _i[0]) < n_pixels:
d = data[i]
i = d
r = i
g = i
b = i
a = i
push()
elif f == (G_IM_FMT.I, G_IM_SIZ._4b):
while (i := _i[0]) < n_pixels:
assert i % 2 == 0
d = data[i // 2]
for dh in (d >> 4, d & 0xF):
i = dh
i = (i << 4) | i
r = i
g = i
b = i
a = i
push()
else:
raise NotImplementedError("from", from_fmt, from_siz)
assert n_pixels * to_siz.bpp % 8 == 0
data_to = bytearray(n_pixels * to_siz.bpp // 8)
for i in range(n_pixels):
r, g, b, a = data_rgba32[i * 4 : i * 4 + 4]
y = round(sum((r, g, b)) / 3) # bad but w/e
t = (to_fmt, to_siz)
if t == (G_IM_FMT.RGBA, G_IM_SIZ._32b):
data_to[4 * i : 4 * i + 4] = r, g, b, a
elif t == (G_IM_FMT.RGBA, G_IM_SIZ._16b):
r = r >> 3
g = g >> 3
b = b >> 3
a = round(a / 255)
d1 = (r << 3) | (g >> 2)
d2 = ((g & 0b11) << 6) | (b << 1) | a
data_to[2 * i : 2 * i + 2] = d1, d2
elif t == (G_IM_FMT.IA, G_IM_SIZ._16b):
a = a
d1 = y
d2 = a
data_to[i * 2 : i * 2 + 2] = d1, d2
elif t == (G_IM_FMT.IA, G_IM_SIZ._8b):
y = y >> 4
a = a >> 4
d = (y << 4) | a
data_to[i] = d
elif t == (G_IM_FMT.IA, G_IM_SIZ._4b):
y = y >> 5
a = round(a / 255)
d = (y << 1) | a
data_to[i // 2] |= (d << 4) if i % 2 == 0 else d
elif t == (G_IM_FMT.I, G_IM_SIZ._8b):
d = y
data_to[i] = d
elif t == (G_IM_FMT.I, G_IM_SIZ._4b):
y = y >> 4
d = y
data_to[i // 2] |= (d << 4) if i % 2 == 0 else d
else:
raise NotImplementedError("to", to_fmt, to_siz)
return data_to

5
tools/assets/png2raw/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
__pycache__/
test_out_folder/
png2raw
png2raw_main
raw2png

View file

@ -0,0 +1,28 @@
CFLAGS := -Wall -O2 -g
default: png2raw raw2png
clean:
$(RM) png2raw raw2png png2raw_main
.PHONY: default clean
png2raw: png2raw.c
$(CC) -shared -fPIC $(CFLAGS) -o $@ $< `pkg-config --libs --cflags libpng`
raw2png: raw2png.c
$(CC) -shared -fPIC $(CFLAGS) -o $@ $< `pkg-config --libs --cflags libpng`
png2raw_main: png2raw.c
$(CC) $(CFLAGS) -o $@ $< `pkg-config --libs --cflags libpng`
raw2terminal := python3 raw2terminal.py
png := /home/dragorn421/Documents/oot/assets/objects/object_link_boy/eyes_shock.ci8.png
test: png2raw_main
./png2raw_main $(png) | $(raw2terminal)
./png2raw_main --rgba32 $(png) | $(raw2terminal)
./png2raw_main --palette-rgba32 $(png) | $(raw2terminal)
./png2raw_main --palette-indices $(png) | $(raw2terminal)
./png2raw_main --palette-indices $(png) | $(raw2terminal) --format=i8
./png2raw_main --dimensions $(png)

View file

@ -0,0 +1,678 @@
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <png.h>
#include "png2raw.h"
void user_error_fn(png_structp png_ptr, png_const_charp error_msg) {
fprintf(stderr, "[png2raw] error: %s\n", error_msg);
abort(); //! TODO ... I guess don't do this (bad for use as library) but idk what, then
}
void user_warning_fn(png_structp png_ptr, png_const_charp warning_msg) {
fprintf(stderr, "[png2raw] warning: %s\n", warning_msg);
}
#define RGBA32_PIXEL_SIZE 4
enum png2raw_instance_state {
PNG2RAW_INSTANCE_STATE_CLEARED, // brand new
PNG2RAW_INSTANCE_STATE_FILE_SET, // fp is set
PNG2RAW_INSTANCE_STATE_BAD_FILE, // fp is set but couldn't be read or not a png
PNG2RAW_INSTANCE_STATE_HEADER_READ, // header has been read from fp
PNG2RAW_INSTANCE_STATE_LIBPNG_STRUCTS_ALLOCATED, // png_ptr and info_ptr are set
PNG2RAW_INSTANCE_STATE_INFO_READ, // png_read_info called
PNG2RAW_INSTANCE_STATE_READ_RGBA32_INITIALIZED, // setup for reading rgba32 has happened
PNG2RAW_INSTANCE_STATE_READ_PALETTED_INITIALIZED, // png is paletted, and setup for reading as paletted has happened
PNG2RAW_INSTANCE_STATE_INVALID // can't be used for anything more (besides freeing)
};
struct png2raw_instance {
enum png2raw_instance_state state;
FILE* fp;
png_structp png_ptr;
png_infop info_ptr;
};
typedef struct png2raw_instance* png2raw_instancep;
png2raw_instancep png2raw_instance_new() {
png2raw_instancep inst = malloc(sizeof(struct png2raw_instance));
if (inst == NULL) {
return NULL;
}
inst->state = PNG2RAW_INSTANCE_STATE_CLEARED;
inst->png_ptr = NULL;
inst->info_ptr = NULL;
return inst;
}
void png2raw_instance_free(png2raw_instancep inst) {
if (inst == NULL) {
return;
}
png_destroy_read_struct(&inst->png_ptr, &inst->info_ptr, (png_infopp)NULL);
fclose(inst->fp);
free(inst);
}
const char* error_messages[] = {
[PNG2RAW_ERR_OK] = "OK",
[PNG2RAW_ERR_INST_NULL] = "inst == NULL",
[PNG2RAW_ERR_INST_BAD_STATE] = "Bad inst state",
[PNG2RAW_ERR_CANT_OPEN_FOR_READING] = "Can't open the file for reading",
[PNG2RAW_ERR_CANT_READ_HEADER_BYTES] = "Can't read header bytes from the file",
[PNG2RAW_ERR_NOT_A_PNG] = "File is not a png",
[PNG2RAW_ERR_LIBPNG_PNG_PTR_NULL] = "png_create_read_struct returned NULL",
[PNG2RAW_ERR_LIBPNG_INFO_PTR_NULL] = "png_create_info_struct returned NULL",
[PNG2RAW_ERR_BUFFER_SIZE_P_NULL] = "pointer argument to buffer size is NULL",
[PNG2RAW_ERR_BAD_BUFFER_SIZE] = "buffer size argument is not the required size"
" (the value pointed to by the pointed argument has been set to the required size)",
[PNG2RAW_ERR_BUFFER_NULL] = "buffer pointer argument is NULL",
[PNG2RAW_ERR_PNG_IS_NOT_PALETTED] = "image is not paletted",
};
const char* png2raw_get_error_message(enum png2raw_error err) {
if (err < 0 || err >= PNG2RAW_ERR_MAX) {
return "png2raw_get_error_message: bad err value";
}
const char* msg = error_messages[err];
if (msg == NULL) {
return "png2raw_get_error_message: missing message for err value";
}
return msg;
}
enum png2raw_error png2raw_set_file_name(png2raw_instancep inst, const char* file_name) {
if (inst == NULL) {
return PNG2RAW_ERR_INST_NULL;
}
if (inst->state != PNG2RAW_INSTANCE_STATE_CLEARED) {
return PNG2RAW_ERR_INST_BAD_STATE;
}
FILE* fp = fopen(file_name, "rb");
if (fp == NULL) {
return PNG2RAW_ERR_CANT_OPEN_FOR_READING;
}
inst->fp = fp;
inst->state = PNG2RAW_INSTANCE_STATE_FILE_SET;
return PNG2RAW_ERR_OK;
}
enum png2raw_error png2raw_read_info(png2raw_instancep inst) {
if (inst == NULL) {
return PNG2RAW_ERR_INST_NULL;
}
if (inst->state != PNG2RAW_INSTANCE_STATE_FILE_SET) {
return PNG2RAW_ERR_INST_BAD_STATE;
}
FILE* fp = inst->fp;
// Check png signature
int num_header_bytes = 8;
uint8_t header[num_header_bytes];
if (fread(header, 1, num_header_bytes, fp) != num_header_bytes) {
inst->state = PNG2RAW_INSTANCE_STATE_BAD_FILE;
return PNG2RAW_ERR_CANT_READ_HEADER_BYTES;
}
bool is_png = !png_sig_cmp(header, 0, num_header_bytes);
if (!is_png) {
inst->state = PNG2RAW_INSTANCE_STATE_BAD_FILE;
return PNG2RAW_ERR_NOT_A_PNG;
}
inst->state = PNG2RAW_INSTANCE_STATE_HEADER_READ;
// Initialize png reading
// Set our own error callback, to avoid having to use the longjmp mechanism libpng uses by default.
//! TODO this is actually very bad behavior as a utility/library function
// While we are at it, also set the warning callback.
void* user_error_ptr = NULL;
png_structp png_ptr =
png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)user_error_ptr, user_error_fn, user_warning_fn);
if (png_ptr == NULL) {
return PNG2RAW_ERR_LIBPNG_PNG_PTR_NULL;
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL) {
png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
return PNG2RAW_ERR_LIBPNG_INFO_PTR_NULL;
}
inst->png_ptr = png_ptr;
inst->info_ptr = info_ptr;
inst->state = PNG2RAW_INSTANCE_STATE_LIBPNG_STRUCTS_ALLOCATED;
png_init_io(png_ptr, fp);
// Indicate the stream was read for the signature bytes
// (for passing to png_sig_cmp above)
png_set_sig_bytes(png_ptr, num_header_bytes);
png_read_info(png_ptr, info_ptr);
inst->state = PNG2RAW_INSTANCE_STATE_INFO_READ;
return PNG2RAW_ERR_OK;
}
enum png2raw_error png2raw_get_dimensions(png2raw_instancep inst, uint32_t* widthp, uint32_t* heightp) {
if (inst == NULL) {
return PNG2RAW_ERR_INST_NULL;
}
// This may be done in more states but whatever
if (inst->state != PNG2RAW_INSTANCE_STATE_INFO_READ) {
return PNG2RAW_ERR_INST_BAD_STATE;
}
png_structp png_ptr = inst->png_ptr;
png_infop info_ptr = inst->info_ptr;
png_uint_32 width = png_get_image_width(png_ptr, info_ptr);
png_uint_32 height = png_get_image_height(png_ptr, info_ptr);
if (widthp != NULL) {
*widthp = width;
}
if (heightp != NULL) {
*heightp = height;
}
return PNG2RAW_ERR_OK;
}
enum png2raw_error png2raw_read_rgba32_init(png2raw_instancep inst) {
if (inst == NULL) {
return PNG2RAW_ERR_INST_NULL;
}
if (inst->state != PNG2RAW_INSTANCE_STATE_INFO_READ) {
return PNG2RAW_ERR_INST_BAD_STATE;
}
png_structp png_ptr = inst->png_ptr;
png_infop info_ptr = inst->info_ptr;
// gamma / alpha stuff : no idea
// (would be before or after png_read_info : idk)
// PNG_ALPHA_PNG
// png_set_alpha_mode(pp, PNG_ALPHA_STANDARD, PNG_GAMMA_LINEAR); // idk
// not sure where png_set_alpha_mode should be anyway (probably with all the other png_set_ s)
/*
Based on:
1) The PNG file gamma from the gAMA chunk. This overwrites the default value
provided by an earlier call to png_set_gamma or png_set_alpha_mode.
calling set_alpha_mode may do more harm than good idk
*/
// Indicate transform operations to be performed
// (it is unclear if the order matters)
png_set_palette_to_rgb(png_ptr); // Convert paletted to RGB if needed
png_set_gray_to_rgb(png_ptr); // Convert grayscale to RGB if needed
png_set_expand(png_ptr); // Convert to 24-bit RGB if needed
png_set_scale_16(png_ptr); // Convert from 16 bits per channel to 8 if needed
png_set_add_alpha(png_ptr, 255, PNG_FILLER_AFTER); // Ensure there is a alpha channel, defaulting to opaque
/*
* From the manual:
*
* > if you are going to call png_read_update_info() you must call png_set_interlace_handling() before it unless you
* > want to receive interlaced output
*/
png_set_interlace_handling(png_ptr);
// (I don't know if I need to call this)
png_read_update_info(png_ptr, info_ptr);
inst->state = PNG2RAW_INSTANCE_STATE_READ_RGBA32_INITIALIZED;
return PNG2RAW_ERR_OK;
}
enum png2raw_error png2raw_read_rgba32(png2raw_instancep inst, size_t* image_data_size_p, uint8_t* image_data) {
if (inst == NULL) {
return PNG2RAW_ERR_INST_NULL;
}
if (inst->state != PNG2RAW_INSTANCE_STATE_READ_RGBA32_INITIALIZED) {
return PNG2RAW_ERR_INST_BAD_STATE;
}
png_structp png_ptr = inst->png_ptr;
png_infop info_ptr = inst->info_ptr;
png_uint_32 height = png_get_image_height(png_ptr, info_ptr);
png_uint_32 width = png_get_image_width(png_ptr, info_ptr);
// libpng allows the buffer to be allocated row-by-row, just use one whole block for simplicity
size_t required_image_data_size = height * width * RGBA32_PIXEL_SIZE;
if (image_data_size_p == NULL) {
return PNG2RAW_ERR_BUFFER_SIZE_P_NULL;
}
if (*image_data_size_p != required_image_data_size) {
// Pass the required buffer size to the caller
*image_data_size_p = required_image_data_size;
return PNG2RAW_ERR_BAD_BUFFER_SIZE;
}
if (image_data == NULL) {
return PNG2RAW_ERR_BUFFER_NULL;
}
// Set the row pointers to the right locations in the image_data buffer
png_bytep row_pointers[height];
for (uint32_t row = 0; row < height; row++) {
row_pointers[row] = image_data + row * width * RGBA32_PIXEL_SIZE;
}
// Read png image
png_read_image(png_ptr, row_pointers);
png_read_end(png_ptr, (png_infop)NULL);
inst->state = PNG2RAW_INSTANCE_STATE_INVALID;
return PNG2RAW_ERR_OK;
}
enum png2raw_error png2raw_is_paletted(png2raw_instancep inst, bool* is_paletted_p) {
if (inst == NULL) {
return PNG2RAW_ERR_INST_NULL;
}
if (inst->state != PNG2RAW_INSTANCE_STATE_INFO_READ) {
return PNG2RAW_ERR_INST_BAD_STATE;
}
png_structp png_ptr = inst->png_ptr;
png_infop info_ptr = inst->info_ptr;
png_byte color_type = png_get_color_type(png_ptr, info_ptr);
*is_paletted_p = color_type == PNG_COLOR_TYPE_PALETTE;
return PNG2RAW_ERR_OK;
}
/*
* Some information on paletted png files:
*
* - They are at most 8 bits per pixel. The pixel data is a palette index starting at 0.
*
* - The palette is stored in a mandatory "PLTE chunk", holding an array of 24-bits RRGGBB colors, indexed by
* palette index.
*
* - Transparency is optional. If any, it is stored in a "tRNS chunk", holding an array of 8-bits AA values, indexed
* by palette index.
*
* Note the following from the PNG specification:
*
* > tRNS can contain fewer values than there are palette entries.
* > In this case, the alpha value for all remaining palette entries is assumed to be 255.
*
* - Additional notes:
* - sPLT "Suggested palette" is unrelated to paletted png files
*/
enum png2raw_error png2raw_get_palette_colors_rgba32(png2raw_instancep inst, size_t* palette_data_size_p,
uint8_t* palette_data) {
if (inst == NULL) {
return PNG2RAW_ERR_INST_NULL;
}
if (inst->state != PNG2RAW_INSTANCE_STATE_INFO_READ) {
// Palette colors may very well be read later, after the image data (palette indices) were read, but just don't
// support that.
return PNG2RAW_ERR_INST_BAD_STATE;
}
png_structp png_ptr = inst->png_ptr;
png_infop info_ptr = inst->info_ptr;
png_byte color_type = png_get_color_type(png_ptr, info_ptr);
if (color_type != PNG_COLOR_TYPE_PALETTE) {
return PNG2RAW_ERR_PNG_IS_NOT_PALETTED;
}
png_colorp palette;
int num_palette;
if (!png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette)) {
// No PLTE chunk.
// I think this is not supposed to happen, since at this point the image is confirmed to be of the paletted
// type, and the specification says the PLTE chunk must come before IDAT, and the manual says png_read_info
// processes all chunk up to the image data (IDAT).
png_error(png_ptr, "No PLTE chunk in palette type png");
}
png_bytep trans_alpha;
int num_trans;
png_color_16p trans_color;
if (!png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &num_trans, &trans_color)) {
// No tRNS chunk.
// Palette indices past the tRNS length are opaque, so making it "0-length" here is equivalent to having all
// palette indices opaque.
num_trans = 0;
}
size_t required_palette_data_size = num_palette * RGBA32_PIXEL_SIZE;
if (palette_data_size_p == NULL) {
return PNG2RAW_ERR_BUFFER_SIZE_P_NULL; //! TODO rename error or add new enum values (this and below)
}
if (*palette_data_size_p != required_palette_data_size) {
// Pass the required buffer size to the caller
*palette_data_size_p = required_palette_data_size;
return PNG2RAW_ERR_BAD_BUFFER_SIZE;
}
if (palette_data == NULL) {
return PNG2RAW_ERR_BUFFER_NULL;
}
for (int palette_index = 0; palette_index < num_palette; palette_index++) {
png_colorp rgb = &palette[palette_index];
png_byte a;
if (palette_index < num_trans) {
a = trans_alpha[palette_index];
} else {
// Palette indices past the tRNS length are opaque.
a = 255;
}
uint8_t* out_rgba = palette_data + palette_index * RGBA32_PIXEL_SIZE;
out_rgba[0] = rgb->red;
out_rgba[1] = rgb->green;
out_rgba[2] = rgb->blue;
out_rgba[3] = a;
}
return PNG2RAW_ERR_OK;
}
enum png2raw_error png2raw_read_palette_indices_init(png2raw_instancep inst) {
if (inst == NULL) {
return PNG2RAW_ERR_INST_NULL;
}
if (inst->state != PNG2RAW_INSTANCE_STATE_INFO_READ) {
return PNG2RAW_ERR_INST_BAD_STATE;
}
png_structp png_ptr = inst->png_ptr;
png_infop info_ptr = inst->info_ptr;
png_byte color_type = png_get_color_type(png_ptr, info_ptr);
if (color_type != PNG_COLOR_TYPE_PALETTE) {
return PNG2RAW_ERR_PNG_IS_NOT_PALETTED;
}
// Pack one pixel per byte, instead of possibly several pixels per byte.
// This effectively means converting from however-many bits per pixel, to 8 bits per pixel.
png_set_packing(png_ptr);
png_set_interlace_handling(png_ptr);
png_read_update_info(png_ptr, info_ptr);
inst->state = PNG2RAW_INSTANCE_STATE_READ_PALETTED_INITIALIZED;
return PNG2RAW_ERR_OK;
}
enum png2raw_error png2raw_read_palette_indices(png2raw_instancep inst, size_t* image_data_size_p,
uint8_t* image_data) {
// This function is mostly a copy-paste from png2raw_read_rgba32
if (inst == NULL) {
return PNG2RAW_ERR_INST_NULL;
}
if (inst->state != PNG2RAW_INSTANCE_STATE_READ_PALETTED_INITIALIZED) {
return PNG2RAW_ERR_INST_BAD_STATE;
}
png_structp png_ptr = inst->png_ptr;
png_infop info_ptr = inst->info_ptr;
png_uint_32 height = png_get_image_height(png_ptr, info_ptr);
png_uint_32 width = png_get_image_width(png_ptr, info_ptr);
// One byte per pixel
size_t required_image_data_size = height * width;
if (image_data_size_p == NULL) {
return PNG2RAW_ERR_BUFFER_SIZE_P_NULL;
}
if (*image_data_size_p != required_image_data_size) {
*image_data_size_p = required_image_data_size;
return PNG2RAW_ERR_BAD_BUFFER_SIZE;
}
if (image_data == NULL) {
return PNG2RAW_ERR_BUFFER_NULL;
}
png_bytep row_pointers[height];
for (uint32_t row = 0; row < height; row++) {
row_pointers[row] = image_data + row * width;
}
png_read_image(png_ptr, row_pointers);
png_read_end(png_ptr, (png_infop)NULL);
inst->state = PNG2RAW_INSTANCE_STATE_INVALID;
return PNG2RAW_ERR_OK;
}
// main (example usage)
enum png2raw_main_mode {
/**
* Read a png and write its pixels to stdout in rgba32 format.
*
* For example if a png is one pixel high, three pixels wide, with
*
* - left pixel: opaque red
* - middle pixel: opaque yellow
* - right pixel: half-transparent blue
*
* the following bytes will be written to stdout (`RR GG BB AA` for each pixel):
*
* FF 00 00 FF FF FF 00 FF 00 00 FF 80
*
* (as binary, not as this textual representation)
*/
PNG2RAW_MAIN_MODE_RGBA32,
/**
* Read a paletted png and write its palette to stdout in rgba32 format, similarly to PNG2RAW_MAIN_MODE_RGBA32.
*/
PNG2RAW_MAIN_MODE_PALETTE_RGBA32,
/**
* Read a paletted png and write its image data to stdout, with one palette index per byte for each pixel.
*/
PNG2RAW_MAIN_MODE_PALETTE_INDICES,
/**
* Read a png and write its width and height to stdout (as text).
*/
PNG2RAW_MAIN_MODE_DIMENSIONS
};
int main(int argc, char** argv) {
if (!(argc == 2 || argc == 3)) {
bad_usage:
fprintf(stderr,
"Usage: %s [--rgba32 | --palette-rgba32 | --palette-indices | --dimensions] <path/to/image.png>\n",
argv[0]);
return EXIT_FAILURE;
}
enum png2raw_main_mode main_mode;
if (argc == 2) {
main_mode = PNG2RAW_MAIN_MODE_RGBA32;
} else {
main_mode = -1;
char* main_mode_arg = argv[1];
const char* modes_arg[] = {
[PNG2RAW_MAIN_MODE_RGBA32] = "--rgba32",
[PNG2RAW_MAIN_MODE_PALETTE_RGBA32] = "--palette-rgba32",
[PNG2RAW_MAIN_MODE_PALETTE_INDICES] = "--palette-indices",
[PNG2RAW_MAIN_MODE_DIMENSIONS] = "--dimensions",
};
for (int i = 0; i < 4; i++) {
if (strcmp(main_mode_arg, modes_arg[i]) == 0) {
main_mode = i;
break;
}
}
if (main_mode < 0) {
goto bad_usage;
}
}
char* file_name = argv[argc - 1];
png2raw_instancep inst = png2raw_instance_new();
enum png2raw_error err;
#define HANDLE_ERR(err) \
if (err != PNG2RAW_ERR_OK) { \
fprintf(stderr, "png2raw main() %d err=%d %s\n", __LINE__, err, png2raw_get_error_message(err)); \
return EXIT_FAILURE; \
}
err = png2raw_set_file_name(inst, file_name);
HANDLE_ERR(err)
err = png2raw_read_info(inst);
HANDLE_ERR(err)
switch (main_mode) {
case PNG2RAW_MAIN_MODE_RGBA32: {
err = png2raw_read_rgba32_init(inst);
HANDLE_ERR(err)
size_t image_data_size = 0;
err = png2raw_read_rgba32(inst, &image_data_size, NULL);
if (err != PNG2RAW_ERR_BAD_BUFFER_SIZE) {
HANDLE_ERR(err)
}
if (err == PNG2RAW_ERR_OK) {
fprintf(stderr, "Expected PNG2RAW_ERR_BAD_BUFFER_SIZE but got PNG2RAW_ERR_OK\n");
return EXIT_FAILURE;
}
uint8_t* image_data = malloc(image_data_size);
err = png2raw_read_rgba32(inst, &image_data_size, image_data);
HANDLE_ERR(err)
// Note png2raw_instance_free could be called here, as image_data is owned by us.
fwrite(image_data, 1, image_data_size, stdout);
free(image_data);
} break;
case PNG2RAW_MAIN_MODE_PALETTE_RGBA32: {
size_t palette_data_size = 0;
err = png2raw_get_palette_colors_rgba32(inst, &palette_data_size, NULL);
if (err != PNG2RAW_ERR_BAD_BUFFER_SIZE) {
HANDLE_ERR(err)
}
if (err == PNG2RAW_ERR_OK) {
fprintf(stderr, "Expected PNG2RAW_ERR_BAD_BUFFER_SIZE but got PNG2RAW_ERR_OK\n");
return EXIT_FAILURE;
}
uint8_t* palette_data = malloc(palette_data_size);
err = png2raw_get_palette_colors_rgba32(inst, &palette_data_size, palette_data);
HANDLE_ERR(err)
fwrite(palette_data, 1, palette_data_size, stdout);
free(palette_data);
} break;
case PNG2RAW_MAIN_MODE_PALETTE_INDICES: {
err = png2raw_read_palette_indices_init(inst);
HANDLE_ERR(err)
size_t image_data_size = 0;
err = png2raw_read_palette_indices(inst, &image_data_size, NULL);
if (err != PNG2RAW_ERR_BAD_BUFFER_SIZE) {
HANDLE_ERR(err)
}
if (err == PNG2RAW_ERR_OK) {
fprintf(stderr, "Expected PNG2RAW_ERR_BAD_BUFFER_SIZE but got PNG2RAW_ERR_OK\n");
return EXIT_FAILURE;
}
uint8_t* image_data = malloc(image_data_size);
err = png2raw_read_palette_indices(inst, &image_data_size, image_data);
HANDLE_ERR(err)
fwrite(image_data, 1, image_data_size, stdout);
free(image_data);
} break;
case PNG2RAW_MAIN_MODE_DIMENSIONS: {
uint32_t width, height;
err = png2raw_get_dimensions(inst, &width, &height);
HANDLE_ERR(err)
fprintf(stdout, "%d %d\n", width, height);
} break;
}
png2raw_instance_free(inst);
return EXIT_SUCCESS;
}

View file

@ -0,0 +1,72 @@
#ifndef PNG2RAW_H
#define PNG2RAW_H
/**
* Most functions return a png2raw_error value. If that value is PNG2RAW_ERR_OK, it means the function completed
* successfully. Otherwise something went wrong (such as passing a wrong argument). See png2raw_get_error_message for
* getting a message from an error value.
*/
enum png2raw_error {
PNG2RAW_ERR_OK, // no error
PNG2RAW_ERR_INST_NULL,
PNG2RAW_ERR_INST_BAD_STATE,
PNG2RAW_ERR_CANT_OPEN_FOR_READING,
PNG2RAW_ERR_CANT_READ_HEADER_BYTES,
PNG2RAW_ERR_NOT_A_PNG,
PNG2RAW_ERR_LIBPNG_PNG_PTR_NULL,
PNG2RAW_ERR_LIBPNG_INFO_PTR_NULL,
PNG2RAW_ERR_BUFFER_SIZE_P_NULL,
PNG2RAW_ERR_BAD_BUFFER_SIZE,
PNG2RAW_ERR_BUFFER_NULL,
PNG2RAW_ERR_PNG_IS_NOT_PALETTED,
PNG2RAW_ERR_MAX
};
struct png2raw_instance;
/**
* Most functions take a png2raw_instancep argument. It stores the state of reading a single png file.
* See png2raw_instance_new and png2raw_instance_free for constructing/freeing a new instance.
*/
typedef struct png2raw_instance* png2raw_instancep;
/**
* Should eventually be freed with png2raw_instance_free.
*/
png2raw_instancep png2raw_instance_new();
void png2raw_instance_free(png2raw_instancep inst);
const char* png2raw_get_error_message(enum png2raw_error err);
// Call after creating an instance to set the file
enum png2raw_error png2raw_set_file_name(png2raw_instancep inst, const char* file_name);
// Call after setting the file
enum png2raw_error png2raw_read_info(png2raw_instancep inst);
// These three can only be called right after png2raw_read_info (before the png2raw_read_..._init functions)
enum png2raw_error png2raw_get_dimensions(png2raw_instancep inst, uint32_t* widthp, uint32_t* heightp);
enum png2raw_error png2raw_is_paletted(png2raw_instancep inst, bool* is_paletted_p);
// The last two arguments work the same as those of the png2raw_read_rgba32 function.
enum png2raw_error png2raw_get_palette_colors_rgba32(png2raw_instancep inst, size_t* palette_data_size_p,
uint8_t* palette_data);
// After calling png2raw_read_info, the png2raw_read_..._init functions may be called
enum png2raw_error png2raw_read_rgba32_init(png2raw_instancep inst);
/**
* Store the image pixels as rgba32 into `image_data`.
*
* png2raw_read_rgba32_init must have been called first.
*
* Call with `&image_data_size, NULL` to get the size of the buffer to allocate.
* Expect the error PNG2RAW_ERR_BAD_BUFFER_SIZE to be returned then.
* Then call again with `&image_data_size, image_data`.
*/
enum png2raw_error png2raw_read_rgba32(png2raw_instancep inst, size_t* image_data_size_p, uint8_t* image_data);
enum png2raw_error png2raw_read_palette_indices_init(png2raw_instancep inst);
// The last two arguments work the same as those of the png2raw_read_rgba32 function.
// Can optionally be called after png2raw_read_palette_indices_init, but before png2raw_read_palette_indices
enum png2raw_error png2raw_read_palette_indices(png2raw_instancep inst, size_t* image_data_size_p, uint8_t* image_data);
#endif

View file

@ -0,0 +1,216 @@
from __future__ import annotations
import ctypes
import enum
from pathlib import Path
from typing import Union
png2raw = ctypes.cdll.LoadLibrary(Path(__file__).parent / "png2raw")
# enum png2raw_error { ... };
png2raw_error = ctypes.c_int
class PNG2RAW_ERR(enum.IntEnum):
OK = 0
INST_NULL = enum.auto()
INST_BAD_STATE = enum.auto()
CANT_OPEN_FOR_READING = enum.auto()
CANT_READ_HEADER_BYTES = enum.auto()
NOT_A_PNG = enum.auto()
LIBPNG_PNG_PTR_NULL = enum.auto()
LIBPNG_INFO_PTR_NULL = enum.auto()
BUFFER_SIZE_P_NULL = enum.auto()
BAD_BUFFER_SIZE = enum.auto()
BUFFER_NULL = enum.auto()
PNG_IS_NOT_PALETTED = enum.auto()
MAX = enum.auto()
# struct png2raw_instance;
class png2raw_instance(ctypes.Structure):
pass
# typedef struct png2raw_instance* png2raw_instancep;
png2raw_instancep = ctypes.POINTER(png2raw_instance)
# png2raw_instancep png2raw_instance_new();
png2raw.png2raw_instance_new.restype = png2raw_instancep
png2raw.png2raw_instance_new.argtypes = []
# void png2raw_instance_free(png2raw_instancep inst);
png2raw.png2raw_instance_free.restype = None
png2raw.png2raw_instance_free.argtypes = [png2raw_instancep]
# const char* png2raw_get_error_message(enum png2raw_error err);
png2raw.png2raw_get_error_message.restype = ctypes.POINTER(ctypes.c_char)
png2raw.png2raw_get_error_message.argtypes = [png2raw_error]
# enum png2raw_error png2raw_set_file_name(png2raw_instancep inst, const char* file_name);
png2raw.png2raw_set_file_name.restype = png2raw_error
png2raw.png2raw_set_file_name.argtypes = [
png2raw_instancep,
ctypes.POINTER(ctypes.c_char),
]
# enum png2raw_error png2raw_read_info(png2raw_instancep inst);
png2raw.png2raw_read_info.restype = png2raw_error
png2raw.png2raw_read_info.argtypes = [png2raw_instancep]
# enum png2raw_error png2raw_get_dimensions(png2raw_instancep inst, uint32_t* widthp, uint32_t* heightp);
png2raw.png2raw_get_dimensions.restype = png2raw_error
png2raw.png2raw_get_dimensions.argtypes = [
png2raw_instancep,
ctypes.POINTER(ctypes.c_uint32),
ctypes.POINTER(ctypes.c_uint32),
]
# enum png2raw_error png2raw_is_paletted(png2raw_instancep inst, bool* is_paletted_p);
png2raw.png2raw_is_paletted.restype = png2raw_error
png2raw.png2raw_is_paletted.argtypes = [
png2raw_instancep,
ctypes.POINTER(ctypes.c_bool),
]
# enum png2raw_error png2raw_get_palette_colors_rgba32(png2raw_instancep inst, size_t* palette_data_size_p, uint8_t* palette_data);
png2raw.png2raw_get_palette_colors_rgba32.restype = png2raw_error
png2raw.png2raw_get_palette_colors_rgba32.argtypes = [
png2raw_instancep,
ctypes.POINTER(ctypes.c_size_t),
ctypes.POINTER(ctypes.c_uint8),
]
# enum png2raw_error png2raw_read_rgba32_init(png2raw_instancep inst);
png2raw.png2raw_read_rgba32_init.restype = png2raw_error
png2raw.png2raw_read_rgba32_init.argtypes = [png2raw_instancep]
# enum png2raw_error png2raw_read_rgba32(png2raw_instancep inst, size_t* image_data_size_p, uint8_t* image_data);
png2raw.png2raw_read_rgba32.restype = png2raw_error
png2raw.png2raw_read_rgba32.argtypes = [
png2raw_instancep,
ctypes.POINTER(ctypes.c_size_t),
ctypes.POINTER(ctypes.c_uint8),
]
# enum png2raw_error png2raw_read_palette_indices_init(png2raw_instancep inst);
png2raw.png2raw_read_palette_indices_init.restype = png2raw_error
png2raw.png2raw_read_palette_indices_init.argtypes = [png2raw_instancep]
# enum png2raw_error png2raw_read_palette_indices(png2raw_instancep inst, size_t* image_data_size_p, uint8_t* image_data);
png2raw.png2raw_read_palette_indices.restype = png2raw_error
png2raw.png2raw_read_palette_indices.argtypes = [
png2raw_instancep,
ctypes.POINTER(ctypes.c_size_t),
ctypes.POINTER(ctypes.c_uint8),
]
class Instance:
def __init__(self, file_name: Union[Path, str]):
self.file_name = file_name
def _check_err(self, err: png2raw_error, okay_errors: set[PNG2RAW_ERR] = set()):
if err == PNG2RAW_ERR.OK or err in okay_errors:
return
err_message = png2raw.png2raw_get_error_message(err)
try:
err_enum = PNG2RAW_ERR(err)
except ValueError:
err_enum = None
raise Exception(err, err_enum, ctypes.string_at(err_message).decode())
def __enter__(self):
self._inst = png2raw.png2raw_instance_new()
self._check_err(
png2raw.png2raw_set_file_name(self._inst, str(self.file_name).encode())
)
self._check_err(png2raw.png2raw_read_info(self._inst))
return self
def get_dimensions(self):
width = ctypes.c_uint32(0)
height = ctypes.c_uint32(0)
self._check_err(
png2raw.png2raw_get_dimensions(
self._inst, ctypes.byref(width), ctypes.byref(height)
)
)
return width.value, height.value
def is_paletted(self):
is_paletted_bool = ctypes.c_bool()
self._check_err(
png2raw.png2raw_is_paletted(self._inst, ctypes.byref(is_paletted_bool))
)
return is_paletted_bool.value
def get_palette_rgba32(self):
palette_data_size = ctypes.c_size_t(0)
self._check_err(
png2raw.png2raw_get_palette_colors_rgba32(
self._inst, ctypes.byref(palette_data_size), None
),
okay_errors={PNG2RAW_ERR.BAD_BUFFER_SIZE, PNG2RAW_ERR.BUFFER_NULL},
)
palette_data = (ctypes.c_uint8 * palette_data_size.value)()
self._check_err(
png2raw.png2raw_get_palette_colors_rgba32(
self._inst, ctypes.byref(palette_data_size), palette_data
)
)
return palette_data
def read_to_rgba32(self):
self._check_err(png2raw.png2raw_read_rgba32_init(self._inst))
image_data_size = ctypes.c_size_t(0)
self._check_err(
png2raw.png2raw_read_rgba32(
self._inst, ctypes.byref(image_data_size), None
),
okay_errors={PNG2RAW_ERR.BAD_BUFFER_SIZE, PNG2RAW_ERR.BUFFER_NULL},
)
image_data = (ctypes.c_uint8 * image_data_size.value)()
self._check_err(
png2raw.png2raw_read_rgba32(
self._inst, ctypes.byref(image_data_size), image_data
)
)
return image_data
def read_palette_indices(self):
self._check_err(png2raw.png2raw_read_palette_indices_init(self._inst))
image_data_size = ctypes.c_size_t(0)
self._check_err(
png2raw.png2raw_read_palette_indices(
self._inst, ctypes.byref(image_data_size), None
),
okay_errors={PNG2RAW_ERR.BAD_BUFFER_SIZE, PNG2RAW_ERR.BUFFER_NULL},
)
image_data = (ctypes.c_uint8 * image_data_size.value)()
self._check_err(
png2raw.png2raw_read_palette_indices(
self._inst, ctypes.byref(image_data_size), image_data
)
)
return image_data
def __exit__(self, exc_type, exc_val, exc_tb):
png2raw.png2raw_instance_free(self._inst)
def main():
with Png2rawInstance(
Path(
"/home/dragorn421/Documents/oot/assets/objects/object_link_boy/eyes_shock.ci8.png"
)
) as inst:
pal_rgba32 = inst.get_palette_rgba32()
print(pal_rgba32[:16])
rgba32 = inst.read_to_rgba32()
print(rgba32[:16])
rgba32 = inst.read_palette_indices()
print(rgba32[:16])
if __name__ == "__main__":
main()

View file

@ -0,0 +1,160 @@
#include <stdlib.h>
#include <stdint.h>
#include <png.h>
#include "raw2png.h"
void user_error_fn(png_structp png_ptr, png_const_charp error_msg) {
fprintf(stderr, "[raw2png] error: %s\n", error_msg);
abort(); //! TODO ... I guess don't do this (bad for use as library) but idk what, then
}
void user_warning_fn(png_structp png_ptr, png_const_charp warning_msg) {
fprintf(stderr, "[raw2png] warning: %s\n", warning_msg);
}
#define RGBA32_PIXEL_SIZE 4
const char* error_messages[] = {
[RAW2PNG_ERR_OK] = "OK",
[RAW2PNG_ERR_CANT_OPEN_FOR_WRITING] = "Can't open the file for writing",
[RAW2PNG_ERR_LIBPNG_PNG_PTR_NULL] = "png_create_read_struct returned NULL",
[RAW2PNG_ERR_LIBPNG_INFO_PTR_NULL] = "png_create_info_struct returned NULL",
[RAW2PNG_ERR_BAD_NUM_PALETTE] = "num_palette should be between 1 and 256 (inclusive)",
[RAW2PNG_ERR_DATA_EXCEEDS_NUM_PALETTE] = "image_data contains indices exceeding (>=) num_palette",
};
const char* raw2png_get_error_message(enum raw2png_error err) {
if (err < 0 || err >= RAW2PNG_ERR_MAX) {
return "raw2png_get_error_message: bad err value";
}
const char* msg = error_messages[err];
if (msg == NULL) {
return "raw2png_get_error_message: missing message for err value";
}
return msg;
}
enum raw2png_error raw2png_write(const char* file_name, uint32_t width, uint32_t height, uint8_t* image_data) {
FILE* fp = fopen(file_name, "wb");
if (fp == NULL) {
return RAW2PNG_ERR_CANT_OPEN_FOR_WRITING;
}
void* user_error_ptr = NULL;
png_structp png_ptr =
png_create_write_struct(PNG_LIBPNG_VER_STRING, (png_voidp)user_error_ptr, user_error_fn, user_warning_fn);
if (png_ptr == NULL) {
fclose(fp);
return RAW2PNG_ERR_LIBPNG_PNG_PTR_NULL;
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL) {
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
fclose(fp);
return RAW2PNG_ERR_LIBPNG_INFO_PTR_NULL;
}
png_init_io(png_ptr, fp);
// Write rgba32: 8 bits per pixel, RGBA
png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_bytep row_pointers[height];
for (uint32_t row = 0; row < height; row++) {
row_pointers[row] = image_data + row * width * RGBA32_PIXEL_SIZE;
}
png_set_rows(png_ptr, info_ptr, row_pointers);
png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
// libpng does not close the stream by itself. (experimentally and from its source)
fclose(fp);
return RAW2PNG_ERR_OK;
}
enum raw2png_error raw2png_write_paletted(const char* file_name, uint32_t width, uint32_t height, uint8_t* palette_data,
int num_palette, uint8_t* image_data) {
if (num_palette < 1 || num_palette > 256) {
// Paletted png files are at most 8 bits per pixel,
// so the palette cannot (or doesn't need to?) be larger than 256 colors.
return RAW2PNG_ERR_BAD_NUM_PALETTE;
}
// Check image_data doesn't index outside the palette
// libpng also checks this and will error if needed, so we check ourselves preemptively.
for (uint32_t y = 0; y != height; y++)
for (uint32_t x = 0; x != width; x++)
if (image_data[y * width + x] >= num_palette)
return RAW2PNG_ERR_DATA_EXCEEDS_NUM_PALETTE;
FILE* fp = fopen(file_name, "wb");
if (fp == NULL) {
return RAW2PNG_ERR_CANT_OPEN_FOR_WRITING;
}
void* user_error_ptr = NULL;
png_structp png_ptr =
png_create_write_struct(PNG_LIBPNG_VER_STRING, (png_voidp)user_error_ptr, user_error_fn, user_warning_fn);
if (png_ptr == NULL) {
fclose(fp);
return RAW2PNG_ERR_LIBPNG_PNG_PTR_NULL;
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL) {
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
fclose(fp);
return RAW2PNG_ERR_LIBPNG_INFO_PTR_NULL;
}
png_init_io(png_ptr, fp);
png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
// 1 <= num_palette <= 256 is enforced above
png_color palette[num_palette];
png_byte trans_alpha[num_palette];
// Split the input rgba32 palette in palette_data between RGB (into palette) and A (into trans_alpha)
for (int palette_index = 0; palette_index < num_palette; palette_index++) {
png_color* rgb = &palette[palette_index];
png_byte* a = &trans_alpha[palette_index];
uint8_t* in_rgba = palette_data + palette_index * RGBA32_PIXEL_SIZE;
rgb->red = in_rgba[0];
rgb->green = in_rgba[1];
rgb->blue = in_rgba[2];
// The tRNS chunk could be shortened (or even missing) if the last alpha values are all opaque 255,
// but this storage optimization hinted at by the png specification is in my opinion not worth implementing.
*a = in_rgba[3];
}
png_set_PLTE(png_ptr, info_ptr, palette, num_palette);
// The trans_color argument is only relevant for non-paletted images, pass NULL
png_set_tRNS(png_ptr, info_ptr, trans_alpha, num_palette, NULL);
png_bytep row_pointers[height];
for (uint32_t row = 0; row < height; row++) {
row_pointers[row] = image_data + row * width;
}
png_set_rows(png_ptr, info_ptr, row_pointers);
png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
fclose(fp);
return RAW2PNG_ERR_OK;
}

View file

@ -0,0 +1,21 @@
#ifndef RAW2PNG_H
#define RAW2PNG_H
enum raw2png_error {
RAW2PNG_ERR_OK, // no error
RAW2PNG_ERR_CANT_OPEN_FOR_WRITING,
RAW2PNG_ERR_LIBPNG_PNG_PTR_NULL,
RAW2PNG_ERR_LIBPNG_INFO_PTR_NULL,
RAW2PNG_ERR_BAD_NUM_PALETTE,
RAW2PNG_ERR_DATA_EXCEEDS_NUM_PALETTE,
RAW2PNG_ERR_MAX
};
const char* raw2png_get_error_message(enum raw2png_error err);
enum raw2png_error raw2png_write(const char* file_name, uint32_t width, uint32_t height, uint8_t* image_data);
enum raw2png_error raw2png_write_paletted(const char* file_name, uint32_t width, uint32_t height, uint8_t* palette_data,
int num_palette, uint8_t* image_data);
#endif

View file

@ -0,0 +1,117 @@
from __future__ import annotations
import ctypes
import enum
from pathlib import Path
from typing import Union
raw2png = ctypes.cdll.LoadLibrary(Path(__file__).parent / "raw2png")
# enum raw2png_error { ... };
raw2png_error = ctypes.c_int
class RAW2PNG_ERR(enum.IntEnum):
OK = 0
CANT_OPEN_FOR_WRITING = enum.auto()
LIBPNG_PNG_PTR_NULL = enum.auto()
LIBPNG_INFO_PTR_NULL = enum.auto()
BAD_NUM_PALETTE = enum.auto()
DATA_EXCEEDS_NUM_PALETTE = enum.auto()
MAX = enum.auto()
# const char* raw2png_get_error_message(enum raw2png_error err);
raw2png.raw2png_get_error_message.restype = ctypes.POINTER(ctypes.c_char)
raw2png.raw2png_get_error_message.argtypes = [raw2png_error]
# enum raw2png_error raw2png_write(const char* file_name, uint32_t width, uint32_t height, uint8_t* image_data);
raw2png.raw2png_write.restype = raw2png_error
raw2png.raw2png_write.argtypes = [
ctypes.POINTER(ctypes.c_char),
ctypes.c_uint32,
ctypes.c_uint32,
ctypes.POINTER(ctypes.c_uint8),
]
# enum raw2png_error raw2png_write_paletted(const char* file_name, uint32_t width, uint32_t height, uint8_t* palette_data, int num_palette, uint8_t* image_data);
raw2png.raw2png_write_paletted.restype = raw2png_error
raw2png.raw2png_write_paletted.argtypes = [
ctypes.POINTER(ctypes.c_char),
ctypes.c_uint32,
ctypes.c_uint32,
ctypes.POINTER(ctypes.c_uint8),
ctypes.c_int,
ctypes.POINTER(ctypes.c_uint8),
]
def _check_err(err: raw2png_error, okay_errors: set[RAW2PNG_ERR] = set()):
if err == RAW2PNG_ERR.OK or err in okay_errors:
return
err_message = raw2png.raw2png_get_error_message(err)
try:
err_enum = RAW2PNG_ERR(err)
except ValueError:
err_enum = None
raise Exception(err, err_enum, ctypes.string_at(err_message).decode())
def write(file_name: Union[Path, str], width: int, height: int, image_data):
# About the type image_data should be:
# ctypes._CData.from_buffer docs: "must support the writeable buffer interface"
# - doesn't work with list() (not bytes-like)
# - doesn't work with bytes() (not writable)
# - bytearray() OK
# - (ctypes.c_uint8 * n)() OK
image_data_ctypes_type = (ctypes.c_uint8 * (height * width * 4))
try:
image_data_ctypes = image_data_ctypes_type.from_buffer(image_data)
except TypeError:
print(type(image_data))
raise
_check_err(
raw2png.raw2png_write(str(file_name).encode(), width, height, image_data_ctypes)
)
def write_paletted(
file_name: Union[Path, str],
width: int,
height: int,
palette_data,
num_palette: int,
image_data,
):
# If those are not integers, there would be an error anyway from constructing the ctypes array types below,
# but this way with asserts the raised error is clearer.
assert isinstance(width, int)
assert isinstance(height, int)
assert isinstance(num_palette, int)
palette_data_ctypes = (ctypes.c_uint8 * (num_palette * 4)).from_buffer(palette_data)
image_data_ctypes = (ctypes.c_uint8 * (height * width)).from_buffer(image_data)
_check_err(
raw2png.raw2png_write_paletted(
str(file_name).encode(),
width,
height,
palette_data_ctypes,
num_palette,
image_data_ctypes,
)
)
def main():
write(
Path(__file__).parent / "my421tést.png",
1,
1,
(ctypes.c_uint8 * 4)(255, 0, 0, 120),
)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,149 @@
#!/usr/bin/env python3
"""
Display rgba32 or i8 binary data read from stdin, by (ab)using ANSI color codes.
"""
import argparse
import sys
import time
ESC = b"\x1B" # '\e'
RESET = ESC + b"[m"
def rgb(r, g, b):
return ESC + b"[48;2;" + ";".join((str(r), str(g), str(b))).encode("ascii") + b"m"
def lerp(a, b, t):
assert 0 <= t <= 1
return a * (1 - t) + b * t
def handle_rgba32(
*,
width,
background=(lambda x, y: ((50, 50, 50) if (x + y) % 2 == 0 else (200, 200, 200))),
):
count = 0
x = 0
y = 0
while True:
rgba32 = b""
while len(rgba32) < 4:
if rgba32:
# Wait 10ms if wasn't able to fetch 4 bytes immediately
time.sleep(0.010)
data = sys.stdin.buffer.read(4 - len(rgba32))
if not data:
return
rgba32 += data
assert len(rgba32) == 4
in_r, in_g, in_b, in_a = rgba32
bg_r, bg_g, bg_b = background(x, y)
r = round(lerp(bg_r, in_r, in_a / 255))
g = round(lerp(bg_g, in_g, in_a / 255))
b = round(lerp(bg_b, in_b, in_a / 255))
sys.stdout.buffer.write(rgb(r, g, b))
sys.stdout.buffer.write(b" ")
count += 1
x += 1
if count % width == 0:
x = 0
y += 1
sys.stdout.buffer.write(RESET + b"\n")
def handle_rgba16(
*,
width,
background=(lambda x, y: ((50, 50, 50) if (x + y) % 2 == 0 else (200, 200, 200))),
):
count = 0
x = 0
y = 0
while True:
rgba16 = b""
while len(rgba16) < 2:
if rgba16:
time.sleep(0.010)
data = sys.stdin.buffer.read(2 - len(rgba16))
if not data:
return
rgba16 += data
assert len(rgba16) == 2
rgba16_hi, rgba16_lo = rgba16
rgba16 = (rgba16_hi << 8) | rgba16_lo
def rgba16_component_expand(rgba16, shift):
v = (rgba16 >> shift) & 0x1F
return (v << 3) | (v >> 2)
in_r = rgba16_component_expand(rgba16, 11)
in_g = rgba16_component_expand(rgba16, 6)
in_b = rgba16_component_expand(rgba16, 1)
in_a = 255 if (rgba16 & 1) else 0
bg_r, bg_g, bg_b = background(x, y)
r = round(lerp(bg_r, in_r, in_a / 255))
g = round(lerp(bg_g, in_g, in_a / 255))
b = round(lerp(bg_b, in_b, in_a / 255))
sys.stdout.buffer.write(rgb(r, g, b))
sys.stdout.buffer.write(b" ")
count += 1
x += 1
if count % width == 0:
x = 0
y += 1
sys.stdout.buffer.write(RESET + b"\n")
def handle_i8(
*,
width,
):
count = 0
while True:
i = sys.stdin.buffer.read(1)
if not i:
return
i = i[0]
r = g = b = i
sys.stdout.buffer.write(rgb(r, g, b))
sys.stdout.buffer.write(b" ")
count += 1
if count % width == 0:
sys.stdout.buffer.write(RESET + b"\n")
handlers = {
"rgba32": handle_rgba32,
"i8": handle_i8,
"rgba16": handle_rgba16,
}
argparser = argparse.ArgumentParser()
argparser.add_argument("--width", default=64, type=int)
argparser.add_argument("--format", default="rgba32", choices=handlers.keys())
args = argparser.parse_args()
try:
handlers[args.format](
width=args.width,
)
finally:
sys.stdout.buffer.write(RESET + b"\n")

View file

@ -0,0 +1,144 @@
import unittest
from pathlib import Path
import random
import raw2png
import png2raw
test_dir = Path(__file__).parent / "test_out_folder"
test_dir.mkdir(parents=True, exist_ok=True)
test_pngs_dirs: list[Path] = []
# https://sourceforge.net/p/libpng/code/ci/master/tree/
# git clone https://git.code.sf.net/p/libpng/code libpng-code
libpng_dir = Path("/home/dragorn421/Documents/libpng-code/")
# libpng_dir = None
if libpng_dir:
test_pngs_dirs.extend(
libpng_dir / libpng_pngs_dir
for libpng_pngs_dir in [
Path("contrib/testpngs/"),
Path("contrib/pngsuite/"),
Path("contrib/pngsuite/interlaced/"),
]
)
test_pngs: list[Path] = []
for test_pngs_dir in test_pngs_dirs:
test_pngs.extend(test_pngs_dir.glob("*.png"))
if libpng_dir:
# apparently ffmpeg fails to properly understand this one, so one test falsely fails
# The right pixel is transparent white, ffmpeg says opaque white
test_pngs.remove(libpng_dir / Path("contrib/testpngs/gray-1-linear-tRNS.png"))
# I guess this one is 16 bits per pixel
# The 8-bit intensity of three pixels is off by 1, so it's probably just a rounding difference
test_pngs.remove(libpng_dir / Path("contrib/testpngs/gray-16-sRGB.png"))
# TODO these false fails happen for a lot more pngs from libpng, from a quick look for similar reasons
# -> check and remove them
# test_pngs.extend(Path("/home/dragorn421/Documents/oot/assets").glob("**/*.png"))
# Testing 100 pngs takes 5-6 seconds for me. Definitely need to reduce the amount of pngs being tested
test_pngs = random.sample(test_pngs, 100)
for p in test_pngs:
assert p.is_file(), p
class Tests(unittest.TestCase):
def test_random_rgba32(self):
for rand_i in range(10):
with self.subTest(rand_i=rand_i):
width = random.randint(10, 20)
height = random.randint(10, 20)
image_data = bytearray(random.randbytes(width * height * 4))
file_name = test_dir / f"random_rgba32_{rand_i}.png"
raw2png.write(file_name, width, height, image_data)
with png2raw.Instance(file_name) as inst:
self.assertFalse(inst.is_paletted())
self.assertEqual(inst.get_dimensions(), (width, height))
self.assertEqual(inst.read_to_rgba32(), image_data)
def test_random_paletted(self):
for rand_i in range(10):
with self.subTest(rand_i=rand_i):
width = random.randint(10, 20)
height = random.randint(10, 20)
num_palette = random.randint(1, 256)
palette_data = bytearray(random.randbytes(num_palette * 4))
image_data = bytearray(
b % num_palette for b in random.randbytes(height * width)
)
file_name = test_dir / f"random_paletted_{rand_i}.png"
raw2png.write_paletted(
file_name, width, height, palette_data, num_palette, image_data
)
with png2raw.Instance(file_name) as inst:
self.assertTrue(inst.is_paletted())
self.assertEqual(inst.get_dimensions(), (width, height))
self.assertEqual(inst.get_palette_rgba32(), palette_data)
self.assertEqual(inst.read_palette_indices(), image_data)
def test_rewrite(self):
for file_name in test_pngs:
with self.subTest(file_name=file_name):
# Copy original files to the test folder for easier visual comparison
og_out = test_dir / (file_name.stem + "#OG" + file_name.suffix)
og_out.write_bytes(file_name.read_bytes())
rewrite_out = test_dir / file_name.name
with png2raw.Instance(file_name) as inst:
width, height = inst.get_dimensions()
if inst.is_paletted():
palette_data = inst.get_palette_rgba32()
image_data = inst.read_palette_indices()
self.assertLessEqual(len(palette_data), 256 * 4)
self.assertEqual(len(image_data), height * width)
raw2png.write_paletted(
rewrite_out,
width,
height,
palette_data,
len(palette_data) // 4,
image_data,
)
else:
image_data = inst.read_to_rgba32()
self.assertEqual(len(image_data), height * width * 4)
raw2png.write(rewrite_out, width, height, image_data)
self.assertTrue(rewrite_out.is_file())
import subprocess
og_out_rgba32_bin = og_out.with_suffix(".rgba32.bin")
rewrite_out_rgba32_bin = rewrite_out.with_suffix(".rgba32.bin")
subprocess.run(
"ffmpeg -y -i".split()
+ [str(og_out)]
+ "-f rawvideo -pix_fmt rgba".split()
+ [str(og_out_rgba32_bin)],
capture_output=subprocess.DEVNULL,
)
subprocess.run(
"ffmpeg -y -i".split()
+ [str(rewrite_out)]
+ "-f rawvideo -pix_fmt rgba".split()
+ [str(rewrite_out_rgba32_bin)],
capture_output=subprocess.DEVNULL,
)
self.assertEqual(
og_out_rgba32_bin.read_bytes(), rewrite_out_rgba32_bin.read_bytes()
)
if __name__ == "__main__":
unittest.main()

View file

@ -2,8 +2,6 @@
# Disassemble a cutscene script
from overlayhelpers import filemap
import argparse, os, struct
import math
@ -711,7 +709,12 @@ Note that this isn't protected against indexing errors since a cutscene should a
end before the end of the file it's in.
"""
def disassemble_cutscene(cs_in):
def disassemble_cutscene(cs_in) -> tuple[int, str]:
"""
Takes a sequence of words cs_in
Returns a tuple (cutscene_size_in_words, cutscene_macros_source)
"""
i = 0
total_entries = cs_in[i]
i+=1
@ -720,12 +723,12 @@ def disassemble_cutscene(cs_in):
if (total_entries < 0 or cutscene_end_frame < 0):
print("This cutscene would abort if played in-engine")
if total_entries < 0:
return "Could not disassemble cutscene: Number of commands is negative"
raise Exception("Could not disassemble cutscene: Number of commands is negative")
macros = format_cmd(begin_cutscene_entry[0], [total_entries, cutscene_end_frame])+line_end
for k in range(0,total_entries+1):
cmd_type = cs_in[i]
if (cmd_type == 0xFFFFFFFF):
return macros + multi_key(-1)[0]+line_end
return (i+2), (macros + multi_key(-1)[0]+line_end)
entry = multi_key(cmd_type)
if entry is None:
entry = unk_data_entry
@ -758,12 +761,14 @@ def disassemble_cutscene(cs_in):
else:
i += n_words
print("Warning: cutscene reached maximum entries without encountering a CS_END_SCRIPT command")
return macros
return i, macros
def hex_parse(s):
return int(s, 16)
def main():
from overlayhelpers import filemap
parser = argparse.ArgumentParser(description="Disassembles cutscenes for OoT")
parser.add_argument('address', help="VRAM or ROM address to disassemble at", type=hex_parse)
args = parser.parse_args()
@ -785,7 +790,7 @@ def main():
ovl_file.seek(file_result.offset)
cs_data = [i[0] for i in struct.iter_unpack(">I", bytearray(ovl_file.read()))]
if cs_data is not None:
print("static CutsceneData D_" + hex(args.address).replace("0x","").upper() + "[] = {\n" + indent+disassemble_cutscene(cs_data).replace(linesep,linesep+indent).rstrip()+"\n};")
print("static CutsceneData D_" + hex(args.address).replace("0x","").upper() + "[] = {\n" + indent+disassemble_cutscene(cs_data)[1].replace(linesep,linesep+indent).rstrip()+"\n};")
if __name__ == "__main__":
main()