diff --git a/Makefile b/Makefile index 2040b822..f443ed97 100644 --- a/Makefile +++ b/Makefile @@ -71,6 +71,8 @@ SPLAT := $(PYTHON) -m splat split SPLAT_YAML := config/$(VERSION)/$(BASENAME).$(VERSION).yaml SPLAT_FLAGS := +FILESYS_GEN := $(PYTHON) tools/pw64_filesys_gen.py + # ------------------------------------------------------------------------------ # Inputs # ------------------------------------------------------------------------------ @@ -78,6 +80,7 @@ SPLAT_FLAGS := ASM_DIR = asm BIN_DIR = bin SRC_DIR = src +FILESYS_DIR = $(BIN_DIR)/filesys ASM_DIRS := $(shell find $(ASM_DIR) -type d -not -path "asm/nonmatchings/*") BIN_DIRS := $(shell find $(BIN_DIR) -type d) @@ -87,6 +90,9 @@ S_FILES := $(foreach dir,$(ASM_DIRS) $(SRC_DIRS),$(wildcard $(dir)/*.s)) B_FILES := $(foreach dir,$(BIN_DIRS),$(wildcard $(dir)/*.bin)) C_FILES := $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.c)) +FS_FILES := $(wildcard $(FILESYS_DIR)/*.yaml $(FILESYS_DIR)/*.raw) +FS_BINS := $(BIN_DIR)/filetable.bin $(BIN_DIR)/filesys.bin + # ------------------------------------------------------------------------------ # Outputs # ------------------------------------------------------------------------------ @@ -103,8 +109,9 @@ LD_SCRIPT := $(BUILD_DIR)/$(BASENAME).$(VERSION).ld S_O_FILES := $(foreach file,$(S_FILES:.s=.o),$(BUILD_DIR)/$(file)) B_O_FILES := $(foreach file,$(B_FILES:.bin=.o),$(BUILD_DIR)/$(file)) C_O_FILES := $(foreach file,$(C_FILES:.c=.o),$(BUILD_DIR)/$(file)) +FS_O_FILES := $(foreach file,$(FS_BINS:.bin=.o),$(BUILD_DIR)/$(file)) -O_FILES := $(C_O_FILES) $(S_O_FILES) $(B_O_FILES) +O_FILES := $(C_O_FILES) $(S_O_FILES) $(B_O_FILES) $(FS_O_FILES) DEP_FILES := $(C_O_FILES:.o=.d) @@ -291,6 +298,10 @@ $(BUILD_DIR)/%.o: %.s @printf "[$(GREEN) as $(NO_COL)] $<\n" $(V)$(CPP) $(CPPFLAGS) $(INCLUDE_CFLAGS) -I $(dir $*) $(ASM_DEFINES) $< | $(AS) $(ASFLAGS) $(INCLUDE_CFLAGS) -o $@ - +$(FS_BINS) &: $(FS_FILES) + @printf "[$(CYAN) fsys $(NO_COL)] $<\n" + $(V)$(FILESYS_GEN) --stats + $(BUILD_DIR)/%.o: %.bin @printf "[$(PINK) linker $(NO_COL)] $<\n" $(V)$(LD) -r -b binary -o $@ $< diff --git a/config/us/pilotwings64.us.yaml b/config/us/pilotwings64.us.yaml index b318bfc9..99173d97 100644 --- a/config/us/pilotwings64.us.yaml +++ b/config/us/pilotwings64.us.yaml @@ -753,11 +753,11 @@ segments: - { type: .bss, vram: 0x8037F910, name: app/wind_objects } - name: filetable - type: bin + type: pw_filetable start: 0xDE720 - name: filesys - type: bin + type: pw_filesys start: 0xDF5B0 - name: audio_seq @@ -772,1333 +772,4 @@ segments: type: bin start: 0x6314D0 -# - name: mio0_segment -# exclusive_ram_id: segment -# type: code -# start: 0x0DE744 -# subalign: 4 -# subsegments: -# - [0x0DE744, bin, mio0_0DE744] -# - [0x0E515C, bin, mio0_0E515C] -# - [0x0ED8E8, bin, mio0_0ED8E8] -# - [0x0EDD74, bin, mio0_0EDD74] -# - [0x0EDE30, bin, mio0_0EDE30] -# - [0x0EE894, bin, mio0_0EE894] -# - [0x0EF378, bin, mio0_0EF378] -# - [0x0EF6CC, bin, mio0_0EF6CC] -# - [0x0EFE20, bin, mio0_0EFE20] -# - [0x0F02CC, bin, mio0_0F02CC] -# - [0x0F04F0, bin, mio0_0F04F0] -# - [0x0F0F6C, bin, mio0_0F0F6C] -# - [0x0F2DF8, bin, mio0_0F2DF8] -# - [0x0F379C, bin, mio0_0F379C] -# - [0x0F4520, bin, mio0_0F4520] -# - [0x0F47AC, bin, mio0_0F47AC] -# - [0x0F4D80, bin, mio0_0F4D80] -# - [0x0F53BC, bin, mio0_0F53BC] -# - [0x0F59C8, bin, mio0_0F59C8] -# - [0x0F5ECC, bin, mio0_0F5ECC] -# - [0x0F6528, bin, mio0_0F6528] -# - [0x0F6E64, bin, mio0_0F6E64] -# - [0x0F75C0, bin, mio0_0F75C0] -# - [0x0F7E8C, bin, mio0_0F7E8C] -# - [0x0F8748, bin, mio0_0F8748] -# - [0x0F8DC4, bin, mio0_0F8DC4] -# - [0x0F9070, bin, mio0_0F9070] -# - [0x0F99E4, bin, mio0_0F99E4] -# - [0x0F9EA0, bin, mio0_0F9EA0] -# - [0x0FB0C4, bin, mio0_0FB0C4] -# - [0x0FB8E0, bin, mio0_0FB8E0] -# - [0x0FBBDC, bin, mio0_0FBBDC] -# - [0x0FBDF8, bin, mio0_0FBDF8] -# - [0x0FBF14, bin, mio0_0FBF14] -# - [0x0FC340, bin, mio0_0FC340] -# - [0x0FC65C, bin, mio0_0FC65C] -# - [0x0FC920, bin, mio0_0FC920] -# - [0x0FCBEC, bin, mio0_0FCBEC] -# - [0x0FCCE0, bin, mio0_0FCCE0] -# - [0x0FD43C, bin, mio0_0FD43C] -# - [0x0FDE50, bin, mio0_0FDE50] -# - [0x0FF064, bin, mio0_0FF064] -# - [0x1041D0, bin, mio0_1041D0] -# - [0x104ADC, bin, mio0_104ADC] -# - [0x104F90, bin, mio0_104F90] -# - [0x105214, bin, mio0_105214] -# - [0x105510, bin, mio0_105510] -# - [0x1057B4, bin, mio0_1057B4] -# - [0x105988, bin, mio0_105988] -# - [0x105FA4, bin, mio0_105FA4] -# - [0x106130, bin, mio0_106130] -# - [0x106354, bin, mio0_106354] -# - [0x106EC0, bin, mio0_106EC0] -# - [0x107DF4, bin, mio0_107DF4] -# - [0x1081F0, bin, mio0_1081F0] -# - [0x1083BC, bin, mio0_1083BC] -# - [0x108718, bin, mio0_108718] -# - [0x10A45C, bin, mio0_10A45C] -# - [0x10A830, bin, mio0_10A830] -# - [0x10AE44, bin, mio0_10AE44] -# - [0x10B508, bin, mio0_10B508] -# - [0x10BD2C, bin, mio0_10BD2C] -# - [0x10C198, bin, mio0_10C198] -# - [0x10C45C, bin, mio0_10C45C] -# - [0x10C6A0, bin, mio0_10C6A0] -# - [0x10C86C, bin, mio0_10C86C] -# - [0x10CB88, bin, mio0_10CB88] -# - [0x10CE34, bin, mio0_10CE34] -# - [0x10CFE8, bin, mio0_10CFE8] -# - [0x10D1FC, bin, mio0_10D1FC] -# - [0x10D9C0, bin, mio0_10D9C0] -# - [0x10DBA4, bin, mio0_10DBA4] -# - [0x10E240, bin, mio0_10E240] -# - [0x10E644, bin, mio0_10E644] -# - [0x10EB70, bin, mio0_10EB70] -# - [0x10F17C, bin, mio0_10F17C] -# - [0x10F9B8, bin, mio0_10F9B8] -# - [0x11013C, bin, mio0_11013C] -# - [0x110780, bin, mio0_110780] -# - [0x110C0C, bin, mio0_110C0C] -# - [0x111088, bin, mio0_111088] -# - [0x1112E4, bin, mio0_1112E4] -# - [0x111520, bin, mio0_111520] -# - [0x123C24, bin, mio0_123C24] -# - [0x124DA0, bin, mio0_124DA0] -# - [0x125184, bin, mio0_125184] -# - [0x125670, bin, mio0_125670] -# - [0x1264E4, bin, mio0_1264E4] -# - [0x127340, bin, mio0_127340] -# - [0x1273FC, bin, mio0_1273FC] -# - [0x127BF0, bin, mio0_127BF0] -# - [0x1287AC, bin, mio0_1287AC] -# - [0x128F78, bin, mio0_128F78] -# - [0x129ABC, bin, mio0_129ABC] -# - [0x129D88, bin, mio0_129D88] -# - [0x12A05C, bin, mio0_12A05C] -# - [0x12A320, bin, mio0_12A320] -# - [0x12A5F4, bin, mio0_12A5F4] -# - [0x12A8C0, bin, mio0_12A8C0] -# - [0x12AA3C, bin, mio0_12AA3C] -# - [0x12AC30, bin, mio0_12AC30] -# - [0x12B164, bin, mio0_12B164] -# - [0x12BD08, bin, mio0_12BD08] -# - [0x12C25C, bin, mio0_12C25C] -# - [0x12E6F8, bin, mio0_12E6F8] -# - [0x12EC24, bin, mio0_12EC24] -# - [0x12F0D8, bin, mio0_12F0D8] -# - [0x12F5C4, bin, mio0_12F5C4] -# - [0x12FA98, bin, mio0_12FA98] -# - [0x13060C, bin, mio0_13060C] -# - [0x1309B0, bin, mio0_1309B0] -# - [0x130DFC, bin, mio0_130DFC] -# - [0x1312B0, bin, mio0_1312B0] -# - [0x1314A4, bin, mio0_1314A4] -# - [0x131738, bin, mio0_131738] -# - [0x131AA4, bin, mio0_131AA4] -# - [0x131E00, bin, mio0_131E00] -# - [0x132124, bin, mio0_132124] -# - [0x1326F8, bin, mio0_1326F8] -# - [0x133574, bin, mio0_133574] -# - [0x133FE0, bin, mio0_133FE0] -# - [0x134FE4, bin, mio0_134FE4] -# - [0x135AC8, bin, mio0_135AC8] -# - [0x135D1C, bin, mio0_135D1C] -# - [0x135EF0, bin, mio0_135EF0] -# - [0x1360AC, bin, mio0_1360AC] -# - [0x136578, bin, mio0_136578] -# - [0x1370B4, bin, mio0_1370B4] -# - [0x137EC0, bin, mio0_137EC0] -# - [0x138C84, bin, mio0_138C84] -# - [0x138FE0, bin, mio0_138FE0] -# - [0x139D0C, bin, mio0_139D0C] -# - [0x139EC0, bin, mio0_139EC0] -# - [0x13A504, bin, mio0_13A504] -# - [0x13AB70, bin, mio0_13AB70] -# - [0x13CA7C, bin, mio0_13CA7C] -# - [0x13D178, bin, mio0_13D178] -# - [0x13D744, bin, mio0_13D744] -# - [0x13E398, bin, mio0_13E398] -# - [0x13E5FC, bin, mio0_13E5FC] -# - [0x13E9A8, bin, mio0_13E9A8] -# - [0x13EC14, bin, mio0_13EC14] -# - [0x13F240, bin, mio0_13F240] -# - [0x13F534, bin, mio0_13F534] -# - [0x13F8A0, bin, mio0_13F8A0] -# - [0x13F9EC, bin, mio0_13F9EC] -# - [0x13FB30, bin, mio0_13FB30] -# - [0x13FFFC, bin, mio0_13FFFC] -# - [0x140E88, bin, mio0_140E88] -# - [0x141BE4, bin, mio0_141BE4] -# - [0x142660, bin, mio0_142660] -# - [0x14293C, bin, mio0_14293C] -# - [0x142D40, bin, mio0_142D40] -# - [0x142F44, bin, mio0_142F44] -# - [0x143A08, bin, mio0_143A08] -# - [0x1449B4, bin, mio0_1449B4] -# - [0x145C20, bin, mio0_145C20] -# - [0x145D0C, bin, mio0_145D0C] -# - [0x146338, bin, mio0_146338] -# - [0x146594, bin, mio0_146594] -# - [0x146940, bin, mio0_146940] -# - [0x14714C, bin, mio0_14714C] -# - [0x147578, bin, mio0_147578] -# - [0x147CDC, bin, mio0_147CDC] -# - [0x148420, bin, mio0_148420] -# - [0x148BA4, bin, mio0_148BA4] -# - [0x1490A8, bin, mio0_1490A8] -# - [0x15609C, bin, mio0_15609C] -# - [0x156670, bin, mio0_156670] -# - [0x1569CC, bin, mio0_1569CC] -# - [0x157128, bin, mio0_157128] -# - [0x157954, bin, mio0_157954] -# - [0x158218, bin, mio0_158218] -# - [0x1583A4, bin, mio0_1583A4] -# - [0x158660, bin, mio0_158660] -# - [0x15905C, bin, mio0_15905C] -# - [0x159830, bin, mio0_159830] -# - [0x15A0F4, bin, mio0_15A0F4] -# - [0x15A5E8, bin, mio0_15A5E8] -# - [0x15AF24, bin, mio0_15AF24] -# - [0x15B9A8, bin, mio0_15B9A8] -# - [0x15CB34, bin, mio0_15CB34] -# - [0x15D140, bin, mio0_15D140] -# - [0x15D614, bin, mio0_15D614] -# - [0x15DAE8, bin, mio0_15DAE8] -# - [0x15E1DC, bin, mio0_15E1DC] -# - [0x15ECD8, bin, mio0_15ECD8] -# - [0x15FA94, bin, mio0_15FA94] -# - [0x15FC88, bin, mio0_15FC88] -# - [0x15FE2C, bin, mio0_15FE2C] -# - [0x15FFF8, bin, mio0_15FFF8] -# - [0x160CC4, bin, mio0_160CC4] -# - [0x161850, bin, mio0_161850] -# - [0x162B44, bin, mio0_162B44] -# - [0x163488, bin, mio0_163488] -# - [0x1640CC, bin, mio0_1640CC] -# - [0x164638, bin, mio0_164638] -# - [0x164BBC, bin, mio0_164BBC] -# - [0x165298, bin, mio0_165298] -# - [0x1654BC, bin, mio0_1654BC] -# - [0x1656F8, bin, mio0_1656F8] -# - [0x165A0C, bin, mio0_165A0C] -# - [0x165CE0, bin, mio0_165CE0] -# - [0x165F4C, bin, mio0_165F4C] -# - [0x166018, bin, mio0_166018] -# - [0x1660DC, bin, mio0_1660DC] -# - [0x1661A8, bin, mio0_1661A8] -# - [0x168B2C, bin, mio0_168B2C] -# - [0x168C38, bin, mio0_168C38] -# - [0x1692E4, bin, mio0_1692E4] -# - [0x169630, bin, mio0_169630] -# - [0x169814, bin, mio0_169814] -# - [0x169A40, bin, mio0_169A40] -# - [0x16B84C, bin, mio0_16B84C] -# - [0x16B9C8, bin, mio0_16B9C8] -# - [0x16BD74, bin, mio0_16BD74] -# - [0x16C110, bin, mio0_16C110] -# - [0x16CD7C, bin, mio0_16CD7C] -# - [0x16CE60, bin, mio0_16CE60] -# - [0x16D224, bin, mio0_16D224] -# - [0x16D570, bin, mio0_16D570] -# - [0x16D8A4, bin, mio0_16D8A4] -# - [0x16DBE0, bin, mio0_16DBE0] -# - [0x16DF94, bin, mio0_16DF94] -# - [0x16E2C0, bin, mio0_16E2C0] -# - [0x16E5E4, bin, mio0_16E5E4] -# - [0x16E910, bin, mio0_16E910] -# - [0x16ECC4, bin, mio0_16ECC4] -# - [0x16EFF8, bin, mio0_16EFF8] -# - [0x16F324, bin, mio0_16F324] -# - [0x16F660, bin, mio0_16F660] -# - [0x16FA14, bin, mio0_16FA14] -# - [0x16FD48, bin, mio0_16FD48] -# - [0x170084, bin, mio0_170084] -# - [0x1703C0, bin, mio0_1703C0] -# - [0x17077C, bin, mio0_17077C] -# - [0x170AB8, bin, mio0_170AB8] -# - [0x170DEC, bin, mio0_170DEC] -# - [0x171128, bin, mio0_171128] -# - [0x17126C, bin, mio0_17126C] -# - [0x1713A8, bin, mio0_1713A8] -# - [0x1714DC, bin, mio0_1714DC] -# - [0x171628, bin, mio0_171628] -# - [0x171C5C, bin, mio0_171C5C] -# - [0x171E00, bin, mio0_171E00] -# - [0x17202C, bin, mio0_17202C] -# - [0x172348, bin, mio0_172348] -# - [0x172664, bin, mio0_172664] -# - [0x172990, bin, mio0_172990] -# - [0x172A84, bin, mio0_172A84] -# - [0x172BE8, bin, mio0_172BE8] -# - [0x172F44, bin, mio0_172F44] -# - [0x173008, bin, mio0_173008] -# - [0x1732FC, bin, mio0_1732FC] -# - [0x1735F0, bin, mio0_1735F0] -# - [0x17550C, bin, mio0_17550C] -# - [0x175618, bin, mio0_175618] -# - [0x1757F4, bin, mio0_1757F4] -# - [0x1759F8, bin, mio0_1759F8] -# - [0x175DCC, bin, mio0_175DCC] -# - [0x1762D0, bin, mio0_1762D0] -# - [0x1767DC, bin, mio0_1767DC] -# - [0x176CC8, bin, mio0_176CC8] -# - [0x17742C, bin, mio0_17742C] -# - [0x177A10, bin, mio0_177A10] -# - [0x177FF4, bin, mio0_177FF4] -# - [0x1785D8, bin, mio0_1785D8] -# - [0x1787BC, bin, mio0_1787BC] -# - [0x179030, bin, mio0_179030] -# - [0x17983C, bin, mio0_17983C] -# - [0x17A180, bin, mio0_17A180] -# - [0x17A994, bin, mio0_17A994] -# - [0x17B308, bin, mio0_17B308] -# - [0x17BC4C, bin, mio0_17BC4C] -# - [0x17C818, bin, mio0_17C818] -# - [0x17C8BC, bin, mio0_17C8BC] -# - [0x17C990, bin, mio0_17C990] -# - [0x17D6CC, bin, mio0_17D6CC] -# - [0x17E140, bin, mio0_17E140] -# - [0x17EC8C, bin, mio0_17EC8C] -# - [0x17F728, bin, mio0_17F728] -# - [0x1802FC, bin, mio0_1802FC] -# - [0x180DC0, bin, mio0_180DC0] -# - [0x1818D4, bin, mio0_1818D4] -# - [0x182318, bin, mio0_182318] -# - [0x182DEC, bin, mio0_182DEC] -# - [0x1837F8, bin, mio0_1837F8] -# - [0x18423C, bin, mio0_18423C] -# - [0x184EF8, bin, mio0_184EF8] -# - [0x184FA4, bin, mio0_184FA4] -# - [0x185090, bin, mio0_185090] -# - [0x18518C, bin, mio0_18518C] -# - [0x185E10, bin, mio0_185E10] -# - [0x186AAC, bin, mio0_186AAC] -# - [0x187808, bin, mio0_187808] -# - [0x1884F4, bin, mio0_1884F4] -# - [0x189188, bin, mio0_189188] -# - [0x18A09C, bin, mio0_18A09C] -# - [0x18A140, bin, mio0_18A140] -# - [0x18A20C, bin, mio0_18A20C] -# - [0x18B1B8, bin, mio0_18B1B8] -# - [0x18C0FC, bin, mio0_18C0FC] -# - [0x18D140, bin, mio0_18D140] -# - [0x18DFDC, bin, mio0_18DFDC] -# - [0x18EF98, bin, mio0_18EF98] -# - [0x18FE1C, bin, mio0_18FE1C] -# - [0x190D98, bin, mio0_190D98] -# - [0x191C3C, bin, mio0_191C3C] -# - [0x192B80, bin, mio0_192B80] -# - [0x1939C4, bin, mio0_1939C4] -# - [0x194920, bin, mio0_194920] -# - [0x1951EC, bin, mio0_1951EC] -# - [0x1959F8, bin, mio0_1959F8] -# - [0x196264, bin, mio0_196264] -# - [0x196BD8, bin, mio0_196BD8] -# - [0x197474, bin, mio0_197474] -# - [0x197CB8, bin, mio0_197CB8] -# - [0x1988CC, bin, mio0_1988CC] -# - [0x198970, bin, mio0_198970] -# - [0x198A44, bin, mio0_198A44] -# - [0x199618, bin, mio0_199618] -# - [0x19A224, bin, mio0_19A224] -# - [0x19AEC8, bin, mio0_19AEC8] -# - [0x19BAF4, bin, mio0_19BAF4] -# - [0x19C6B0, bin, mio0_19C6B0] -# - [0x19D154, bin, mio0_19D154] -# - [0x19E078, bin, mio0_19E078] -# - [0x19EBEC, bin, mio0_19EBEC] -# - [0x19F680, bin, mio0_19F680] -# - [0x1A00AC, bin, mio0_1A00AC] -# - [0x1A0158, bin, mio0_1A0158] -# - [0x1A0B54, bin, mio0_1A0B54] -# - [0x1A1A28, bin, mio0_1A1A28] -# - [0x1A2464, bin, mio0_1A2464] -# - [0x1A3360, bin, mio0_1A3360] -# - [0x1A433C, bin, mio0_1A433C] -# - [0x1A52D0, bin, mio0_1A52D0] -# - [0x1A617C, bin, mio0_1A617C] -# - [0x1A6C90, bin, mio0_1A6C90] -# - [0x1A6D34, bin, mio0_1A6D34] -# - [0x1A77E8, bin, mio0_1A77E8] -# - [0x1A8354, bin, mio0_1A8354] -# - [0x1A8F88, bin, mio0_1A8F88] -# - [0x1A9B34, bin, mio0_1A9B34] -# - [0x1AA698, bin, mio0_1AA698] -# - [0x1AAE2C, bin, mio0_1AAE2C] -# - [0x1AB4C8, bin, mio0_1AB4C8] -# - [0x1AB73C, bin, mio0_1AB73C] -# - [0x1AD900, bin, mio0_1AD900] -# - [0x1AF7FC, bin, mio0_1AF7FC] -# - [0x1B1620, bin, mio0_1B1620] -# - [0x1B3E9C, bin, mio0_1B3E9C] -# - [0x1B6558, bin, mio0_1B6558] -# - [0x1B8E54, bin, mio0_1B8E54] -# - [0x1B9198, bin, mio0_1B9198] -# - [0x1B94A4, bin, mio0_1B94A4] -# - [0x1B96E0, bin, mio0_1B96E0] -# - [0x1B98F4, bin, mio0_1B98F4] -# - [0x1B9BC8, bin, mio0_1B9BC8] -# - [0x1BA04C, bin, mio0_1BA04C] -# - [0x1BA318, bin, mio0_1BA318] -# - [0x1BA52C, bin, mio0_1BA52C] -# - [0x1BA880, bin, mio0_1BA880] -# - [0x1BAB2C, bin, mio0_1BAB2C] -# - [0x1BB0D0, bin, mio0_1BB0D0] -# - [0x1BE41C, bin, mio0_1BE41C] -# - [0x1C0F00, bin, mio0_1C0F00] -# - [0x1C4314, bin, mio0_1C4314] -# - [0x1C6B68, bin, mio0_1C6B68] -# - [0x1C6DB4, bin, mio0_1C6DB4] -# - [0x1C72F8, bin, mio0_1C72F8] -# - [0x1C8134, bin, mio0_1C8134] -# - [0x1C8660, bin, mio0_1C8660] -# - [0x1C8C5C, bin, mio0_1C8C5C] -# - [0x1C9FD0, bin, mio0_1C9FD0] -# - [0x1CA63C, bin, mio0_1CA63C] -# - [0x1CACA8, bin, mio0_1CACA8] -# - [0x1CB0AC, bin, mio0_1CB0AC] -# - [0x1CB418, bin, mio0_1CB418] -# - [0x1CBA9C, bin, mio0_1CBA9C] -# - [0x1CD998, bin, mio0_1CD998] -# - [0x1CE0B4, bin, mio0_1CE0B4] -# - [0x1CE908, bin, mio0_1CE908] -# - [0x1CF2B4, bin, mio0_1CF2B4] -# - [0x1D01A0, bin, mio0_1D01A0] -# - [0x1D0F14, bin, mio0_1D0F14] -# - [0x1D1420, bin, mio0_1D1420] -# - [0x1D1944, bin, mio0_1D1944] -# - [0x1D1D30, bin, mio0_1D1D30] -# - [0x1D3194, bin, mio0_1D3194] -# - [0x1D33E8, bin, mio0_1D33E8] -# - [0x1D37FC, bin, mio0_1D37FC] -# - [0x1D43F8, bin, mio0_1D43F8] -# - [0x1D4934, bin, mio0_1D4934] -# - [0x1D50F8, bin, mio0_1D50F8] -# - [0x1D558C, bin, mio0_1D558C] -# - [0x1D73C0, bin, mio0_1D73C0] -# - [0x1D7DBC, bin, mio0_1D7DBC] -# - [0x1D8108, bin, mio0_1D8108] -# - [0x1D877C, bin, mio0_1D877C] -# - [0x1DB1C8, bin, mio0_1DB1C8] -# - [0x1DB8DC, bin, mio0_1DB8DC] -# - [0x1DBCE8, bin, mio0_1DBCE8] -# - [0x1DC49C, bin, mio0_1DC49C] -# - [0x1DD0D8, bin, mio0_1DD0D8] -# - [0x1DD5DC, bin, mio0_1DD5DC] -# - [0x1DF798, bin, mio0_1DF798] -# - [0x1DFE74, bin, mio0_1DFE74] -# - [0x1E0618, bin, mio0_1E0618] -# - [0x1E1894, bin, mio0_1E1894] -# - [0x1E21A0, bin, mio0_1E21A0] -# - [0x1E253C, bin, mio0_1E253C] -# - [0x1E28C0, bin, mio0_1E28C0] -# - [0x1E2C0C, bin, mio0_1E2C0C] -# - [0x1E6C60, bin, mio0_1E6C60] -# - [0x1EC024, bin, mio0_1EC024] -# - [0x1ED870, bin, mio0_1ED870] -# - [0x1EE6CC, bin, mio0_1EE6CC] -# - [0x1EF4D8, bin, mio0_1EF4D8] -# - [0x1EFE1C, bin, mio0_1EFE1C] -# - [0x1F0A88, bin, mio0_1F0A88] -# - [0x1F0E0C, bin, mio0_1F0E0C] -# - [0x1F1070, bin, mio0_1F1070] -# - [0x1F277C, bin, mio0_1F277C] -# - [0x1F45B0, bin, mio0_1F45B0] -# - [0x1F558C, bin, mio0_1F558C] -# - [0x1F7608, bin, mio0_1F7608] -# - [0x1F7D8C, bin, mio0_1F7D8C] -# - [0x1F8E58, bin, mio0_1F8E58] -# - [0x1FA75C, bin, mio0_1FA75C] -# - [0x1FAFE8, bin, mio0_1FAFE8] -# - [0x1FE34C, bin, mio0_1FE34C] -# - [0x200EA0, bin, mio0_200EA0] -# - [0x202514, bin, mio0_202514] -# - [0x203D78, bin, mio0_203D78] -# - [0x2044EC, bin, mio0_2044EC] -# - [0x2056F8, bin, mio0_2056F8] -# - [0x2092D4, bin, mio0_2092D4] -# - [0x20B470, bin, mio0_20B470] -# - [0x20C11C, bin, mio0_20C11C] -# - [0x20D160, bin, mio0_20D160] -# - [0x20F0C4, bin, mio0_20F0C4] -# - [0x211DB0, bin, mio0_211DB0] -# - [0x213A7C, bin, mio0_213A7C] -# - [0x215BB0, bin, mio0_215BB0] -# - [0x2199A4, bin, mio0_2199A4] -# - [0x21AF08, bin, mio0_21AF08] -# - [0x21B05C, bin, mio0_21B05C] -# - [0x21BAB8, bin, mio0_21BAB8] -# - [0x21D134, bin, mio0_21D134] -# - [0x21E260, bin, mio0_21E260] -# - [0x21F504, bin, mio0_21F504] -# - [0x2214D8, bin, mio0_2214D8] -# - [0x221654, bin, mio0_221654] -# - [0x2222D0, bin, mio0_2222D0] -# - [0x22332C, bin, mio0_22332C] -# - [0x2255A8, bin, mio0_2255A8] -# - [0x227F2C, bin, mio0_227F2C] -# - [0x22EAA8, bin, mio0_22EAA8] -# - [0x230AD4, bin, mio0_230AD4] -# - [0x232B88, bin, mio0_232B88] -# - [0x2374C4, bin, mio0_2374C4] -# - [0x238ED8, bin, mio0_238ED8] -# - [0x23C11C, bin, mio0_23C11C] -# - [0x240070, bin, mio0_240070] -# - [0x240124, bin, mio0_240124] -# - [0x240910, bin, mio0_240910] -# - [0x240C24, bin, mio0_240C24] -# - [0x242604, bin, mio0_242604] -# - [0x2430F0, bin, mio0_2430F0] -# - [0x2435D4, bin, mio0_2435D4] -# - [0x243D88, bin, mio0_243D88] -# - [0x244984, bin, mio0_244984] -# - [0x245F7C, bin, mio0_245F7C] -# - [0x246F20, bin, mio0_246F20] -# - [0x247924, bin, mio0_247924] -# - [0x247B60, bin, mio0_247B60] -# - [0x248BB4, bin, mio0_248BB4] -# - [0x248DE0, bin, mio0_248DE0] -# - [0x24901C, bin, mio0_24901C] -# - [0x2494A0, bin, mio0_2494A0] -# - [0x249EE4, bin, mio0_249EE4] -# - [0x24A358, bin, mio0_24A358] -# - [0x24A7BC, bin, mio0_24A7BC] -# - [0x24B170, bin, mio0_24B170] -# - [0x24BA6C, bin, mio0_24BA6C] -# - [0x24C2F0, bin, mio0_24C2F0] -# - [0x24D184, bin, mio0_24D184] -# - [0x24D9E0, bin, mio0_24D9E0] -# - [0x24E07C, bin, mio0_24E07C] -# - [0x24E5E0, bin, mio0_24E5E0] -# - [0x24F15C, bin, mio0_24F15C] -# - [0x24F930, bin, mio0_24F930] -# - [0x2500D4, bin, mio0_2500D4] -# - [0x250708, bin, mio0_250708] -# - [0x250B54, bin, mio0_250B54] -# - [0x250D00, bin, mio0_250D00] -# - [0x251504, bin, mio0_251504] -# - [0x251648, bin, mio0_251648] -# - [0x251C7C, bin, mio0_251C7C] -# - [0x2522D0, bin, mio0_2522D0] -# - [0x252DA4, bin, mio0_252DA4] -# - [0x2552D0, bin, mio0_2552D0] -# - [0x255DF4, bin, mio0_255DF4] -# - [0x256710, bin, mio0_256710] -# - [0x256C94, bin, mio0_256C94] -# - [0x257400, bin, mio0_257400] -# - [0x25797C, bin, mio0_25797C] -# - [0x258794, bin, mio0_258794] -# - [0x258EB0, bin, mio0_258EB0] -# - [0x259A54, bin, mio0_259A54] -# - [0x25AA68, bin, mio0_25AA68] -# - [0x25B504, bin, mio0_25B504] -# - [0x25C020, bin, mio0_25C020] -# - [0x25CC0C, bin, mio0_25CC0C] -# - [0x25D6F8, bin, mio0_25D6F8] -# - [0x25E3B4, bin, mio0_25E3B4] -# - [0x25E5E0, bin, mio0_25E5E0] -# - [0x25EC7C, bin, mio0_25EC7C] -# - [0x25F188, bin, mio0_25F188] -# - [0x25F4BC, bin, mio0_25F4BC] -# - [0x25FA70, bin, mio0_25FA70] -# - [0x25FC04, bin, mio0_25FC04] -# - [0x25FDE0, bin, mio0_25FDE0] -# - [0x260284, bin, mio0_260284] -# - [0x260CA0, bin, mio0_260CA0] -# - [0x26162C, bin, mio0_26162C] -# - [0x261E48, bin, mio0_261E48] -# - [0x262564, bin, mio0_262564] -# - [0x262CB8, bin, mio0_262CB8] -# - [0x262FC4, bin, mio0_262FC4] -# - [0x2632F0, bin, mio0_2632F0] -# - [0x2635F4, bin, mio0_2635F4] -# - [0x2638F8, bin, mio0_2638F8] -# - [0x263BF4, bin, mio0_263BF4] -# - [0x263F00, bin, mio0_263F00] -# - [0x26421C, bin, mio0_26421C] -# - [0x264548, bin, mio0_264548] -# - [0x264844, bin, mio0_264844] -# - [0x264B30, bin, mio0_264B30] -# - [0x264E34, bin, mio0_264E34] -# - [0x265140, bin, mio0_265140] -# - [0x26545C, bin, mio0_26545C] -# - [0x265778, bin, mio0_265778] -# - [0x265A84, bin, mio0_265A84] -# - [0x265D78, bin, mio0_265D78] -# - [0x266E04, bin, mio0_266E04] -# - [0x267090, bin, mio0_267090] -# - [0x267AC4, bin, mio0_267AC4] -# - [0x267EA0, bin, mio0_267EA0] -# - [0x268094, bin, mio0_268094] -# - [0x269140, bin, mio0_269140] -# - [0x269B4C, bin, mio0_269B4C] -# - [0x26AA40, bin, mio0_26AA40] -# - [0x26B46C, bin, mio0_26B46C] -# - [0x26B9A0, bin, mio0_26B9A0] -# - [0x26BF14, bin, mio0_26BF14] -# - [0x26C488, bin, mio0_26C488] -# - [0x26CD3C, bin, mio0_26CD3C] -# - [0x26D518, bin, mio0_26D518] -# - [0x26D85C, bin, mio0_26D85C] -# - [0x26DC88, bin, mio0_26DC88] -# - [0x26E36C, bin, mio0_26E36C] -# - [0x26E430, bin, mio0_26E430] -# - [0x26F234, bin, mio0_26F234] -# - [0x26FAB8, bin, mio0_26FAB8] -# - [0x26FF14, bin, mio0_26FF14] -# - [0x270930, bin, mio0_270930] -# - [0x272C6C, bin, mio0_272C6C] -# - [0x273050, bin, mio0_273050] -# - [0x2738CC, bin, mio0_2738CC] -# - [0x274398, bin, mio0_274398] -# - [0x2747D4, bin, mio0_2747D4] -# - [0x274980, bin, mio0_274980] -# - [0x27513C, bin, mio0_27513C] -# - [0x276070, bin, mio0_276070] -# - [0x276354, bin, mio0_276354] -# - [0x277188, bin, mio0_277188] -# - [0x2777E4, bin, mio0_2777E4] -# - [0x278020, bin, mio0_278020] -# - [0x2788C4, bin, mio0_2788C4] -# - [0x279290, bin, mio0_279290] -# - [0x279E24, bin, mio0_279E24] -# - [0x27A750, bin, mio0_27A750] -# - [0x27AB74, bin, mio0_27AB74] -# - [0x27B6C0, bin, mio0_27B6C0] -# - [0x27D340, bin, mio0_27D340] -# - [0x27D8FC, bin, mio0_27D8FC] -# - [0x27E7D8, bin, mio0_27E7D8] -# - [0x27F31C, bin, mio0_27F31C] -# - [0x280EDC, bin, mio0_280EDC] -# - [0x281AC0, bin, mio0_281AC0] -# - [0x28267C, bin, mio0_28267C] -# - [0x283258, bin, mio0_283258] -# - [0x28375C, bin, mio0_28375C] -# - [0x283D00, bin, mio0_283D00] -# - [0x28428C, bin, mio0_28428C] -# - [0x2848C8, bin, mio0_2848C8] -# - [0x284D6C, bin, mio0_284D6C] -# - [0x285518, bin, mio0_285518] -# - [0x286294, bin, mio0_286294] -# - [0x2868C8, bin, mio0_2868C8] -# - [0x286D04, bin, mio0_286D04] -# - [0x287998, bin, mio0_287998] -# - [0x287F34, bin, mio0_287F34] -# - [0x2889E0, bin, mio0_2889E0] -# - [0x2892DC, bin, mio0_2892DC] -# - [0x289820, bin, mio0_289820] -# - [0x28A004, bin, mio0_28A004] -# - [0x28AA08, bin, mio0_28AA08] -# - [0x28B4B4, bin, mio0_28B4B4] -# - [0x28C2A0, bin, mio0_28C2A0] -# - [0x28CD04, bin, mio0_28CD04] -# - [0x28D238, bin, mio0_28D238] -# - [0x28D9FC, bin, mio0_28D9FC] -# - [0x28F5EC, bin, mio0_28F5EC] -# - [0x290CD4, bin, mio0_290CD4] -# - [0x291798, bin, mio0_291798] -# - [0x2922BC, bin, mio0_2922BC] -# - [0x292E28, bin, mio0_292E28] -# - [0x293694, bin, mio0_293694] -# - [0x293F20, bin, mio0_293F20] -# - [0x29499C, bin, mio0_29499C] -# - [0x295588, bin, mio0_295588] -# - [0x2962EC, bin, mio0_2962EC] -# - [0x297264, bin, mio0_297264] -# - [0x2983B4, bin, mio0_2983B4] -# - [0x2994EC, bin, mio0_2994EC] -# - [0x29A478, bin, mio0_29A478] -# - [0x29B424, bin, mio0_29B424] -# - [0x29BE98, bin, mio0_29BE98] -# - [0x29BFCC, bin, mio0_29BFCC] -# - [0x29C108, bin, mio0_29C108] -# - [0x29C3CC, bin, mio0_29C3CC] -# - [0x29CEA0, bin, mio0_29CEA0] -# - [0x29D13C, bin, mio0_29D13C] -# - [0x29DC88, bin, mio0_29DC88] -# - [0x29E88C, bin, mio0_29E88C] -# - [0x29EEE0, bin, mio0_29EEE0] -# - [0x29FA0C, bin, mio0_29FA0C] -# - [0x2A0500, bin, mio0_2A0500] -# - [0x2A14CC, bin, mio0_2A14CC] -# - [0x2A298C, bin, mio0_2A298C] -# - [0x2A3540, bin, mio0_2A3540] -# - [0x2A459C, bin, mio0_2A459C] -# - [0x2A4C18, bin, mio0_2A4C18] -# - [0x2A55E4, bin, mio0_2A55E4] -# - [0x2A5FC8, bin, mio0_2A5FC8] -# - [0x2A69C4, bin, mio0_2A69C4] -# - [0x2A6EA0, bin, mio0_2A6EA0] -# - [0x2A711C, bin, mio0_2A711C] -# - [0x2A7B28, bin, mio0_2A7B28] -# - [0x2A813C, bin, mio0_2A813C] -# - [0x2A8778, bin, mio0_2A8778] -# - [0x2A8DDC, bin, mio0_2A8DDC] -# - [0x2A9618, bin, mio0_2A9618] -# - [0x2A9C14, bin, mio0_2A9C14] -# - [0x2AA820, bin, mio0_2AA820] -# - [0x2AADDC, bin, mio0_2AADDC] -# - [0x2AB348, bin, mio0_2AB348] -# - [0x2ABD24, bin, mio0_2ABD24] -# - [0x2AC700, bin, mio0_2AC700] -# - [0x2ACFB4, bin, mio0_2ACFB4] -# - [0x2AD5B8, bin, mio0_2AD5B8] -# - [0x2ADFB4, bin, mio0_2ADFB4] -# - [0x2AE920, bin, mio0_2AE920] -# - [0x2AF154, bin, mio0_2AF154] -# - [0x2AFA28, bin, mio0_2AFA28] -# - [0x2B012C, bin, mio0_2B012C] -# - [0x2B06E0, bin, mio0_2B06E0] -# - [0x2B12CC, bin, mio0_2B12CC] -# - [0x2B1678, bin, mio0_2B1678] -# - [0x2B1DF4, bin, mio0_2B1DF4] -# - [0x2B2340, bin, mio0_2B2340] -# - [0x2B2D6C, bin, mio0_2B2D6C] -# - [0x2B36B8, bin, mio0_2B36B8] -# - [0x2B841C, bin, mio0_2B841C] -# - [0x2BA0F4, bin, mio0_2BA0F4] -# - [0x2BBDE4, bin, mio0_2BBDE4] -# - [0x2BC820, bin, mio0_2BC820] -# - [0x2BCEFC, bin, mio0_2BCEFC] -# - [0x2BD4E8, bin, mio0_2BD4E8] -# - [0x2BDD64, bin, mio0_2BDD64] -# - [0x2BE178, bin, mio0_2BE178] -# - [0x2BE91C, bin, mio0_2BE91C] -# - [0x2BF4C8, bin, mio0_2BF4C8] -# - [0x2BFD04, bin, mio0_2BFD04] -# - [0x2C0268, bin, mio0_2C0268] -# - [0x2C0A34, bin, mio0_2C0A34] -# - [0x2C0C40, bin, mio0_2C0C40] -# - [0x2C12E4, bin, mio0_2C12E4] -# - [0x2C1BB8, bin, mio0_2C1BB8] -# - [0x2C1DCC, bin, mio0_2C1DCC] -# - [0x2C26D8, bin, mio0_2C26D8] -# - [0x2C30AC, bin, mio0_2C30AC] -# - [0x2C3478, bin, mio0_2C3478] -# - [0x2C4334, bin, mio0_2C4334] -# - [0x2C5348, bin, mio0_2C5348] -# - [0x2C63B4, bin, mio0_2C63B4] -# - [0x2C6760, bin, mio0_2C6760] -# - [0x2C7374, bin, mio0_2C7374] -# - [0x2C7D60, bin, mio0_2C7D60] -# - [0x2C8834, bin, mio0_2C8834] -# - [0x2C9408, bin, mio0_2C9408] -# - [0x2C9CEC, bin, mio0_2C9CEC] -# - [0x2CAB80, bin, mio0_2CAB80] -# - [0x2CBA8C, bin, mio0_2CBA8C] -# - [0x2CC618, bin, mio0_2CC618] -# - [0x2CD13C, bin, mio0_2CD13C] -# - [0x2CDE70, bin, mio0_2CDE70] -# - [0x2CE464, bin, mio0_2CE464] -# - [0x2CEF48, bin, mio0_2CEF48] -# - [0x2CF784, bin, mio0_2CF784] -# - [0x2CFF58, bin, mio0_2CFF58] -# - [0x2D0194, bin, mio0_2D0194] -# - [0x2D14EC, bin, mio0_2D14EC] -# - [0x2D1C90, bin, mio0_2D1C90] -# - [0x2D249C, bin, mio0_2D249C] -# - [0x2D2D48, bin, mio0_2D2D48] -# - [0x2D338C, bin, mio0_2D338C] -# - [0x2D3C80, bin, mio0_2D3C80] -# - [0x2D407C, bin, mio0_2D407C] -# - [0x2D46F8, bin, mio0_2D46F8] -# - [0x2D5EC0, bin, mio0_2D5EC0] -# - [0x2D6664, bin, mio0_2D6664] -# - [0x2D68F8, bin, mio0_2D68F8] -# - [0x2D6A34, bin, mio0_2D6A34] -# - [0x2D6B78, bin, mio0_2D6B78] -# - [0x2D6C6C, bin, mio0_2D6C6C] -# - [0x2D6DB0, bin, mio0_2D6DB0] -# - [0x2D6F3C, bin, mio0_2D6F3C] -# - [0x2D7088, bin, mio0_2D7088] -# - [0x2D71D4, bin, mio0_2D71D4] -# - [0x2D7518, bin, mio0_2D7518] -# - [0x2D789C, bin, mio0_2D789C] -# - [0x2D7BE8, bin, mio0_2D7BE8] -# - [0x2D7F1C, bin, mio0_2D7F1C] -# - [0x2D83D0, bin, mio0_2D83D0] -# - [0x2D86AC, bin, mio0_2D86AC] -# - [0x2D8868, bin, mio0_2D8868] -# - [0x2D8A7C, bin, mio0_2D8A7C] -# - [0x2D8D58, bin, mio0_2D8D58] -# - [0x2D8FEC, bin, mio0_2D8FEC] -# - [0x2D9168, bin, mio0_2D9168] -# - [0x2D92F4, bin, mio0_2D92F4] -# - [0x2D94C8, bin, mio0_2D94C8] -# - [0x2D959C, bin, mio0_2D959C] -# - [0x2D96A8, bin, mio0_2D96A8] -# - [0x2D97B4, bin, mio0_2D97B4] -# - [0x2D98C0, bin, mio0_2D98C0] -# - [0x2D99CC, bin, mio0_2D99CC] -# - [0x2D9AE0, bin, mio0_2D9AE0] -# - [0x2D9BAC, bin, mio0_2D9BAC] -# - [0x2D9C88, bin, mio0_2D9C88] -# - [0x2D9F34, bin, mio0_2D9F34] -# - [0x2DA318, bin, mio0_2DA318] -# - [0x2DA7D4, bin, mio0_2DA7D4] -# - [0x2DAC18, bin, mio0_2DAC18] -# - [0x2DB2CC, bin, mio0_2DB2CC] -# - [0x2DB598, bin, mio0_2DB598] -# - [0x2DB83C, bin, mio0_2DB83C] -# - [0x2DB998, bin, mio0_2DB998] -# - [0x2DBBB4, bin, mio0_2DBBB4] -# - [0x2DBDA0, bin, mio0_2DBDA0] -# - [0x2DBE94, bin, mio0_2DBE94] -# - [0x2DBFC0, bin, mio0_2DBFC0] -# - [0x2DC0D4, bin, mio0_2DC0D4] -# - [0x2DC1B8, bin, mio0_2DC1B8] -# - [0x2DC354, bin, mio0_2DC354] -# - [0x2DC480, bin, mio0_2DC480] -# - [0x2DCABC, bin, mio0_2DCABC] -# - [0x2DCC38, bin, mio0_2DCC38] -# - [0x2DCE44, bin, mio0_2DCE44] -# - [0x2DD050, bin, mio0_2DD050] -# - [0x2DD254, bin, mio0_2DD254] -# - [0x2DD460, bin, mio0_2DD460] -# - [0x2DD67C, bin, mio0_2DD67C] -# - [0x2DD8F8, bin, mio0_2DD8F8] -# - [0x2DDB7C, bin, mio0_2DDB7C] -# - [0x2DDDF8, bin, mio0_2DDDF8] -# - [0x2DDEE4, bin, mio0_2DDEE4] -# - [0x2DE518, bin, mio0_2DE518] -# - [0x2DE684, bin, mio0_2DE684] -# - [0x2DE808, bin, mio0_2DE808] -# - [0x2DF33C, bin, mio0_2DF33C] -# - [0x2DF458, bin, mio0_2DF458] -# - [0x2DF96C, bin, mio0_2DF96C] -# - [0x2DFA80, bin, mio0_2DFA80] -# - [0x2E0324, bin, mio0_2E0324] -# - [0x2E0520, bin, mio0_2E0520] -# - [0x2E065C, bin, mio0_2E065C] -# - [0x2E09D0, bin, mio0_2E09D0] -# - [0x2E10EC, bin, mio0_2E10EC] -# - [0x2E15C0, bin, mio0_2E15C0] -# - [0x2E1914, bin, mio0_2E1914] -# - [0x2E20C8, bin, mio0_2E20C8] -# - [0x2E264C, bin, mio0_2E264C] -# - [0x2E2BD0, bin, mio0_2E2BD0] -# - [0x2E318C, bin, mio0_2E318C] -# - [0x2E34A8, bin, mio0_2E34A8] -# - [0x2E35B4, bin, mio0_2E35B4] -# - [0x2E3B98, bin, mio0_2E3B98] -# - [0x2E4184, bin, mio0_2E4184] -# - [0x2E4578, bin, mio0_2E4578] -# - [0x2E468C, bin, mio0_2E468C] -# - [0x2E4F58, bin, mio0_2E4F58] -# - [0x2E506C, bin, mio0_2E506C] -# - [0x2E5980, bin, mio0_2E5980] -# - [0x2E5D64, bin, mio0_2E5D64] -# - [0x2E6430, bin, mio0_2E6430] -# - [0x2E66EC, bin, mio0_2E66EC] -# - [0x2E6B70, bin, mio0_2E6B70] -# - [0x2E713C, bin, mio0_2E713C] -# - [0x2E7938, bin, mio0_2E7938] -# - [0x2E7EFC, bin, mio0_2E7EFC] -# - [0x2E8438, bin, mio0_2E8438] -# - [0x2E88F4, bin, mio0_2E88F4] -# - [0x2E8A38, bin, mio0_2E8A38] -# - [0x2E926C, bin, mio0_2E926C] -# - [0x2E9388, bin, mio0_2E9388] -# - [0x2E98AC, bin, mio0_2E98AC] -# - [0x2E99C0, bin, mio0_2E99C0] -# - [0x2E9FE4, bin, mio0_2E9FE4] -# - [0x2EA578, bin, mio0_2EA578] -# - [0x2EAC74, bin, mio0_2EAC74] -# - [0x2EB358, bin, mio0_2EB358] -# - [0x2EB6C4, bin, mio0_2EB6C4] -# - [0x2EB7B8, bin, mio0_2EB7B8] -# - [0x2EBBBC, bin, mio0_2EBBBC] -# - [0x2EBCC8, bin, mio0_2EBCC8] -# - [0x2EBDE4, bin, mio0_2EBDE4] -# - [0x2EBED0, bin, mio0_2EBED0] -# - [0x2EC444, bin, mio0_2EC444] -# - [0x2EC888, bin, mio0_2EC888] -# - [0x2ECFF4, bin, mio0_2ECFF4] -# - [0x2ED138, bin, mio0_2ED138] -# - [0x2ED44C, bin, mio0_2ED44C] -# - [0x2EDA60, bin, mio0_2EDA60] -# - [0x2EE104, bin, mio0_2EE104] -# - [0x2EE320, bin, mio0_2EE320] -# - [0x2EE674, bin, mio0_2EE674] -# - [0x2EEDA8, bin, mio0_2EEDA8] -# - [0x2EF52C, bin, mio0_2EF52C] -# - [0x2EF898, bin, mio0_2EF898] -# - [0x2EF9F4, bin, mio0_2EF9F4] -# - [0x2EFD10, bin, mio0_2EFD10] -# - [0x2F03B4, bin, mio0_2F03B4] -# - [0x2F09A8, bin, mio0_2F09A8] -# - [0x2F0E44, bin, mio0_2F0E44] -# - [0x2F0F38, bin, mio0_2F0F38] -# - [0x2F153C, bin, mio0_2F153C] -# - [0x2F1738, bin, mio0_2F1738] -# - [0x2F1C1C, bin, mio0_2F1C1C] -# - [0x2F23D8, bin, mio0_2F23D8] -# - [0x2F2924, bin, mio0_2F2924] -# - [0x2F2A40, bin, mio0_2F2A40] -# - [0x2F2FC4, bin, mio0_2F2FC4] -# - [0x2F3180, bin, mio0_2F3180] -# - [0x2F336C, bin, mio0_2F336C] -# - [0x2F3A88, bin, mio0_2F3A88] -# - [0x2F41D4, bin, mio0_2F41D4] -# - [0x2F4490, bin, mio0_2F4490] -# - [0x2F4614, bin, mio0_2F4614] -# - [0x2F4750, bin, mio0_2F4750] -# - [0x2F52FC, bin, mio0_2F52FC] -# - [0x2F5848, bin, mio0_2F5848] -# - [0x2F5BD4, bin, mio0_2F5BD4] -# - [0x2F5E40, bin, mio0_2F5E40] -# - [0x2F6004, bin, mio0_2F6004] -# - [0x2F65A0, bin, mio0_2F65A0] -# - [0x2F6B2C, bin, mio0_2F6B2C] -# - [0x2F6E58, bin, mio0_2F6E58] -# - [0x2F72EC, bin, mio0_2F72EC] -# - [0x2F7448, bin, mio0_2F7448] -# - [0x2F76AC, bin, mio0_2F76AC] -# - [0x2F80D8, bin, mio0_2F80D8] -# - [0x2F8814, bin, mio0_2F8814] -# - [0x2F8EC8, bin, mio0_2F8EC8] -# - [0x2F96BC, bin, mio0_2F96BC] -# - [0x2F9F18, bin, mio0_2F9F18] -# - [0x2FA604, bin, mio0_2FA604] -# - [0x2FA768, bin, mio0_2FA768] -# - [0x2FB44C, bin, mio0_2FB44C] -# - [0x2FBF28, bin, mio0_2FBF28] -# - [0x2FCC5C, bin, mio0_2FCC5C] -# - [0x2FD870, bin, mio0_2FD870] -# - [0x2FE44C, bin, mio0_2FE44C] -# - [0x2FF060, bin, mio0_2FF060] -# - [0x2FF584, bin, mio0_2FF584] -# - [0x2FFAC8, bin, mio0_2FFAC8] -# - [0x2FFE34, bin, mio0_2FFE34] -# - [0x300730, bin, mio0_300730] -# - [0x300F7C, bin, mio0_300F7C] -# - [0x301510, bin, mio0_301510] -# - [0x30183C, bin, mio0_30183C] -# - [0x301AB0, bin, mio0_301AB0] -# - [0x30284C, bin, mio0_30284C] -# - [0x303058, bin, mio0_303058] -# - [0x3039A4, bin, mio0_3039A4] -# - [0x3041D8, bin, mio0_3041D8] -# - [0x304DAC, bin, mio0_304DAC] -# - [0x305708, bin, mio0_305708] -# - [0x306244, bin, mio0_306244] -# - [0x306CF8, bin, mio0_306CF8] -# - [0x307594, bin, mio0_307594] -# - [0x308620, bin, mio0_308620] -# - [0x308678, bin, mio0_308678] -# - [0x3086D8, bin, mio0_3086D8] -# - [0x30888C, bin, mio0_30888C] -# - [0x3089C4, bin, mio0_3089C4] -# - [0x308A64, bin, mio0_308A64] -# - [0x308AD4, bin, mio0_308AD4] -# - [0x308B7C, bin, mio0_308B7C] -# - [0x308BF4, bin, mio0_308BF4] -# - [0x308E58, bin, mio0_308E58] -# - [0x308EB0, bin, mio0_308EB0] -# - [0x308F40, bin, mio0_308F40] -# - [0x308F90, bin, mio0_308F90] -# - [0x30905C, bin, mio0_30905C] -# - [0x3090DC, bin, mio0_3090DC] -# - [0x30914C, bin, mio0_30914C] -# - [0x3091CC, bin, mio0_3091CC] -# - [0x3092B4, bin, mio0_3092B4] -# - [0x30932C, bin, mio0_30932C] -# - [0x30939C, bin, mio0_30939C] -# - [0x30941C, bin, mio0_30941C] -# - [0x309484, bin, mio0_309484] -# - [0x3094F4, bin, mio0_3094F4] -# - [0x309564, bin, mio0_309564] -# - [0x309610, bin, mio0_309610] -# - [0x309760, bin, mio0_309760] -# - [0x309858, bin, mio0_309858] -# - [0x3098D8, bin, mio0_3098D8] -# - [0x309938, bin, mio0_309938] -# - [0x3099D0, bin, mio0_3099D0] -# - [0x309A38, bin, mio0_309A38] -# - [0x309B1C, bin, mio0_309B1C] -# - [0x309B9C, bin, mio0_309B9C] -# - [0x309D6C, bin, mio0_309D6C] -# - [0x309DD4, bin, mio0_309DD4] -# - [0x309E44, bin, mio0_309E44] -# - [0x309EB4, bin, mio0_309EB4] -# - [0x309F24, bin, mio0_309F24] -# - [0x309F94, bin, mio0_309F94] -# - [0x30A264, bin, mio0_30A264] -# - [0x30A304, bin, mio0_30A304] -# - [0x30A4EC, bin, mio0_30A4EC] -# - [0x30A55C, bin, mio0_30A55C] -# - [0x30A5CC, bin, mio0_30A5CC] -# - [0x30A634, bin, mio0_30A634] -# - [0x30A6A4, bin, mio0_30A6A4] -# - [0x30A714, bin, mio0_30A714] -# - [0x30A7B0, bin, mio0_30A7B0] -# - [0x30A830, bin, mio0_30A830] -# - [0x30AA80, bin, mio0_30AA80] -# - [0x30AAE8, bin, mio0_30AAE8] -# - [0x30AB58, bin, mio0_30AB58] -# - [0x30ABC8, bin, mio0_30ABC8] -# - [0x30AC38, bin, mio0_30AC38] -# - [0x30ACA8, bin, mio0_30ACA8] -# - [0x30AD54, bin, mio0_30AD54] -# - [0x30B064, bin, mio0_30B064] -# - [0x30B0D4, bin, mio0_30B0D4] -# - [0x30B1B4, bin, mio0_30B1B4] -# - [0x30B21C, bin, mio0_30B21C] -# - [0x30B468, bin, mio0_30B468] -# - [0x30B568, bin, mio0_30B568] -# - [0x30B680, bin, mio0_30B680] -# - [0x30B700, bin, mio0_30B700] -# - [0x30B924, bin, mio0_30B924] -# - [0x30BA2C, bin, mio0_30BA2C] -# - [0x30BABC, bin, mio0_30BABC] -# - [0x30BBD4, bin, mio0_30BBD4] -# - [0x30C158, bin, mio0_30C158] -# - [0x30C1D0, bin, mio0_30C1D0] -# - [0x30C320, bin, mio0_30C320] -# - [0x30C674, bin, mio0_30C674] -# - [0x30CD04, bin, mio0_30CD04] -# - [0x30CE14, bin, mio0_30CE14] -# - [0x30DD54, bin, mio0_30DD54] -# - [0x30DE1C, bin, mio0_30DE1C] -# - [0x30DE84, bin, mio0_30DE84] -# - [0x30DFFC, bin, mio0_30DFFC] -# - [0x30E0FC, bin, mio0_30E0FC] -# - [0x30E1B4, bin, mio0_30E1B4] -# - [0x30E2BC, bin, mio0_30E2BC] -# - [0x30E3C4, bin, mio0_30E3C4] -# - [0x30E600, bin, mio0_30E600] -# - [0x30E690, bin, mio0_30E690] -# - [0x30E708, bin, mio0_30E708] -# - [0x30E788, bin, mio0_30E788] -# - [0x30E818, bin, mio0_30E818] -# - [0x30E898, bin, mio0_30E898] -# - [0x30EC4C, bin, mio0_30EC4C] -# - [0x30ECC4, bin, mio0_30ECC4] -# - [0x30ED2C, bin, mio0_30ED2C] -# - [0x30ED94, bin, mio0_30ED94] -# - [0x30EE0C, bin, mio0_30EE0C] -# - [0x30EE7C, bin, mio0_30EE7C] -# - [0x30F138, bin, mio0_30F138] -# - [0x30F700, bin, mio0_30F700] -# - [0x30F810, bin, mio0_30F810] -# - [0x30F928, bin, mio0_30F928] -# - [0x3101D8, bin, mio0_3101D8] -# - [0x310808, bin, mio0_310808] -# - [0x310900, bin, mio0_310900] -# - [0x3109C0, bin, mio0_3109C0] -# - [0x310A98, bin, mio0_310A98] -# - [0x310B78, bin, mio0_310B78] -# - [0x310C38, bin, mio0_310C38] -# - [0x310CE8, bin, mio0_310CE8] -# - [0x310DF0, bin, mio0_310DF0] -# - [0x310EB0, bin, mio0_310EB0] -# - [0x310F70, bin, mio0_310F70] -# - [0x3110A8, bin, mio0_3110A8] -# - [0x3111C0, bin, mio0_3111C0] -# - [0x3112F8, bin, mio0_3112F8] -# - [0x3114FC, bin, mio0_3114FC] -# - [0x31161C, bin, mio0_31161C] -# - [0x3116AC, bin, mio0_3116AC] -# - [0x3117F4, bin, mio0_3117F4] -# - [0x311D00, bin, mio0_311D00] -# - [0x311D80, bin, mio0_311D80] -# - [0x311DE8, bin, mio0_311DE8] -# - [0x311EC0, bin, mio0_311EC0] -# - [0x311F38, bin, mio0_311F38] -# - [0x31221C, bin, mio0_31221C] -# - [0x3127E4, bin, mio0_3127E4] -# - [0x3128EC, bin, mio0_3128EC] -# - [0x3129EC, bin, mio0_3129EC] -# - [0x312E34, bin, mio0_312E34] -# - [0x313274, bin, mio0_313274] -# - [0x313944, bin, mio0_313944] -# - [0x313A7C, bin, mio0_313A7C] -# - [0x313C64, bin, mio0_313C64] -# - [0x313D94, bin, mio0_313D94] -# - [0x3141B8, bin, mio0_3141B8] -# - [0x3142C8, bin, mio0_3142C8] -# - [0x314F8C, bin, mio0_314F8C] -# - [0x31501C, bin, mio0_31501C] -# - [0x315124, bin, mio0_315124] -# - [0x31596C, bin, mio0_31596C] -# - [0x3159DC, bin, mio0_3159DC] -# - [0x315AC4, bin, mio0_315AC4] -# - [0x315B34, bin, mio0_315B34] -# - [0x315F70, bin, mio0_315F70] -# - [0x316558, bin, mio0_316558] -# - [0x316620, bin, mio0_316620] -# - [0x316900, bin, mio0_316900] -# - [0x316AB0, bin, mio0_316AB0] -# - [0x316C48, bin, mio0_316C48] -# - [0x316D38, bin, mio0_316D38] -# - [0x316E60, bin, mio0_316E60] -# - [0x316F68, bin, mio0_316F68] -# - [0x3171E8, bin, mio0_3171E8] -# - [0x3172C8, bin, mio0_3172C8] -# - [0x31769C, bin, mio0_31769C] -# - [0x318318, bin, mio0_318318] -# - [0x3183A0, bin, mio0_3183A0] -# - [0x318420, bin, mio0_318420] -# - [0x3184A8, bin, mio0_3184A8] -# - [0x318530, bin, mio0_318530] -# - [0x318B40, bin, mio0_318B40] -# - [0x318BB0, bin, mio0_318BB0] -# - [0x318C20, bin, mio0_318C20] -# - [0x318C90, bin, mio0_318C90] -# - [0x318D00, bin, mio0_318D00] -# - [0x318D70, bin, mio0_318D70] -# - [0x319124, bin, mio0_319124] -# - [0x31975C, bin, mio0_31975C] -# - [0x3198BC, bin, mio0_3198BC] -# - [0x319A5C, bin, mio0_319A5C] -# - [0x319D80, bin, mio0_319D80] -# - [0x319E98, bin, mio0_319E98] -# - [0x319F98, bin, mio0_319F98] -# - [0x31A858, bin, mio0_31A858] -# - [0x31AB6C, bin, mio0_31AB6C] -# - [0x31ABC4, bin, mio0_31ABC4] -# - [0x31ACCC, bin, mio0_31ACCC] -# - [0x31AD4C, bin, mio0_31AD4C] -# - [0x31ADD4, bin, mio0_31ADD4] -# - [0x31AE5C, bin, mio0_31AE5C] -# - [0x31AEDC, bin, mio0_31AEDC] -# - [0x31B5EC, bin, mio0_31B5EC] -# - [0x31B65C, bin, mio0_31B65C] -# - [0x31B73C, bin, mio0_31B73C] -# - [0x31B7AC, bin, mio0_31B7AC] -# - [0x31BBE0, bin, mio0_31BBE0] -# - [0x31C190, bin, mio0_31C190] -# - [0x31C240, bin, mio0_31C240] -# - [0x31C2C8, bin, mio0_31C2C8] -# - [0x31C358, bin, mio0_31C358] -# - [0x31C3E0, bin, mio0_31C3E0] -# - [0x31C488, bin, mio0_31C488] -# - [0x31C550, bin, mio0_31C550] -# - [0x31C5F8, bin, mio0_31C5F8] -# - [0x31CB9C, bin, mio0_31CB9C] -# - [0x31EAF0, bin, mio0_31EAF0] -# - [0x31EBC0, bin, mio0_31EBC0] -# - [0x31ED58, bin, mio0_31ED58] -# - [0x31EE00, bin, mio0_31EE00] -# - [0x31FA58, bin, mio0_31FA58] -# - [0x31FD58, bin, mio0_31FD58] -# - [0x320C70, bin, mio0_320C70] -# - [0x320D18, bin, mio0_320D18] -# - [0x321970, bin, mio0_321970] -# - [0x321A40, bin, mio0_321A40] -# - [0x321BD8, bin, mio0_321BD8] -# - [0x321C80, bin, mio0_321C80] -# - [0x322524, bin, mio0_322524] -# - [0x322824, bin, mio0_322824] -# - [0x323388, bin, mio0_323388] -# - [0x323430, bin, mio0_323430] -# - [0x324374, bin, mio0_324374] -# - [0x3243C4, bin, mio0_3243C4] -# - [0x324464, bin, mio0_324464] -# - [0x32466C, bin, mio0_32466C] -# - [0x3256FC, bin, mio0_3256FC] -# - [0x3257A4, bin, mio0_3257A4] -# - [0x325884, bin, mio0_325884] -# - [0x3258DC, bin, mio0_3258DC] -# - [0x3264B0, bin, mio0_3264B0] -# - [0x3265A0, bin, mio0_3265A0] -# - [0x3265F8, bin, mio0_3265F8] -# - [0x326650, bin, mio0_326650] -# - [0x326730, bin, mio0_326730] -# - [0x327238, bin, mio0_327238] -# - [0x327288, bin, mio0_327288] -# - [0x327328, bin, mio0_327328] -# - [0x327530, bin, mio0_327530] -# - [0x327F30, bin, mio0_327F30] -# - [0x327FD8, bin, mio0_327FD8] -# - [0x3280B8, bin, mio0_3280B8] -# - [0x328110, bin, mio0_328110] -# - [0x328AC0, bin, mio0_328AC0] -# - [0x328BB0, bin, mio0_328BB0] -# - [0x328C08, bin, mio0_328C08] -# - [0x329230, bin, mio0_329230] -# - [0x3292C0, bin, mio0_3292C0] -# - [0x329370, bin, mio0_329370] -# - [0x3293F8, bin, mio0_3293F8] -# - [0x329450, bin, mio0_329450] -# - [0x3294D8, bin, mio0_3294D8] -# - [0x329530, bin, mio0_329530] -# - [0x3295A0, bin, mio0_3295A0] -# - [0x329610, bin, mio0_329610] -# - [0x329678, bin, mio0_329678] -# - [0x3296E0, bin, mio0_3296E0] -# - [0x329748, bin, mio0_329748] -# - [0x3297B0, bin, mio0_3297B0] -# - [0x32985C, bin, mio0_32985C] -# - [0x3298DC, bin, mio0_3298DC] -# - [0x32997C, bin, mio0_32997C] -# - [0x329A04, bin, mio0_329A04] -# - [0x329A94, bin, mio0_329A94] -# - [0x329B3C, bin, mio0_329B3C] -# - [0x329BC4, bin, mio0_329BC4] -# - [0x329C64, bin, mio0_329C64] -# - [0x329D14, bin, mio0_329D14] -# - [0x329D94, bin, mio0_329D94] -# - [0x329E44, bin, mio0_329E44] -# - [0x329EF4, bin, mio0_329EF4] -# - [0x329F94, bin, mio0_329F94] -# - [0x32A3E0, bin, mio0_32A3E0] -# - [0x32A440, bin, mio0_32A440] -# - [0x32A558, bin, mio0_32A558] -# - [0x32A620, bin, mio0_32A620] -# - [0x32A6F8, bin, mio0_32A6F8] -# - [0x32A7E0, bin, mio0_32A7E0] -# - [0x32A9D0, bin, mio0_32A9D0] -# - [0x32ABD4, bin, mio0_32ABD4] -# - [0x32AC5C, bin, mio0_32AC5C] -# - [0x32AE14, bin, mio0_32AE14] -# - [0x32AF14, bin, mio0_32AF14] -# - [0x32B094, bin, mio0_32B094] -# - [0x32B17C, bin, mio0_32B17C] -# - [0x32B204, bin, mio0_32B204] -# - [0x32B4F0, bin, mio0_32B4F0] -# - [0x32B578, bin, mio0_32B578] -# - [0x32B5E8, bin, mio0_32B5E8] -# - [0x32B668, bin, mio0_32B668] -# - [0x32B6E8, bin, mio0_32B6E8] -# - [0x32B770, bin, mio0_32B770] -# - [0x32B858, bin, mio0_32B858] -# - [0x32BBDC, bin, mio0_32BBDC] -# - [0x32BC9C, bin, mio0_32BC9C] -# - [0x32BDD4, bin, mio0_32BDD4] -# - [0x32BEA8, bin, mio0_32BEA8] -# - [0x32BFA0, bin, mio0_32BFA0] -# - [0x32C048, bin, mio0_32C048] -# - [0x32C0A0, bin, mio0_32C0A0] -# - [0x32C454, bin, mio0_32C454] -# - [0x32C52C, bin, mio0_32C52C] -# - [0x32C9C0, bin, mio0_32C9C0] -# - [0x32CA20, bin, mio0_32CA20] -# - [0x32CB2C, bin, mio0_32CB2C] -# - [0x32CBF4, bin, mio0_32CBF4] -# - [0x32CF88, bin, mio0_32CF88] -# - [0x32D068, bin, mio0_32D068] -# - [0x32D394, bin, mio0_32D394] -# - [0x32D43C, bin, mio0_32D43C] -# - [0x32D5F4, bin, mio0_32D5F4] -# - [0x32D7BC, bin, mio0_32D7BC] -# - [0x32D984, bin, mio0_32D984] -# - [0x32DA90, bin, mio0_32DA90] -# - [0x32DB70, bin, mio0_32DB70] -# - [0x32DE10, bin, mio0_32DE10] -# - [0x32E098, bin, mio0_32E098] -# - [0x32E374, bin, mio0_32E374] -# - [0x32E404, bin, mio0_32E404] -# - [0x32E76C, bin, mio0_32E76C] -# - [0x32EB2C, bin, mio0_32EB2C] -# - [0x32EEC4, bin, mio0_32EEC4] -# - [0x32F29C, bin, mio0_32F29C] -# - [0x32F66C, bin, mio0_32F66C] -# - [0x32F8E0, bin, mio0_32F8E0] -# - [0x32F950, bin, mio0_32F950] -# - [0x32FC58, bin, mio0_32FC58] -# - [0x32FDE4, bin, mio0_32FDE4] -# - [0x32FE5C, bin, mio0_32FE5C] -# - [0x33014C, bin, mio0_33014C] -# - [0x44D4B8, bin, mio0_44D4B8] -# - [0x44D6A4, bin, mio0_44D6A4] -# - [0x44D898, bin, mio0_44D898] -# - [0x44DA7C, bin, mio0_44DA7C] -# - [0x44DC78, bin, mio0_44DC78] -# - [0x44F294, bin, mio0_44F294] -# - [0x450D10, bin, mio0_450D10] -# - [0x451304, bin, mio0_451304] -# - [0x455088, bin, mio0_455088] -# - [0x458404, bin, mio0_458404] -# - [0x4616B0, bin, mio0_4616B0] -# - [0x46177C, bin, mio0_46177C] -# - [0x462ECC, bin, mio0_462ECC] -# - [0x462F90, bin, mio0_462F90] -# - [0x4632F4, bin, mio0_4632F4] -# - [0x463700, bin, mio0_463700] -# - [0x463B74, bin, mio0_463B74] -# - [0x463F20, bin, mio0_463F20] -# - [0x464224, bin, mio0_464224] -# - [0x464700, bin, mio0_464700] -# - [0x464A9C, bin, mio0_464A9C] -# - [0x464B38, bin, mio0_464B38] -# - [0x464BCC, bin, mio0_464BCC] -# - [0x464CB8, bin, mio0_464CB8] -# - [0x464D24, bin, mio0_464D24] -# - [0x464D98, bin, mio0_464D98] -# - [0x464E14, bin, mio0_464E14] -# - [0x464FB0, bin, mio0_464FB0] -# - [0x4650EC, bin, mio0_4650EC] -# - [0x465F38, bin, mio0_465F38] -# - [0x4661CC, bin, mio0_4661CC] -# - [0x467660, bin, mio0_467660] -# - [0x468A6C, bin, mio0_468A6C] -# - [0x46A548, bin, mio0_46A548] -# - [0x46BB6C, bin, mio0_46BB6C] -# - [0x46CE50, bin, mio0_46CE50] -# - [0x46E204, bin, mio0_46E204] -# - [0x46F580, bin, mio0_46F580] -# - [0x471B04, bin, mio0_471B04] -# - [0x4736B8, bin, mio0_4736B8] -# - [0x475464, bin, mio0_475464] -# - [0x4772E0, bin, mio0_4772E0] -# - [0x47918C, bin, mio0_47918C] -# - [0x47B338, bin, mio0_47B338] -# - [0x47D2E4, bin, mio0_47D2E4] -# - [0x47E998, bin, mio0_47E998] -# - [0x47EB7C, bin, mio0_47EB7C] -# - [0x47ED38, bin, mio0_47ED38] -# - [0x47EE44, bin, mio0_47EE44] -# - [0x47EF50, bin, mio0_47EF50] -# - [0x47F0BC, bin, mio0_47F0BC] -# - [0x47F198, bin, mio0_47F198] -# - [0x47F28C, bin, mio0_47F28C] -# - [0x47F448, bin, mio0_47F448] -# - [0x47F60C, bin, mio0_47F60C] -# - [0x47F758, bin, mio0_47F758] -# - [0x47F854, bin, mio0_47F854] -# - [0x47FA78, bin, mio0_47FA78] -# - [0x47FBDC, bin, mio0_47FBDC] -# - [0x4924F8, bin, mio0_4924F8] -# - [0x4927AC, bin, mio0_4927AC] -# - [0x4931A8, bin, mio0_4931A8] -# - [0x493B6C, bin, mio0_493B6C] -# - [0x494488, bin, mio0_494488] -# - [0x495074, bin, mio0_495074] -# - [0x495B40, bin, mio0_495B40] -# - [0x496774, bin, mio0_496774] -# - [0x4A1ED8, bin, mio0_4A1ED8] -# - [0x4A4444, bin, mio0_4A4444] -# - [0x4A6978, bin, mio0_4A6978] -# - [0x4A8A6C, bin, mio0_4A8A6C] -# - [0x4AAFA0, bin, mio0_4AAFA0] -# - [0x4AD074, bin, mio0_4AD074] -# - [0x4AF798, bin, mio0_4AF798] -# - [0x4B09A4, bin, mio0_4B09A4] -# - [0x4B0D88, bin, mio0_4B0D88] -# - [0x4B0E7C, bin, mio0_4B0E7C] -# - [0x4B13A0, bin, mio0_4B13A0] -# - [0x4B1454, bin, mio0_4B1454] -# - [0x4B1698, bin, mio0_4B1698] -# - [0x4B19FC, bin, mio0_4B19FC] -# - [0x4B1E80, bin, mio0_4B1E80] -# - [0x4B22DC, bin, mio0_4B22DC] -# - [0x4B2508, bin, mio0_4B2508] -# - [0x4B2714, bin, mio0_4B2714] -# - [0x4B2958, bin, mio0_4B2958] -# - [0x4B2C7C, bin, mio0_4B2C7C] -# - [0x4B2D78, bin, mio0_4B2D78] -# - [0x4B34BC, bin, mio0_4B34BC] -# - [0x4B38C8, bin, mio0_4B38C8] -# - [0x4B3AFC, bin, mio0_4B3AFC] -# - [0x4B3D70, bin, mio0_4B3D70] -# - [0x4B3F84, bin, mio0_4B3F84] -# - [0x4B4200, bin, mio0_4B4200] -# - [0x4B429C, bin, mio0_4B429C] -# - [0x4B4318, bin, mio0_4B4318] -# - [0x4B4394, bin, mio0_4B4394] -# - [0x4B4858, bin, mio0_4B4858] -# - [0x4B4D64, bin, mio0_4B4D64] -# - [0x4B51D8, bin, mio0_4B51D8] -# - [0x4B54FC, bin, mio0_4B54FC] - - [0x800000] diff --git a/docs/pilotwings64_filesystem.md b/docs/pilotwings64_filesystem.md index cb82a33f..aeff2130 100644 --- a/docs/pilotwings64_filesystem.md +++ b/docs/pilotwings64_filesystem.md @@ -72,6 +72,7 @@ User files are specific to the game's data (e.g. landing pads, balloons, rings). | `TABL` | func_802246a0 | Table of FORM data entries | `3VUE` | userPath_8034A4F8 | User path data | `ADAT` | textLoadBlock | ASCII text for on-screen messages +| `PDAT` | demoAttLoadPos | Position and controller data for demo recordings | `UPWL` | levelLoadMapObjects | User Pilotwings Level (map) | `UPWT` | taskLoadCommObj | User Pilotwings Task (test) | `UVAN` | uvJanimLoad | Animation @@ -243,6 +244,68 @@ The bold letters are representing by adding 0x60 to the character. | 0x08 | u16[] | PW64 custom u16 encoded string +### File PDAT / Position Data + +`PDAT` contains the position, angle, and controller button data used in +the demo recordings. + +#### PDAT::PHDR / position header + +`PHDR` contains the timing information for the position recording. + +| Offset | Type | Description +|--------|---------|------------ +| 0x00 | char[4] | `PHDR` +| 0x04 | u32 | length (bytes) +| 0x08 | s32 | duration +| 0x0C | s32 | count of `PPOS` +| *0x10* | | **Total length** + +#### PDAT::PPOS / position, angle + +`PPOS` contains the position and rotation information. +Each data point contains the full `PPOS` header. + +| Offset | Type | Description +|--------|---------|------------ +| 0x00 | char[4] | `PPOS` +| 0x04 | u32 | length (bytes) +| 0x08 | Vec3F | position +| 0x14 | Vec3F | rotation +| *0x20* | | **Total length** + +#### PDAT::RHDR / recording header + +`RHDR` contains the meta data for the demo recording. +This includes test, class, and vehicle IDs. + +| Offset | Type | Description +|--------|---------|------------ +| 0x00 | char[4] | `RHDR` +| 0x04 | u32 | length (bytes) +| 0x08 | s32 | test ID +| 0x0C | s32 | TBD, unused but set in range [0-5] +| 0x10 | s32 | class ID +| 0x14 | s32 | vehicle ID +| 0x18 | s32 | TBD (always 0) +| *0x1C* | | **Total length** + +#### PDAT::RPKT / button recording packet + +`RPKT` contains the controller recording packet data. Each data point contains +a full `RPKT` header and contains the time, analog stick, and button values. + +| Offset | Type | Description +|--------|---------|------------ +| 0x00 | char[4] | `RPKT` +| 0x04 | u32 | length (bytes) +| 0x08 | f32 | timestamp +| 0x0C | f32 | analog stick X +| 0x10 | f32 | analog stick Y +| 0x14 | u32 | button mask +| *0x18* | | **Total length** + + ### File UPWL / PW64 Level The `UPWL` is a user file that contains the Pilotwings 64 Level (map) data. @@ -311,7 +374,7 @@ Each map only defines one bonus star, but the game can support up to two. |--------|---------|------------ | 0x00 | char[4] | `ESND` | 0x04 | u32 | length (bytes) -| 0x08 | BNUS[] | array of entries +| 0x08 | ESND[] | array of entries | Entry | Type | Description |--------|---------|----------------- @@ -744,7 +807,7 @@ There is one entry for each photo required for the test. | 0x00 | Vec3F | position | 0x0C | Vec3F | angle (degrees) | 0x18 | s32 | TBD -| 0x1C | pad[1] | +| 0x1C | u8 | unknown, copied but unused [0-1] | 0x1D | u8 | child ring count | 0x1E | pad[2] | | 0x20 | s32[5] | array of child ring indexes @@ -759,7 +822,7 @@ There is one entry for each photo required for the test. | 0x55 | u8 | is active | 0x56 | pad[2] | | 0x58 | f32 | rotation rate 0 -| 0x5C | f32 | x,y,z traslation +| 0x5C | f32 | x,y,z translation | 0x60 | char | rotation axis 0 ('x', 'y', 'z', or 'n') | 0x61 | pad[3] | | 0x64 | f32 | rotation rate 1 (after timeout) @@ -774,7 +837,7 @@ There is one entry for each photo required for the test. #### UPWT::SDFM -`SDFM` is not present in the filesystem, but is copied in code so only size is known. +`SDFM` is not present in the filesystem and is only copied in code so only size is known. | Offset | Type | Description |--------|--------|------------ @@ -922,19 +985,20 @@ parse out a single `COMM` entry that is 4 bytes long. TBD -### File UVSQ / sequences +### File UVSQ / texture animation sequences | Offset | Type | Description |--------|--------|----------------- -| 0x00 | u8 | count of entries -| 0x01 | UVSQ[] | sequence entries -| | u8 | TBD -| | f32 | TBD +| 0x00 | u8 | frame count +| 0x01 | UVSQ[] | frame table +| | u8 | mode +| | u8 | reverse +| | f32 | frame rate -| Entry | Type | Description +| Frame | Type | Description |--------|-------|----------------------- -| 0x00 | u16 | TBD, range [0x45-0x54] -| 0x02 | f32 | TBD, always 1.0 +| 0x00 | u16 | texture ID +| 0x02 | f32 | frame time (always 1.0) | 0x06 | | **Total length** @@ -966,8 +1030,8 @@ structure. | Offset | Type | Description |--------|--------|----------------- -| 0x00 | u8 | count of entries -| 0x01 | UVTP[] | sequence entries +| 0x00 | u16 | count of entries +| 0x02 | UVTP[] | sequence entries | Entry | Type | Description |--------|-------|------------ diff --git a/docs/pilotwings64_imhex.hexpat b/docs/pilotwings64_imhex.hexpat index 5cf47a6a..53a442fa 100644 --- a/docs/pilotwings64_imhex.hexpat +++ b/docs/pilotwings64_imhex.hexpat @@ -96,11 +96,23 @@ fn format_Vec3F(Vec3F v) { struct Vec4F { f32 x, y, z, w; -} [[static]]; // size = 0x10 +} [[static, format("format_Vec4F")]]; // size = 0x10 + +fn format_Vec4F(Vec4F v) { + return std::format("[ {}, {}, {}, {} ]", v.x, v.y, v.z, v.w); +}; struct Mtx4F { f32 m[16]; -} [[static]]; // size = 0x40 +} [[static, format("format_Mtx4F")]]; // size = 0x40 + +fn format_Mtx4F(Mtx4F m) { + return std::format("| {}, {}, {}, {} |\n| {}, {}, {}, {} |\n| {}, {}, {}, {} |\n| {}, {}, {}, {} |", + m.m[0], m.m[1], m.m[2], m.m[3], + m.m[4], m.m[5], m.m[6], m.m[7], + m.m[8], m.m[9], m.m[10], m.m[11], + m.m[12], m.m[13], m.m[14], m.m[15]); +}; // --- UV kernel types struct UVSY { @@ -212,17 +224,17 @@ struct UVLV { }; -struct UVSQ_Unk4 { - u16 unk0; - f32 unk4; +struct uvSeqFrame { + u16 textureId; + f32 frameTime; } [[static]]; // size = 0x6 struct UVSQ { - u8 count; - UVSQ_Unk4 unk4[count]; - u8 unk8; - u8 unk9; - f32 unkC; + u8 frameCount; + uvSeqFrame frameTable[frameCount]; + u8 mode; + u8 reverse; + f32 framerate; }; @@ -343,8 +355,8 @@ struct ADAT { struct PDAT_PHDR { - s32 unk0; s32 duration; + s32 pposCount; } [[static]]; // size = 0x8 struct PDAT_PPOS { @@ -378,9 +390,9 @@ struct PDAT { match (tag) { ("PAD "): u8 pad[length] [[hidden]]; ("PHDR"): PDAT_PHDR phdr; - ("PPOS"): PDAT_PPOS pos[while($ - Start < length)]; + ("PPOS"): PDAT_PPOS pos; ("RHDR"): PDAT_RHDR rhdr; - ("RPKT"): PDAT_RPKT rpkt[while($ - Start < length)]; + ("RPKT"): PDAT_RPKT rpkt; (_): u8 data[length]; } padding[while (($ - Start) < length)]; // length is always to 8-bytes, not offsets @@ -400,7 +412,7 @@ struct SPTH { ("PAD "): u8 pad[length] [[hidden]]; ("SCPH"): SPTH_SCP scph; ("SCPP"): SPTH_SCP scpp; - ("SCPR"): SPTH_SCP scpp; + ("SCPR"): SPTH_SCP scpr; ("SCPX"): SPTH_SCP scpx; ("SCPY"): SPTH_SCP scpy; ("SCPZ"): SPTH_SCP scpz; @@ -443,7 +455,7 @@ struct LevelESND { Mtx4F unk0; Vec3F unk40; Vec3F unk4C; - s8 unk58; + u8 sndId; u8 pad59[3]; f32 unk5C; f32 unk60; @@ -514,6 +526,19 @@ struct UPWL { }; // --- UV user types +struct TaskObjects_Unk10 { + Vec3F unk0; + f32 unkC; + f32 unk10; + f32 unk14; + f32 unk18; +} [[static]]; // size = 0x1C + +struct TaskObjUnk2C { + Vec3F unk0; + Vec3F unkC; +} [[static]]; // size = 0x18 + struct Unk803599D0 { s32 unk0; f32 unk4; @@ -539,36 +564,31 @@ struct Unk803599D0 { } [[static]]; // size = 0x54 struct Unk80345C80 { - Unk803599D0 unk0; - Unk803599D0 unk54; - Unk803599D0 unkA8; - Unk803599D0 unkFC; - Unk803599D0 unk150; - u8 pad1A4[0x3B4-0x1A4]; + Unk803599D0 unk0[11]; + u8 unk39C[0x3B4-0x39C]; s32 unk3B4; s32 unk3B8; f32 unk3BC; f32 unk3C0; - f32 unk3C4; - f32 unk3C8; + f32 unk3C4; // stored to D_8034F1C4 (s32) + f32 unk3C8; // stored to D_8034F1C0 (f32) } [[static]]; // size = 0x3CC struct UPWT_COMM { - u8 classNum; - u8 vehNum; - u8 testNum; - u8 unk3; - u8 unk4; - u8 unk5[0x3]; - u8 unk8[4]; - u8 unkC[4]; - s32 unk10; - u8 unk14[0x18]; - s32 unk2C; - u8 unk30[0x14]; + u8 classId; + u8 vehId; + u8 testId; + u8 mapId; + u8 unk4; // values [0-4] + padding[3]; + u8 unk8[4]; // array of IDs [0: env or "cond"?, 1: enable snow?] + f32 unkC; + TaskObjects_Unk10 unk10; + TaskObjUnk2C unk2C; f32 unk44; Unk80345C80 unk48; - u8 unk414[8]; + u8 unk414[4]; + f32 unk418; u8 countTHER; u8 countLWIN; u8 countTPAD; @@ -707,10 +727,10 @@ struct TaskHPAD { s32 unk18; HPADType type; u8 points; - u8 pad1E[2]; + padding[2]; f32 fuelAdded; u8 nextHpadCount [[comment("number of HPADs to activate")]]; - u8 pad25[3]; + padding[3]; s32 nextHpads[5] [[comment("array of HPADs to activate")]]; u8 active [[comment("active at start")]]; padding[3]; @@ -751,9 +771,7 @@ struct TaskLWIN { // OBSV is not actually present in FS, but is decoded in code struct TaskOBSV { - f32 x; - f32 y; - f32 z; + Vec3F unk0; f32 unkC; } [[static]]; // size = 0x10 @@ -769,7 +787,7 @@ struct TaskRNGS { Vec3F pos [[comment("position vector")]]; Vec3F rot [[comment("rotation vector (degrees)")]]; s32 unk18; - padding[1]; + u8 unk1C; u8 childCount; padding[2]; s32 childRings[5]; @@ -807,6 +825,7 @@ struct TaskTARG { Vec3F rot; u8 targetType; u8 unk19; + padding[2]; s32 pad1C; } [[static]]; // size = 0x20 @@ -916,6 +935,7 @@ struct FORM { N64Header N64Header @ 0x00; // --------- Filesystem +FORM uvrm @ 0xDE720; FORM uvsy @ 0xDF5B0; FORM uven @ 0xDF5F8; // ignore UVLT @ 0xDFD70 because it is empty @@ -929,7 +949,7 @@ FORM uvtx[463] @ 0x240100; FORM uvan[115] @ 0x3085DC; FORM uvft[9] @ 0x32C3F8; FORM upwl[4] @ 0x33020C; -FORM spth0 @ 0x342B7C; +FORM spth0[1] @ 0x342B7C; FORM upwt[61] @ 0x342E2C; FORM adat0 @ 0x35C08C; FORM spth1[5] @ 0x36E214; diff --git a/requirements.txt b/requirements.txt index a5141f30..06bd3be5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ ipl3checksum>=1.0.0,<2.0.0 splat64[mips]==0.39.1 +crunch64>=0.6.1 mapfile_parser>=2.12.1,<3.0.0 GitPython diff --git a/src/app/demo.c b/src/app/demo.c index 790acc86..ea10d76c 100644 --- a/src/app/demo.c +++ b/src/app/demo.c @@ -10,7 +10,7 @@ typedef struct { s32 test; - u8 pad4[4]; + s32 unk4; // unused, but set in range [0-5] s32 cls; s32 veh; s32 unk10; diff --git a/src/app/demo_attitude.c b/src/app/demo_attitude.c index ea25338d..2721c671 100644 --- a/src/app/demo_attitude.c +++ b/src/app/demo_attitude.c @@ -17,8 +17,8 @@ typedef struct { } PPOS; typedef struct { - s32 unk0; - s32 unk4; + s32 duration; + s32 pposCount; } PHDR; typedef struct Unk8034F970 { @@ -61,8 +61,8 @@ void demoAttLoadPos(void) { switch (tag) { case 'PHDR': // 0x50484452 phdr = (PHDR*)data; - D_8036D320 = phdr->unk0; - D_8036D324 = phdr->unk4; + D_8036D320 = phdr->duration; + D_8036D324 = phdr->pposCount; D_8034F970 = (Unk8034F970*)_uvMemAllocAlign8(D_8036D324 * sizeof(Unk8034F970)); if (D_8034F970 == NULL) { return; diff --git a/src/app/targets.c b/src/app/targets.c index 983cc95e..bc547fa5 100644 --- a/src/app/targets.c +++ b/src/app/targets.c @@ -16,7 +16,7 @@ STATIC_DATA f32 D_803506A0[] = { 0.0f, 0.0f, 0.0f, 0.0f, 17.0f, 0.0f, 0.0f, 0.0f, 0.0f, 18.0f, 0.0f, 0.0f, 0.01f, 0.15f, 18.5f, 0.0f, 0.01f, 0.03f, 0.25f, 19.0f, 0.0f, 0.0f, 0.0f, 0.65f, 19.5f, 0.0f, 0.0f, 0.0f, 0.96f, 20.0f, 0.0f, 0.0f, 0.0f, 0.99f, 24.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; -TaskObjects* D_8035078C = 0; +TaskObjects* D_8035078C = NULL; TaskTARG* sRefTARG; u8 sMissileTargetCount; diff --git a/src/app/task.c b/src/app/task.c index e774ff29..4ff04735 100644 --- a/src/app/task.c +++ b/src/app/task.c @@ -227,7 +227,7 @@ s32 taskInitTest(s32 classIdx, s32 vehicle, s32 testIdx, u16* map, u16* terraId, sTaskVehicleU8 = (u8)vehicle; sTaskTestU8 = (u8)testIdx; - *map = sTaskMapLookup[D_8035078C->comm.unk3]; + *map = sTaskMapLookup[D_8035078C->comm.mapId]; switch (*map) { case MAP_CRESCENT_ISLAND: *terraId = 1; diff --git a/src/app/task.h b/src/app/task.h index 02d7676e..251e2cfe 100644 --- a/src/app/task.h +++ b/src/app/task.h @@ -232,7 +232,7 @@ typedef struct { Vec3F pos; Vec3F angle; s32 unk18; - u8 pad1C[1]; + u8 unk1C; u8 childCount; u8 pad1E[2]; s32 childRings[5]; @@ -269,6 +269,7 @@ typedef struct { Vec3F rot; u8 targetType; u8 unk19; + u8 pad1A[2]; s32 pad1C; } TaskTARG; // size = 0x20 @@ -327,28 +328,29 @@ typedef struct { f32 unk10; f32 unk14; f32 unk18; -} TaskObjects_Unk10; +} TaskObjects_Unk10; // size = 0x1C typedef struct { Vec3F unk0; Vec3F unkC; -} TaskObjUnk2C; +} TaskObjUnk2C; // size = 0x18 typedef struct { struct { u8 classNum; u8 vehNum; u8 testNum; - u8 unk3; + u8 mapId; u8 unk4; u8 unk5[0x3]; u8 unk8[4]; - u8 unkC[4]; + f32 unkC; TaskObjects_Unk10 unk10; TaskObjUnk2C unk2C; f32 unk44; Unk80345C80 unk48; - u8 unk40C[8]; + u8 unk414[4]; + f32 unk418; u8 countTHER; u8 countLWIN; u8 countTPAD; diff --git a/tools/pw64/codec.py b/tools/pw64/codec.py new file mode 100644 index 00000000..25b0a279 --- /dev/null +++ b/tools/pw64/codec.py @@ -0,0 +1,151 @@ +""" +Pilotwings 64 string codec + +Provides string encoding and decoding to translate between utf-8 strings +and PW64 encoded u16 strings. This module registers the codecs in import +and can be used with .encode("pw64") and .decode("pw64") methods. +""" + +import codecs +import string +import struct +from typing import Tuple + +_pw64CharMap = { + 0x00: '0', 0x01: '1', 0x02: '2', 0x03: '3', 0x04: '4', 0x05: '5', 0x06: '6', 0x07: '7', + 0x08: '8', 0x09: '9', 0x0A: 'A', 0x0B: 'B', 0x0C: 'C', 0x0D: 'D', 0x0E: 'E', 0x0F: 'F', + 0x10: 'G', 0x11: 'H', 0x12: 'I', 0x13: 'J', 0x14: 'K', 0x15: 'L', 0x16: 'M', 0x17: 'N', + 0x18: 'O', 0x19: 'P', 0x1A: 'Q', 0x1B: 'R', 0x1C: 'S', 0x1D: 'T', 0x1E: 'U', 0x1F: 'V', + 0x20: 'W', 0x21: 'X', 0x22: 'Y', 0x23: 'Z', 0x24: 'a', 0x25: 'b', 0x26: 'c', 0x27: 'd', + 0x28: 'e', 0x29: 'f', 0x2A: 'g', 0x2B: 'h', 0x2C: 'i', 0x2D: 'j', 0x2E: 'k', 0x2F: 'l', + 0x30: 'm', 0x31: 'n', 0x32: 'o', 0x33: 'p', 0x34: 'q', 0x35: 'r', 0x36: 's', 0x37: 't', + 0x38: 'u', 0x39: 'v', 0x3A: 'w', 0x3B: 'x', 0x3C: 'y', 0x3D: 'z', 0x3E: '-', 0x3F: '#', + 0x40: '<', 0x41: '>', 0x42: ' ', 0x43: '\"', 0x44: '(', 0x45: ')', 0x46: '*', 0x47: '&', + 0x48: ',', 0x49: '.', 0x4A: '/', 0x4B: '!', 0x4C: '?', 0x4D: '\'', 0x4E: '>', 0x4F: ':', + 0xD4: '%d', 0xFE: '\n', 0xFD: '\t' +} + +def search_function(encoding: str): + if encoding == "pw64": + return codecs.CodecInfo(pw64_encode, str_decode, None, None) + else: + return None + +def pw64_encode(text: str, errors="strict") -> Tuple[bytes, int]: + """ + encode UTF-8 string to PW64 u16 string + + Parameters + ---------- + text: str + string to encode + + Returns + ------- + (bytes, int) + bytes: big-endian encoded PW64 string representation + int: number of characters consumed + + Supported characters + -------------------- + - [0-9A-Za-z-#<> "()*&,./!?':\\n\\t] + - Wide `>` arrow used in sound settings + - Bold font, enclosed with {} + - Replacement token `%d` + - `%x####` for unknown tokens + """ + encData = b'' + isBold = False + inSpecial = False + hexLeft = 0 + hexVal = 0 + for c in text: + val = -1 + if inSpecial: + inSpecial = False + if c == 'd': + val = 0xD4 + elif c == 'x': + hexLeft = 4 + hexVal = 0 + elif hexLeft > 0: + hexVal = (hexVal * 0x10) + int(c, 16) + hexLeft -= 1 + val = hexVal if hexLeft == 0 else -1 + elif c == '{': + isBold = True + elif c == '}': + isBold = False + elif c == '%': + inSpecial = True + else: + if c in string.digits: + val = ord(c) - ord('0') + elif c in string.ascii_uppercase: + val = ord(c) - ord('A') + 0x0A + elif c in string.ascii_lowercase: + val = ord(c) - ord('a') + 0x24 + else: + lut = { + '-': 0x3E, '#': 0x3F, '<': 0x40, '>': 0x41, ' ': 0x42, '\"': 0x43, '(': 0x44, ')': 0x45, + '*': 0x46, '&': 0x47, ',': 0x48, '.': 0x49, '/': 0x4A, '!': 0x4B, '?': 0x4C, '\'': 0x4D, + '>': 0x4E, ':': 0x4F, '\n': 0xFE, '\t': 0xFD + } + assert c in lut, f"Unknown char {c}" + if c in lut: + val = lut[c] + if isBold and val >= 0 and val < 0x60: + val += 0x60 + if val >= 0: + encData += struct.pack(">H", val) + encData += struct.pack(">H", 0xFF) + return encData, len(text) + +def str_decode(data: bytes, errors="strict") -> Tuple[str, int]: + """ + decode from raw bytes to UTF-8 string representation + + Parameters + ---------- + data: bytes + big-endian encoded stream of u16 representation of the string + + Returns + ------- + (str, int) + str: UTF-8 encoded string + int: number of bytes consumed + + Supported characters + -------------------- + - [0-9A-Za-z-#<> "()*&,./!?':\\n\\t] + - Wide `>` arrow used in sound settings + - Bold font, enclosed with {} + - Replacement token replaced with `%d` + - Unknown sequences replaced with `%x####` + """ + res = "" + isBold = False + consumed = 0 + for code, in struct.iter_unpack(">H", data): + consumed += 2 + if code >= 0x60 and code < 0xB0: + code -= 0x60 + if not isBold: + res += "{" + isBold = True + elif isBold: + res += "}" + isBold = False + if code in _pw64CharMap: + res += _pw64CharMap[code] + elif code == 0xFF: # terminator + break + else: + res += f"%x{code:04x}" + if isBold: + res += "}" + return res, consumed + +# register PW64 string codec when imported +codecs.register(search_function) \ No newline at end of file diff --git a/tools/pw64/filesys.py b/tools/pw64/filesys.py new file mode 100644 index 00000000..93487a60 --- /dev/null +++ b/tools/pw64/filesys.py @@ -0,0 +1,824 @@ +#!/bin/env python3 + +import struct +from . import codec + +class FORM_3VUE: # prefix with `FORM_` since class names cannot begin with a number + """User path data stored as quaternions and translations""" + + _commStruct = struct.Struct(">5l h 10x") + _3vueTags = { + "QUAT": struct.Struct(">4f l 4x"), + "XLAT": struct.Struct(">3f l"), + } + + def __init__(self, tag=None, pad_count=0, comm=None, entries=None): + self.tag = tag if tag is not None else "3VUE" + self.pad_count = pad_count + self.comm = [] if comm is None else comm + self.entries = {} if entries is None else entries + + @classmethod + def from_dict(cls, d: dict): + """Construct 3VUE from dictionary""" + return cls(d["tag"], d["pad_count"], d["comm"], d["entries"]) + + @classmethod + def from_bytes(cls, form: bytes): + """Construct 3VUE from raw filesystem bytes""" + ftag, flen, vtag = struct.unpack_from(">4s L 4s", form) + assert ftag == b'FORM', f"Expected 'FORM', got ${ftag}" + vtag = vtag.decode() + assert vtag == "3VUE", f"Expected '3VUE', got ${vtag}" + idx = 0xC + pad_count = 0 + comm = None + entries = {} + while idx < flen: + tag, length = struct.unpack_from(">4s L", form, idx) + idx += 8 + tag = tag.decode() + assert tag in ("PAD ", "COMM", "QUAT", "XLAT"), f"Unexpected tag '${tag}'" + if tag == "PAD ": + pad_count += 1 + elif tag == "COMM": + comm = list(cls._commStruct.unpack_from(form, idx)) + elif tag in cls._3vueTags: + parser = cls._3vueTags[tag] + vIdx = idx + count, tbd = struct.unpack_from(">2L", form, vIdx) + vIdx += 8 + blockLen = parser.size * count + block = [list(e) for e in parser.iter_unpack(form[vIdx:vIdx+blockLen])] + vIdx += blockLen + entries[tag] = block + idx += length + return cls(vtag, pad_count, comm, entries) + + def as_dict(self) -> dict: + """Generate dictionary suitable for creating JSON representation""" + return { + "tag": self.tag, + "pad_count": self.pad_count, + "comm": self.comm, + "entries": self.entries + } + + def __bytes__(self) -> bytes: + """Generate raw bytes suitable for regenerating filesystem data""" + records = struct.pack(">4s", self.tag.encode()) + records += struct.pack(">4s L L", b'PAD ', 4, 0) * self.pad_count + records += b'COMM' + struct.pack(">L", self._commStruct.size) + self._commStruct.pack(*self.comm) + for tag, dat in self.entries.items(): + parser = self._3vueTags[tag] + block = struct.pack(">LL", len(dat), 0) + b''.join([parser.pack(*e) for e in dat]) + block += b'\0' * ((8 - (len(block) % 8)) % 8) + records += struct.pack(">4s L", tag.encode(), len(block)) + block + return b'FORM' + struct.pack(">L", len(records)) + records + + +class ADAT: + """ + ADAT stores string identifiers and data strings encoded in custom u16 chars. + """ + + def __init__(self, tag=None, pad_count=0, entries=None): + self.tag = tag if tag is not None else self.__class__.__name__ + self.pad_count = pad_count + self.entries = entries if entries is not None else {} + + @classmethod + def from_dict(cls, d: dict): + """Construct ADAT from dictionary""" + return cls(d["tag"], d["pad_count"], d["entries"]) + + @classmethod + def from_bytes(cls, form: bytes): + pad_count = 0 + entries = [] + ftag, flen, tag = struct.unpack_from(">4s L 4s", form) + assert ftag == b'FORM', f"Expected 'FORM', got ${ftag}" + assert tag == b'ADAT', f"Expected 'ADAT', got ${tag}" + idx = 0xC + aSize = 0 + aName = "" + while idx < flen: + atag, alen = struct.unpack_from(">4s L", form, idx) + idx += 8 + if atag == b'PAD ': + pad_count += 1 + elif atag == b'SIZE': + aSize, = struct.unpack_from(">L", form, idx) + elif atag == b'NAME': + aName = form[idx:idx+alen].decode().rstrip('\x00') + elif atag == b'DATA': + aDataRaw = form[idx:idx+alen] + aData = aDataRaw.decode("pw64") + entries.append({"NAME": aName, "DATA": aData}) + idx += alen + assert len(entries) == aSize, f"Mismatch length: {len(entries)} != {aSize}" + return cls("ADAT", pad_count, entries) + + def as_dict(self) -> dict: + """Generate dictionary suitable for creating JSON representation""" + return { + "tag": self.tag, + "pad_count": self.pad_count, + "entries": self.entries + } + + def __bytes__(self) -> bytes: + """Generate raw bytes suitable for regenerating filesystem data""" + records = struct.pack(">4s", self.tag.encode()) + records += struct.pack(">4s L L", b'PAD ', 4, 0) * self.pad_count + records += struct.pack(">4s L L L", b'SIZE', 8, len(self.entries), 0) + for ent in self.entries: + name = ent["NAME"].encode() + b'\0' + name += b'\0' * ((8 - (len(name) % 8)) % 8) + encData = ent["DATA"].encode("pw64") + encData += b'\0' * ((8 - (len(encData) % 8)) % 8) + records += struct.pack(">4s L", b'NAME', len(name)) + name + records += struct.pack(">4s L", b'DATA', len(encData)) + encData + return struct.pack(">4s L", b'FORM', len(records)) + records + + +class PDAT: + """Demo recording position, angle, and button data""" + + _pdatTags = { + "PHDR": struct.Struct(">2l"), + "PPOS": struct.Struct(">3f 3f"), + "RHDR": struct.Struct(">5l"), + "RPKT": struct.Struct(">f 2f L"), + } + + def __init__(self, tag=None, pad_count=0, entries=None): + self.tag = tag if tag is not None else self.__class__.__name__ + self.pad_count = pad_count + self.entries = [] if entries is None else entries + + @classmethod + def from_dict(cls, d: dict): + """Construct PDAT from dictionary""" + return cls(d["tag"], d["pad_count"], d["entries"]) + + @classmethod + def from_bytes(cls, form: bytes): + """Construct PDAT from raw filesystem bytes""" + ftag, flen, ptag = struct.unpack_from(">4s L 4s", form) + assert ftag == b'FORM', f"Expected 'FORM', got ${ftag}" + ptag = ptag.decode() + assert ptag == cls.__name__, f"Expected '{cls.__name__}', got ${ptag}" + idx = 0xC + pad_count = 0 + entries = [] + while idx < flen: + tag, length = struct.unpack_from(">4s L", form, idx) + idx += 8 + tag = tag.decode() + assert tag == "PAD " or tag in cls._pdatTags, f"Unexpected tag '${tag}'" + if tag == "PAD ": + pad_count += 1 + elif tag in cls._pdatTags: + parser = cls._pdatTags[tag] + entries.append([tag] + list(parser.unpack_from(form, idx))) + idx += length + return cls(ptag, pad_count, entries) + + def as_dict(self) -> dict: + """Generate dictionary suitable for creating JSON representation""" + return { + "tag": self.tag, + "pad_count": self.pad_count, + "entries": self.entries, + } + + def __bytes__(self) -> bytes: + """Generate raw bytes suitable for regenerating filesystem data""" + records = struct.pack(">4s", self.tag.encode()) + records += struct.pack(">4s L L", b'PAD ', 4, 0) * self.pad_count + for dat in self.entries: + tag = dat[0] + parser = self._pdatTags[tag] + block = parser.pack(*dat[1:]) + block += b'\0' * ((8 - (len(block) % 8)) % 8) + records += struct.pack(">4s L", tag.encode(), len(block)) + block + return b'FORM' + struct.pack(">L", len(records)) + records + + +class SPTH: + """ + SPath class which stores x,y,z and heading,pitch,roll data for paths + + each of these datasets are stored as separate arrays, with each entry + containing a time and value. the values used at runtime are then interpolated + between the array. this means each dataset need not be of the same length. + """ + + # order of tags is important + _spathTags = ("SCPP", "SCPH", "SCPX", "SCPY", "SCPR", "SCPZ", "SCP#") + _timeVal = struct.Struct(">2f") + + def __init__(self, tag=None, pad_count=0, entries=None): + self.tag = tag if tag is not None else self.__class__.__name__ + self.pad_count = pad_count + self.entries = entries if entries is not None else {} + + @classmethod + def from_dict(cls, d: dict): + """Construct SPTH from dictionary""" + return cls(d["tag"], d["pad_count"], d["entries"]) + + @classmethod + def from_bytes(cls, form: bytes): + """Construct SPTH from raw filesystem bytes""" + ftag, flen, stag = struct.unpack_from(">4s L 4s", form) + assert ftag == b'FORM', f"Expected 'FORM', got ${ftag}" + stag = stag.decode() + assert stag == cls.__name__, f"Expected '{cls.__name__}', got ${stag}" + idx = 0xC + pad_count = 0 + entries = {} + while idx < flen: + tag, length = struct.unpack_from(">4s L", form, idx) + idx += 8 + tag = tag.decode() + assert tag == "PAD " or tag in cls._spathTags, f"Unexpected tag '${tag}'" + if tag == "PAD ": + pad_count += 1 + elif tag in cls._spathTags: + spIdx = idx + count, = struct.unpack_from(">L", form, spIdx) + spIdx += 4 + sp = [{"time": e[0], "val": e[1]} for e in cls._timeVal.iter_unpack(form[spIdx:spIdx+8*count])] + spIdx += 8*count + entries[tag] = sp + idx += length + return cls(stag, pad_count, entries) + + def as_dict(self) -> dict: + """Generate dictionary suitable for creating JSON representation""" + return { + "tag": self.tag, + "pad_count": self.pad_count, + "entries": self.entries + } + + def __bytes__(self) -> bytes: + """Generate raw bytes suitable for regenerating filesystem data""" + records = struct.pack(">4s", self.tag.encode()) + records += struct.pack(">4s L L", b'PAD ', 4, 0) * self.pad_count + for tag in self._spathTags: + scpCount = len(self.entries[tag]) + scpLength = 8 + 8 * scpCount + records += struct.pack(">4s L L", tag.encode(), scpLength, scpCount) + records += b''.join([self._timeVal.pack(e['time'], e['val']) for e in self.entries[tag]]) + records += b'\0' * ((8 - (len(records) % 8)) % 8) + spth = b'FORM' + struct.pack(">L", len(records)) + records + return spth + +class UPWL: + """PW64 Level (map) data""" + + # order of tags must match order of LEVL counts + _upwlTags = { + "ESND": struct.Struct(">16f 3f 3f B 3x 2f L 2f b 3x L"), + "WOBJ": struct.Struct(">3f B 3x"), + "LPAD": struct.Struct(">3f f L B 3x"), + "TOYS": struct.Struct(">3f 4B"), + "TPTS": struct.Struct(">B 3x 3f 2f L f L f 3f"), + "APTS": struct.Struct(">3f 5f"), + "BNUS": struct.Struct(">3f 3f L") + } + + def __init__(self, tag=None, pad_count=0, entries=None): + self.tag = tag if tag is not None else self.__class__.__name__ + self.pad_count = pad_count + self.entries = {} if entries is None else entries + + @classmethod + def from_dict(cls, d: dict): + """Construct UPWL from dictionary""" + return cls(d["tag"], d["pad_count"], d["entries"]) + + @classmethod + def from_bytes(cls, form: bytes): + """Construct UPWL from raw filesystem bytes""" + ftag, flen, utag = struct.unpack_from(">4s L 4s", form) + assert ftag == b'FORM', f"Expected 'FORM', got ${ftag}" + utag = utag.decode() + assert utag == cls.__name__, f"Expected '{cls.__name__}', got ${utag}" + idx = 0xC + pad_count = 0 + entries = {} + counts = [0] * 8 + while idx < flen: + tag, length = struct.unpack_from(">4s L", form, idx) + idx += 8 + tag = tag.decode() + assert tag in ("PAD ", "LEVL") or tag in cls._upwlTags, f"Unexpected tag '${tag}'" + if tag == "PAD ": + pad_count += 1 + elif tag == "LEVL": + counts = struct.unpack_from(">7Bx", form, idx) + elif tag in cls._upwlTags: + expectedCount = counts[list(cls._upwlTags.keys()).index(tag)] + entryStruct = cls._upwlTags[tag] + entries[tag] = [list(v) for v in entryStruct.iter_unpack(form[idx:idx+expectedCount*entryStruct.size])] + idx += length + return cls(utag, pad_count, entries) + + def as_dict(self) -> dict: + """Generate dictionary suitable for creating JSON representation""" + return { + "tag": self.tag, + "pad_count": self.pad_count, + "entries": self.entries + } + + def __bytes__(self) -> bytes: + """Generate raw bytes suitable for regenerating filesystem data""" + records = struct.pack(">4s", self.tag.encode()) + records += struct.pack(">4s L L", b'PAD ', 4, 0) * self.pad_count + counts = [0 if t not in self.entries else len(self.entries[t]) for t in self._upwlTags] + records += b'LEVL' + struct.pack(">L 7B x", 8, *counts) + for tag, vals in self.entries.items(): + entry = b'' + if tag in self._upwlTags: + entry = b''.join([self._upwlTags[tag].pack(*e) for e in vals]) + entry += b'\0' * ((8 - (len(entry) % 8)) % 8) + records += struct.pack(">4s L", tag.encode(), len(entry)) + entry + upwl = b'FORM' + struct.pack(">L", len(records)) + records + return upwl + + +class UPWT: + """PW64 Task (test) data""" + + # order of tags must match order of COMM counts + _upwtTags = { + "THER": struct.Struct(">3f 2f L 4f"), + "LWIN": struct.Struct(">3f 3f 3f 3f 3f L 3f f 2B 2x"), + "TPAD": struct.Struct(">3f 3f 4x 3f B 3x f"), + "LPAD": struct.Struct(">3f 3f 4x 3f 4x B 3x"), + "LSTP": struct.Struct(">3f 3f 4x B 3x f"), + "RNGS": struct.Struct(">3f 3f L 2B 2x 5L B 3x 5L 2B 2x f 2B 2x 2f b 3x 3f b 3B 16b"), + "BALS": struct.Struct(">3f 3f L f B B 2x 4f 2L 2f B 3x 3f 2L 3f"), + "TARG": struct.Struct(">3f 3f 2B 2x L"), + "HPAD": struct.Struct(">3f 3f L 2B 2x f B 3x 5L B 3x"), + "BTGT": struct.Struct(">3f L 2f 2B 2x"), + "PHTS": struct.Struct(">2L 3f"), + "FALC": struct.Struct(">3f f 4B 4B 37f"), + "SDFM": struct.Struct(">76B"), # not present in FS + "CNTG": struct.Struct(">3f 3f B 3x"), + "HOPD": struct.Struct(">4B 3f L 2f 4B"), + "OBSV": struct.Struct(">3f f"), + } + _upwtStrTags = ("JPTX", "NAME", "INFO") + _commHeader = struct.Struct(">5B 3x 4B f") + _commObjUnk10 = struct.Struct(">3f 4f") + _commObjUnk2C = struct.Struct(">3f 3f") + _commUnk10 = struct.Struct(">3f 4f") + _commUnk2C = struct.Struct(">3f 3f") + _commUnk803599D0 = struct.Struct(">L 16f 4L") + _commUnk80345C80Footer = struct.Struct(">24B 2L 4f") + + def __init__(self, tag=None, pad_count=0, info=None, comm=None, entries=None): + self.tag = tag if tag is not None else self.__class__.__name__ + self.pad_count = pad_count + self.info = {} if info is None else info + self.comm = {} if comm is None else comm + self.entries = {} if entries is None else entries + + @classmethod + def from_dict(cls, d: dict): + """Construct UPWT from dictionary""" + return cls(d["tag"], d["pad_count"], d["info"], d["comm"], d["entries"]) + + @classmethod + def from_bytes(cls, form: bytes): + """Construct UPWT from raw filesystem bytes""" + ftag, flen, utag = struct.unpack_from(">4s L 4s", form) + assert ftag == b'FORM', f"Expected 'FORM', got ${ftag}" + utag = utag.decode() + assert utag == cls.__name__, f"Expected '{cls.__name__}', got ${utag}" + idx = 0xC + pad_count = 0 + info = {} + comm = {} + entries = {} + counts = [0] * 16 + while idx < flen: + tag, length = struct.unpack_from(">4s L", form, idx) + idx += 8 + tag = tag.decode() + assert tag in ("PAD ", "COMM") or tag in cls._upwtStrTags or tag in cls._upwtTags, f"Unexpected tag '${tag}'" + if tag == "PAD ": + pad_count += 1 + elif tag in cls._upwtStrTags: + info[tag] = form[idx:idx+length].decode().rstrip('\x00') + pass + elif tag == "COMM": + cIdx = idx + comm["header"] = list(cls._commHeader.unpack_from(form, cIdx)) + cIdx += cls._commHeader.size + comm["unk10"] = list(cls._commObjUnk10.unpack_from(form, cIdx)) + cIdx += cls._commObjUnk10.size + comm["unk2C"] = list(cls._commObjUnk2C.unpack_from(form, cIdx)) + cIdx += cls._commObjUnk2C.size + comm["unk44"], = struct.unpack_from(">f", form, cIdx) + cIdx += 4 + comm["unk48"] = {} + unk0Size = cls._commUnk803599D0.size * 11 + comm["unk48"]["unk0"] = [list(u) for u in cls._commUnk803599D0.iter_unpack(form[cIdx:cIdx+unk0Size])] + cIdx += unk0Size + comm["unk48"]["footer"] = list(cls._commUnk80345C80Footer.unpack_from(form, cIdx)) + cIdx += cls._commUnk80345C80Footer.size + comm["unk414"] = list(struct.unpack_from(">4B", form, cIdx)) + cIdx += 4 + comm["unk418"], = struct.unpack_from(">f", form, cIdx) + cIdx += 4 + counts = struct.unpack_from(">16B", form, cIdx) + cIdx += 16 + assert cIdx - idx == 0x42C + elif tag in cls._upwtTags: + expectedCount = counts[list(cls._upwtTags.keys()).index(tag)] + entryStruct = cls._upwtTags[tag] + end = idx + expectedCount * entryStruct.size + entries[tag] = [list(v) for v in entryStruct.iter_unpack(form[idx:end])] + idx += length + return cls(utag, pad_count, info, comm, entries) + + def as_dict(self) -> dict: + """Generate dictionary suitable for creating JSON representation""" + return { + "tag": self.tag, + "pad_count": self.pad_count, + "info": self.info, + "comm": self.comm, + "entries": self.entries + } + + def __bytes__(self) -> bytes: + """Generate raw bytes suitable for regenerating filesystem data""" + task = struct.pack(">4s", self.tag.encode()) + task += struct.pack(">4s L L", b'PAD ', 4, 0) * self.pad_count + # JPTX/NAME/INFO + for t, s in self.info.items(): + paddedStr = s.encode() + b'\0' + paddedStr += b'\0' * ((8 - (len(paddedStr) % 8)) % 8) + task += struct.pack(">4s L", t.encode(), len(paddedStr)) + paddedStr + # COMM + comm = self._commHeader.pack(*self.comm["header"]) + comm += self._commObjUnk10.pack(*self.comm["unk10"]) + comm += self._commObjUnk2C.pack(*self.comm["unk2C"]) + comm += struct.pack(">f", self.comm["unk44"]) + comm += b''.join([self._commUnk803599D0.pack(*e) for e in self.comm["unk48"]["unk0"]]) + comm += self._commUnk80345C80Footer.pack(*self.comm["unk48"]["footer"]) + comm += struct.pack(">4B", *self.comm["unk414"]) + comm += struct.pack(">f", self.comm["unk418"]) + counts = [0 if t not in self.entries else len(self.entries[t]) for t in self._upwtTags] + comm += struct.pack(">16B", *counts) + comm += b'\0' * ((8 - (len(comm) % 8)) % 8) + task += b'COMM' + struct.pack(">L", len(comm)) + comm + # entry types + for tag, vals in self.entries.items(): + entry = b'' + if tag in self._upwtTags: + entry = b''.join([self._upwtTags[tag].pack(*e) for e in vals]) + entry += b'\0' * ((8 - (len(entry) % 8)) % 8) + task += struct.pack(">4s L", tag.encode(), len(entry)) + entry + upwt = b'FORM' + struct.pack(">L", len(task)) + task + return upwt + + +class UV_COMM: + """Boilerplate class for UV** that have one type of COMM entry""" + + def __init__(self, tag=None, pad_count=0, comm=None): + self.tag = tag if tag is not None else self.__class__.__name__ + self.pad_count = pad_count + self.comm = [] if comm is None else comm + + @classmethod + def from_dict(cls, d: dict): + """Construct from dictionary""" + return cls(d["tag"], d["pad_count"], d["comm"]) + + def as_dict(self) -> dict: + """Generate dictionary suitable for creating JSON representation""" + return { + "tag": self.tag, + "pad_count": self.pad_count, + "comm": self.comm + } + + +class UVEN(UV_COMM): + """Environment""" + + _dataStruct = struct.Struct(">4B 4B 4B 8x 2f B 17x 2B L B 3x L") + + @classmethod + def from_bytes(cls, form: bytes): + """Construct from raw filesystem bytes""" + ftag, flen, utag = struct.unpack_from(">4s L 4s", form) + assert ftag == b'FORM', f"Expected 'FORM', got ${ftag}" + utag = utag.decode() + assert utag == cls.__name__, f"Expected '{cls.__name__}', got ${utag}" + idx = 0xC + pad_count = 0 + comm = [] + while idx < flen: + tag, length = struct.unpack_from(">4s L", form, idx) + idx += 8 + tag = tag.decode() + assert tag in ("PAD ", "COMM"), f"Unexpected tag '${tag}'" + if tag == "PAD ": + pad_count += 1 + else: + models = [] + commIdx = idx + count, = struct.unpack_from(">B", form, commIdx) + commIdx += 1 + if count > 0: + models = [list(m) for m in struct.iter_unpack(">HB", form[commIdx:commIdx+3*count])] + commIdx += 3*count + data = list(cls._dataStruct.unpack_from(form, commIdx)) + commIdx += cls._dataStruct.size + comm.append({"models": models, "data": data}) + idx += length + return cls(utag, pad_count, comm) + + def __bytes__(self) -> bytes: + """Generate raw bytes suitable for regenerating filesystem data""" + records = struct.pack(">4s", self.tag.encode()) + records += struct.pack(">4s L L", b'PAD ', 4, 0) * self.pad_count + for c in self.comm: + comm = struct.pack(">B", len(c["models"])) + comm += b''.join([struct.pack(">HB", m[0], m[1]) for m in c["models"]]) + comm += self._dataStruct.pack(*c["data"]) + comm += b'\0' * ((8 - (len(comm) % 8)) % 8) + records += b'COMM' + struct.pack(">L", len(comm)) + comm + return b'FORM' + struct.pack(">L", len(records)) + records + + +class UVLT(UV_COMM): + """ + UVLT exists in the filesystem, but only contains 'PAD ' fields. + The code will read 4 bytes from COMM if it existed, but it does not. + """ + + _ltStruct = struct.Struct(">4B") + + @classmethod + def from_bytes(cls, form: bytes): + """Construct from raw filesystem bytes""" + ftag, flen, utag = struct.unpack_from(">4s L 4s", form) + assert ftag == b'FORM', f"Expected 'FORM', got ${ftag}" + utag = utag.decode() + assert utag == cls.__name__, f"Expected '{cls.__name__}', got ${utag}" + idx = 0xC + pad_count = 0 + comm = [] + while idx < flen: + tag, length = struct.unpack_from(">4s L", form, idx) + idx += 8 + tag = tag.decode() + assert tag in ("PAD ", "COMM"), f"Unexpected tag '${tag}'" + if tag == "PAD ": + pad_count += 1 + else: + ltIdx = idx + lt = list(cls._ltStruct.unpack_from(form, ltIdx)) + ltIdx += cls._ltStruct.size + comm.append(lt) + idx += length + return cls(utag, pad_count, comm) + + def __bytes__(self) -> bytes: + """Generate raw bytes suitable for regenerating filesystem data""" + records = struct.pack(">4s", self.tag.encode()) + records += struct.pack(">4s L L", b'PAD ', 4, 0) * self.pad_count + for c in self.comm: + comm = self._ltStruct.pack(*c) + comm += b'\0' * ((8 - (len(comm) % 8)) % 8) + records += b'COMM' + struct.pack(">L", len(comm)) + comm + return b'FORM' + struct.pack(">L", len(records)) + records + + +class UVLV(UV_COMM): + """ + `UVLV` contains the counts and IDs used for the terrain, models, texture, animation for map data. + """ + + _levelFields = ("terra", "light", "environment", "model", "contour", "texture", "sequence", "animation", "font", "blit") + + @classmethod + def from_bytes(cls, form: bytes): + """Construct from raw filesystem bytes""" + ftag, flen, utag = struct.unpack_from(">4s L 4s", form) + assert ftag == b'FORM', f"Expected 'FORM', got ${ftag}" + utag = utag.decode() + assert utag == cls.__name__, f"Expected '{cls.__name__}', got ${utag}" + idx = 0xC + pad_count = 0 + comm = [] + while idx < flen: + tag, length = struct.unpack_from(">4s L", form, idx) + idx += 8 + tag = tag.decode() + assert tag in ("PAD ", "COMM"), f"Unexpected tag '${tag}'" + if tag == "PAD ": + pad_count += 1 + else: + lv = {} + lvIdx = idx + for field in cls._levelFields: + count, = struct.unpack_from(">H", form, lvIdx) + lvIdx += 2 + lvLen = 2 * count + lv[field] = [i for i, in struct.iter_unpack(">H", form[lvIdx:lvIdx + lvLen])] + lvIdx += lvLen + comm.append(lv) + idx += length + return cls(utag, pad_count, comm) + + def __bytes__(self) -> bytes: + """Generate raw bytes suitable for regenerating filesystem data""" + records = struct.pack(">4s", self.tag.encode()) + records += struct.pack(">4s L L", b'PAD ', 4, 0) * self.pad_count + for c in self.comm: + comm = b'' + for field in self._levelFields: + ids = c[field] + comm += struct.pack(">H", len(ids)) + comm += b''.join([struct.pack(">H", i) for i in ids]) + comm += b'\0' * ((8 - (len(comm) % 8)) % 8) + records += b'COMM' + struct.pack(">L", len(comm)) + comm + return b'FORM' + struct.pack(">L", len(records)) + records + + +class UVSQ(UV_COMM): + """ + `UVSQ` contains animation sequences for dynamic textures. + """ + + _frameStruct = struct.Struct(">Hf") + _uvsqStruct = struct.Struct(">B B f") + + @classmethod + def from_bytes(cls, form: bytes): + """Construct from raw filesystem bytes""" + ftag, flen, utag = struct.unpack(">4s L 4s", form[:0xC]) + assert ftag == b'FORM', f"Expected 'FORM', got ${ftag}" + utag = utag.decode() + assert utag == cls.__name__, f"Expected '{cls.__name__}', got ${utag}" + idx = 0xC + pad_count = 0 + comm = [] + while idx < flen: + tag, length = struct.unpack_from(">4s L", form, idx) + idx += 8 + tag = tag.decode() + assert tag in ("PAD ", "COMM"), f"Unexpected tag '${tag}'" + if tag == "PAD ": + pad_count += 1 + else: + sqIdx = idx + frameCount, = struct.unpack_from(">B", form, sqIdx) + sqIdx += 1 + frameLen = 6 * frameCount + frames = [{"textureId": t, "frameTime": f} for t, f in cls._frameStruct.iter_unpack(form[sqIdx:sqIdx + frameLen])] + sqIdx += frameLen + mode, reverse, framerate = cls._uvsqStruct.unpack_from(form, sqIdx) + sq = { + "frames": frames, + "mode": mode, + "reverse": reverse, + "framerate": framerate + } + comm.append(sq) + idx += length + return cls(utag, pad_count, comm) + + def __bytes__(self) -> bytes: + """Generate raw bytes suitable for regenerating filesystem data""" + records = struct.pack(">4s", self.tag.encode()) + records += struct.pack(">4s L L", b'PAD ', 4, 0) * self.pad_count + for c in self.comm: + comm = struct.pack(">B", len(c["frames"])) + comm += b''.join([self._frameStruct.pack(f["textureId"], f["frameTime"]) for f in c["frames"]]) + comm += self._uvsqStruct.pack(c["mode"], c["reverse"], c["framerate"]) + comm += b'\0' * ((8 - (len(comm) % 8)) % 8) + records += b'COMM' + struct.pack(">L", len(comm)) + comm + return b'FORM' + struct.pack(">L", len(records)) + records + + +class UVTP(UV_COMM): + """ + `UVTP` contains texture palette information + """ + + _uvtpStruct = struct.Struct(">HH") + + @classmethod + def from_bytes(cls, form: bytes): + """Construct from raw filesystem bytes""" + ftag, flen, utag = struct.unpack(">4s L 4s", form[:0xC]) + assert ftag == b'FORM', f"Expected 'FORM', got ${ftag}" + utag = utag.decode() + assert utag == cls.__name__, f"Expected '{cls.__name__}', got ${utag}" + idx = 0xC + pad_count = 0 + comm = [] + while idx < flen: + tag, length = struct.unpack_from(">4s L", form, idx) + idx += 8 + tag = tag.decode() + assert tag in ("PAD ", "COMM"), f"Unexpected tag '${tag}'" + if tag == "PAD ": + pad_count += 1 + else: + tpIdx = idx + count, = struct.unpack_from(">H", form, tpIdx) + tpIdx += 2 + tpLen = count * cls._uvtpStruct.size + tp = [list(e) for e in cls._uvtpStruct.iter_unpack(form[tpIdx:tpIdx + tpLen])] + tpIdx += tpLen + comm.append(tp) + idx += length + return cls(utag, pad_count, comm) + + def __bytes__(self) -> bytes: + """Generate raw bytes suitable for regenerating filesystem data""" + records = struct.pack(">4s", self.tag.encode()) + records += struct.pack(">4s L L", b'PAD ', 4, 0) * self.pad_count + for c in self.comm: + comm = struct.pack(">H", len(c)) + comm += b''.join([self._uvtpStruct.pack(*i) for i in c]) + comm += b'\0' * ((8 - (len(comm) % 8)) % 8) + records += b'COMM' + struct.pack(">L", len(comm)) + comm + return b'FORM' + struct.pack(">L", len(records)) + records + + +class UVTR(UV_COMM): + """ + `UVTR` contains terrain data. + """ + + _headerStruct = struct.Struct(">6f 2B 3f") + _tileStruct = struct.Struct(">16f B H") + + @classmethod + def from_bytes(cls, form: bytes): + """Construct from raw filesystem bytes""" + ftag, flen, utag = struct.unpack(">4s L 4s", form[:0xC]) + assert ftag == b'FORM', f"Expected 'FORM', got ${ftag}" + utag = utag.decode() + assert utag == cls.__name__, f"Expected '{cls.__name__}', got ${utag}" + idx = 0xC + pad_count = 0 + comm = [] + while idx < flen: + tag, length = struct.unpack_from(">4s L", form, idx) + idx += 8 + tag = tag.decode() + assert tag in ("PAD ", "COMM"), f"Unexpected tag '${tag}'" + if tag == "PAD ": + pad_count += 1 + else: + trIdx = idx + header = list(cls._headerStruct.unpack_from(form, trIdx)) + trIdx += cls._headerStruct.size + count = header[6] * header[7] + tiles = [] + for _ in range(count): + tileType, = struct.unpack_from(">B", form, trIdx) + trIdx += 1 + tile = [tileType] + if tileType != 0: + tile += list(cls._tileStruct.unpack_from(form, trIdx)) + trIdx += cls._tileStruct.size + tiles.append(tile) + tr = { + "header": header, + "tiles": tiles, + } + comm.append(tr) + idx += length + return cls(utag, pad_count, comm) + + def __bytes__(self) -> bytes: + """Generate raw bytes suitable for regenerating filesystem data""" + records = struct.pack(">4s", self.tag.encode()) + records += struct.pack(">4s L L", b'PAD ', 4, 0) * self.pad_count + for c in self.comm: + comm = self._headerStruct.pack(*c["header"]) + for t in c["tiles"]: + comm += struct.pack(">B", t[0]) + if t[0] != 0: + comm += self._tileStruct.pack(*t[1:]) + comm += b'\0' * ((8 - (len(comm) % 8)) % 8) + records += b'COMM' + struct.pack(">L", len(comm)) + comm + return b'FORM' + struct.pack(">L", len(records)) + records diff --git a/tools/pw64/mio0.py b/tools/pw64/mio0.py new file mode 100644 index 00000000..530e225d --- /dev/null +++ b/tools/pw64/mio0.py @@ -0,0 +1,79 @@ +import crunch64 +import hashlib +import struct + +# MIO0 compression doesn't yet match Paradigm's algo +# collect compressed window information to be used during rebuild + +def decompress(mio0Data: bytes, mio0Info: dict | None = None) -> bytes: + """wrapper for MIO0 decompress which collects compressed window information""" + + compBlocks = [] + magic = mio0Data[:4] + assert magic == b'MIO0', f"Expected 'MIO0', got {magic}" + dLen, cOff, uOff = struct.unpack_from(">LLL", mio0Data, 4) + flags = mio0Data[0x10:cOff] + flagIdx = 0 + outSize = 0 + while outSize < dLen: + flagOff = flagIdx // 8 + flagBit = 1 << (7 - (flagIdx % 8)) + flagIdx += 1 + if flags[flagOff] & flagBit != 0: + outSize += 1 + else: # compressed + windowInfo, = struct.unpack_from(">H", mio0Data, cOff) + cOff += 2 + winOff = (windowInfo & 0x0FFF) + 1 + winLen = (windowInfo >> 12) + 3 + compBlocks.append({"file_offset": outSize, "offset": winOff, "length": winLen}) + outSize += winLen + data = crunch64.mio0.decompress(mio0Data) + if mio0Info is not None: + mio0Info["sha1sum"] = hashlib.sha1(data).hexdigest() + mio0Info["comp_blocks"] = compBlocks + return data + +def compress(data: bytes, mio0Info: dict | None = None) -> bytes: + """wrapper for matching compression MIO0 workaround""" + + def PUT_BIT(buf, bit, val): + mask = 1 << (7 - (bit % 8)) + offset = bit // 8 + while len(buf) < offset + 1: + buf.append(0) + buf[offset] = (buf[offset] & ~(mask)) | (mask if val else 0) + + if not mio0Info or hashlib.sha1(data).hexdigest() != mio0Info["sha1sum"]: + cData = crunch64.mio0.compress(data) + else: + uBuf = b'' + cBuf = b'' + flags = [] + offset = 0x0 + bitIdx = 0 + for m in mio0Info["comp_blocks"]: + uLen = m["file_offset"] - offset + uBuf += data[offset:offset + uLen] + offset += uLen + for i in range(uLen): + PUT_BIT(flags, bitIdx, 1) + bitIdx += 1 + windowInfo = ((m["length"] - 3) << 12) | (m["offset"] - 1) + cBuf += struct.pack(">H", windowInfo) + PUT_BIT(flags, bitIdx, 0) + bitIdx += 1 + offset += m["length"] + uLen = len(data) - offset + uBuf += data[offset:offset + uLen] + offset += uLen + for i in range(uLen): + PUT_BIT(flags, bitIdx, 1) + bitIdx += 1 + + bitLength = (bitIdx + 7) // 8 + compOffset = (((0x10 + bitLength) + 3) // 4) * 4 + uncompOffset = compOffset + len(cBuf) + cData = b'MIO0' + struct.pack(">L", len(data)) + struct.pack(">L", compOffset) + struct.pack(">L", uncompOffset) + cData += bytes(flags) + cBuf + uBuf + return cData diff --git a/tools/pw64_filesys_gen.py b/tools/pw64_filesys_gen.py new file mode 100644 index 00000000..069cbdc3 --- /dev/null +++ b/tools/pw64_filesys_gen.py @@ -0,0 +1,104 @@ +#!/bin/env python3 + +import json +import os +from pathlib import Path +import struct +import time +from pw64 import filesys, mio0 + +def print_stats(fileTimes: dict): + print("+------+-----+-------+-------+-------+") + print("| tag | # | json | bin | total |") + print("+------+-----+-------+-------+-------+") + totTime = 0 + totCount = 0 + for tag, vals in sorted(fileTimes.items()): + parse, generate = 0, 0 + for t in vals["times"]: + parse += t[1] - t[0] + generate += t[2] - t[1] + count = len(vals["times"]) + totCount += count + sum = parse + generate + totTime += sum + print(f"| {tag} | {count:>3d} | {parse:.3f} | {generate:.3f} | {sum:.3f} |") + print("+------+-----+-------+-------+-------+") + print(f"|Total | {totCount:>3d} | | | {totTime:.3f} |") + print("+------+-----+-------+-------+-------+") + +def read_table(tablePath): + return json.load(open(tablePath, "r")) + +def generate_bins(table: dict, fileDir: Path, tableFile: str, filesysFile: str, dumpData: bool, printStats: bool): + stats = {} + # 1. output filesystem binary and accumulate sizes + with open(filesysFile, "wb") as fsBin: + offset = 0 + for form in table["contents"]: + fileName = Path(form["file"]) + filePath = fileDir / fileName + if form["file"].endswith(".raw"): + formData = open(filePath, "rb").read() + elif form["file"].endswith(".json"): + className = f"FORM_{form['tag']}" if form["tag"][0].isdigit() else form["tag"] + assert hasattr(filesys, className), f"Unknown tag '{form['tag']}" + generator = getattr(filesys, className) + a = time.perf_counter() + fileDat = json.load(open(filePath, "r")) + b = time.perf_counter() + formData = bytes(generator.from_dict(fileDat)) + c = time.perf_counter() + if dumpData: + dumpPath = fileDir / "dump" / fileName.with_suffix(".bin") + os.makedirs(dumpPath.parent, exist_ok=True) + with open(dumpPath, "wb") as dumpFile: + dumpFile.write(formData) + if form["tag"] not in stats: + stats[form["tag"]] = {"times": [], "parse": 0, "generate": 0} + stats[form["tag"]]["times"].append((a, b, c)) + else: + formData = b'' + formLen = len(formData) + if formLen != form["length"]: + print(f"Warning: length mismatch {form['file']} {form['length']} != {formLen}") + form["length"] = formLen + offset += formLen + fsBin.write(formData) + + # 2. generate filetable, compress, and output file + tableData = b''.join([struct.pack(">4s L", f["tag"].encode(), f["length"]) for f in table["contents"]]) + tableLen = len(tableData) + # until matching MIO0 compression is found, use workaround instead of crunch64 + # cTableData = crunch64.mio0.compress(tableData) + cTableData = mio0.compress(tableData, table["mio0_matching_info"]) + cTableLen = (((len(cTableData) + 8) + 3) // 4) * 4 # round up to 4-byte boundary + uvRm = b'UVRM' + uvRm += struct.pack(">4s L L", b'PAD ', 4, 0) * table["pad_count"] + uvRm += struct.pack(">4s L", "GZIP".encode(), cTableLen) + uvRm += struct.pack(">4s L", "TABL".encode(), tableLen) + uvRm += cTableData + uvRmLen = ((len(uvRm) + 3) // 4) * 4 # round up to 4 byte boundary + with open(tableFile, "wb") as tableBin: + tableBin.write(struct.pack(">4s L", "FORM".encode(), uvRmLen)) + tableBin.write(uvRm) + + if printStats: + print_stats(stats) + +def main(): + import argparse + parser = argparse.ArgumentParser( + description="PW64 filesystem generator", + epilog="Example: python3 pw64_filesys_gen.py --dir ./filesys --dump --stats" + ) + parser.add_argument("--table", "-t", action="store", default="./bin/filesys/filetable.json", help="input table file") + parser.add_argument("--dir", "-d", action="store", default="./bin/filesys", help="input base directory") + parser.add_argument("--dump", action="store_true", help="dump individual file binaries") + parser.add_argument("--stats", action="store_true", help="print timing stats after generating file system") + args = parser.parse_args() + table = read_table(args.table) + generate_bins(table, Path(args.dir), "bin/filetable.bin", "bin/filesys.bin", args.dump, args.stats) + +if __name__ == "__main__": + main() diff --git a/tools/splat_ext/pw_filesys.py b/tools/splat_ext/pw_filesys.py new file mode 100644 index 00000000..8cdc64d4 --- /dev/null +++ b/tools/splat_ext/pw_filesys.py @@ -0,0 +1,66 @@ +from pathlib import Path + +import json +import struct +from splat.util import options +from splat.segtypes.segment import Segment +from splat.segtypes.linker_entry import LinkerEntry +from tools.pw64 import filesys as pw64_fs + +class N64SegPw_filesys(Segment): + def __init__(self, rom_start, rom_end, type, name, vram_start, args, yaml): + super().__init__( + rom_start, rom_end, type, name, vram_start, args=args, yaml=yaml + ) + self.align = 0x10 + self.fs_path = "filesys" + + def scan(self, rom_bytes): + fsFiles = [] + idx = self.rom_start + # iterate over filesystem 'FORM' entries and store their tags and offsets + while idx + 0xC < self.rom_end: + ftag, length, tag = struct.unpack_from(">4s L 4s", rom_bytes, idx) + assert ftag == b'FORM', f"Expected 'FORM', got ${ftag}" + tag = tag.decode() + form = {"tag": tag, "length": length, "offset": idx} + fsFiles.append(form) + idx += 8 + length + self.fs_files = fsFiles + + def split(self, rom_bytes): + path = options.opts.asset_path / self.dir / self.fs_path + path.mkdir(parents=True, exist_ok=True) + # split out raw binary blobs of each FORM + for form in self.fs_files: + basename = f"FORM_{form['tag']}_{form['offset']:06X}" + start = form["offset"] + end = start + form["length"] + 8 + formData = rom_bytes[start:end] + # workaround to prefix tags that begin with number like 3VUE + className = f"FORM_{form['tag']}" if form['tag'][0].isdigit() else form["tag"] + # if the class exists, parse and dump the data as json, else dump raw binary + if hasattr(pw64_fs, className): + fileName = basename + ".json" + filePath = options.opts.asset_path / self.dir / self.fs_path / fileName + generator = getattr(pw64_fs, className) + parsedData = generator.from_bytes(formData).as_dict() + with open(filePath, "w", newline="\n") as fout: + json.dump(parsedData, fout, indent=2) + else: + fileName = basename + ".raw" + filePath = options.opts.asset_path / self.dir / self.fs_path / fileName + with open(filePath, "wb") as fout: + fout.write(formData) + + def get_linker_entries(self): + return [ + LinkerEntry( + self, + [options.opts.asset_path / self.dir / f"{self.name}.bin"], + options.opts.asset_path / self.dir / f"{self.name}", + self.get_linker_section_order(), + self.get_linker_section_linksection(), + self.is_noload(), + ) + ] diff --git a/tools/splat_ext/pw_filetable.py b/tools/splat_ext/pw_filetable.py new file mode 100644 index 00000000..f41df2d9 --- /dev/null +++ b/tools/splat_ext/pw_filetable.py @@ -0,0 +1,85 @@ +import json +import struct +from splat.util import options +from splat.segtypes.segment import Segment +from splat.segtypes.linker_entry import LinkerEntry +from tools.pw64 import filesys, mio0 + +class N64SegPw_filetable(Segment): + def __init__(self, rom_start, rom_end, type, name, vram_start, args, yaml): + super().__init__( + rom_start, rom_end, type, name, vram_start, args=args, yaml=yaml + ) + self.align = 0x10 + self.fs_path = "filesys" + + def parse_table(self, rom_bytes: bytes, idx: int) -> dict: + formTag, formLen = struct.unpack(">4s L", rom_bytes[idx:idx+8]) + print(f"form 0x{idx:x} 0x{formLen:x}") + assert formTag == b'FORM', f"Expected 'FORM', got {formTag}" + idx += 8 + fsDataStart = (((idx + formLen) + 15) // 16) * 16 + rtag, = struct.unpack(">4s", rom_bytes[idx:idx+4]) + assert rtag == b'UVRM', f"Expected 'UVRM', got {rtag}" + idx += 4 + table = { + "tag": formTag.decode(), + "length": formLen, + "type": rtag.decode(), + "pad_count": 0, + "contents": [] + } + cur = 0 + fsDataOffset = fsDataStart + while cur < formLen: + tag, length = struct.unpack(">4s L", rom_bytes[cur+idx:cur+idx+8]) + cur += 8 + data = rom_bytes[cur+idx:cur+idx+length] + # compressed data, unwrap MIO0 compression first + if tag == b'GZIP': + tag, dlength = struct.unpack(">4s L", rom_bytes[cur+idx:cur+idx+8]) + cur += 8 + cdata = rom_bytes[cur+idx:cur+idx+length-8] + table["mio0_matching_info"] = {} + data = mio0.decompress(cdata, table["mio0_matching_info"]) + assert len(data) == dlength, f"Expected {len(data)} == {dlength}" + if tag == b'PAD ': + table["pad_count"] += 1 + elif tag == b'TABL': + for entryTag, entryLen in struct.iter_unpack(">4s L", data): + table["contents"].append({ + "tag": entryTag.decode(), + "offset": fsDataOffset, + "length": entryLen + }) + fsDataOffset += entryLen + cur += length + return table + + def scan(self, rom_bytes): + self.fs_table = self.parse_table(rom_bytes, self.rom_start) + + def split(self, rom_bytes): + path = options.opts.asset_path / self.dir / self.fs_path + path.mkdir(parents=True, exist_ok=True) + # assign file path and name which align with pw_filesys + for form in self.fs_table["contents"]: + className = f"FORM_{form['tag']}" if form["tag"][0].isdigit() else form["tag"] + ext = "json" if hasattr(filesys, className) else "raw" + form["file"] = f"FORM_{form['tag']}_{form['offset']:06X}.{ext}" + # emit top-level filetable json + assert self.fs_table and path, f"Unexpected {self.fs_table} {path}" + with open(path / f"{self.name}.json", "w", newline="\n") as fout: + json.dump(self.fs_table, fout, indent=2) + + def get_linker_entries(self): + return [ + LinkerEntry( + self, + [options.opts.asset_path / self.dir / f"{self.name}.bin"], + options.opts.asset_path / self.dir / f"{self.name}", + self.get_linker_section_order(), + self.get_linker_section_linksection(), + self.is_noload(), + ) + ]