mirror of
https://github.com/zeldaret/oot.git
synced 2025-07-13 03:14:38 +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:
parent
748859595a
commit
8411c34b38
80 changed files with 535882 additions and 112 deletions
25
Makefile
25
Makefile
|
@ -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 $@
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -74,9 +74,5 @@
|
|||
<Vtx/>
|
||||
</Array>
|
||||
|
||||
<Array Name="gMorphaVtx_007BB8" Count="4" Offset="0x7BB8">
|
||||
<Vtx/>
|
||||
</Array>
|
||||
|
||||
</File>
|
||||
</Root>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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. -->
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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]);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
9
tools/assets/Makefile
Normal 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
1
tools/assets/bin2c/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
bin2c
|
9
tools/assets/bin2c/Makefile
Normal file
9
tools/assets/bin2c/Makefile
Normal file
|
@ -0,0 +1,9 @@
|
|||
CFLAGS := -Wall
|
||||
|
||||
bin2c: bin2c.c
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
clean:
|
||||
$(RM) bin2c
|
||||
|
||||
.PHONY: clean
|
86
tools/assets/bin2c/bin2c.c
Normal file
86
tools/assets/bin2c/bin2c.c
Normal 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
109
tools/assets/build_from_png.py
Executable 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
2
tools/assets/conf.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
WRITE_HINTS = True
|
||||
I_D_OMEGALUL = True # IDO specific things that may be otherwise different
|
23
tools/assets/descriptor/__main__.py
Normal file
23
tools/assets/descriptor/__main__.py
Normal 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()
|
307
tools/assets/descriptor/base.py
Normal file
307
tools/assets/descriptor/base.py
Normal 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
|
176
tools/assets/descriptor/n64resources.py
Normal file
176
tools/assets/descriptor/n64resources.py
Normal 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
|
||||
)
|
2970
tools/assets/descriptor/pools.txt
Normal file
2970
tools/assets/descriptor/pools.txt
Normal file
File diff suppressed because it is too large
Load diff
518087
tools/assets/descriptor/resources.txt
Normal file
518087
tools/assets/descriptor/resources.txt
Normal file
File diff suppressed because it is too large
Load diff
14
tools/assets/descriptor/spec.md
Normal file
14
tools/assets/descriptor/spec.md
Normal 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.
|
||||
|
||||
----------
|
171
tools/assets/descriptor/z64resources.py
Normal file
171
tools/assets/descriptor/z64resources.py
Normal 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)
|
10
tools/assets/extract/__main__.py
Normal file
10
tools/assets/extract/__main__.py
Normal 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()
|
1064
tools/assets/extract/extase/__init__.py
Normal file
1064
tools/assets/extract/extase/__init__.py
Normal file
File diff suppressed because it is too large
Load diff
391
tools/assets/extract/extase/cdata_resources.py
Normal file
391
tools/assets/extract/extase/cdata_resources.py
Normal 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()
|
408
tools/assets/extract/extase/memorymap.py
Normal file
408
tools/assets/extract/extase/memorymap.py
Normal 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)
|
223
tools/assets/extract/extase/repr_c_struct.py
Normal file
223
tools/assets/extract/extase/repr_c_struct.py
Normal 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()
|
194
tools/assets/extract/extase_oot64/animation_resources.py
Normal file
194
tools/assets/extract/extase_oot64/animation_resources.py
Normal 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}"
|
748
tools/assets/extract/extase_oot64/collision_resources.py
Normal file
748
tools/assets/extract/extase_oot64/collision_resources.py
Normal 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()
|
1434
tools/assets/extract/extase_oot64/dlist_resources.py
Normal file
1434
tools/assets/extract/extase_oot64/dlist_resources.py
Normal file
File diff suppressed because it is too large
Load diff
49
tools/assets/extract/extase_oot64/misc_resources.py
Normal file
49
tools/assets/extract/extase_oot64/misc_resources.py
Normal 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}[]"
|
28
tools/assets/extract/extase_oot64/playeranim_resources.py
Normal file
28
tools/assets/extract/extase_oot64/playeranim_resources.py
Normal 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}"
|
341
tools/assets/extract/extase_oot64/room_shape_resources.py
Normal file
341
tools/assets/extract/extase_oot64/room_shape_resources.py
Normal 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
|
692
tools/assets/extract/extase_oot64/scene_commands_resource.py
Normal file
692
tools/assets/extract/extase_oot64/scene_commands_resource.py
Normal 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}[]"
|
426
tools/assets/extract/extase_oot64/scene_rooms_resources.py
Normal file
426
tools/assets/extract/extase_oot64/scene_rooms_resources.py
Normal 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}[]"
|
355
tools/assets/extract/extase_oot64/skelcurve_resources.py
Normal file
355
tools/assets/extract/extase_oot64/skelcurve_resources.py
Normal 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()
|
188
tools/assets/extract/extase_oot64/skeleton_resources.py
Normal file
188
tools/assets/extract/extase_oot64/skeleton_resources.py
Normal 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}"
|
334
tools/assets/extract/extract_xml_z64.py
Normal file
334
tools/assets/extract/extract_xml_z64.py
Normal 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())
|
62
tools/assets/extract/oot64_data/Makefile
Normal file
62
tools/assets/extract/oot64_data/Makefile
Normal 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 ')' >> $@
|
137
tools/assets/extract/oot64_data/__init__.py
Normal file
137
tools/assets/extract/oot64_data/__init__.py
Normal 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]
|
475
tools/assets/extract/oot64_data/actor_ids.py
Normal file
475
tools/assets/extract/oot64_data/actor_ids.py
Normal 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",
|
||||
)
|
141
tools/assets/extract/oot64_data/audio_ids.py
Normal file
141
tools/assets/extract/oot64_data/audio_ids.py
Normal 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",
|
||||
}
|
1536
tools/assets/extract/oot64_data/dmadata_table.py
Normal file
1536
tools/assets/extract/oot64_data/dmadata_table.py
Normal file
File diff suppressed because it is too large
Load diff
11
tools/assets/extract/oot64_data/entrance_ids_special.py
Normal file
11
tools/assets/extract/oot64_data/entrance_ids_special.py
Normal 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",
|
||||
}
|
1560
tools/assets/extract/oot64_data/entrance_table_mini.py
Normal file
1560
tools/assets/extract/oot64_data/entrance_table_mini.py
Normal file
File diff suppressed because it is too large
Load diff
103
tools/assets/extract/oot64_data/misc_ids.py
Normal file
103
tools/assets/extract/oot64_data/misc_ids.py
Normal 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",
|
||||
}
|
406
tools/assets/extract/oot64_data/object_ids.py
Normal file
406
tools/assets/extract/oot64_data/object_ids.py
Normal 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",
|
||||
)
|
114
tools/assets/extract/oot64_data/scene_table_mini.py
Normal file
114
tools/assets/extract/oot64_data/scene_table_mini.py
Normal 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"),
|
||||
)
|
445
tools/assets/extract/z64_resource_handlers.py
Normal file
445
tools/assets/extract/z64_resource_handlers.py
Normal 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
50
tools/assets/n64.py
Normal 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
203
tools/assets/n64yatc.py
Normal 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
5
tools/assets/png2raw/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
__pycache__/
|
||||
test_out_folder/
|
||||
png2raw
|
||||
png2raw_main
|
||||
raw2png
|
28
tools/assets/png2raw/Makefile
Normal file
28
tools/assets/png2raw/Makefile
Normal 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)
|
678
tools/assets/png2raw/png2raw.c
Normal file
678
tools/assets/png2raw/png2raw.c
Normal 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;
|
||||
}
|
72
tools/assets/png2raw/png2raw.h
Normal file
72
tools/assets/png2raw/png2raw.h
Normal 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
|
216
tools/assets/png2raw/png2raw.py
Normal file
216
tools/assets/png2raw/png2raw.py
Normal 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()
|
160
tools/assets/png2raw/raw2png.c
Normal file
160
tools/assets/png2raw/raw2png.c
Normal 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;
|
||||
}
|
21
tools/assets/png2raw/raw2png.h
Normal file
21
tools/assets/png2raw/raw2png.h
Normal 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
|
117
tools/assets/png2raw/raw2png.py
Normal file
117
tools/assets/png2raw/raw2png.py
Normal 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()
|
149
tools/assets/png2raw/raw2terminal.py
Executable file
149
tools/assets/png2raw/raw2terminal.py
Executable 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")
|
144
tools/assets/png2raw/test.py
Normal file
144
tools/assets/png2raw/test.py
Normal 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()
|
|
@ -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()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue