diff --git a/tools/extract_game_assets.py b/tools/extract_game_assets.py index 9ae1777c95..b23ebf0cd2 100644 --- a/tools/extract_game_assets.py +++ b/tools/extract_game_assets.py @@ -3,6 +3,7 @@ import sys import libarc from pathlib import Path import libyaz0 +import libstage """ Extracts the game assets and stores them in the game folder @@ -133,14 +134,18 @@ def writeFolder(parsedFstBin, i): Use the parsed fst.bin contents to write assets to file """ -convertDefinitions = [ - { - "extension": ".arc", +convertDefinitions = { + ".arc": { "function": libarc.extract_to_directory, "exceptions": ["archive/dat/speakerse.arc"], + }, + ".dzs": { + "function": libstage.extract_to_json + }, + ".dzr": { + "function": libstage.extract_to_json } -] - +} def writeFile(name, data): if data[0:4] == bytes("Yaz0", "ascii"): @@ -151,14 +156,10 @@ def writeFile(name, data): extractDef = None splitName = os.path.splitext(name) ext = splitName[1] - for extractData in convertDefinitions: - if ext == extractData["extension"]: - extractDef = extractData - if extractData["exceptions"] != None: - for exception in extractData["exceptions"]: - if str(name) == exception: - extractDef = None - break + if ext in convertDefinitions: + extractDef = convertDefinitions[ext] + if "exceptions" in extractDef and str(name) in extractDef["exceptions"]: + extractDef = None if extractDef == None: file = open(name, "wb") diff --git a/tools/libarc/arc.py b/tools/libarc/arc.py index ce895ba989..61970f7801 100644 --- a/tools/libarc/arc.py +++ b/tools/libarc/arc.py @@ -431,6 +431,8 @@ def convert_dir_to_arc(sourceDir, convertFunction): dirOffset = dirOffset + dirOffsetPadding stringTableOffset = dirOffset + (len(dirs) * 20) stringTablePadding = 0x20 - (stringTableOffset % 0x20) + if stringTablePadding == 0x20: + stringTablePadding = 0 stringTableOffset = stringTableOffset + stringTablePadding stringTableLen = len(bytearray(stringTable, "shift-jis")) fileOffset = stringTableOffset + stringTableLen diff --git a/tools/libstage/__init__.py b/tools/libstage/__init__.py new file mode 100644 index 0000000000..838aefc044 --- /dev/null +++ b/tools/libstage/__init__.py @@ -0,0 +1 @@ +from .libstage import * \ No newline at end of file diff --git a/tools/libstage/libstage.py b/tools/libstage/libstage.py new file mode 100644 index 0000000000..c80c4a36c2 --- /dev/null +++ b/tools/libstage/libstage.py @@ -0,0 +1,1864 @@ +import struct +from sys import argv +import json +import traceback + + +# struct dStage_Elst_data { +# /* 0x0 */ u8 m_layerTable[15]; +# }; +def extractEVLY(m_entrynum, m_offset, data): + offset = m_offset + entries = [] + for i in range(m_entrynum): + layerTable = [] + for val in data[offset : offset + 15]: + layerTable.append(val) + entry = {"layerTable": layerTable} + # print(f"Layer Table Entry {i}: {entry}") + entries.append(entry) + offset += 15 + return entries + + +def extractRPPN(m_entrynum, m_offset, data): + entries = [] + offset = m_offset + for i in range(m_entrynum): + _0, _1, _2, _3, x, y, z = struct.unpack( + ">BBBBfff", data[offset : offset + 0x10] + ) + entry = { + "field_0x0": _0, + "field_0x1": _1, + "field_0x2": _2, + "field_0x3": _3, + "Position X": x, + "Position Y": y, + "Position Z": z, + } + entries.append(entry) + offset += 0x10 + return entries + + +def extractRPAT(m_entrynum, m_offset, data): + entries = [] + offset = m_offset + for i in range(m_entrynum): + numPoints, pathIndex, _4, isLoop, _6, firstEntryOffset = struct.unpack( + ">HhBBHI", data[offset : offset + 12] + ) + entry = { + "Number of Points": numPoints, + "Path Index": pathIndex, + "field_0x4": _4, + "Looped": isLoop, + "field_0x6": _6, + "RPPN Entry Index": firstEntryOffset // 0x10, + } + entries.append(entry) + offset += 12 + return entries + + +# struct dStage_Mult_info { +# /* 0x0 */ f32 mTransX; +# /* 0x4 */ f32 mTransY; +# /* 0x8 */ s16 mAngle; +# /* 0xA */ u8 mRoomNo; +# }; // Size: 0xC +def extractMULT(m_entrynum, m_offset, data): + entires = [] + offset = m_offset + for i in range(m_entrynum): + x, y, angle, roomNo, _b = struct.unpack(">ffhBB", data[offset : offset + 12]) + entry = {"x": x, "y": y, "Angle": angle, "roomNo": roomNo, "field_0xb": _b} + # print(f"MULT Entry {i}: {entry}") + entires.append(entry) + offset += 12 + return entires + + +# class stage_actor_data_class { +# public: +# /* 0x00 */ char mName[8]; +# /* 0x08 */ u32 mParameter; +# /* 0x0C */ cXyz mSpawnPos; +# /* 0x18 */ csXyz mAngle; +# /* 0x1E */ u16 mEnemyNo; +# }; // Size: 0x20 +def extractACTR(m_entrynum, m_offset, data): + entries = [] + offset = m_offset + for i in range(m_entrynum): + name = str(data[offset : offset + 8], "ascii").rstrip("\x00") + param, x, y, z, ax, ay, az, enemyNo = struct.unpack( + ">Ifffhhhh", data[offset + 8 : offset + 0x20] + ) + entry = { + "Name": name, + "param": param, + "x": x, + "y": y, + "z": z, + "Angle X": ax, + "Angle Y": ay, + "Angle Z": az, + "EnemyNo": enemyNo, + } + # print(f"PLYR Entry {i}: {entry}") + entries.append(entry) + offset += 0x20 + return entries + + +# struct stage_camera2_data_class { +# /* 0x00 */ int field_0x0; +# /* 0x04 */ f32 field_0x4; +# /* 0x08 */ f32 field_0x8; +# /* 0x0C */ f32 field_0xc; +# /* 0x10 */ u8 field_0x10; +# /* 0x11 */ u8 field_0x11; +# /* 0x12 */ u8 field_0x12; +# /* 0x13 */ u8 field_0x13; +# /* 0x14 */ u16 field_0x14; +# /* 0x16 */ u16 field_0x16; +# }; // Size: 0x18 +def extractCAM(m_entrynum, m_offset, data): + entries = [] + offset = m_offset + for i in range(m_entrynum): + camera_type = str(data[offset : offset + 16], "ascii").rstrip("\x00") + _10, _11, _12, _13, _14, _16 = struct.unpack( + ">BBBBHH", data[offset + 16 : offset + 0x18] + ) + # Beginning value seems to be a string, TODO check + entry = { + "Camera Type": camera_type, + "field_0x10": _10, + "field_0x11": _11, + "field_0x12": _12, + "field_0x13": _13, + "field_0x14": _14, + "field_0x16": _16, + } + # print(f"CAMERA Entry {i}: {entry}") + entries.append(entry) + offset += 0x18 + return entries + + +# struct roomRead_class { +# /* 0x0 */ int field_0x0; +# /* 0x4 */ roomRead_data_class** field_0x4; +# }; +# struct roomRead_data_class { +# /* 0x0 */ u8 field_0x0; +# /* 0x1 */ u8 field_0x1; +# /* 0x2 */ u8 field_0x2; +# /* 0x4 */ u8* field_0x4; +# }; +def extractRTBL(m_entrynum, m_offset, data): + entries = [] + offset = m_offset + for i in range(m_entrynum): + room_data_offset = struct.unpack(">I", data[offset : offset + 4])[0] + offset += 4 + infoTblLen, _1, _2, _pad, infoTblOffset = struct.unpack( + ">BBBBI", data[room_data_offset : room_data_offset + 8] + ) + infoTbl = [] + for j in range(infoTblLen): + infoTbl.append( + struct.unpack(">B", data[infoTblOffset : infoTblOffset + 1])[0] + ) + infoTblOffset += 1 + # These contain room properties such as time pass, vrbox, sound info + + # See dStage_roomRead_dt_c_GetReverb for field1 + # See dStage_roomRead_dt_c_GetTimePass and dStage_roomRead_dt_c_GetVrboxswitch for field2 + # See dStage_roomRead_dt_c_ChkBg and dStage_roomRead_dt_c_GetLoadRoomIndex for infoTbl + + # The entries in the tables are room numbers and the first entry is usually ORd by 0x80 + # If an entry & 0x80 > 0, dStage_roomRead_dt_c_ChkBg will be true + # Note: The table will always have as many entries as there is the maximum number of rooms + # For example, if only rooms 0, 1, and 3 exist, there will be 4 entries in the info table + entry = {"field_0x1": _1, "field_0x2": _2, "Table": infoTbl} + # print(f"RTBL Entry {i}: {entry}") + entries.append(entry) + return entries + + +# struct stage_arrow_data_class { +# /* 0x00 */ cXyz mPosition; +# /* 0x0C */ csXyz mAngle; +# }; // Size: 0x14 +def extractRARO(m_entrynum, m_offset, data): + offset = m_offset + entries = [] + for i in range(m_entrynum): + x, y, z, ax, ay, az, _12, _13 = struct.unpack( + ">fffhhhBB", data[offset : offset + 0x14] + ) + entry = { + "x": x, + "y": y, + "z": z, + "Angle X": ax, + "Angle Y": ay, + "Angle Z": az, + "field_0x12": _12, + "field_0x13": _13, + } + # print(f"Arrow Entry {i}: {entry}") + entries.append(entry) + offset += 0x14 + return entries + + +# struct stage_scls_info_class { +# /* 0x0 */ char mStage[8]; +# /* 0x8 */ u8 mStart; +# /* 0x9 */ s8 mRoom; +# /* 0xA */ u8 field_0xa; +# /* 0xB */ u8 field_0xb; +# /* 0xC */ s8 mWipe; +# }; // Size: 0xD +def extractSCLS(m_entrynum, m_offset, data): + offset = m_offset + entries = [] + for i in range(m_entrynum): + stage = str(data[offset : offset + 8], "ascii").rstrip("\x00") + start, room, _a, _b, wipe = struct.unpack( + ">BbBBb", data[offset + 8 : offset + 13] + ) + entry = { + "Stage": stage, + "Start": start, + "Room": room, + "field_0xa": _a, + "field_0xb": _b, + "Wipe": wipe, + } + entries.append(entry) + # print(f"SCLS Entry {i}: {entry}") + offset += 0xD + return entries + + +def extractTGSC(m_entrynum, m_offset, data): + entries = [] + offset = m_offset + for i in range(m_entrynum): + name = str(data[offset : offset + 8], "ascii").rstrip("\x00") + ( + param, + x, + y, + z, + ax, + ay, + az, + enemyNo, + scaleX, + scaleY, + scaleZ, + _23, + ) = struct.unpack(">IfffhhhhBBBB", data[offset + 8 : offset + 0x24]) + entry = { + "Name": name, + "param": param, + "x": x, + "y": y, + "z": z, + "Angle X": ax, + "Angle Y": ay, + "Angle Z": az, + "EnemyNo": enemyNo, + "Scale X": scaleX, + "Scale Y": scaleY, + "Scale Z": scaleZ, + "field_0x23": _23, + } + entries.append(entry) + offset += 0x24 + return entries + + +# Any stage with the format room%d.dzs should use FileList2 +# class dStage_FileList_dt_c { +# public: +# /* 0x00 */ u32 mParameters; +# /* 0x04 */ f32 mSeaLevel; +# /* 0x08 */ f32 field_0x8; +# /* 0x0C */ f32 field_0xc; +# /* 0x10 */ u8 field_0x10[10]; +# /* 0x1A */ u8 mDefaultCamera; +# /* 0x1B */ u8 mBitSw; +# /* 0x1C */ u16 mMsg; +# }; // Size: 0x20 +# class dStage_FileList2_dt_c { +# public: +# /* 0x00 */ f32 mLeftRmX; +# /* 0x04 */ f32 mInnerRmZ; +# /* 0x08 */ f32 mRightRmX; +# /* 0x0C */ f32 mFrontRmZ; +# /* 0x10 */ u8 mMinFloorNo; +# /* 0x11 */ u8 mMaxFloorNo; +# /* 0x12 */ u8 field_0x12; +# /* 0x13 */ u8 field_0x13; +# /* 0x14 */ f32 field_0x14; +# /* 0x18 */ f32 field_0x18; +# /* 0x1C */ s16 field_0x1c; +# }; // Size: 0x20 +def extractFILI(m_entrynum, m_offset, data): + offset = m_offset + entries = [] + for i in range(m_entrynum): + parameters, sea_level, _8, _c = struct.unpack( + ">Ifff", data[offset : offset + 0x10] + ) + table = [] + for val in data[offset + 0x10 : offset + 0x1A]: + table.append(val) + default_camera, bit_sw, msg = struct.unpack( + ">BBH", data[offset + 0x1A : offset + 0x1E] + ) + entry = { + "Parameters": parameters, + "Sea Level": sea_level, + "field_0x8": _8, + "field_0xc": _c, + "field_0x10": table, + "Default Camera": default_camera, + "Bit Sw": bit_sw, + "Msg": msg, + } + # print(f"FileList Entry {i}: {entry}") + entries.append(entry) + offset += 0x20 + return entries + + +def dStage_filiInfo2Init(m_entrynum, m_offset, data): + offset = m_offset + entries = [] + for i in range(m_entrynum): + ( + leftRmX, + innerRmZ, + RightRmX, + FrontRmZ, + MinFloorNo, + MaxFloorNo, + _12, + _13, + _14, + _18, + _1c, + ) = struct.unpack(">ffffBBBBffh", data[offset : offset + 30]) + entry = { + "Left Room X": leftRmX, + "Inner Room Z": innerRmZ, + "Right Room X": RightRmX, + "Front Room Z": FrontRmZ, + "Min Floor No.": MinFloorNo, + "Max Floor No.": MaxFloorNo, + "field_0x12": _12, + "field_0x13": _13, + "field_0x14": _14, + "field_0x18": _18, + "field_0x1c": _1c, + } + entries.append(entry) + offset += 0x20 + return entries + + +# struct dStage_MapEvent_dt_c { +# /* 0x00 */ u8 mType; +# /* 0x01 */ u8 field_0x1; +# /* 0x02 */ u8 field_0x2; +# /* 0x03 */ u8 field_0x3; +# /* 0x04 */ u8 field_0x4; +# /* 0x05 */ u8 field_0x5; +# /* 0x06 */ u8 mPriority; +# /* 0x07 */ u8 field_0x7; +# /* 0x08 */ u8 field_0x8; +# /* 0x09 */ u8 field_0x9; +# /* 0x0A */ u8 field_0xA; +# /* 0x0B */ u8 field_0xB; +# /* 0x0C */ u8 field_0xC; +# /* 0x0D */ char mName[7]; +# /* 0x14 */ u16 field_0x14; +# /* 0x16 */ u8 field_0x16; +# /* 0x17 */ u8 field_0x17; +# /* 0x18 */ u8 mSeType; // 1: RIDDLE_A, 2: RIDDLE_B +# /* 0x19 */ u8 field_0x19[0x1B - 0x19]; +# /* 0x1B */ u8 mSwitch; +# }; // SIZE = 0x1C +def extractREVT(m_entrynum, m_offset, data): + offset = m_offset + entries = [] + for i in range(m_entrynum): + _type, _1, _2, _3, _4, _5, priority, _7, _8, _9, _a, _b, _c = struct.unpack( + ">BBBBBBBBBBBBB", data[offset : offset + 0xD] + ) + name = None + try: + name = str(data[offset + 0xD : offset + 0x18], "ascii").rstrip("\x00") + except Exception: + name = list(data[offset + 0xD : offset + 0x18]) + # TODO: Check name length + seType, _1a, _1b, switch = struct.unpack( + ">BBBB", data[offset + 0x18 : offset + 0x1C] + ) + entry = { + "Type": _type, + "field_0x1": _1, + "field_0x2": _2, + "field_0x3": _3, + "field_0x4": _4, + "field_0x5": _5, + "Priority": priority, + "field_0x7": _7, + "field_0x8": _8, + "field_0x9": _9, + "field_0xa": _a, + "field_0xb": _b, + "field_0xc": _c, + "Name": name, + "seType": seType, + "field_0x1a": _1a, + "field_0x1b": _1b, + "switch": switch, + } + # print(f"MapEvent {i}: {entry}") + offset += 0x1C + entries.append(entry) + return entries + + +# struct stage_sound_data { +# /* 0x00 */ char field_0x0[8]; +# /* 0x08 */ Vec field_0x8; +# /* 0x14 */ u8 field_0x14; +# /* 0x15 */ u8 field_0x15; +# /* 0x16 */ u8 field_0x16; +# /* 0x17 */ u8 field_0x17; +# /* 0x18 */ u8 field_0x18; +# /* 0x19 */ u8 field_0x19; +# /* 0x1A */ u8 field_0x1a; +# }; // Size: 0x1C +def extractSOND(m_entrynum, m_offset, data): + entries = [] + offset = m_offset + for i in range(m_entrynum): + name = str(data[offset : offset + 8], "ascii").rstrip("\x00") + # print(name) + x, y, z, _14, _15, _16, _17, _18, _19, _1a, _1b = struct.unpack( + ">fffBBBBBBBB", data[offset + 8 : offset + 0x1C] + ) + entry = { + "Name": name, + "x": x, + "y": y, + "z": z, + "field_0x14": _14, + "field_0x15": _15, + "field_0x16": _16, + "field_0x17": _17, + "field_0x18": _18, + "field_0x19": _19, + "field_0x1a": _1a, + "field_0x1b": _1b, + } + entries.append(entry) + offset += 0x1C + return entries + + +# struct stage_pure_lightvec_info_class { +# // LGT +# /* 0x00 */ Vec m_position; +# /* 0x0C */ f32 m_radius; +# /* 0x10 */ f32 m_directionX; +# /* 0x14 */ f32 m_directionY; +# /* 0x18 */ f32 m_spotCutoff; +# /* 0x1C */ u8 field_0x1c; +# /* 0x1D */ u8 field_0x1d; +# /* 0x1E */ u8 field_0x1e; +# /* 0x1F */ u8 field_0x1f; +# }; // Size: 0x20 +def extractLGTV(m_entrynum, m_offset, data): + entries = [] + offset = m_offset + for i in range(m_entrynum): + x, y, z, rad, dirX, dirY, spot_cutoff, _1c, _1d, _1e, _1f = struct.unpack( + ">fffffffBBBB", data[offset : offset + 0x20] + ) + entry = { + "x": x, + "y": y, + "z": z, + "Radius": rad, + "Direction X": dirX, + "Direction Y": dirY, + "Spotlight Cutoff": spot_cutoff, + "field_0x1c": _1c, + "field_0x1d": _1d, + "field_0x1e": _1e, + "field_0x1f": _1f, + } + # print(f"LGTV Entry {i}: {entry}") + entries.append(entry) + offset += 0x20 + return entries + + +# struct stage_envr_info_class { +# /* 0x0 */ u8 m_pselectID[65]; +# }; // Size: 0x41 +def extractENVR(m_entrynum, m_offset, data): + offset = m_offset + entries = [] + for i in range(m_entrynum): + dataTable = [] + for val in data[offset : offset + 65]: + dataTable.append(val) + entry = {"Pselect ID Table": dataTable} + # print(f"envrInfo Entry {i}: {entry}") + entries.append(entry) + offset += 0x41 + return entries + + +# struct stage_pselect_info_class { +# /* 0x0 */ u8 mPalIdx[8]; +# /* 0x8 */ f32 mChangeRate; +# }; // Size: 0xC +def extractCol(m_entrynum, m_offset, data): + entries = [] + offset = m_offset + for i in range(m_entrynum): + palIds = [] + for val in data[offset : offset + 8]: + palIds.append(val) + change_rate = struct.unpack(">f", data[offset + 8 : offset + 12])[0] + entry = {"Palette Ids": palIds, "Change Rate": change_rate} + entries.append(entry) + # print(f"Col Info Entry {i}: {entry}") + offset += 0xC + return entries + + +def parse_RGB_class(data): + return struct.unpack(">BBB", data) + + +# struct stage_palette_info_class { +# /* 0x00 */ color_RGB_class mActorAmbColor; +# /* 0x03 */ color_RGB_class mBgAmbColor[4]; +# /* 0x0F */ color_RGB_class mPlightColor[6]; +# /* 0x21 */ color_RGB_class mFogColor; +# /* 0x24 */ f32 mFogStartZ; +# /* 0x28 */ f32 mFogEndZ; +# /* 0x2C */ u8 mVirtIdx; +# /* 0x2D */ u8 mTerrainLightInfluence; +# /* 0x2E */ u8 mCloudShadowDensity; +# /* 0x2F */ u8 field_0x2f; +# /* 0x30 */ u8 mBloomTblIdx; +# /* 0x31 */ u8 mBgAmbColor1A; +# /* 0x32 */ u8 mBgAmbColor2A; +# /* 0x33 */ u8 mBgAmbColor3A; +# }; // Size: 0x34 +def extractPAL(m_entrynum, m_offset, data): + entries = [] + offset = m_offset + for i in range(m_entrynum): + # print(f"Data {data[offset:offset+0x34]}") + actor_amb_color = parse_RGB_class(data[offset : offset + 3]) + offset += 3 + bg_amb_colors = [] + for j in range(4): + bg_amb_colors.append(parse_RGB_class(data[offset : offset + 3])) + offset += 3 + p_light_colors = [] + for j in range(6): + p_light_colors.append(parse_RGB_class(data[offset : offset + 3])) + offset += 3 + fog_color = parse_RGB_class(data[offset : offset + 3]) + offset += 3 + ( + fog_start_z, + fog_end_z, + virt_idx, + terrain_light_influence, + cloud_shadow_density, + _2f, + bloom_tbl_idx, + bg_amb_color1a, + bg_amb_color2a, + bg_amb_color3a, + ) = struct.unpack(">ffBBBBBBBB", data[offset : offset + 16]) + offset += 16 + entry = { + "Actor Ambient Color": actor_amb_color, + "BG Ambient Colors": bg_amb_colors, + "P Light Colors": p_light_colors, + "Fog Color": fog_color, + "Fog Start Z": fog_start_z, + "Fog End Z": fog_end_z, + "Virt Idx": virt_idx, + "Terrain Light Influence": terrain_light_influence, + "Cloud Shadow Density": cloud_shadow_density, + "field_0x2f": _2f, + "Bloom Table Idx": bloom_tbl_idx, + "BG Ambient Color 1a": bg_amb_color1a, + "BG Ambient Color 2a": bg_amb_color2a, + "BG Ambient Color 3a": bg_amb_color3a, + } + entries.append(entry) + # print(f"Palette Entry {i}: {entry}") + return entries + + +# struct stage_vrboxcol_info_class { +# // VRB +# }; // Size: 0x18 +def extractVRB(m_entrynum, m_offset, data): + entries = [] + offset = m_offset + for i in range(m_entrynum): + skyColor = parse_RGB_class(data[offset : offset + 3]) + cloudColor = parse_RGB_class(data[offset + 3 : offset + 6]) + colorTable = [] + for j in range(5): + colorTable.append( + parse_RGB_class(data[offset + 6 + (j * 3) : offset + 6 + ((j + 1) * 3)]) + ) + offset += 0x15 + entry = { + "Sky Color": skyColor, + "Cloud Color": cloudColor, + "Other Colors": colorTable, + } + # print(f"vrboxcolinfo {i}: {entry}") + entries.append(entry) + return entries + + +# class dStage_Lbnk_dt_c { +# public: +# /* 0x0 */ u8 field_0x0[0x2 - 0x0]; +# /* 0x2 */ u8 field_0x2; +# }; +def extractLBNK(m_entrynum, m_offset, data): + # TODO: Check + offset = m_offset + entries = [] + for i in range(m_entrynum): + _0, _1, _2 = struct.unpack(">BBB", data[offset : offset + 3]) + entry = {"field_0x0": _0, "field_0x1": _1, "field_0x2": _2} + # print(f"LBNK Entry {i}: {entry}") + entries.append(entry) + offset += 3 + return entries + + +# class stage_tresure_class { +# public: +# /* 0x00 */ char mName[8]; +# /* 0x08 */ u8 field_0x8; +# /* 0x09 */ u8 mTypeFlag; +# /* 0x0A */ u8 field_0xa; // part of flag +# /* 0x0B */ u8 mAppearType; +# /* 0x0C */ Vec mPosition; +# /* 0x18 */ s16 mRoomNo; +# /* 0x1A */ s16 mRotation; +# /* 0x1C */ u8 mItem; +# /* 0x1D */ u8 mFlagID; +# }; // Size: 0x20 +def extractTRES(m_entrynum, m_offset, data): + offset = m_offset + entries = [] + for i in range(m_entrynum): + name = str(data[offset : offset + 8], "ascii").rstrip("\x00") + ( + _8, + type_flag, + _a, + appear_type, + x, + y, + z, + room, + rotation, + item, + flagId, + _1e, + _1f, + ) = struct.unpack(">BBBBfffhhBBBB", data[offset + 8 : offset + 32]) + entry = { + "Name": name, + "field_0x8": _8, + "Type Flag": type_flag, + "field_0xa": _a, + "Appear Type": appear_type, + "Position X": x, + "Position Y": y, + "Position Z": z, + "Room No.": room, + "Rotation": rotation, + "Item": item, + "Flag ID": flagId, + "field_0x1e": _1e, + "field_0x1f": _1f, + } + entries.append(entry) + offset += 0x20 + return entries + + +# struct data_s { +# /* 0x00 */ u8 mNo; +# /* 0x01 */ s8 mRoomNo; +# /* 0x02 */ u8 mStatus; +# /* 0x03 */ u8 mArg1; +# /* 0x04 */ Vec mPos; +# /* 0x10 */ u8 mSwBit; +# /* 0x11 */ u8 mType; +# /* 0x12 */ u8 mArg2; +# /* 0x13 */ s8 mAngleY; +# }; +def dStage_stageKeepTresureInit(m_entrynum, m_offset, data): + offset = m_offset + entries = [] + for i in range(m_entrynum): + num, roomNo, status, arg1, x, y, z, swBit, _type, arg2, angleY = struct.unpack( + ">BbBBfffBBBb", data[offset : offset + 0x14] + ) + entry = { + "Number": num, + "Room Number": roomNo, + "Status": status, + "Argument 1": arg1, + "Position X": x, + "Position Y": y, + "Position Z": z, + "Sw Bit": swBit, + "Type": _type, + "Argument 2": arg2, + "Angle Y": angleY, + } + entries.append(entry) + offset += 0x14 + return entries + + +# struct stage_stag_info_class { +# /* 0x00 */ f32 field_0x0; +# /* 0x04 */ f32 field_0x4; +# /* 0x08 */ u8 mCameraType; +# /* 0x09 */ u8 field_0x09; +# /* 0x0A */ u16 field_0x0a; +# /* 0x0C */ u32 field_0x0c; +# /* 0x10 */ u32 field_0x10; +# /* 0x14 */ u8 field_0x14[6]; // usually all 0xFF +# /* 0x1A */ s16 mGapLevel; +# /* 0x1C */ s16 mRangeUp; +# /* 0x1E */ s16 mRangeDown; +# /* 0x20 */ f32 field_0x20; +# /* 0x24 */ f32 field_0x24; +# /* 0x28 */ u8 mMsgGroup; +# /* 0x2A */ u16 mStageTitleNo; +# /* 0x2C */ u8 mParticleNo[16]; +# }; // SIZE: 0x3C +def extractSTAG(m_entrynum, m_offset, data): + offset = m_offset + entries = [] + for i in range(m_entrynum): + _0, _4, camera_type, _9, _a, _c, _10 = struct.unpack( + ">ffBBHII", data[offset : offset + 0x14] + ) + _14 = [] + for j in range(6): + _14.append(data[offset + 0x14 + j]) + ( + gap_level, + range_up, + range_down, + _20, + _24, + msg_group, + _29, + stage_title_no, + ) = struct.unpack(">hhhffBBH", data[offset + 0x1A : offset + 0x2C]) + particle_no = [] + for j in range(16): + particle_no.append(data[offset + 0x2C + j]) + entry = { + "field_0x0": _0, + "field_0x4": _4, + "Camera Type": camera_type, + "field_0x9": _9, + "field_0xa": _a, + "field_0xc": _c, + "field_0x10": _10, + "field_0x14": _14, + "Gap Level": gap_level, + "Range Up": range_up, + "Range Down": range_down, + "field_0x20": _20, + "field_0x24": _24, + "Msg Group": msg_group, + "field_0x29": _29, + "Stage Title No": stage_title_no, + "Particle No": particle_no, + } + entries.append(entry) + # print(f"STAGE Entry {i}: {entry}") + offset += 0x3C + return entries + + +def extractMEMA(m_entrynum, m_offset, data): + offset = m_offset + entries = [] + for i in range(m_entrynum): + entries.append(struct.unpack(">I", data[offset : offset + 4])[0]) + offset += 4 + return entries + + +# struct dStage_MemoryConfig_data { +# /* 0x0 */ u8 m_roomNo; +# /* 0x1 */ u8 m_blockID; +# }; // Size: 0x2 +def extractMECO(m_entrynum, m_offset, data): + offset = m_offset + entries = [] + for i in range(m_entrynum): + roomNo, blockId = struct.unpack(">BB", data[offset : offset + 2]) + entry = {"Room Number": roomNo, "Block Id": blockId} + entries.append(entry) + offset += 2 + return entries + + +# struct line_class { +# /* 0x00 */ u8 field_0x0; +# /* 0x01 */ u8 field_0x1; +# /* 0x02 */ u8 mDataNum; //Number of entries in mpData +# /* 0x03 */ u8 field_0x3; +# /* 0x04 */ u16* mpData; +# }; // Size: 0x8 +# struct poly_class { +# /* 0x00 */ u8 field_0x0; +# /* 0x01 */ u8 mDataNum; //Number of entries in mpData +# /* 0x04 */ u16* mpData; +# }; // Size: 0x8 +# struct group_class { +# /* 0x00 */ u8 field_0x0; +# /* 0x01 */ u8 field_0x1; +# /* 0x02 */ u8 mLineNum; //Number of Lines +# /* 0x03 */ u8 field_0x3; +# /* 0x04 */ u8 mPolyNum; //Number of Polygons +# /* 0x08 */ dDrawPath_c::line_class* mpLine; +# /* 0x0C */ u8 field_0xc[4]; +# /* 0x10 */ dDrawPath_c::poly_class* mpPoly; +# }; // Size: 0x14 +# struct floor_class { +# /* 0x0 */ s8 mFloorNo; //Floor Index +# /* 0x1 */ u8 mGroupNum; //Number of Groups +# /* 0x4 */ dDrawPath_c::group_class* mpGroup; +# }; // Size: 0x8 +# struct room_class { +# /* 0x0 */ u8 mFloorNum; +# /* 0x4 */ dDrawPath_c::floor_class* mpFloor; +# /* 0x8 */ f32* mpFloatData; // vec2 +# }; +def extractMPAT(m_entrynum, m_offset, data): + # Formatted In The Order: Main Struct, Floor Structs, Float Data, Group Structs, Line than Polygon, one after another, Then The Data inside of lines and polygons + offset = m_offset + entry = {} + floor_entries, _1, xy_entries, floor_offset, floatData_offset = struct.unpack( + ">BBHII", data[offset : offset + 12] + ) + entry["field_0x1"] = _1 + entry["Entry Num"] = m_entrynum + floor_offset += offset + floatData_offset += offset + # print(f"Entry Numbers: {m_entrynum}") + # print(f"Float Offset: {floatData_offset}") + floatData = [] + for j in range(xy_entries): + x, z = struct.unpack(">ff", data[floatData_offset : floatData_offset + 8]) + # print(f"X:{x} Y:{y}") + floatData.append({"x": x, "z": z}) + floatData_offset += 8 + entry["Vertices"] = floatData + # print(f"Floor Offset: {floor_offset}") + floors = [] + for j in range(floor_entries): + floor_id, group_entries, _2, _3, group_offset = struct.unpack( + ">BBBBI", data[floor_offset : floor_offset + 8] + ) + floor = {"Id": floor_id, "field_0x2": _2, "field_0x3": _3} + group_offset += offset + # print(f" Group Offset: {group_offset}") + # print(f" Group Entries: {group_entries}") + groups = [] + for k in range(group_entries): + ( + _0, + _1, + line_entries, + _3, + poly_entries, + _5, + _6, + _7, + line_offset, + _c, + _d, + _e, + _f, + polygon_offset, + ) = struct.unpack( + ">BBBBBBBBIBBBBI", data[group_offset : group_offset + 0x14] + ) + group = { + "field_0x0": _0, + "field_0x1": _1, + "field_0x3": _3, + "field_0x5": _5, + "field_0x6": _6, + "field_0x7": _7, + "field_0xc": _c, + "field_0xd": _d, + "field_0xe": _e, + "field_0xf": _f, + } + line_offset += offset + polygon_offset += offset + # print(f" Line Offset: {line_offset}") + lines = [] + for l in range(line_entries): + _0, _1, data_entries, _3, data_offset = struct.unpack( + ">BBBBI", data[line_offset : line_offset + 8] + ) + line = {"field_0x0": _0, "field_0x1": _1, "field_0x3": _3} + data_offset += offset + # print(f" {l}: Data Offset: {data_offset}") + # print(f" {l}: Data Entries: {data_entries}") + data_list = [] + for m in range(data_entries): + data_list.append( + struct.unpack( + ">H", + data[data_offset + (m * 2) : data_offset + ((m + 1) * 2)], + )[0] + ) + line["Vertex Indexes"] = data_list + lines.append(line) + line_offset += 8 + # print(f" Polygon Offset: {polygon_offset}") + polygons = [] + for l in range(poly_entries): + _0, data_entries, _2, _3, data_offset = struct.unpack( + ">BBBBI", data[polygon_offset : polygon_offset + 8] + ) + polygon = {"field_0x0": _0, "field_0x2": _2, "field_0x3": _3} + data_offset += offset + # print(f" {l}: Data Offset: {data_offset}") + # print(f" {l}: Data Entries: {data_entries}") + data_list = [] + for m in range(data_entries): + data_list.append( + struct.unpack( + ">H", + data[data_offset + (m * 2) : data_offset + ((m + 1) * 2)], + )[0] + ) + polygon["Vertex Indexes"] = data_list + polygons.append(polygon) + polygon_offset += 8 + group["Lines"] = lines + group["Polygons"] = polygons + groups.append(group) + group_offset += 0x14 + floor["Groups"] = groups + floors.append(floor) + floor_offset += 8 + entry["Floors"] = floors + return entry + + +decodeFuncTbl = { + "STAG": extractSTAG, + "EVLY": extractEVLY, + "RPPN": extractRPPN, + "RPAT": extractRPAT, + "MULT": extractMULT, + "PLYR": extractACTR, + "CAMR": extractCAM, + "RCAM": extractCAM, + "ACTR": extractACTR, + "TGOB": extractACTR, + "RTBL": extractRTBL, + "AROB": extractRARO, + "RARO": extractRARO, + # "Virt": dStage_vrboxInfoInit, #Unused + "SCLS": extractSCLS, + "TGSC": extractTGSC, + # "LGHT": dStage_plightInfoInit, #Unused + "PPNT": extractRPPN, + # "PATH": dStage_pathInfoInit, #Unused + "SCOB": extractTGSC, + "FILI": extractFILI, + "Door": extractTGSC, + # "FLOR": dStage_floorInfoInit, #Unused + "TGDR": extractTGSC, + # "DMAP": dStage_dmapInfoInit, #Unused + "REVT": extractREVT, + "SOND": extractSOND, + # Layer Functions + "SON0": extractSOND, + "LGT0": extractLGTV, + "LGTV": extractLGTV, + "Env0": extractENVR, + "Col0": extractCol, + "PAL0": extractPAL, + "VRB0": extractVRB, + "Doo0": extractTGSC, + "SCO0": extractTGSC, + "ACT0": extractACTR, + "TRE0": extractACTR, + # Room Functions + "LBNK": extractLBNK, + "TRES": extractTRES, + "MEM0": extractMEMA, + "MEC0": extractMECO, + "MPAT": extractMPAT, + "MPA0": extractMPAT, +} + + +def packageSTAG(entries, offset): + data = bytearray() + for e in entries: + data += struct.pack( + ">ffBBHII", + e["field_0x0"], + e["field_0x4"], + e["Camera Type"], + e["field_0x9"], + e["field_0xa"], + e["field_0xc"], + e["field_0x10"], + ) + for i in e["field_0x14"]: + data.append(i) + data += struct.pack( + ">hhhffBBH", + e["Gap Level"], + e["Range Up"], + e["Range Down"], + e["field_0x20"], + e["field_0x24"], + e["Msg Group"], + e["field_0x29"], + e["Stage Title No"], + ) + for i in e["Particle No"]: + data.append(i) + return data + + +def packageRTBL(entries, offset): + enddata = bytearray() + middledata = bytearray() + data = bytearray() + middle_offset = offset + (4 * len(entries)) + end_offset = middle_offset + (len(entries) * 8) + for i, e in enumerate(entries): + data += struct.pack(">I", middle_offset + (i * 8)) + middledata += struct.pack( + ">BBBBI", + len(e["Table"]), + e["field_0x1"], + e["field_0x2"], + 0, + end_offset + len(enddata), + ) + for b in e["Table"]: + enddata.append(b) + data += middledata + enddata + return data + + +def packageMULT(entries, offset): + data = bytearray() + for e in entries: + data += struct.pack( + ">ffhBB", e["x"], e["y"], e["Angle"], e["roomNo"], e["field_0xb"] + ) + return data + + +def packageCAM(entries, offset): + data = bytearray() + for e in entries: + data += struct.pack( + ">16sBBBBHH", + bytes(e["Camera Type"], "ascii"), + e["field_0x10"], + e["field_0x11"], + e["field_0x12"], + e["field_0x13"], + e["field_0x14"], + e["field_0x16"], + ) + return data + + +def packageRARO(entries, offset): + data = bytearray() + for e in entries: + data += struct.pack( + ">fffhhhBB", + e["x"], + e["y"], + e["z"], + e["Angle X"], + e["Angle Y"], + e["Angle Z"], + e["field_0x12"], + e["field_0x13"], + ) + return data + + +def packageEVLY(entries, offset): + data = bytearray() + for e in entries: + for v in e["layerTable"]: + data.append(v) + return data + + +def packageVRB(entries, offset): + data = bytearray() + for e in entries: + data += packRGB(e["Sky Color"]) + data += packRGB(e["Cloud Color"]) + for col in e["Other Colors"]: + data += packRGB(col) + return data + + +def packageEnv(entries, offset): + data = bytearray() + for e in entries: + for v in e["Pselect ID Table"]: + data.append(v) + return data + + +def packageCol(entries, offset): + data = bytearray() + for e in entries: + for v in e["Palette Ids"]: + data.append(v) + data += struct.pack(">f", e["Change Rate"]) + return data + + +def packRGB(arr): + return struct.pack(">BBB", arr[0], arr[1], arr[2]) + + +def packagePAL(entries, offset): + data = bytearray() + for e in entries: + data += packRGB(e["Actor Ambient Color"]) + for col in e["BG Ambient Colors"]: + data += packRGB(col) + for col in e["P Light Colors"]: + data += packRGB(col) + data += packRGB(e["Fog Color"]) + data += struct.pack( + ">ffBBBBBBBB", + e["Fog Start Z"], + e["Fog End Z"], + e["Virt Idx"], + e["Terrain Light Influence"], + e["Cloud Shadow Density"], + e["field_0x2f"], + e["Bloom Table Idx"], + e["BG Ambient Color 1a"], + e["BG Ambient Color 2a"], + e["BG Ambient Color 3a"], + ) + + return data + + +def packageFILI(entries, offset): + data = bytearray() + for e in entries: + data += struct.pack( + ">Ifff", e["Parameters"], e["Sea Level"], e["field_0x8"], e["field_0xc"] + ) + for i in e["field_0x10"]: + data.append(i) + data += struct.pack(">BBH", e["Default Camera"], e["Bit Sw"], e["Msg"]) + return data + + +def packageFILI2(entries, offset): + data = bytearray() + for e in entries: + data += struct.pack( + ">ffffBBBBffh", + e["Left Room X"], + e["Inner Room Z"], + e["Right Room X"], + e["Front Room Z"], + e["Min Floor No."], + e["Max Floor No."], + e["field_0x12"], + e["field_0x13"], + e["field_0x14"], + e["field_0x18"], + e["field_0x1c"], + ) + return data + + +def packageSCLS(entries, offset): + data = bytearray() + for e in entries: + data += struct.pack( + ">8sBbBBb", + bytes(e["Stage"], "ascii"), + e["Start"], + e["Room"], + e["field_0xa"], + e["field_0xb"], + e["Wipe"], + ) + return data + + +def packageLBNK(entries, offset): + data = bytearray() + for e in entries: + data += struct.pack(">BBB", e["field_0x0"], e["field_0x1"], e["field_0x2"]) + return data + + +def packageREVT(entries, offset): + data = bytearray() + for e in entries: + data += struct.pack( + ">BBBBBBBBBBBBB", + e["Type"], + e["field_0x1"], + e["field_0x2"], + e["field_0x3"], + e["field_0x4"], + e["field_0x5"], + e["Priority"], + e["field_0x7"], + e["field_0x8"], + e["field_0x9"], + e["field_0xa"], + e["field_0xb"], + e["field_0xc"], + ) + if isinstance(e["Name"], str): + data += struct.pack(">11s", bytes(e["Name"], "ascii")) + else: + for i in e["Name"]: + data.append(i) + data += struct.pack( + ">BBBB", e["seType"], e["field_0x1a"], e["field_0x1b"], e["switch"] + ) + return data + + +def packageACTR(entries, offset): + data = bytearray() + for e in entries: + data += struct.pack( + ">8sIfffhhhh", + bytes(e["Name"], "ascii"), + e["param"], + e["x"], + e["y"], + e["z"], + e["Angle X"], + e["Angle Y"], + e["Angle Z"], + e["EnemyNo"], + ) + return data + + +def packageLGT(entries, offset): + data = bytearray() + for e in entries: + data += struct.pack( + ">fffffffBBBB", + e["x"], + e["y"], + e["z"], + e["Radius"], + e["Direction X"], + e["Direction Y"], + e["Spotlight Cutoff"], + e["field_0x1c"], + e["field_0x1d"], + e["field_0x1e"], + e["field_0x1f"], + ) + + return data + + +def packageTRES(entries, offset): + data = bytearray() + for e in entries: + data += struct.pack( + ">8sBBBBfffhhBBBB", + bytes(e["Name"], "ascii"), + e["field_0x8"], + e["Type Flag"], + e["field_0xa"], + e["Appear Type"], + e["Position X"], + e["Position Y"], + e["Position Z"], + e["Room No."], + e["Rotation"], + e["Item"], + e["Flag ID"], + e["field_0x1e"], + e["field_0x1f"], + ) + return data + + +def packageRPAT(entries, offset): + data = bytearray() + for e in entries: + data += struct.pack( + ">HhBBHI", + e["Number of Points"], + e["Path Index"], + e["field_0x4"], + e["Looped"], + e["field_0x6"], + e["RPPN Entry Index"] * 0x10, + ) + return data + + +def packageRPPN(entries, offset): + data = bytearray() + for e in entries: + data += struct.pack( + ">BBBBfff", + e["field_0x0"], + e["field_0x1"], + e["field_0x2"], + e["field_0x3"], + e["Position X"], + e["Position Y"], + e["Position Z"], + ) + return data + + +def packageTGSC(entries, offset): + data = bytearray() + for e in entries: + data += struct.pack( + ">8sIfffhhhhBBBB", + bytes(e["Name"], "ascii"), + e["param"], + e["x"], + e["y"], + e["z"], + e["Angle X"], + e["Angle Y"], + e["Angle Z"], + e["EnemyNo"], + e["Scale X"], + e["Scale Y"], + e["Scale Z"], + e["field_0x23"], + ) + return data + + +def packageStageTRES(entries, offset): + data = bytearray() + for e in entries: + data += struct.pack( + ">BbBBfffBBBb", + e["Number"], + e["Room Number"], + e["Status"], + e["Argument 1"], + e["Position X"], + e["Position Y"], + e["Position Z"], + e["Sw Bit"], + e["Type"], + e["Argument 2"], + e["Angle Y"], + ) + return data + + +def packageMPAT(entry, offset): + data = bytearray() + o = 12 + (len(entry["Floors"]) * 8) + data += struct.pack( + ">BBHII", + len(entry["Floors"]), + entry["field_0x1"], + len(entry["Vertices"]), + 12, + o, + ) + o += 8 * len(entry["Vertices"]) + group_length = 0 + poly_line_length = 0 + for floor in entry["Floors"]: + for g in floor["Groups"]: + group_length += 1 + poly_line_length += len(g["Lines"]) + len(g["Polygons"]) + poly_line_offset = o + (group_length * 20) + index_data_offset = poly_line_offset + (poly_line_length * 8) + + float_data = bytearray() + for vertex in entry["Vertices"]: + float_data += struct.pack(">ff", vertex["x"], vertex["z"]) + group_data = bytearray() + line_poly_data = bytearray() + index_data = bytearray() + + for floor in entry["Floors"]: + data += struct.pack( + ">BBBBI", + floor["Id"], + len(floor["Groups"]), + floor["field_0x2"], + floor["field_0x3"], + o + len(group_data), + ) + for g in floor["Groups"]: + line_offset = poly_line_offset + len(line_poly_data) + poly_offset = line_offset + (len(g["Lines"]) * 8) + group_data += struct.pack( + ">BBBBBBBBIBBBBI", + g["field_0x0"], + g["field_0x1"], + len(g["Lines"]), + g["field_0x3"], + len(g["Polygons"]), + g["field_0x5"], + g["field_0x6"], + g["field_0x7"], + line_offset, + g["field_0xc"], + g["field_0xd"], + g["field_0xe"], + g["field_0xf"], + poly_offset, + ) + for l in g["Lines"]: + line_poly_data += struct.pack( + ">BBBBI", + l["field_0x0"], + l["field_0x1"], + len(l["Vertex Indexes"]), + l["field_0x3"], + index_data_offset + len(index_data), + ) + for i in l["Vertex Indexes"]: + index_data += struct.pack(">H", i) + for p in g["Polygons"]: + line_poly_data += struct.pack( + ">BBBBI", + p["field_0x0"], + len(p["Vertex Indexes"]), + p["field_0x2"], + p["field_0x3"], + index_data_offset + len(index_data), + ) + for i in p["Vertex Indexes"]: + index_data += struct.pack(">H", i) + return data + float_data + group_data + line_poly_data + index_data + + +def packageSOND(entries, offset): + data = bytearray() + for e in entries: + data += struct.pack( + ">8sfffBBBBBBBB", + bytes(e["Name"], "ascii"), + e["x"], + e["y"], + e["z"], + e["field_0x14"], + e["field_0x15"], + e["field_0x16"], + e["field_0x17"], + e["field_0x18"], + e["field_0x19"], + e["field_0x1a"], + e["field_0x1b"], + ) + return data + + +def packageMECO(entries, offset): + data = bytearray() + for e in entries: + data += struct.pack(">BB", e["Room Number"], e["Block Id"]) + return data + + +def packageMEMO(entries, offset): + data = bytearray() + for e in entries: + data += struct.pack(">I", e) + return data + + +def extract(data, roomStage, roomFile): + chunkCount = struct.unpack(">I", data[:4])[0] + + offset = 4 + + chunk_table = [] + + # struct dStage_nodeHeader { + # /* 0x0 */ u32 m_tag; + # /* 0x4 */ int m_entryNum; + # /* 0x8 */ u32 m_offset; + # }; + tbl = dict(decodeFuncTbl) + if roomStage: + tbl["FILI"] = dStage_filiInfo2Init + tbl["TRES"] = dStage_stageKeepTresureInit + tbl["TRE0"] = dStage_stageKeepTresureInit + + for i in range(chunkCount): + m_entrynum, m_offset = struct.unpack(">iI", data[offset + 4 : offset + 12]) + tag = str(data[offset : offset + 4], "ascii").rstrip("\x00") + offset += 12 + real_tag = tag # Used for writing correct layer tags + # print(f"{real_tag}:{m_offset}") + if not tag in tbl: + if tag[-1] in [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "a", + "b", + "c", + "d", + "e", + ]: + real_tag = tag + tag = tag[:-1] + "0" + if not tag in tbl: + print(f"Unkown layer tag: {real_tag}") + continue + else: + print(f"Unkown tag: {tag}") + continue + entries = tbl[tag](m_entrynum, m_offset, data) + chunk_table.append({"Tag": real_tag, "Offset": m_offset, "Entries": entries}) + + # Here, we sort the entries by their original offsets, so we can rebuild their data in the correct order + chunk_table.sort(key=lambda x: x["Offset"]) + for chunk in chunk_table: + del chunk["Offset"] + + return json.dumps(chunk_table, indent=2) + + +encodeFuncTbl = { + "STAG": packageSTAG, + "RTBL": packageRTBL, + "MULT": packageMULT, + "RCAM": packageCAM, + "RARO": packageRARO, + "AROB": packageRARO, + "EVLY": packageEVLY, + "VRB0": packageVRB, + "Env0": packageEnv, + "Col0": packageCol, + "PAL0": packagePAL, + "SCLS": packageSCLS, + "FILI": packageFILI, + "LBNK": packageLBNK, + "REVT": packageREVT, + "PLYR": packageACTR, + "ACTR": packageACTR, + "TGOB": packageACTR, + "TRES": packageTRES, + "ACT0": packageACTR, + "TRE0": packageACTR, + "LGT0": packageLGT, + "LGTV": packageLGT, + "RPAT": packageRPAT, + "RPPN": packageRPPN, + "SCOB": packageTGSC, + "TGSC": packageTGSC, + "TGDR": packageTGSC, + "SCO0": packageTGSC, + "MPAT": packageMPAT, + "MPA0": packageMPAT, + "SOND": packageSOND, + "SON0": packageSOND, + "MEC0": packageMECO, + "MEM0": packageMEMO, + "Door": packageTGSC, + "Doo0": packageTGSC, +} + +package_order = [ + "STAG", + "RTBL", + "SCLS", + "REVT", + "MULT", + "FILI", + "LBNK", + "RPAT", + "RPPN", + "RCAM", + "RARO", + "EVLY", + "PLYR", + "ACTR", + "SCOB", + "SOND", + "LGTV", + "TRES", + "MPAT", + "Door", +] + +layer_order = [ + "ACT", + "SCO", + "SON", + "MEC", + "MEM", + "LGT", + "MPA", + "VRB", + "Env", + "Col", + "PAL", + "Doo", + "TRE", +] + + +# PACKAGE +def package(chunk_table, roomStage, roomFile): + tbl = dict(encodeFuncTbl) + if roomStage: + tbl["FILI"] = packageFILI2 + tbl["TRES"] = packageStageTRES + tbl["TRE0"] = packageStageTRES + + data = bytearray() + header_table = [] + header_size = 4 + (len(chunk_table) * 12) + for chunk in chunk_table: + tag = chunk["Tag"] + real_tag = tag + if not tag in tbl: + if tag[-1] in [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "a", + "b", + "c", + "d", + "e", + ]: + real_tag = tag + tag = tag[:-1] + "0" + if not tag in tbl: + print(f"Unkown layer tag: {real_tag}") + continue + else: + print(f"Unkown tag: {tag}") + continue + offset = header_size + len(data) + # print(f"{real_tag}:{offset}") + num_entries = 0 + if isinstance(chunk["Entries"], dict): + num_entries = chunk["Entries"]["Entry Num"] + else: + num_entries = len(chunk["Entries"]) + header_data = {"Tag": real_tag, "Offset": offset, "Entries": num_entries} + data += tbl[tag](chunk["Entries"], offset) + if len(data) % 4 != 0: + data += bytearray([0xFF] * (4 - (len(data) % 4))) + header_table.append(header_data) + + order = [] + order += package_order + for i in range(15): + for j in layer_order: + order.append(j + (f"{i:x}")) + tag_to_index = {tag: index for index, tag in enumerate(order)} + header_table.sort(key=lambda item: tag_to_index[item["Tag"]]) + header_data = bytearray() + header_data += struct.pack(">I", len(header_table)) + for item in header_table: + # print(f"{item['Tag']}:{item['Offset']}") + t = item["Tag"] + header_data += struct.pack( + ">BBBBiI", + ord(t[0]), + ord(t[1]), + ord(t[2]), + ord(t[3]), + item["Entries"], + item["Offset"], + ) + + data = header_data + data + if roomFile: + data += struct.pack(">IIIIIIII", 0, 0x10, 0x10, 0x10, 0, 0x10, 0x10, 0x10) + if len(data) % 0x20 != 0: + data += bytearray([0xFF] * (0x20 - (len(data) % 0x20))) + + return data + + # To package, we first must write the entry data in the order + # given by the JSON, then we write Chunk data in the correct order, pointing + # to the right data. + + +def extract_to_json(name, data, writeFunc): + name = str(name) + roomFile = name.rfind(".dzr") != -1 + roomStage = False + if not roomFile: + roomStage = name.find("room") != -1 + print(f"Converting {name} to {name+'.json'}") + out = extract(data, roomStage, roomFile) + writeFunc(name + ".json", bytes(out, "ascii")) + return name + ".json" + + +def package_from_json(name, convertFunc): + name = str(name) + roomFile = name.rfind(".dzr") != -1 + roomStage = False + if not roomFile: + roomStage = name.find("room") != -1 + data = None + with open(name, "r") as f: + data = json.loads(f.read()) + out = package(data, roomStage, roomFile) + return out + + +def main(): + if len(argv) < 3: + print( + "Usage: 'python3 libstage.py input.dzs output.json' or 'python3 libstage.py input.json output.dzs'" + ) + if argv[1] == "test": + testFiles = argv[2:] + for file in testFiles: + room_file = file.rfind(".dzr") != -1 + room_stage = False + if not room_file: + room_stage = file.find("room") != -1 + data = None + with open(file, "rb") as f: + data = f.read() + print(f"Testing {file}") + out = "" + data2 = bytearray() + try: + print(" Extract:") + out = extract(data, room_stage, room_file) + print(" Package:") + data2 = package(json.loads(out), room_stage, room_file) + if data != data2: + print("Diff Found") + raise Exception + except Exception: + print("Dumping") + ext = ".dzs" if not room_file else ".dzr" + with open("in" + ext, "wb") as f: + f.write(data) + with open("out.json", "w") as f: + f.write(out) + with open("out" + ext, "wb") as f: + f.write(data2) + print(traceback.format_exc()) + return + print("Done!") + elif argv[2].rfind(".json") == -1: + chunk_table = None + with open(argv[1], "r") as f: + chunk_table = json.load(f) + out = None + room_file = argv[2].rfind(".dzr") != -1 + room_stage = False + if not room_file: + room_stage = argv[2].find("room") != -1 + out = package(chunk_table, room_stage, room_file) + with open(argv[2], "wb") as f: + f.write(out) + else: + data = None + with open(argv[1], "rb") as f: + data = f.read() + out = None + room_file = argv[1].rfind(".dzr") != -1 + room_stage = False + if not room_file: + room_stage = argv[1].find("room") != -1 + out = extract(data, room_stage, room_file) + with open(argv[2], "w") as f: + f.write(out) + + +if __name__ == "__main__": + main() diff --git a/tools/package_game_assets.py b/tools/package_game_assets.py index 677c5b8903..58fb190232 100644 --- a/tools/package_game_assets.py +++ b/tools/package_game_assets.py @@ -5,6 +5,7 @@ import extract_game_assets from pathlib import Path import libyaz0 import libarc +import libstage from datetime import datetime @@ -20,37 +21,48 @@ def getMaxDateFromDir(path): convertDefinitions = [ { - "sourceExtension": ".arc", + "sourceExtension": "arc", "destExtension": ".arc", "convertFunction": libarc.convert_dir_to_arc, "exceptions": ["game/files/res/Object/HomeBtn.c.arc/archive/dat/speakerse.arc"], + }, + { + "sourceExtension": "dzs.json", + "destExtension": ".dzs", + "convertFunction": libstage.package_from_json, + }, + { + "sourceExtension": "dzr.json", + "destExtension": ".dzr", + "convertFunction": libstage.package_from_json, } ] yaz0CompressFunction = libyaz0.compress def convertEntry(file, path, destPath, returnData): - split = os.path.splitext(file) + split = str(file).split(".") mustBeCompressed = False - destFileName = file - if split[0].split(".")[-1] == "c": - destFileName = split[0][0:-2] + split[-1] + destFileName = str(file) + if len(split)>1 and split[1] == "c": + destFileName = ".".join([split[0]] + split[2:]) mustBeCompressed = True - sourceExtension = split[-1] + # print(destFileName) + sourceExtension = ".".join(destFileName.split(".")[1:]) data = None extractDef = None for extractData in convertDefinitions: if sourceExtension == extractData["sourceExtension"]: extractDef = extractData - if extractData["exceptions"] != None: + if "exceptions" in extractData: for exception in extractData["exceptions"]: if str(path / file) == exception: extractDef = None break if extractDef != None: - destFileName = os.path.splitext(destFileName)[0] + extractDef["destExtension"] + destFileName = destFileName.split(".")[0] + extractDef["destExtension"] targetTime = 0 if destPath != None and os.path.exists(destPath / destFileName):