From 5f03062f424cb8bbf77c60e95ed52d35e50e2519 Mon Sep 17 00:00:00 2001 From: l-proger Date: Tue, 13 May 2025 02:59:41 +0300 Subject: [PATCH] * --- FireTools/.gitignore | 3 + FireTools/Dds.cs | 102 +++++++++++ FireTools/FireTools.csproj | 16 ++ FireTools/FireTools.sln | 25 +++ FireTools/FireTools.sln.DotSettings | 2 + FireTools/FourCC.cs | 35 ++++ FireTools/LICENSE | 21 +++ FireTools/MemoryUtils.cs | 24 +++ FireTools/Pak.cs | 266 ++++++++++++++++++++++++++++ FireTools/Pak.sln | 25 +++ FireTools/Phyre/Archive.cs | 136 ++++++++++++++ FireTools/Phyre/ClassInstance.cs | 18 ++ FireTools/Phyre/ObjectsTable.cs | 180 +++++++++++++++++++ FireTools/Program.cs | 225 +++++++++++++++++++++++ FireTools/README.md | 1 + FireTools/SpaceFillingCurve.cs | 35 ++++ FireTools/StreamExtensions.cs | 85 +++++++++ FireTools/TextureSwizzling.cs | 50 ++++++ 18 files changed, 1249 insertions(+) create mode 100644 FireTools/.gitignore create mode 100644 FireTools/Dds.cs create mode 100644 FireTools/FireTools.csproj create mode 100644 FireTools/FireTools.sln create mode 100644 FireTools/FireTools.sln.DotSettings create mode 100644 FireTools/FourCC.cs create mode 100644 FireTools/LICENSE create mode 100644 FireTools/MemoryUtils.cs create mode 100644 FireTools/Pak.cs create mode 100644 FireTools/Pak.sln create mode 100644 FireTools/Phyre/Archive.cs create mode 100644 FireTools/Phyre/ClassInstance.cs create mode 100644 FireTools/Phyre/ObjectsTable.cs create mode 100644 FireTools/Program.cs create mode 100644 FireTools/README.md create mode 100644 FireTools/SpaceFillingCurve.cs create mode 100644 FireTools/StreamExtensions.cs create mode 100644 FireTools/TextureSwizzling.cs diff --git a/FireTools/.gitignore b/FireTools/.gitignore new file mode 100644 index 0000000..7a848e2 --- /dev/null +++ b/FireTools/.gitignore @@ -0,0 +1,3 @@ +/bin +/obj +/.vs diff --git a/FireTools/Dds.cs b/FireTools/Dds.cs new file mode 100644 index 0000000..02a8319 --- /dev/null +++ b/FireTools/Dds.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace FireTools { + public static class Dds { + + public struct DdsMagic + { + public uint fourCC; + public bool IsValid => fourCC == Make().fourCC; + + public static DdsMagic Make() + { + return new DdsMagic {fourCC = 0x20534444}; + } + } + + + [Flags] + public enum DDS_PIXELFORMAT_FLAGS : uint { + DDS_FOURCC = 0x00000004, // DDPF_FOURCC + DDS_RGB = 0x00000040, // DDPF_RGB + DDS_RGBA = 0x00000041, // DDPF_RGB | DDPF_ALPHAPIXELS + DDS_LUMINANCE = 0x00020000, // DDPF_LUMINANCE + DDS_LUMINANCEA = 0x00020001, // DDPF_LUMINANCE | DDPF_ALPHAPIXELS + DDS_ALPHAPIXELS = 0x00000001, // DDPF_ALPHAPIXELS + DDS_ALPHA = 0x00000002, // DDPF_ALPHA + DDS_PAL8 = 0x00000020, // DDPF_PALETTEINDEXED8 + DDS_PAL8A = 0x00000021, // DDPF_PALETTEINDEXED8 | DDPF_ALPHAPIXELS + DDS_BUMPDUDV = 0x00080000 // DDPF_BUMPDUDV + } + + public struct DDS_PIXELFORMAT { + public uint size; + public DDS_PIXELFORMAT_FLAGS flags; + public FourCC fourCC; + public uint RGBBitCount; + public uint RBitMask; + public uint GBitMask; + public uint BBitMask; + public uint ABitMask; + + public static DDS_PIXELFORMAT make(DDS_PIXELFORMAT_FLAGS flags, + FourCC fourCC, uint RGBBitCount, uint RBitMask, + uint GBitMask, uint BBitMask, uint ABitMask) { + + DDS_PIXELFORMAT result = new DDS_PIXELFORMAT(); + result.size = (uint)Marshal.SizeOf(); + result.flags = flags; + result.fourCC = fourCC; + result.RGBBitCount = RGBBitCount; + result.RBitMask = RBitMask; + result.GBitMask = GBitMask; + result.BBitMask = BBitMask; + result.ABitMask = ABitMask; + return result; + } + + public static DDS_PIXELFORMAT DXT1 => make(DDS_PIXELFORMAT_FLAGS.DDS_FOURCC, FourCC.Make('D', 'X', 'T', '1'), 0, 0, 0, 0, 0); + public static DDS_PIXELFORMAT BC4_UNORM => make(DDS_PIXELFORMAT_FLAGS.DDS_FOURCC, FourCC.Make('B', 'C', '4', 'U'), 0, 0, 0, 0, 0); + } + + [Flags] + public enum DdsHeaderFlags : uint + { + DDSD_CAPS = 0x1, + DDSD_HEIGHT = 0x2, + DDSD_WIDTH = 0x4, + DDSD_PITCH = 0x8, + DDSD_PIXELFORMAT = 0x1000, + DDSD_MIPMAPCOUNT = 0x20000, + DDSD_LINEARSIZE = 0x80000, + DDSD_DEPTH = 0x800000 + } + + + + public struct DDS_HEADER { + public uint size; + public DdsHeaderFlags flags; + public uint height; + public uint width; + public uint pitchOrLinearSize; + public uint depth; // only if DDS_HEADER_FLAGS_VOLUME is set in flags + public uint mipMapCount; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)] + public uint[] reserved1; + + public DDS_PIXELFORMAT ddspf; + public uint caps; + public uint caps2; + public uint caps3; + public uint caps4; + public uint reserved2; + } + + + } +} diff --git a/FireTools/FireTools.csproj b/FireTools/FireTools.csproj new file mode 100644 index 0000000..0581f1a --- /dev/null +++ b/FireTools/FireTools.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp3.1 + + + + true + + + + true + + + diff --git a/FireTools/FireTools.sln b/FireTools/FireTools.sln new file mode 100644 index 0000000..60d8b80 --- /dev/null +++ b/FireTools/FireTools.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30907.101 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FireTools", "FireTools.csproj", "{6B9F9F80-66A0-4FFB-A04F-3825E7FC9AE4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6B9F9F80-66A0-4FFB-A04F-3825E7FC9AE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B9F9F80-66A0-4FFB-A04F-3825E7FC9AE4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6B9F9F80-66A0-4FFB-A04F-3825E7FC9AE4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6B9F9F80-66A0-4FFB-A04F-3825E7FC9AE4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {280C51FA-6E1E-44CC-8D48-808908716837} + EndGlobalSection +EndGlobal diff --git a/FireTools/FireTools.sln.DotSettings b/FireTools/FireTools.sln.DotSettings new file mode 100644 index 0000000..3e42f03 --- /dev/null +++ b/FireTools/FireTools.sln.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/FireTools/FourCC.cs b/FireTools/FourCC.cs new file mode 100644 index 0000000..2dd662a --- /dev/null +++ b/FireTools/FourCC.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace FireTools { + [StructLayout(LayoutKind.Sequential)] + public struct FourCC { + [MarshalAs(UnmanagedType.U1)] + public char c0; + [MarshalAs(UnmanagedType.U1)] + public char c1; + [MarshalAs(UnmanagedType.U1)] + public char c2; + [MarshalAs(UnmanagedType.U1)] + public char c3; + + public uint Value + { + get + { + return (uint)(c0 | c1 << 8 | c2 << 16 | c3 << 24); + } + } + + public override string ToString() + { + return new string(new char[]{c0, c1, c2, c3}); + } + + public static FourCC Make(char ch0, char ch1, char ch2, char ch3) { + return new FourCC { c0 = ch0, c1 = ch1, c2 = ch2, c3 = ch3 }; + } + } +} diff --git a/FireTools/LICENSE b/FireTools/LICENSE new file mode 100644 index 0000000..e8789f2 --- /dev/null +++ b/FireTools/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Sergey Serb + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/FireTools/MemoryUtils.cs b/FireTools/MemoryUtils.cs new file mode 100644 index 0000000..58910b2 --- /dev/null +++ b/FireTools/MemoryUtils.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace FireTools { + public static class MemoryUtils { + public static byte[] StructToBytes(T value) where T : struct + { + byte[] result = new byte[Marshal.SizeOf()]; + var handle = GCHandle.Alloc(result, GCHandleType.Pinned); + Marshal.StructureToPtr(value, handle.AddrOfPinnedObject(), false); + handle.Free(); + return result; + } + + public static T BytesToStruct(byte[] data) where T : struct { + var handle = GCHandle.Alloc(data, GCHandleType.Pinned); + var result = Marshal.PtrToStructure( handle.AddrOfPinnedObject()); + handle.Free(); + return result; + } + } +} diff --git a/FireTools/Pak.cs b/FireTools/Pak.cs new file mode 100644 index 0000000..1a87d53 --- /dev/null +++ b/FireTools/Pak.cs @@ -0,0 +1,266 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace FireTools { + + + + namespace Native + { + public struct Header { + public struct PageDesc { + public uint offset; + public uint size; + public uint index; + }; + public struct SomeRecordTableDesc { + public uint unk3; + public uint someRecordsOffset; + public uint someRecordsCount; + }; + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BaseInfo { + public ushort magic; + public ushort someType; + public uint totalHeaderSize; + public uint loginTableFilePageId; + public uint loginTableFileOffset; + public uint pagesCount; + public uint pageTableOffset; + public uint someConstantValue; // always == 8 + public uint someRecordTableDescOffset; // always == 8 + public uint someAttachedDataSize; //bottom of pak file (after last page) + + public uint vramDescTableFilePageId; + public uint vramDescTableFileOffset; + } + + public BaseInfo baseInfo; + + public PageDesc[] pageTable; + + public SomeRecordTableDesc someRecordTableDesc; + + public byte[] someZeroData; + + public struct SomeRecord { + public ushort w0; + public ushort w1; + public uint v1; + }; + + public SomeRecord[] someRecords; + } + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FileHeader { + public uint fileNameOffset; + public uint alwaysZeroValue; + public uint fileClassNameOffset; + public uint u3; + public uint fileSize; + public uint dummy0; //0x12345 + public uint dummy1; //0x12345 + public uint dummy2; //0x12345 + } + } + + + public class Page + { + public PakFileInput pakFile; + public uint id; + public class File + { + public PakFileInput Pak => page.pakFile; + public Page page; + public uint id; + public Native.FileHeader header; + public uint Size => header.fileSize; + public uint LocalOffset => page.fileTable[id].fileOffset; + public uint GlobalOffset => LocalOffset + page.Offset; + + public uint LocalDataOffset => (uint)(page.fileTable[id].fileOffset + Marshal.SizeOf()); + public uint GlobalDataOffset => LocalDataOffset + page.Offset; + + public string name; + public string className; + } + public int FilesCount => baseInfo.filesTableHeader.filesCount; + + public uint Offset => pakFile.header.pageTable[id].offset; + public uint Size => pakFile.header.pageTable[id].size; + + public long TotalFilesSize { + get { + return files.Select(v => (long)v.header.fileSize).Sum(); + } + } + + + public Native.Header.PageDesc desc; + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BaseInfo { + public struct FilesTableHeader { + public ushort unk0; + public ushort filesCount; + } + + public uint magic0; //0xDEADBEEF + public uint magic1; //0xDEADBEEF + + public uint unk0; + public uint fullPageSize; + + public FilesTableHeader filesTableHeader; + } + + public BaseInfo baseInfo; + + + + public struct FileTableRecord { + public uint fileNameOffset; + public uint alwaysZeroValue; + public uint fileOffset; + public uint dummy; + } + + public FileTableRecord[] fileTable; + public File[] files; + } + + public class VRamDescFile { + public struct Header { + public uint u0; + public uint u1; + public uint maybeOffset; + public uint u3; + public uint maybeLength; + public ushort h0; + public ushort h1; + public ushort h2; + public ushort h3; + public uint maybeFormat; + + public ushort h6; + public ushort h7; + public uint mipCount; + public uint width; + public uint height; + public uint u12; + public uint u13; + public uint u14; + public uint u15; + } + + public Header header; + public string fileName; + public Page.File file; + + public static VRamDescFile Read(Stream stream, Page.File file) { + var result = new VRamDescFile(); + result.file = file; + stream.Seek(file.GlobalDataOffset, SeekOrigin.Begin); + stream.ReadValue(out result.header); + result.fileName = stream.ReadString(); + return result; + } + + public byte[] ReadRawData(Stream stm) + { + stm.Seek((long)stm.Length - file.Pak.RawDataSize + header.maybeOffset, SeekOrigin.Begin); + return stm.ReadBytes((int)header.maybeLength); + } + } + + + public class PakFileInput { + public string sourceName; + public long realFullSize; + public Native.Header header; + public Page[] pages; + + public uint RawDataSize => header.baseInfo.someAttachedDataSize; + public int TotalFilesCount { + get { + return pages.Select(v => v.files.Length).Sum(); + } + } + + public PakFileInput(Stream stm, string sourceName) { + this.sourceName = sourceName; + realFullSize = stm.Length; + stm.Seek(0, SeekOrigin.Begin); + + ParseHeader(stm); + + pages = new Page[header.baseInfo.pagesCount]; + for (uint i = 0; i < pages.Length; ++i) { + pages[i] = ParsePage(stm, i); + } + } + + private Page ParsePage(Stream stm, uint id) { + Page result = new Page(); + result.id = id; + result.pakFile = this; + + var pageDesc = header.pageTable[id]; + result.desc = pageDesc; + + stm.Seek(pageDesc.offset, SeekOrigin.Begin); + + stm.ReadValue(out result.baseInfo); + + result.fileTable = new Page.FileTableRecord[result.FilesCount]; + for (uint i = 0; i < result.FilesCount; ++i) { + stm.ReadValue(out result.fileTable[i]); + } + + result.files = new Page.File[result.FilesCount]; + for (uint i = 0; i < result.FilesCount; ++i) { + result.files[i] = new Page.File(); + result.files[i].id = i; + result.files[i].page = result; + stm.Seek(pageDesc.offset + result.fileTable[i].fileOffset, SeekOrigin.Begin); + stm.ReadValue(out result.files[i].header); + + stm.Seek(pageDesc.offset + result.files[i].header.fileNameOffset, SeekOrigin.Begin); + result.files[i].name = stm.ReadString(); + stm.Seek(pageDesc.offset + result.files[i].header.fileClassNameOffset, SeekOrigin.Begin); + result.files[i].className = stm.ReadString(); + } + return result; + } + + + private void ParseHeader(Stream stm) { + stm.ReadValue(out header.baseInfo); + + header.pageTable = new Native.Header.PageDesc[header.baseInfo.pagesCount]; + for (uint i = 0; i < header.baseInfo.pagesCount; ++i) { + stm.ReadValue(out header.pageTable[i]); + } + + stm.Seek(header.baseInfo.someRecordTableDescOffset, SeekOrigin.Begin); + stm.ReadValue(out header.someRecordTableDesc); + + var someZeroDataSize = (int)(header.someRecordTableDesc.someRecordsOffset - stm.Position); + header.someZeroData = stm.ReadBytes(someZeroDataSize); + + if (header.someRecordTableDesc.someRecordsCount != 0) { + header.someRecords = new Native.Header.SomeRecord[header.someRecordTableDesc.someRecordsCount]; + for (uint i = 0; i < header.someRecords.Length; ++i) { + stm.ReadValue(out header.someRecords[i]); + } + } else { + header.someRecords = new Native.Header.SomeRecord[0]; + } + } + } +} diff --git a/FireTools/Pak.sln b/FireTools/Pak.sln new file mode 100644 index 0000000..41c4a0f --- /dev/null +++ b/FireTools/Pak.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30804.86 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssetsProcessing", "AssetsProcessing.csproj", "{9F8773F6-C1A2-48C4-8468-06A099828A9B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9F8773F6-C1A2-48C4-8468-06A099828A9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F8773F6-C1A2-48C4-8468-06A099828A9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F8773F6-C1A2-48C4-8468-06A099828A9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F8773F6-C1A2-48C4-8468-06A099828A9B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6F7FE027-76D1-4565-B7D6-4F24B4CEA99C} + EndGlobalSection +EndGlobal diff --git a/FireTools/Phyre/Archive.cs b/FireTools/Phyre/Archive.cs new file mode 100644 index 0000000..131aa57 --- /dev/null +++ b/FireTools/Phyre/Archive.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace FireTools.Phyre { + public class Archive { + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BaseHeader { + public FourCC magic; + public uint size; + public uint objectsTableSize; + public FourCC platformId; + public uint instanceListCount; + public uint arrayFixupSize; + public uint arrayFixupCount; + public uint pointerFixupSize; + public uint pointerFixupCount; + public uint pointerArrayFixupSize; + public uint pointerArrayFixupCount; + public uint pointersInArraysCount; + public uint userFixupCount; + public uint userFixupDataSize; + public uint totalDataSize; + public uint headerClassInstanceCount; + public uint headerClassChildCount; + public uint physicsEngineID; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DX11Header { + public BaseHeader baseHeader; + public uint indexBufferSize; + public uint vertexBufferSize; + public uint maxTextureMipBufferSize; + } + + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PInstanceListHeader + { + public uint m_classID; // The ID of the class stored in this instance list. + public uint m_count; // The number of instances of the class stored in this instance list. + public uint m_size; // The size of the data for this instance list in bytes. This should be equal to m_objectsSize + m_arraysSize; + public uint m_objectsSize; // The size of the data for the objects in this instance list in bytes. Also includes the padding to make sure that the array data is correctly aligned. + public uint m_arraysSize; // The size of the non-object array data in this instance list in bytes + public uint m_pointersInArraysCount; // The number of pointers in pointer arrays. + public uint m_arrayFixupCount; // The count of array fixups required. + public uint m_pointerFixupCount; // The count of pointer fixups required. + public uint m_pointerArrayFixupCount; // The count of pointer array fixups required. + }; + + + public static PInstanceListHeader ReadInstanceListHeader(Stream data) + { + return data.ReadStruct(); + } + + public static object ReadHeader(Stream data) + { + var baseHeader = data.ReadStruct(); + var baseSize = Marshal.SizeOf(); + if (baseHeader.size < baseSize) + { + throw new Exception("Unexpected header size"); + } + + if (baseHeader.size == baseSize) + { + return baseHeader; + } + + if (baseHeader.platformId.Value == 0x44583131) + { + //dx11 platform + var dx11Size = Marshal.SizeOf(); + var extraBytesCount = dx11Size - baseSize; + var extraBytes = data.ReadBytes(extraBytesCount); + var baseBytes = MemoryUtils.StructToBytes(baseHeader); + List dx11Bytes = new List(); + dx11Bytes.AddRange(baseBytes); + dx11Bytes.AddRange(extraBytes); + return MemoryUtils.BytesToStruct(dx11Bytes.ToArray()); + + } + throw new Exception("Unknown platform"); + } + + public static ObjectsTable ReadObjectsTable( Stream data) + { + /*var baseArcHeader = archiveHeader is BaseHeader + ? (BaseHeader) archiveHeader + : ((DX11Header) archiveHeader).baseHeader;*/ + ObjectsTable result = new ObjectsTable(); + result.header = data.ReadStruct(); + result.typeDescriptors = data.ReadStructArray(result.header.typeCount).ToArray(); + result.classDescriptors = data.ReadStructArray(result.header.classCount).ToArray(); + result.classMemberDescriptors = data.ReadStructArray(result.header.classMemberCount).ToArray(); + result.stringTable = data.ReadBytes((int)result.header.stringTableSize); + + var s0 = Marshal.SizeOf(); + var s1 = Marshal.SizeOf() * result.header.typeCount; + var s2 = Marshal.SizeOf() * result.header.classCount; + var s3 = Marshal.SizeOf() * result.header.classMemberCount; + var s4 = result.header.stringTableSize; + + var paddingSize = result.header.size - (s0 + s1 + s2 + s3 + s4); + if (paddingSize != 0) + { + data.ReadBytes((int)paddingSize); + } + return result; + } + + + /*SetBackColor(0x0000ff); + Header header; + SetBackColor(0x00ff00); + PackedNamespaceHeader namespaceHeader; + SetBackColor(0xff0000); + + PPackedType namespaceTypes[namespaceHeader.m_typeCount]; + PPackedClassDescriptor namespaceClassDescriptors[namespaceHeader.m_classCount]; + PPackedDataMember namespaceDataMembers[namespaceHeader.m_classDataMemberCount]; + + CHAR namespaceStringTable[namespaceHeader.m_stringTableSize];*/ + + + //PChar* string table + //PUInt8 * default buffers + + + } +} diff --git a/FireTools/Phyre/ClassInstance.cs b/FireTools/Phyre/ClassInstance.cs new file mode 100644 index 0000000..83b88e9 --- /dev/null +++ b/FireTools/Phyre/ClassInstance.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace FireTools.Phyre { + public class FieldInstance + { + public ObjectsTable.ClassMemberDescriptor descriptor; + public object value; + } + + public class ClassInstance + { + public ClassInstance parentInstance; + public ObjectsTable.Class thisClass; + + } +} diff --git a/FireTools/Phyre/ObjectsTable.cs b/FireTools/Phyre/ObjectsTable.cs new file mode 100644 index 0000000..1362301 --- /dev/null +++ b/FireTools/Phyre/ObjectsTable.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace FireTools.Phyre { + public class ObjectsTable { + public struct Header { + public uint magic; + public uint size; + public uint typeCount; + public uint classCount; + public uint classMemberCount; + public uint stringTableSize; + public uint someBufferCount; + public uint someBufferSize; + } + + public struct TypeDescriptor { + public uint nameOffset; + } + + public struct ClassDescriptor { + public int baseClassID; // 0 implies not set, positive implies local, negative implies global. + public uint sizeInBytesAndAlignment; // The size of this class in bytes with the alignment shift in the top 4 bits. + public uint nameOffset; // The offset to the name of this class in the string table. + public uint classDataMemberCount; // The number of data members in this class. + public uint offsetFromParent; // The offset of the start of this class from the start of the parent class. + public uint offsetToBase; // The offset of the start of this class from the start of the PBase class. + public uint offsetToBaseInAllocatedBlock; // The offset of the start of the PBase class in a block allocated for this class. + public uint flags; // The flags for this class. + public uint defaultBufferOffset; // 0 implies not saved. Subtract one to get the correct default buffer offset if non-zero. + } + + public struct ClassMemberDescriptor { + public uint nameOffset; // The offset to the name of this member in the string table. + public uint typeID; // The type of this member + public uint valueOffset; // The offset to the value of this member in the class. + public uint sizeInBytes; // The size of this member in bytes. + public uint flags; // The flags for this member. + public uint fixedArraySize; // If non zero, this represents the size of a fixed size array. + } + + public Header header; + public TypeDescriptor[] typeDescriptors; + public ClassDescriptor[] classDescriptors; + public ClassMemberDescriptor[] classMemberDescriptors; + public byte[] stringTable; + + public class ClassMember + { + public Type fieldType; + public string name; + public uint offset; + public uint size; + public uint flags; + public uint fixedArraySize; + + public override string ToString() + { + return name; + } + } + + + public class Type + { + public string name; + public override string ToString() + { + return name; + } + } + + public class Class : Type + { + public Class baseClass; + public List members = new List(); + + public override string ToString() + { + var baseStr = baseClass == null ? "" : $" Base: {baseClass.name}"; + return $"Name: {name} Members: {members.Count} Local members size: {members.Sum(v=>v.size)} {baseStr}"; + } + } + + public class ExternalClass : Class { + public ExternalClass() + { + name = "ExternalClass"; + } + } + + public Type[] Types; + public Class[] Classes; + + + public void ParseTypes() + { + Types = new Type[typeDescriptors.Length]; + + for (uint i = 0; i < Types.Length; ++i) { + Types[i] = new Type(); + Types[i].name = ReadString((int)typeDescriptors[i].nameOffset); + } + } + + public void ParseClasses() { + Classes = new Class[classDescriptors.Length]; + for (uint m = 0; m < Classes.Length; ++m) + { + Classes[m] = new Class(); + } + + + int memberId = 0; + + for (uint i = 0; i < Classes.Length; ++i) + { + var c = classDescriptors[i]; + var cls = Classes[i]; + + cls.name = ReadString((int) c.nameOffset); + + + if (c.baseClassID < 0) + { + cls.baseClass = new ExternalClass(); + }else if (c.baseClassID > 0) + { + cls.baseClass = Classes[c.baseClassID - 1 ]; + } + + for (uint m = 0; m < c.classDataMemberCount; ++m) + { + var d = classMemberDescriptors[memberId++]; + ClassMember mem = new ClassMember(); + mem.size = d.sizeInBytes; + mem.fixedArraySize = d.fixedArraySize; + mem.flags = d.flags; + mem.offset = d.valueOffset; + mem.name = ReadString((int)d.nameOffset); + + Console.WriteLine("Member type ID: " + d.typeID); + + if (d.typeID >= Types.Length) { + mem.fieldType = Classes[(int)d.typeID - Types.Length - 1]; + } else { + mem.fieldType = Types[(int)d.typeID]; + } + + + + + + cls.members.Add(mem); + } + + + } + } + + public void Unpack() + { + ParseTypes(); + ParseClasses(); + } + + public string ReadString(int offset) + { + var handle = GCHandle.Alloc(stringTable, GCHandleType.Pinned); + var result = Marshal.PtrToStringAnsi(handle.AddrOfPinnedObject() + offset); + handle.Free(); + return result; + } + + + } +} diff --git a/FireTools/Program.cs b/FireTools/Program.cs new file mode 100644 index 0000000..eea1fc6 --- /dev/null +++ b/FireTools/Program.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Mime; +using System.Runtime.InteropServices; +using System.Text; +using FireTools.Phyre; + +namespace FireTools { + + class Program { + static void PrintPak(PakFileInput pak) + { + Console.WriteLine("Path: " + pak.sourceName); + Console.WriteLine("Page count: " + pak.pages.Length); + for (int i = 0; i < pak.pages.Length; ++i) + { + var page = pak.pages[i]; + Console.WriteLine(" Files count: " + page.FilesCount); + Console.WriteLine(" Total files size: " + page.TotalFilesSize); + for (int j = 0; j < page.files.Length; ++j) + { + var file = page.files[j]; + Console.WriteLine($" File size: {file.header.fileSize}\tname: {file.name} \tclass name: { file.className}"); + } + } + } + + static byte[] UnswizzleTexture(byte[] data, uint width, uint height, uint mipCount) + { + List result = new List(); + + uint offset = 0; + for (uint i = 0; i < mipCount; ++i) + { + var rowPitch = width * 2; + var slicePitch = (width * height) / 2; + + if (width < 4 || height < 4) + { + rowPitch = 8; + slicePitch = 8; + } + + var mipDataSegment = new ArraySegment(data, (int) offset, (int) slicePitch); + var mipData = mipDataSegment.ToArray(); + + var unswizzledMipData = TextureSwizzling.UnSwizzle(mipData, (int)width, (int)height, 8); + result.AddRange(unswizzledMipData); + + + width /= 2; + height /= 2; + + offset += slicePitch; + } + + return result.ToArray(); + } + + static void ExportDdsTexture(Stream stm, VRamDescFile texture) + { + if (texture.header.width == texture.header.height) + { + return; + } + Dds.DDS_HEADER header = new Dds.DDS_HEADER(); + header.flags = Dds.DdsHeaderFlags.DDSD_CAPS | Dds.DdsHeaderFlags.DDSD_HEIGHT | + Dds.DdsHeaderFlags.DDSD_WIDTH | Dds.DdsHeaderFlags.DDSD_PIXELFORMAT | + Dds.DdsHeaderFlags.DDSD_MIPMAPCOUNT | Dds.DdsHeaderFlags.DDSD_LINEARSIZE; + + + + + + var realFilePath = Path.GetDirectoryName(texture.fileName); + var fileName = Path.GetFileName(realFilePath); + + string outFileName = Path.Join("D:\\TextureExport", + fileName + + $".{texture.header.width}_{texture.header.height}_unswizzled_page{texture.file.page.id}_file{texture.file.id}_fmt{texture.header.maybeFormat}" + + ".dds"); + + Console.WriteLine("Exporting texture " + outFileName); + + var rawData = texture.ReadRawData(stm); + var unswizzledData = UnswizzleTexture(rawData, texture.header.width, texture.header.height, texture.header.mipCount); + + if (texture.header.maybeFormat == 80) { + header.ddspf = Dds.DDS_PIXELFORMAT.BC4_UNORM; + } else if (texture.header.maybeFormat == 71 || (texture.header.maybeFormat == 72)) { + header.ddspf = Dds.DDS_PIXELFORMAT.DXT1; + } else if (texture.header.maybeFormat == 35) { + header.ddspf = Dds.DDS_PIXELFORMAT.DXT1; + unswizzledData = rawData; + } else { + header.ddspf = Dds.DDS_PIXELFORMAT.DXT1; + Console.WriteLine($"unknown format {texture.header.maybeFormat} for texture {texture.fileName}"); + //return; + } + + + header.width = texture.header.width; + header.height = texture.header.height; + header.pitchOrLinearSize = (header.width * header.height) / 2; + header.depth = 1; + header.mipMapCount = texture.header.mipCount; + header.reserved1 = new uint[11]; + header.caps = 0x1000; //DDSCAPS_TEXTURE + header.size = (uint)Marshal.SizeOf(); + + + + + var ofs = File.OpenWrite(outFileName); + + + ofs.WriteStruct(Dds.DdsMagic.Make()); + + ofs.WriteStruct(header); + ofs.Write(unswizzledData); + ofs.Close(); + + Console.WriteLine("Exporting texture done"); + + } + static void ExportTextures() + { + var path = @".pak"; + var stm = File.OpenRead(path); + var p = new PakFileInput(stm, path); + + for (int i = 0; i < p.pages.Length; ++i) + { + var page = p.pages[i]; + for(int j = 0; j < page.files.Length; ++j) + { + if (page.files[j].className == "VRAM_DESC") + { + var f = VRamDescFile.Read(stm, page.files[j]); + + ExportDdsTexture(stm, f); + + Console.WriteLine("Ololo"); + } + } + } + } + + + + class ClassPrinter + { + public List printedClasses = new List(); + + public void Print(Phyre.ObjectsTable.Class c) + { + string ident = " "; + + if (printedClasses.Contains(c)) + { + return; + } + + if (c.baseClass != null) + { + Print(c.baseClass); + } + + Console.Write($"class {c.name}"); + if (c.baseClass != null) + { + + Console.Write($" : {c.baseClass.name}"); + } + Console.WriteLine($"{{"); + Console.WriteLine($"public:"); + + foreach (var classMember in c.members) + { + Console.WriteLine($"{ident}{classMember.fieldType.name} {classMember.name}; //is class: {classMember.fieldType is ObjectsTable.Class}"); + } + + Console.WriteLine($"}};"); + Console.WriteLine($""); + + printedClasses.Add(c); + } + } + + static void Main(string[] args) + { + var stm = File.OpenRead(@"D:\tex.dds.phyre"); + var header = Phyre.Archive.ReadHeader(stm); + var objectsTable = Phyre.Archive.ReadObjectsTable(stm); + objectsTable.Unpack(); + + ClassPrinter printer = new ClassPrinter(); + + foreach (var c in objectsTable.Classes) + { + printer.Print(c); + } + + var instanceListHeaders = new List(); + var instances = ((Archive.DX11Header) header).baseHeader.instanceListCount; + for (uint i = 0; i < instances; ++i) + { + var h = Phyre.Archive.ReadInstanceListHeader(stm); + instanceListHeaders.Add(h); + + Console.WriteLine($"Objects count: {h.m_count} Type: {objectsTable.Classes[h.m_classID - 1]} {h.m_objectsSize}"); + + } + + + var mems = objectsTable.classDescriptors.Sum(v => v.classDataMemberCount); + Console.WriteLine(); + + } + } +} diff --git a/FireTools/README.md b/FireTools/README.md new file mode 100644 index 0000000..f631275 --- /dev/null +++ b/FireTools/README.md @@ -0,0 +1 @@ +# FireTools \ No newline at end of file diff --git a/FireTools/SpaceFillingCurve.cs b/FireTools/SpaceFillingCurve.cs new file mode 100644 index 0000000..f18dc8c --- /dev/null +++ b/FireTools/SpaceFillingCurve.cs @@ -0,0 +1,35 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace FireTools { + public static class SpaceFillingCurve { + public static int Morton2D(int t, int sx, int sy) { + int num1; + int num2 = num1 = 1; + int num3 = t; + int num4 = sx; + int num5 = sy; + int num6 = 0; + int num7 = 0; + + while (num4 > 1 || num5 > 1) { + if (num4 > 1) { + num6 += num2 * (num3 & 1); + num3 >>= 1; + num2 *= 2; + num4 >>= 1; + } + if (num5 > 1) { + num7 += num1 * (num3 & 1); + num3 >>= 1; + num1 *= 2; + num5 >>= 1; + } + } + + return num7 * sx + num6; + } + } +} diff --git a/FireTools/StreamExtensions.cs b/FireTools/StreamExtensions.cs new file mode 100644 index 0000000..ff6f7af --- /dev/null +++ b/FireTools/StreamExtensions.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace FireTools { + public static class StreamExtensions { + + public static byte[] ReadBytes(this Stream stm, int size) { + byte[] bytes = new byte[size]; + if (stm.Read(bytes, 0, size) != size) { + throw new Exception("Failed to read stream"); + } + return bytes; + } + + + public static void WriteStruct(this Stream stm, T value) where T : struct + { + var bytes = MemoryUtils.StructToBytes(value); + stm.Write(bytes, 0, bytes.Length); + } + + public static T ReadStruct(this Stream stm) where T : struct { + byte[] bytes = new byte[Marshal.SizeOf()]; + if (stm.Read(bytes, 0, Marshal.SizeOf()) != bytes.Length) { + throw new Exception("Failed to read stream"); + } + var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + var result = Marshal.PtrToStructure(handle.AddrOfPinnedObject()); + handle.Free(); + return result; + } + + + public static List ReadStructArray(this Stream stm, uint count) where T : struct { + List result = new List((int)count); + for (int i = 0; i < count; ++i) + { + result.Add(stm.ReadStruct()); + } + return result; + } + + public static uint ReadUInt(this Stream stm) { + return ReadStruct(stm); + } + public static int ReadInt(this Stream stm) { + return ReadStruct(stm); + } + public static ushort ReadUShort(this Stream stm) { + return ReadStruct(stm); + } + public static short ReadShort(this Stream stm) { + return ReadStruct(stm); + } + + + public static string ReadString(this Stream stm) { + var result = new List(); + while (true) { + int c = stm.ReadByte(); + if (c < 0) { + throw new Exception("End of file"); + } + if (c == 0) { + break; + } + result.Add((byte)(c & 0xff)); + } + return Encoding.ASCII.GetString(result.ToArray()); + } + + + + public static void ReadValue(this Stream stm, out string result) { + result = ReadString(stm); + } + + public static void ReadValue(this Stream stm, out T result) where T : struct { + result = ReadStruct(stm); + } + } +} diff --git a/FireTools/TextureSwizzling.cs b/FireTools/TextureSwizzling.cs new file mode 100644 index 0000000..0a59e73 --- /dev/null +++ b/FireTools/TextureSwizzling.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace FireTools { + public static class TextureSwizzling { + public static byte[] Swizzle(byte[] data, int width, int height, int blockSize) { + return DoSwizzle(data, width, height, blockSize, false); + } + + public static byte[] UnSwizzle(byte[] data, int width, int height, int blockSize) { + return DoSwizzle(data, width, height, blockSize, true); + } + + private static byte[] DoSwizzle(byte[] data, int width, int height, int blockSize, bool unswizzle) { + var processed = new byte[data.Length]; + var heightTexels = height / 4; + var heightTexelsAligned = (heightTexels + 7) / 8; + int widthTexels = width / 4; + var widthTexelsAligned = (widthTexels + 7) / 8; + var dataIndex = 0; + + for (int y = 0; y < heightTexelsAligned; ++y) { + for (int x = 0; x < widthTexelsAligned; ++x) { + for (int t = 0; t < 64; ++t) { + int pixelIndex = SpaceFillingCurve.Morton2D(t, 8, 8); + int num8 = pixelIndex / 8; + int num9 = pixelIndex % 8; + var yOffset = (y * 8) + num8; + var xOffset = (x * 8) + num9; + + if (xOffset < widthTexels && yOffset < heightTexels) { + var destPixelIndex = yOffset * widthTexels + xOffset; + int destIndex = blockSize * destPixelIndex; + + if (unswizzle) + Array.Copy(data, dataIndex, processed, destIndex, blockSize); + else + Array.Copy(data, destIndex, processed, dataIndex, blockSize); + } + + dataIndex += blockSize; + } + } + } + + return processed; + } + } +}