From fd67938e0dc8159ccf8113ee296fe3a8d8629237 Mon Sep 17 00:00:00 2001 From: Scobalula Date: Fri, 16 Nov 2018 19:38:06 +0000 Subject: [PATCH] Images, optimizations, clean up --- README.md | 22 + src/Husky/Husky.sln | 6 - src/Husky/Husky/FileFormats/WavefrontOBJ.cs | 463 ++++++ src/Husky/Husky/GameStructures/Shared.cs | 240 +++ src/Husky/Husky/Games/AdvancedWarfare.cs | 324 +++- src/Husky/Husky/Games/GameStructures.cs | 803 --------- src/Husky/Husky/Games/Ghosts.cs | 325 +++- src/Husky/Husky/Games/InfiniteWarfare.cs | 222 --- src/Husky/Husky/Games/ModernWarfare2.cs | 299 +++- src/Husky/Husky/Games/ModernWarfare3.cs | 255 ++- src/Husky/Husky/Games/ModernWarfareRM.cs | 329 +++- src/Husky/Husky/Games/WorldAtWar.cs | 313 +++- src/Husky/Husky/Husky.csproj | 11 +- src/Husky/Husky/Program.cs | 1 + src/Husky/Husky/Properties/AssemblyInfo.cs | 7 +- src/Husky/Husky/Utility/Color.cs | 6 + src/Husky/Husky/Utility/Vectors.cs | 109 ++ src/Husky/Husky/Utility/Vertex.cs | 28 + src/Husky/Husky/Utility/VertexNormal.cs | 39 + src/Husky/PhilLibX/ByteUtil.cs | 22 + src/Husky/PhilLibX/IO/ProcessWriter.cs | 4 - src/Husky/SELib/Properties/AssemblyInfo.cs | 36 - src/Husky/SELib/SEAnim.cs | 1443 ----------------- src/Husky/SELib/SELib.csproj | 59 - src/Husky/SELib/SEModel.cs | 1019 ------------ .../SELib/Utilities/ExtendedBinaryReader.cs | 45 - .../SELib/Utilities/ExtendedBinaryWriter.cs | 43 - src/Husky/SELib/Utilities/ExtendedMath.cs | 118 -- 28 files changed, 2390 insertions(+), 4201 deletions(-) create mode 100644 src/Husky/Husky/FileFormats/WavefrontOBJ.cs create mode 100644 src/Husky/Husky/GameStructures/Shared.cs delete mode 100644 src/Husky/Husky/Games/GameStructures.cs delete mode 100644 src/Husky/Husky/Games/InfiniteWarfare.cs create mode 100644 src/Husky/Husky/Utility/Color.cs create mode 100644 src/Husky/Husky/Utility/Vectors.cs create mode 100644 src/Husky/Husky/Utility/Vertex.cs create mode 100644 src/Husky/Husky/Utility/VertexNormal.cs delete mode 100644 src/Husky/SELib/Properties/AssemblyInfo.cs delete mode 100644 src/Husky/SELib/SEAnim.cs delete mode 100644 src/Husky/SELib/SELib.csproj delete mode 100644 src/Husky/SELib/SEModel.cs delete mode 100644 src/Husky/SELib/Utilities/ExtendedBinaryReader.cs delete mode 100644 src/Husky/SELib/Utilities/ExtendedBinaryWriter.cs delete mode 100644 src/Husky/SELib/Utilities/ExtendedMath.cs diff --git a/README.md b/README.md index 6c6136a..df886b5 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,14 @@ To download Husky, go to the [Releases](https://github.com/Scobalula/Husky/relea To use Husky, simply run the game, load the map you want to extract, and run Husky. +Once the map is exported, you will have 3 files for it: + +* **mapname**.obj - Main 3D Obj File +* **mapname**.mtl - Material Info +* **mapname**.txt - A search string for Wraith/Greyhound (only contains color maps) + +If you wish to use textures (be warned they can result in high RAM usage) then make sure to have the _images folder in the same location as the obj/mtl file and export PNGs (do not ask for other formats, it's staying as PNG, do a find/replace if you want to use other formats). + ### License/Disclaimers Husky is licensed under the GPL license and it and its source code is free to use and modify under the. Husky comes with NO warranty, any damages caused are solely the responsibility of the user. See the LICENSE file for more information. @@ -26,6 +34,20 @@ Some of the exported models can get pretty big. While all have loaded in Maya wi **Husky is currently in alpha, and with that in mind, bugs, errors, you know, the bad stuff.** +## FAQ + +* Q: Husky says it cannot find my game? + +* A: Check the above list for a supported game, when searching for a supported game, Husky loops over all processes and stops at the first match, if it's not finding your game and it is supported, your exe is not the same name as what Husky expects or something is blocking .NET from returning its process. + +* Q: Husky says my game is not supported? + +* A: I verify the addresses on legitimate up to date copies of the supported games in the English locale. If you're using a modified executable (Pirated, etc.) I will not support it. + +* Q: The exported OBJ is corrupt when imported? + +* A: Tons of BSPs across all supported games have been verified, if you have find a legitimate instance of a corrupt export, please open an issue with the name of the map, etc. as much info as you can. + ## Credits * DTZxPorter - Normal Unpacking Code from Wraith + Other misc info from Wraith, SELib diff --git a/src/Husky/Husky.sln b/src/Husky/Husky.sln index c121a03..30ebd61 100644 --- a/src/Husky/Husky.sln +++ b/src/Husky/Husky.sln @@ -7,8 +7,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Husky", "Husky\Husky.csproj EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhilLibX", "PhilLibX\PhilLibX.csproj", "{8F5C1BA4-88C1-4177-B91B-DD093DC849B9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SELib", "SELib\SELib.csproj", "{E03DA150-718D-4EA9-AFCA-D3E17A7DF3E8}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -23,10 +21,6 @@ Global {8F5C1BA4-88C1-4177-B91B-DD093DC849B9}.Debug|Any CPU.Build.0 = Debug|Any CPU {8F5C1BA4-88C1-4177-B91B-DD093DC849B9}.Release|Any CPU.ActiveCfg = Release|Any CPU {8F5C1BA4-88C1-4177-B91B-DD093DC849B9}.Release|Any CPU.Build.0 = Release|Any CPU - {E03DA150-718D-4EA9-AFCA-D3E17A7DF3E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E03DA150-718D-4EA9-AFCA-D3E17A7DF3E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E03DA150-718D-4EA9-AFCA-D3E17A7DF3E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E03DA150-718D-4EA9-AFCA-D3E17A7DF3E8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Husky/Husky/FileFormats/WavefrontOBJ.cs b/src/Husky/Husky/FileFormats/WavefrontOBJ.cs new file mode 100644 index 0000000..7891a4b --- /dev/null +++ b/src/Husky/Husky/FileFormats/WavefrontOBJ.cs @@ -0,0 +1,463 @@ +// ------------------------------------------------------------------------ +// Husky - Call of Duty BSP Extractor +// Copyright (C) 2018 Philip/Scobalula +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ------------------------------------------------------------------------ +// TODO: Fix up a some code such as newly added material shite +using System; +using System.IO; +using System.Collections.Generic; + +namespace Husky +{ + /// + /// OBJ Exception Class + /// + public class OBJReadException : Exception + { + /// + /// Initializes OBJ Read Exception + /// + public OBJReadException() { } + + /// + /// Initializes OBJ Read Exception with a Message + /// + /// Exception Message + public OBJReadException(string message) : base(message) { } + + /// + /// Initializes OBJ Read Exception with Message and Inner Exception + /// + /// Exception Message + /// Inner Exception + public OBJReadException(string message, Exception inner) : base(message, inner) { } + } + + /// + /// Wavefront OBJ Model Class + /// + public class WavefrontOBJ + { + /// + /// OBJ Material Class + /// + public class Material + { + /// + /// Material Name + /// + public string Name { get; set; } + + /// + /// Diffuse/Color Map + /// + public string DiffuseMap { get; set; } + + /// + /// Normal/Bump Map + /// + public string NormalMap { get; set; } + + /// + /// Specular Map + /// + public string SpecularMap { get; set; } + + /// + /// Initializes an OBJ Material with a Name + /// + /// Material Name + public Material(string name) + { + Name = name; + } + } + + /// + /// OBJ Polygon Face Class + /// + public class Face + { + /// + /// Face Vertex with Vert, Normal and UV index + /// + public class Vertex + { + /// + /// Vertex Index + /// + public int VertexIndex { get; set; } + + /// + /// Normal Index + /// + public int NormalIndex { get; set; } + + /// + /// UV Index + /// + public int UVIndex { get; set; } + + /// + /// Initializes Face Vertex with Indices + /// + /// Vertex Index + /// Normal Index + /// UV Index + public Vertex(int vertexIndex, int normalIndex, int uvIndex) + { + VertexIndex = vertexIndex; + NormalIndex = normalIndex; + UVIndex = uvIndex; + } + } + + /// + /// Material Index + /// + public string MaterialName { get; set; } + + /// + /// Face Vertex Points + /// + public Vertex[] Vertices = new Vertex[3]; + + /// + /// Initializes Face with Material Index + /// + /// Material Index + public Face(string materialName) + { + MaterialName = materialName; + } + } + + /// + /// Random Float (For Material Colors) + /// + private readonly Random RandomInt = new Random(); + + /// + /// Vertex Points + /// + public List Vertices = new List(); + + /// + /// Vertex Normals + /// + public List Normals = new List(); + + /// + /// UV Texture Cooridinates + /// + public List UVs = new List(); + + /// + /// Faces + /// + public List Faces = new List(); + + /// + /// Materials + /// + public Dictionary Materials = new Dictionary(); + + /// + /// Comments + /// + public List Comments = new List(); + + /// + /// Active Material + /// + private string ActiveMaterial { get; set; } + + /// + /// Active OBJ File Line Number + /// + private long ActiveLine { get; set; } + + /// + /// Initializes OBJ + /// + public WavefrontOBJ() { } + + /// + /// Initializes OBJ and loads an OBJ file + /// + /// File path + public WavefrontOBJ(string fileName) + { + Load(fileName); + } + + /// + /// Loads an OBJ File + /// + /// File path + public void Load(string fileName) + { + ActiveLine = 0; + + using (StreamReader reader = new StreamReader(fileName)) + { + string line; + string[] lineSplit; + + while ((line = reader.ReadLine()?.Trim()) != null) + { + ActiveLine++; + + if (String.IsNullOrWhiteSpace(line)) + continue; + + if (line[0] == '#') + continue; + + lineSplit = line.Split(); + + switch (lineSplit[0]) + { + case "v": + LoadVertexPoint(lineSplit); + break; + case "vt": + LoadUVPoint(lineSplit); + break; + case "vn": + LoadNormal(lineSplit); + break; + case "f": + LoadFace(lineSplit); + break; + case "usemtl": + LoadMaterial(lineSplit); + break; + } + } + } + } + + /// + /// Loads material name from the OBJ File + /// + /// Line split with material name + private void LoadMaterial(string[] lineSplit) + { + if (lineSplit.Length < 2) + throw new OBJReadException("Hit material line but no material name present."); + + string materialName = lineSplit[1]; + + AddMaterial(new Material(materialName)); + + ActiveMaterial = materialName; + } + + /// + /// Loads a vertex point from the OBJ File + /// + /// Line split containing x,y,z as indexes 1,2,3 + private void LoadVertexPoint(string[] lineSplit) + { + if (!Single.TryParse(lineSplit[1], out float x)) + throw new OBJReadException(String.Format("Failed to parse an X Value from Vertex::{0} Line::{1}", Vertices.Count, ActiveLine)); + + if (!Single.TryParse(lineSplit[2], out float y)) + throw new OBJReadException(String.Format("Failed to parse a Y Value from Vertex::{0} Line::{1}", Vertices.Count, ActiveLine)); + + if (!Single.TryParse(lineSplit[3], out float z)) + throw new OBJReadException(String.Format("Failed to parse a Z Value from Vertex::{0} Line::{1}", Vertices.Count, ActiveLine)); + + Vertices.Add(new Vector3(x, y, z)); + } + + /// + /// Loads a UV Texture Point from the OBJ File + /// + /// Line split containing u,v as indexes 1,2 + private void LoadUVPoint(string[] lineSplit) + { + if (!Single.TryParse(lineSplit[1], out float u)) + throw new OBJReadException(String.Format("Failed to parse a U Value from UV::{0} Line::{1}", UVs.Count, ActiveLine)); + + if (!Single.TryParse(lineSplit[2], out float v)) + throw new OBJReadException(String.Format("Failed to parse a V Value from UV::{0} Line::{1}", UVs.Count, ActiveLine)); + + UVs.Add(new Vector2(u, v)); + } + + /// + /// Loads a vertex point from the OBJ File + /// + /// Line split containing x,y,z as indexes 1,2,3 + private void LoadNormal(string[] lineSplit) + { + if (!Single.TryParse(lineSplit[1], out float x)) + throw new OBJReadException(String.Format("Failed to parse an X Value from Normal::{0} Line::{1}", Normals.Count, ActiveLine)); + + if (!Single.TryParse(lineSplit[2], out float y)) + throw new OBJReadException(String.Format("Failed to parse a Y Value from Normal::{0} Line::{1}", Normals.Count, ActiveLine)); + + if (!Single.TryParse(lineSplit[3], out float z)) + throw new OBJReadException(String.Format("Failed to parse a Z Value from Normal::{0} Line::{1}", Normals.Count, ActiveLine)); + + Normals.Add(new Vector3(x, y, z)); + } + + /// + /// Loads a Polygon Face from the OBJ File. + /// + /// Line split containing face data. + private void LoadFace(string[] lineSplit) + { + if (lineSplit.Length != 4) + throw new OBJReadException(String.Format("Only polygons are supported, {1} indices on Face::{0} Line::{2}", Faces.Count, lineSplit.Length - 1, ActiveLine)); + + Face face = new Face(ActiveMaterial); + + for (int i = 1; i < 4; i++) + { + string[] faceSplit = lineSplit[i].Split('/'); + + if (faceSplit.Length != 3) + throw new OBJReadException(String.Format("No UV and/or Normal index on Face::{0} Vertex::{1} Line::{2}", Faces.Count, i, ActiveLine)); + + if (!Int32.TryParse(faceSplit[0], out int vertexIndex)) + throw new OBJReadException(String.Format("Failed to parse Vertex Index on Face::{0} Vertex::{1} Line::{2}", Faces.Count, i, ActiveLine)); + + if (!Int32.TryParse(faceSplit[1], out int uvIndex)) + throw new OBJReadException(String.Format("Failed to parse UV Index on Face::{0} Vertex::{1} Line::{2}", Faces.Count, i, ActiveLine)); + + if (!Int32.TryParse(faceSplit[2], out int normalIndex)) + throw new OBJReadException(String.Format("Failed to parse Normal Index on Face::{0} Vertex::{1} Line::{2}", Faces.Count, i, ActiveLine)); + + face.Vertices[i - 1] = new Face.Vertex + ( + vertexIndex - 1, + uvIndex - 1, + normalIndex - 1 + ); + } + + Faces.Add(face); + } + + /// + /// Adds a material to the OBJ file, if a material with the same name exists already, the previous material is overwritten + /// + /// + public void AddMaterial(Material material) + { + Materials[material.Name] = material; + } + + /// + /// Saves OBJ Object to a file + /// + /// Output file path + public void Save(string outputPath, bool randomizeColors = false) + { + // Get folder + string folder = Path.GetDirectoryName(outputPath); + // Create it + Directory.CreateDirectory(folder); + // Material Tracker + string materialNameTracker = null; + + using (StreamWriter writer = new StreamWriter(outputPath)) + { + foreach (string comment in Comments) + writer.WriteLine("# {0}", comment); + + writer.WriteLine("# Export Path : {0}", outputPath); + writer.WriteLine("# Export Time : {0}", DateTime.Now); + writer.WriteLine("# Vertex Count: {0}", Vertices.Count); + writer.WriteLine("# Normal Count: {0}", Normals.Count); + writer.WriteLine("# UV Count : {0}", UVs.Count); + writer.WriteLine("# Face Count : {0}", Faces.Count); + writer.WriteLine(); + + foreach (var vertex in Vertices) + writer.WriteLine("v {0:0.00000} {1:0.00000} {2:0.00000}", + vertex.X, + vertex.Y, + vertex.Z); + writer.WriteLine(); + foreach (var normal in Normals) + writer.WriteLine("vn {0:0.00000} {1:0.00000} {2:0.00000}", + normal.X, + normal.Y, + normal.Z); + writer.WriteLine(); + foreach (var uv in UVs) + writer.WriteLine("vt {0:0.00000} {1:0.00000}", + uv.X, + uv.Y); + writer.WriteLine(); + foreach (var face in Faces) + { + if (face.MaterialName != materialNameTracker) + { + writer.WriteLine(); + writer.WriteLine("g {0}", face.MaterialName); + writer.WriteLine("usemtl {0}", face.MaterialName); + writer.WriteLine("mtllib {0}", Path.GetFileName(Path.ChangeExtension(outputPath, ".mtl"))); + materialNameTracker = face.MaterialName; + } + writer.Write("f"); + foreach (var faceVertex in face.Vertices) + writer.Write(" {0}/{1}/{2}", + faceVertex.VertexIndex + 1, + faceVertex.UVIndex + 1, + faceVertex.NormalIndex + 1 + ); + writer.WriteLine(); + } + writer.WriteLine(); + } + + // Write Material Library + using (StreamWriter writer = new StreamWriter(Path.ChangeExtension(outputPath, ".mtl"))) + { + // Loop over materials + foreach (var material in Materials) + { + // Write Name + writer.WriteLine("newmtl {0}", material.Key); + // Write Illumination model + writer.WriteLine("illum 4"); + // Write colors, randomize diffuse if requested + writer.WriteLine("Kd {0:0.00} {1:0.00} {2:0.00}", + randomizeColors ? RandomInt.Next(128, 255) / 255.0 : 1.0, + randomizeColors ? RandomInt.Next(128, 255) / 255.0 : 1.0, + randomizeColors ? RandomInt.Next(128, 255) / 255.0 : 1.0); + writer.WriteLine("Ka 0.00 0.00 0.00"); + writer.WriteLine("Ks 0.00 0.00 0.00"); + // Write Maps, if we have them + if (!String.IsNullOrWhiteSpace(material.Value.DiffuseMap)) + writer.WriteLine("map_Kd {0}", material.Value.DiffuseMap); + if (!String.IsNullOrWhiteSpace(material.Value.NormalMap)) + writer.WriteLine("map_bump {0}", material.Value.NormalMap); + if (!String.IsNullOrWhiteSpace(material.Value.SpecularMap)) + writer.WriteLine("map_Ks {0}", material.Value.SpecularMap); + // Space + writer.WriteLine(); + } + } + } + } +} diff --git a/src/Husky/Husky/GameStructures/Shared.cs b/src/Husky/Husky/GameStructures/Shared.cs new file mode 100644 index 0000000..0ee5de2 --- /dev/null +++ b/src/Husky/Husky/GameStructures/Shared.cs @@ -0,0 +1,240 @@ +using System.Runtime.InteropServices; + +namespace Husky +{ + /// + /// Gfx Color + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct GfxColor + { + /// + /// Red Value + /// + public byte R { get; set; } + + /// + /// Red Value + /// + public byte G { get; set; } + + /// + /// Red Value + /// + public byte B { get; set; } + + /// + /// Red Value + /// + public byte A { get; set; } + } + + /// + /// Gfx Vertex + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct GfxVertex + { + /// + /// X Position + /// + public float X { get; set; } + + /// + /// Y Position + /// + public float Y { get; set; } + + /// + /// Z Position + /// + public float Z { get; set; } + + /// + /// Bi Normal + /// + public float BiNormal { get; set; } + + /// + /// RGBA Color + /// + public GfxColor Color { get; set; } + + /// + /// U Texture Position + /// + public float U { get; set; } + + /// + /// V Texture Position + /// + public float V { get; set; } + + /// + /// Unknown Bytes (Possibly tangent, etc.) + /// + public long Padding { get; set; } + + /// + /// Packed Vertex Normal (same as XModels) + /// + public PackedUnitVector Normal { get; set; } + + /// + /// Unknown Bytes (2 shorts?) + /// + public int Padding2 { get; set; } + } + + /// + /// Packed Unit Vector as 4 bytes + /// + [StructLayout(LayoutKind.Explicit)] + public struct PackedUnitVector + { + /// + /// Packed Value + /// + [FieldOffset(0)] + public int Value; + + /// + /// First Byte + /// + [FieldOffset(0)] + public byte Byte1; + + /// + /// Second Byte + /// + [FieldOffset(1)] + public byte Byte2; + + /// + /// Third Byte + /// + [FieldOffset(2)] + public byte Byte3; + + /// + /// Fourth Byte + /// + [FieldOffset(3)] + public byte Byte4; + } + + /// + /// Gfx Static Model + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct GfxStaticModel + { + /// + /// X Origin + /// + public float X { get; set; } + + /// + /// Y Origin + /// + public float Y { get; set; } + + /// + /// Z Origin + /// + public float Z { get; set; } + + /// + /// Rotation (TODO: Look into it) + /// + public fixed byte UnknownBytes1[36]; + + /// + /// Model Scale + /// + public float ModelScale { get; set; } + + /// + /// Null Padding + /// + public int Padding { get; set; } + + /// + /// Pointer to the XModel Asset + /// + public long ModelPointer { get; set; } + + /// + /// Unknown Bytes + /// + public fixed byte UnknownBytes2[0x10]; + } + + /// + /// Material Image for: WaW + /// + public unsafe struct MaterialImage32A + { + /// + /// Semantic Hash/Usage + /// + public uint SemanticHash { get; set; } + + /// + /// Unknown Int + /// + public uint UnknownInt { get; set; } + + /// + /// Null Padding + /// + public int Padding { get; set; } + + /// + /// Pointer to the Image Asset + /// + public int ImagePointer { get; set; } + } + + /// + /// Material Image for: MW2, MW3 + /// + public unsafe struct MaterialImage32B + { + /// + /// Semantic Hash/Usage + /// + public uint SemanticHash { get; set; } + + /// + /// Unknown Int + /// + public uint UnknownInt { get; set; } + + /// + /// Pointer to the Image Asset + /// + public int ImagePointer { get; set; } + } + + /// + /// Material Image for: Ghosts, AW, MWR + /// + public unsafe struct MaterialImage64A + { + /// + /// Semantic Hash/Usage + /// + public uint SemanticHash { get; set; } + + /// + /// Unknown Int (It's possible the semantic hash is actually 64bit, and this is apart of the actual hash) + /// + public uint UnknownInt { get; set; } + + /// + /// Pointer to the Image Asset + /// + public long ImagePointer { get; set; } + } +} diff --git a/src/Husky/Husky/Games/AdvancedWarfare.cs b/src/Husky/Husky/Games/AdvancedWarfare.cs index 65cda78..acd2142 100644 --- a/src/Husky/Husky/Games/AdvancedWarfare.cs +++ b/src/Husky/Husky/Games/AdvancedWarfare.cs @@ -19,9 +19,9 @@ using PhilLibX.IO; using System; using System.IO; -using SELib; -using SELib.Utilities; using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Collections.Generic; namespace Husky { @@ -30,6 +30,176 @@ namespace Husky /// public class AdvancedWarfare { + /// + /// AW GfxMap Asset (some pointers we skip over point to DirectX routines, etc. if that means anything to anyone) + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct GfxMap + { + /// + /// A pointer to the name of this GfxMap Asset + /// + public long NamePointer { get; set; } + + /// + /// A pointer to the name of the map + /// + public long MapNamePointer { get; set; } + + /// + /// Unknown Bytes (Possibly counts for other data we don't care about) + /// + public fixed byte Padding[0xC]; + + /// + /// Number of Surfaces + /// + public int SurfaceCount { get; set; } + + /// + /// Unknown Bytes (Possibly counts, pointers, etc. for other data we don't care about) + /// + public fixed byte Padding1[0x110]; + + /// + /// Number of Gfx Vertices (XYZ, etc.) + /// + public long GfxVertexCount { get; set; } + + /// + /// Pointer to the Gfx Vertex Data + /// + public long GfxVerticesPointer { get; set; } + + /// + /// Unknown Bytes (more BSP data we probably don't care for) + /// + public fixed byte Padding2[0x20]; + + /// + /// Number of Gfx Indices (for Faces) + /// + public long GfxIndicesCount { get; set; } + + /// + /// Pointer to the Gfx Index Data + /// + public long GfxIndicesPointer { get; set; } + + /// + /// Points, etc. + /// + public fixed byte Padding3[0x830]; + + /// + /// Pointer to the Gfx Index Data + /// + public long GfxSurfacesPointer { get; set; } + } + + /// + /// Gfx Map Surface + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct GfxSurface + { + /// + /// Unknown Int (I know which pointer in the GfxMap it correlates it, but doesn't seem to interest us) + /// + public int UnknownBaseIndex { get; set; } + + /// + /// Base Vertex Index (this is what allows the GfxMap to have 65k+ verts with only 2 byte indices) + /// + public int VertexIndex { get; set; } + + /// + /// Unknown Bytes + /// + public int Padding1 { get; set; } + + /// + /// Number of Vertices this surface has + /// + public ushort VertexCount { get; set; } + + /// + /// Number of Faces this surface has + /// + public ushort FaceCount { get; set; } + + /// + /// Base Face Index (this is what allows the GfxMap to have 65k+ faces with only 2 byte indices) + /// + public int FaceIndex { get; set; } + + /// + /// Unknown Bytes + /// + public int Padding2 { get; set; } + + /// + /// Pointer to the Material Asset of this Surface + /// + public long MaterialPointer { get; set; } + + /// + /// Unknown Bytes + /// + public fixed byte Padding3[8]; + } + + /// + /// Material Asset Info + /// + public unsafe struct Material + { + /// + /// A pointer to the name of this material + /// + public long NamePointer { get; set; } + + /// + /// Unknown Bytes (Flags, settings, etc.) + /// + public fixed byte UnknownBytes[0xDC]; + + /// + /// Number of Images this Material has + /// + public byte ImageCount { get; set; } + + /// + /// Unknown Bytes (Flags, settings, etc.) + /// + public fixed byte UnknownBytes1[0xB]; + + /// + /// A pointer to the Tech Set this Material uses + /// + public long TechniqueSetPointer { get; set; } + + /// + /// A pointer to this Material's Image table + /// + public long ImageTablePointer { get; set; } + + /// + /// Null Bytes + /// + public long Padding { get; set; } + + /// + /// UnknownPointer (Probably settings that changed based off TechSet) + /// + public long UnknownPointer { get; set; } + + /// + /// Unknown Bytes (Flags, settings, etc.) + /// + public fixed byte UnknownBytes2[0xD0]; + } + /// /// Reads BSP Data /// @@ -42,7 +212,7 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l if (reader.ReadNullTerminatedString(reader.ReadInt64(reader.ReadInt64(assetPoolsAddress + 0x38) + 8)) == "fx") { // Load BSP Pools (they only have a size of 1 so we don't care about reading more than 1) - var gfxMapAsset = reader.ReadStruct(reader.ReadInt64(assetPoolsAddress + 0xF8)); + var gfxMapAsset = reader.ReadStruct(reader.ReadInt64(assetPoolsAddress + 0xF8)); // Name string gfxMapName = reader.ReadNullTerminatedString(gfxMapAsset.NamePointer); @@ -89,51 +259,70 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l // Reset timer stopWatch.Restart(); - // Write SEModel + // Write OBJ Printer.WriteLine("INFO", "Converting to OBJ...."); - // Create Dir - Directory.CreateDirectory(Path.GetDirectoryName(gfxMapName)); + // Create new OBJ + var obj = new WavefrontOBJ(); - // Create OBJ output - using (StreamWriter writer = new StreamWriter(Path.ChangeExtension(gfxMapName, ".obj"))) + // Append Vertex Data + foreach (var vertex in vertices) { - // Dump vertex data - foreach (var vertex in vertices) - { - writer.WriteLine("v {0} {1} {2}", vertex.Position.X, vertex.Position.Y, vertex.Position.Z); - writer.WriteLine("vn {0} {1} {2}", vertex.VertexNormal.X, vertex.VertexNormal.Y, vertex.VertexNormal.Z); - writer.WriteLine("vt {0} {1}", vertex.UVSets[0].X, vertex.UVSets[0].Y); - } + obj.Vertices.Add(vertex.Position); + obj.Normals.Add(vertex.Normal); + obj.UVs.Add(vertex.UV); + } - // Dump Surfaces - foreach (var surface in surfaces) - { - // Get Material Name - var materialName = Path.GetFileNameWithoutExtension(reader.ReadNullTerminatedString(reader.ReadInt64(surface.MaterialPointer)).Replace("*", "")); + // Image Names (for Search String) + HashSet imageNames = new HashSet(); - // Write MTL and Group - writer.WriteLine("g {0}", materialName); - writer.WriteLine("usemtl {0}", materialName); + // Append Faces + foreach (var surface in surfaces) + { + // Create new Material + var material = ReadMaterial(reader, surface.MaterialPointer); + // Add to images + imageNames.Add(material.DiffuseMap); + // Add it + obj.AddMaterial(material); + // Add points + for (ushort i = 0; i < surface.FaceCount; i++) + { + // Face Indices + var faceIndex1 = indices[i * 3 + surface.FaceIndex] + surface.VertexIndex; + var faceIndex2 = indices[i * 3 + surface.FaceIndex + 1] + surface.VertexIndex; + var faceIndex3 = indices[i * 3 + surface.FaceIndex + 2] + surface.VertexIndex; - // Add points - for (ushort i = 0; i < surface.FaceCount; i++) + // Validate unique points, and write to OBJ + if (faceIndex1 != faceIndex2 && faceIndex1 != faceIndex3 && faceIndex2 != faceIndex3) { - // Face Indices - var faceIndex1 = indices[i * 3 + surface.FaceIndex] + surface.VertexIndex + 1; - var faceIndex2 = indices[i * 3 + surface.FaceIndex + 1] + surface.VertexIndex + 1; - var faceIndex3 = indices[i * 3 + surface.FaceIndex + 2] + surface.VertexIndex + 1; - - // Validate unique points, and write to OBJ - if (faceIndex1 != faceIndex2 && faceIndex1 != faceIndex3 && faceIndex2 != faceIndex3) - writer.WriteLine("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}", - faceIndex1, - faceIndex3, - faceIndex2); + // new Obj Face + var objFace = new WavefrontOBJ.Face(material.Name); + + // Add points + objFace.Vertices[0] = new WavefrontOBJ.Face.Vertex(faceIndex1, faceIndex1, faceIndex1); + objFace.Vertices[2] = new WavefrontOBJ.Face.Vertex(faceIndex2, faceIndex2, faceIndex2); + objFace.Vertices[1] = new WavefrontOBJ.Face.Vertex(faceIndex3, faceIndex3, faceIndex3); + + // Add to OBJ + obj.Faces.Add(objFace); } } } + // Save it + obj.Save(Path.ChangeExtension(gfxMapName, ".obj")); + + // Build search strinmg + string searchString = ""; + + // Loop through images, and append each to the search string (for Wraith/Greyhound) + foreach(string imageName in imageNames) + searchString += String.Format("{0},", Path.GetFileNameWithoutExtension(imageName)); + + // Dump it + File.WriteAllText(Path.ChangeExtension(gfxMapName, ".txt"), searchString); + // Done Printer.WriteLine("INFO", String.Format("Converted to OBJ in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); } @@ -148,15 +337,15 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l /// /// Reads Gfx Surfaces /// - public static GfxSurfaceAW[] ReadGfxSufaces(ProcessReader reader, long address, int count) + public static GfxSurface[] ReadGfxSufaces(ProcessReader reader, long address, int count) { // Preallocate array - GfxSurfaceAW[] surfaces = new GfxSurfaceAW[count]; + GfxSurface[] surfaces = new GfxSurface[count]; // Loop number of indices we have for (int i = 0; i < count; i++) // Add it - surfaces[i] = reader.ReadStruct(address + i * 40); + surfaces[i] = reader.ReadStruct(address + i * 40); // Done return surfaces; @@ -181,48 +370,57 @@ public static ushort[] ReadGfxIndices(ProcessReader reader, long address, int co /// /// Reads Gfx Vertices /// - public static SEModelVertex[] ReadGfxVertices(ProcessReader reader, long address, int count) + public static Vertex[] ReadGfxVertices(ProcessReader reader, long address, int count) { // Preallocate vertex array - SEModelVertex[] vertices = new SEModelVertex[count]; + Vertex[] vertices = new Vertex[count]; // Read buffer var byteBuffer = reader.ReadBytes(address, count * 44); // Loop number of vertices we have for (int i = 0; i < count; i++) { - // Grab Offset - float x = BitConverter.ToSingle(byteBuffer, i * 44); - float y = BitConverter.ToSingle(byteBuffer, i * 44 + 4); - float z = BitConverter.ToSingle(byteBuffer, i * 44 + 8); - - // Grab UV - float u = BitConverter.ToSingle(byteBuffer, i * 44 + 20); - float v = BitConverter.ToSingle(byteBuffer, i * 44 + 24); - - // Grab Normal - int vertexNormal = BitConverter.ToInt32(byteBuffer, i * 44 + 36); + // Read Struct + var gfxVertex = ByteUtil.BytesToStruct(byteBuffer, i * 44); // Create new SEModel Vertex - vertices[i] = new SEModelVertex() + vertices[i] = new Vertex() { // Set offset Position = new Vector3( - x, - y, - z), + gfxVertex.X, + gfxVertex.Y, + gfxVertex.Z), // Decode and set normal (from DTZxPorter - Wraith, same as XModels) - VertexNormal = new Vector3( - (float)(((vertexNormal & 0x3FF) / 1023.0) * 2.0 - 1.0), - (float)((((vertexNormal >> 10) & 0x3FF) / 1023.0) * 2.0 - 1.0), - (float)((((vertexNormal >> 20) & 0x3FF) / 1023.0) * 2.0 - 1.0)), + Normal = VertexNormal.UnpackB(gfxVertex.Normal), + // Set UV + UV = new Vector2(gfxVertex.U, 1 - gfxVertex.V) }; - - // Set UV - vertices[i].UVSets.Add(new Vector2(u, v)); } // Done return vertices; } + + /// + /// Reads a material for the given surface and its associated images + /// + public static WavefrontOBJ.Material ReadMaterial(ProcessReader reader, long address) + { + // Read Material + var material = reader.ReadStruct(address); + // Create new OBJ Image + var objMaterial = new WavefrontOBJ.Material(Path.GetFileNameWithoutExtension(reader.ReadNullTerminatedString(reader.ReadInt64(address)).Replace("*", ""))); + // Loop over images + for (byte i = 0; i < material.ImageCount; i++) + { + // Read Material Image + var materialImage = reader.ReadStruct(material.ImageTablePointer + i * Marshal.SizeOf()); + // Check for color map for now + if (materialImage.SemanticHash == 0xA0AB1041) + objMaterial.DiffuseMap = "_images\\\\" + reader.ReadNullTerminatedString(reader.ReadInt64(materialImage.ImagePointer + 96)) + ".png"; + } + // Done + return objMaterial; + } } } diff --git a/src/Husky/Husky/Games/GameStructures.cs b/src/Husky/Husky/Games/GameStructures.cs deleted file mode 100644 index d4b2cdc..0000000 --- a/src/Husky/Husky/Games/GameStructures.cs +++ /dev/null @@ -1,803 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Husky -{ - /// - /// WaW GfxMap Asset (some pointers we skip over point to DirectX routines, etc. if that means anything to anyone) - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public unsafe struct GfxMapWaW - { - /// - /// A pointer to the name of this GfxMap Asset - /// - public int NamePointer { get; set; } - - /// - /// A pointer to the name of the map - /// - public int MapNamePointer { get; set; } - - /// - /// Unknown Bytes (Possibly counts for other data we don't care about) - /// - public fixed byte Padding[8]; - - /// - /// Number of Gfx Indices (for Faces) - /// - public int GfxIndicesCount { get; set; } - - /// - /// Pointer to the Gfx Index Data - /// - public int GfxIndicesPointer { get; set; } - - /// - /// Number of Surfaces - /// - public int SurfaceCount { get; set; } - - /// - /// Unknown Bytes (Possibly counts, pointers, etc. for other data we don't care about) - /// - public fixed byte Padding1[0x18]; - - /// - /// Number of Gfx Vertices (XYZ, etc.) - /// - public int GfxVertexCount { get; set; } - - /// - /// Pointer to the Gfx Vertex Data - /// - public int GfxVerticesPointer { get; set; } - - /// - /// Unknown Bytes (more BSP data we probably don't care for) - /// - public fixed byte Padding2[0x26C]; - - /// - /// Pointer to the Gfx Index Data - /// - public int GfxSurfacesPointer { get; set; } - } - - /// - /// Gfx Map Surface - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public unsafe struct GfxSurfaceWaW - { - /// - /// Unknown Int (I know which pointer in the GfxMap it correlates it, but doesn't seem to interest us) - /// - public int UnknownBaseIndex { get; set; } - - /// - /// Base Vertex Index (this is what allows the GfxMap to have 65k+ verts with only 2 byte indices) - /// - public int VertexIndex { get; set; } - - /// - /// Number of Vertices this surface has - /// - public ushort VertexCount { get; set; } - - /// - /// Number of Faces this surface has - /// - public ushort FaceCount { get; set; } - - /// - /// Base Face Index (this is what allows the GfxMap to have 65k+ faces with only 2 byte indices) - /// - public int FaceIndex { get; set; } - - /// - /// Always 0xFFFFFFFF? - /// - public int Padding1 { get; set; } - - /// - /// Pointer to the Material Asset of this Surface - /// - public int MaterialPointer { get; set; } - - /// - /// Unknown Bytes - /// - public fixed byte Padding[0x1C]; - } - - /// - /// MW2 GfxMap Asset (some pointers we skip over point to DirectX routines, etc. if that means anything to anyone) - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public unsafe struct GfxMapMW2 - { - /// - /// A pointer to the name of this GfxMap Asset - /// - public int NamePointer { get; set; } - - /// - /// A pointer to the name of the map - /// - public int MapNamePointer { get; set; } - - /// - /// Unknown Bytes (Possibly counts for other data we don't care about) - /// - public fixed byte Padding[8]; - - /// - /// Number of Surfaces - /// - public int SurfaceCount { get; set; } - - /// - /// Unknown Bytes (Possibly counts, pointers, etc. for other data we don't care about) - /// - public fixed byte Padding1[0x64]; - - /// - /// Number of Gfx Vertices (XYZ, etc.) - /// - public int GfxVertexCount { get; set; } - - /// - /// Pointer to the Gfx Vertex Data - /// - public int GfxVerticesPointer { get; set; } - - /// - /// Unknown Bytes (more BSP data we probably don't care for) - /// - public fixed byte Padding2[0x10]; - - /// - /// Number of Gfx Indices (for Faces) - /// - public int GfxIndicesCount { get; set; } - - /// - /// Pointer to the Gfx Index Data - /// - public int GfxIndicesPointer { get; set; } - - /// - /// Unknown Bytes (more BSP data we probably don't care for) - /// - public fixed byte Padding3[0x184]; - - /// - /// Pointer to the Gfx Index Data - /// - public int GfxSurfacesPointer { get; set; } - } - - /// - /// MW3 GfxMap Asset (some pointers we skip over point to DirectX routines, etc. if that means anything to anyone) - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public unsafe struct GfxMapMW3 - { - /// - /// A pointer to the name of this GfxMap Asset - /// - public int NamePointer { get; set; } - - /// - /// A pointer to the name of the map - /// - public int MapNamePointer { get; set; } - - /// - /// Unknown Bytes (Possibly counts for other data we don't care about) - /// - public fixed byte Padding[8]; - - /// - /// Number of Surfaces - /// - public int SurfaceCount { get; set; } - - /// - /// Unknown Bytes (Possibly counts, pointers, etc. for other data we don't care about) - /// - public fixed byte Padding1[0x70]; - - /// - /// Number of Gfx Vertices (XYZ, etc.) - /// - public int GfxVertexCount { get; set; } - - /// - /// Pointer to the Gfx Vertex Data - /// - public int GfxVerticesPointer { get; set; } - - /// - /// Unknown Bytes (more BSP data we probably don't care for) - /// - public fixed byte Padding2[0x10]; - - /// - /// Number of Gfx Indices (for Faces) - /// - public int GfxIndicesCount { get; set; } - - /// - /// Pointer to the Gfx Index Data - /// - public int GfxIndicesPointer { get; set; } - - /// - /// Unknown Bytes (more BSP data we probably don't care for) - /// - public fixed byte Padding3[0x184]; - - /// - /// Pointer to the Gfx Index Data - /// - public int GfxSurfacesPointer { get; set; } - } - - /// - /// Gfx Map Surface - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public unsafe struct GfxSurfaceMW3 - { - /// - /// Unknown Int (I know which pointer in the GfxMap it correlates it, but doesn't seem to interest us) - /// - public int UnknownBaseIndex { get; set; } - - /// - /// Base Vertex Index (this is what allows the GfxMap to have 65k+ verts with only 2 byte indices) - /// - public int VertexIndex { get; set; } - - /// - /// Number of Vertices this surface has - /// - public ushort VertexCount { get; set; } - - /// - /// Number of Faces this surface has - /// - public ushort FaceCount { get; set; } - - /// - /// Base Face Index (this is what allows the GfxMap to have 65k+ faces with only 2 byte indices) - /// - public int FaceIndex { get; set; } - - /// - /// Pointer to the Material Asset of this Surface - /// - public int MaterialPointer { get; set; } - - /// - /// Unknown Bytes - /// - public fixed byte Padding[4]; - } - - /// - /// MWR Map Ents Asset - /// - public unsafe struct MapEnts - { - /// - /// A pointer to the name of this MapEnts Asset - /// - public long NamePointer { get; set; } - - /// - /// A pointer to the entity map string - /// - public long EntityMapStringPointer { get; set; } - - /// - /// Size of the entity map string - /// - public int EntityMapStringSize { get; set; } - } - - /// - /// Gfx Map Surface Ghosts - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public unsafe struct GfxSurfaceGhosts - { - /// - /// Unknown Int (I know which pointer in the GfxMap it correlates it, but doesn't seem to interest us) - /// - public int UnknownBaseIndex { get; set; } - - /// - /// Base Vertex Index (this is what allows the GfxMap to have 65k+ verts with only 2 byte indices) - /// - public int VertexIndex { get; set; } - - /// - /// Unknown Bytes (float?) - /// - public fixed byte Padding[4]; - - /// - /// Number of Vertices this surface has - /// - public ushort VertexCount { get; set; } - - /// - /// Number of Faces this surface has - /// - public ushort FaceCount { get; set; } - - /// - /// Base Face Index (this is what allows the GfxMap to have 65k+ faces with only 2 byte indices) - /// - public int FaceIndex { get; set; } - - /// - /// Null Padding - /// - public int Padding1 { get; set; } - - /// - /// Pointer to the Material Asset of this Surface - /// - public long MaterialPointer { get; set; } - - /// - /// Unknown Bytes - /// - public fixed byte Padding2[8]; - } - - /// - /// Gfx Map Surface - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public unsafe struct GfxSurfaceMWR - { - /// - /// Unknown Int (I know which pointer in the GfxMap it correlates it, but doesn't seem to interest us) - /// - public int UnknownBaseIndex { get; set; } - - /// - /// Base Vertex Index (this is what allows the GfxMap to have 65k+ verts with only 2 byte indices) - /// - public int VertexIndex { get; set; } - - /// - /// Unknown Bytes (Possibly color? And vertex count, along with some float that might be size) - /// - public fixed byte Padding[0xA]; - - /// - /// Number of Faces this surface has - /// - public ushort FaceCount { get; set; } - - /// - /// Base Face Index (this is what allows the GfxMap to have 65k+ faces with only 2 byte indices) - /// - public int FaceIndex { get; set; } - - /// - /// Pointer to the Material Asset of this Surface - /// - public long MaterialPointer { get; set; } - - /// - /// Unknown Bytes - /// - public fixed byte Padding2[8]; - } - - /// - /// Gfx Color - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public unsafe struct GfxColor - { - /// - /// Red Value - /// - public byte R { get; set; } - - /// - /// Red Value - /// - public byte G { get; set; } - - /// - /// Red Value - /// - public byte B { get; set; } - - /// - /// Red Value - /// - public byte A { get; set; } - } - - /// - /// Gfx Vertex - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public unsafe struct GfxVertex - { - /// - /// X Position - /// - public float X { get; set; } - - /// - /// Y Position - /// - public float Y { get; set; } - - /// - /// Z Position - /// - public float Z { get; set; } - - /// - /// Bi Normal - /// - public float BiNormal { get; set; } - - /// - /// RGBA Color - /// - public GfxColor Color { get; set; } - - /// - /// U Texture Position - /// - public float U { get; set; } - - /// - /// V Texture Position - /// - public float V { get; set; } - - /// - /// Unknown Bytes (Possibly tangent, etc.) - /// - public long Padding { get; set; } - - /// - /// Packed Vertex Normal (same as XModels) - /// - public int Normal { get; set; } - - /// - /// Unknown Bytes (2 shorts?) - /// - public int Padding2 { get; set; } - } - - /// - /// Ghosts Gfx Map Asset (some pointers we skip over point to DirectX routines, etc. if that means anything to anyone) - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public unsafe struct GfxMapGhosts - { - /// - /// A pointer to the name of this GfxMap Asset - /// - public long NamePointer { get; set; } - - /// - /// A pointer to the name of the map - /// - public long MapNamePointer { get; set; } - - /// - /// Unknown Bytes (Possibly counts for other data we don't care about) - /// - public fixed byte Padding[0xC]; - - /// - /// Number of Surfaces - /// - public int SurfaceCount { get; set; } - - /// - /// Unknown Bytes (Possibly counts, pointers, etc. for other data we don't care about) - /// - public fixed byte Padding1[0xD4]; - - /// - /// Number of Gfx Vertices (XYZ, etc.) - /// - public int GfxVertexCount { get; set; } - - /// - /// Pointer to the Gfx Vertex Data - /// - public long GfxVerticesPointer { get; set; } - - /// - /// Unknown Bytes (more BSP data we probably don't care for) - /// - public fixed byte Padding2[0x20]; - - /// - /// Number of Gfx Indices (for Faces) - /// - public long GfxIndicesCount { get; set; } - - /// - /// Pointer to the Gfx Index Data - /// - public long GfxIndicesPointer { get; set; } - - /// - /// Pointers, etc. - /// - public fixed byte Padding3[0x838]; - - /// - /// Pointer to the Gfx Index Data - /// - public long GfxSurfacesPointer { get; set; } - } - - /// - /// MWR GfxMap Asset (some pointers we skip over point to DirectX routines, etc. if that means anything to anyone) - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public unsafe struct GfxMapAW - { - /// - /// A pointer to the name of this GfxMap Asset - /// - public long NamePointer { get; set; } - - /// - /// A pointer to the name of the map - /// - public long MapNamePointer { get; set; } - - /// - /// Unknown Bytes (Possibly counts for other data we don't care about) - /// - public fixed byte Padding[0xC]; - - /// - /// Number of Surfaces - /// - public int SurfaceCount { get; set; } - - /// - /// Unknown Bytes (Possibly counts, pointers, etc. for other data we don't care about) - /// - public fixed byte Padding1[0x110]; - - /// - /// Number of Gfx Vertices (XYZ, etc.) - /// - public long GfxVertexCount { get; set; } - - /// - /// Pointer to the Gfx Vertex Data - /// - public long GfxVerticesPointer { get; set; } - - /// - /// Unknown Bytes (more BSP data we probably don't care for) - /// - public fixed byte Padding2[0x20]; - - /// - /// Number of Gfx Indices (for Faces) - /// - public long GfxIndicesCount { get; set; } - - /// - /// Pointer to the Gfx Index Data - /// - public long GfxIndicesPointer { get; set; } - - /// - /// Points, etc. - /// - public fixed byte Padding3[0x830]; - - /// - /// Pointer to the Gfx Index Data - /// - public long GfxSurfacesPointer { get; set; } - } - - /// - /// Gfx Map Surface - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public unsafe struct GfxSurfaceAW - { - /// - /// Unknown Int (I know which pointer in the GfxMap it correlates it, but doesn't seem to interest us) - /// - public int UnknownBaseIndex { get; set; } - - /// - /// Base Vertex Index (this is what allows the GfxMap to have 65k+ verts with only 2 byte indices) - /// - public int VertexIndex { get; set; } - - /// - /// Unknown Bytes - /// - public int Padding1 { get; set; } - - /// - /// Number of Vertices this surface has - /// - public ushort VertexCount { get; set; } - - /// - /// Number of Faces this surface has - /// - public ushort FaceCount { get; set; } - - /// - /// Base Face Index (this is what allows the GfxMap to have 65k+ faces with only 2 byte indices) - /// - public int FaceIndex { get; set; } - - /// - /// Unknown Bytes - /// - public int Padding2 { get; set; } - - /// - /// Pointer to the Material Asset of this Surface - /// - public long MaterialPointer { get; set; } - - /// - /// Unknown Bytes - /// - public fixed byte Padding3[8]; - } - - /// - /// MWR GfxMap Asset (some pointers we skip over point to DirectX routines, etc. if that means anything to anyone) - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public unsafe struct GfxMapMWR - { - /// - /// A pointer to the name of this GfxMap Asset - /// - public long NamePointer { get; set; } - - /// - /// A pointer to the name of the map - /// - public long MapNamePointer { get; set; } - - /// - /// Unknown Bytes (Possibly counts for other data we don't care about) - /// - public fixed byte Padding[0xC]; - - /// - /// Number of Surfaces - /// - public int SurfaceCount { get; set; } - - /// - /// Unknown Bytes (Possibly counts, pointers, etc. for other data we don't care about) - /// - public fixed byte Padding1[0x110]; - - /// - /// Number of Gfx Vertices (XYZ, etc.) - /// - public long GfxVertexCount { get; set; } - - /// - /// Pointer to the Gfx Vertex Data - /// - public long GfxVerticesPointer { get; set; } - - /// - /// Unknown Bytes (more BSP data we probably don't care for) - /// - public fixed byte Padding2[0x30]; - - /// - /// Number of Gfx Indices (for Faces) - /// - public long GfxIndicesCount { get; set; } - - /// - /// Pointer to the Gfx Index Data - /// - public long GfxIndicesPointer { get; set; } - - /// - /// Points, etc. - /// - public fixed byte Padding3[0x5C8]; - - /// - /// Number of Static Models - /// - public long StaticModelsCount { get; set; } - - /// - /// Unknown Bytes (more BSP data we probably don't care for) - /// - public fixed byte Padding4[0x290]; - - /// - /// Pointer to the Gfx Index Data - /// - public long GfxSurfacesPointer { get; set; } - } - - /// - /// Gfx Static Model - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public unsafe struct GfxStaticModel - { - /// - /// X Origin - /// - public float X { get; set; } - - /// - /// Y Origin - /// - public float Y { get; set; } - - /// - /// Z Origin - /// - public float Z { get; set; } - - /// - /// Rotation (TODO: Look into it) - /// - public fixed byte UnknownBytes1[36]; - - /// - /// Model Scale - /// - public float ModelScale { get; set; } - - /// - /// Null Padding - /// - public int Padding { get; set; } - - /// - /// Pointer to the XModel Asset - /// - public long ModelPointer { get; set; } - - /// - /// Unknown Bytes - /// - public fixed byte UnknownBytes2[0x10]; - } -} diff --git a/src/Husky/Husky/Games/Ghosts.cs b/src/Husky/Husky/Games/Ghosts.cs index 96f4c23..a90a113 100644 --- a/src/Husky/Husky/Games/Ghosts.cs +++ b/src/Husky/Husky/Games/Ghosts.cs @@ -19,9 +19,9 @@ using PhilLibX.IO; using System; using System.IO; -using SELib; -using SELib.Utilities; using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Collections.Generic; namespace Husky { @@ -30,6 +30,176 @@ namespace Husky /// class Ghosts { + /// + /// Ghosts Gfx Map Asset (some pointers we skip over point to DirectX routines, etc. if that means anything to anyone) + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct GfxMap + { + /// + /// A pointer to the name of this GfxMap Asset + /// + public long NamePointer { get; set; } + + /// + /// A pointer to the name of the map + /// + public long MapNamePointer { get; set; } + + /// + /// Unknown Bytes (Possibly counts for other data we don't care about) + /// + public fixed byte Padding[0xC]; + + /// + /// Number of Surfaces + /// + public int SurfaceCount { get; set; } + + /// + /// Unknown Bytes (Possibly counts, pointers, etc. for other data we don't care about) + /// + public fixed byte Padding1[0xD4]; + + /// + /// Number of Gfx Vertices (XYZ, etc.) + /// + public int GfxVertexCount { get; set; } + + /// + /// Pointer to the Gfx Vertex Data + /// + public long GfxVerticesPointer { get; set; } + + /// + /// Unknown Bytes (more BSP data we probably don't care for) + /// + public fixed byte Padding2[0x20]; + + /// + /// Number of Gfx Indices (for Faces) + /// + public long GfxIndicesCount { get; set; } + + /// + /// Pointer to the Gfx Index Data + /// + public long GfxIndicesPointer { get; set; } + + /// + /// Pointers, etc. + /// + public fixed byte Padding3[0x838]; + + /// + /// Pointer to the Gfx Index Data + /// + public long GfxSurfacesPointer { get; set; } + } + + /// + /// Gfx Map Surface Ghosts + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct GfxSurface + { + /// + /// Unknown Int (I know which pointer in the GfxMap it correlates it, but doesn't seem to interest us) + /// + public int UnknownBaseIndex { get; set; } + + /// + /// Base Vertex Index (this is what allows the GfxMap to have 65k+ verts with only 2 byte indices) + /// + public int VertexIndex { get; set; } + + /// + /// Unknown Bytes (float?) + /// + public fixed byte Padding[4]; + + /// + /// Number of Vertices this surface has + /// + public ushort VertexCount { get; set; } + + /// + /// Number of Faces this surface has + /// + public ushort FaceCount { get; set; } + + /// + /// Base Face Index (this is what allows the GfxMap to have 65k+ faces with only 2 byte indices) + /// + public int FaceIndex { get; set; } + + /// + /// Null Padding + /// + public int Padding1 { get; set; } + + /// + /// Pointer to the Material Asset of this Surface + /// + public long MaterialPointer { get; set; } + + /// + /// Unknown Bytes + /// + public fixed byte Padding2[8]; + } + + /// + /// Material Asset Info + /// + public unsafe struct Material + { + /// + /// A pointer to the name of this material + /// + public long NamePointer { get; set; } + + /// + /// Unknown Bytes (Flags, settings, etc.) + /// + public fixed byte UnknownBytes[0x1BC]; + + /// + /// Number of Images this Material has + /// + public byte ImageCount { get; set; } + + /// + /// Unknown Bytes (Flags, settings, etc.) + /// + public fixed byte UnknownBytes1[0xB]; + + /// + /// A pointer to the Tech Set this Material uses + /// + public long TechniqueSetPointer { get; set; } + + /// + /// A pointer to this Material's Image table + /// + public long ImageTablePointer { get; set; } + + /// + /// UnknownPointer (Probably settings that changed based off TechSet) + /// + public long UnknownPointer1 { get; set; } + + /// + /// UnknownPointer (Probably settings that changed based off TechSet) + /// + public long UnknownPointer2 { get; set; } + + /// + /// Unknown Bytes (Flags, settings, etc.) + /// + public fixed byte UnknownBytes2[0x1B8]; + } + /// /// Reads BSP Data /// @@ -42,7 +212,7 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l if (reader.ReadNullTerminatedString(reader.ReadInt64(reader.ReadInt64(assetPoolsAddress + 0x20) + 8)) == "void") { // Load BSP Pools (they only have a size of 1 so we don't care about reading more than 1) - var gfxMapAsset = reader.ReadStruct(reader.ReadInt64(assetPoolsAddress + 0xD0)); + var gfxMapAsset = reader.ReadStruct(reader.ReadInt64(assetPoolsAddress + 0xD0)); // Name string gfxMapName = reader.ReadNullTerminatedString(gfxMapAsset.NamePointer); @@ -89,50 +259,70 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l // Reset timer stopWatch.Restart(); - // Write SEModel + // Write OBJ Printer.WriteLine("INFO", "Converting to OBJ...."); - // Create Dir - Directory.CreateDirectory(Path.GetDirectoryName(gfxMapName)); + // Create new OBJ + var obj = new WavefrontOBJ(); - // Create OBJ output - using (StreamWriter writer = new StreamWriter(Path.ChangeExtension(gfxMapName, ".obj"))) + // Append Vertex Data + foreach (var vertex in vertices) { - // Dump vertex data - foreach (var vertex in vertices) - { - writer.WriteLine("v {0} {1} {2}", vertex.Position.X, vertex.Position.Y, vertex.Position.Z); - writer.WriteLine("vn {0} {1} {2}", vertex.VertexNormal.X, vertex.VertexNormal.Y, vertex.VertexNormal.Z); - writer.WriteLine("vt {0} {1}", vertex.UVSets[0].X, vertex.UVSets[0].Y); - } + obj.Vertices.Add(vertex.Position); + obj.Normals.Add(vertex.Normal); + obj.UVs.Add(vertex.UV); + } + + // Image Names (for Search String) + HashSet imageNames = new HashSet(); - // Dump Surfaces - foreach (var surface in surfaces) + // Append Faces + foreach (var surface in surfaces) + { + // Create new Material + var material = ReadMaterial(reader, surface.MaterialPointer); + // Add to images + imageNames.Add(material.DiffuseMap); + // Add it + obj.AddMaterial(material); + // Add points + for (ushort i = 0; i < surface.FaceCount; i++) { - // Get Material Name, purge any prefixes and Auto-Gen star characters - var materialName = Path.GetFileNameWithoutExtension(reader.ReadNullTerminatedString(reader.ReadInt64(surface.MaterialPointer)).Replace("*", "")); - - // Write MTL and Group - writer.WriteLine("g {0}", materialName); - writer.WriteLine("usemtl {0}", materialName); - // Add points - for (ushort i = 0; i < surface.FaceCount; i++) + // Face Indices + var faceIndex1 = indices[i * 3 + surface.FaceIndex] + surface.VertexIndex; + var faceIndex2 = indices[i * 3 + surface.FaceIndex + 1] + surface.VertexIndex; + var faceIndex3 = indices[i * 3 + surface.FaceIndex + 2] + surface.VertexIndex; + + // Validate unique points, and write to OBJ + if (faceIndex1 != faceIndex2 && faceIndex1 != faceIndex3 && faceIndex2 != faceIndex3) { - // Face Indices - var faceIndex1 = indices[i * 3 + surface.FaceIndex] + surface.VertexIndex + 1; - var faceIndex2 = indices[i * 3 + surface.FaceIndex + 1] + surface.VertexIndex + 1; - var faceIndex3 = indices[i * 3 + surface.FaceIndex + 2] + surface.VertexIndex + 1; - - // Validate unique points, and write to OBJ - if (faceIndex1 != faceIndex2 && faceIndex1 != faceIndex3 && faceIndex2 != faceIndex3) - writer.WriteLine("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}", - faceIndex1, - faceIndex3, - faceIndex2); + // new Obj Face + var objFace = new WavefrontOBJ.Face(material.Name); + + // Add points + objFace.Vertices[0] = new WavefrontOBJ.Face.Vertex(faceIndex1, faceIndex1, faceIndex1); + objFace.Vertices[2] = new WavefrontOBJ.Face.Vertex(faceIndex2, faceIndex2, faceIndex2); + objFace.Vertices[1] = new WavefrontOBJ.Face.Vertex(faceIndex3, faceIndex3, faceIndex3); + + // Add to OBJ + obj.Faces.Add(objFace); } } } + // Save it + obj.Save(Path.ChangeExtension(gfxMapName, ".obj")); + + // Build search strinmg + string searchString = ""; + + // Loop through images, and append each to the search string (for Wraith/Greyhound) + foreach (string imageName in imageNames) + searchString += String.Format("{0},", Path.GetFileNameWithoutExtension(imageName)); + + // Dump it + File.WriteAllText(Path.ChangeExtension(gfxMapName, ".txt"), searchString); + // Done Printer.WriteLine("INFO", String.Format("Converted to OBJ in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); } @@ -147,15 +337,15 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l /// /// Reads Gfx Surfaces /// - public static GfxSurfaceGhosts[] ReadGfxSufaces(ProcessReader reader, long address, int count) + public static GfxSurface[] ReadGfxSufaces(ProcessReader reader, long address, int count) { // Preallocate short array - GfxSurfaceGhosts[] surfaces = new GfxSurfaceGhosts[count]; + GfxSurface[] surfaces = new GfxSurface[count]; // Loop number of indices we have for (int i = 0; i < count; i++) // Add it - surfaces[i] = reader.ReadStruct(address + i * 40); + surfaces[i] = reader.ReadStruct(address + i * 40); // Done return surfaces; @@ -180,48 +370,57 @@ public static ushort[] ReadGfxIndices(ProcessReader reader, long address, int co /// /// Reads Gfx Vertices /// - public static SEModelVertex[] ReadGfxVertices(ProcessReader reader, long address, int count) + public static Vertex[] ReadGfxVertices(ProcessReader reader, long address, int count) { // Preallocate vertex array - SEModelVertex[] vertices = new SEModelVertex[count]; + Vertex[] vertices = new Vertex[count]; // Read buffer var byteBuffer = reader.ReadBytes(address, count * 44); // Loop number of vertices we have for (int i = 0; i < count; i++) { - // Grab Offset - float x = BitConverter.ToSingle(byteBuffer, i * 44); - float y = BitConverter.ToSingle(byteBuffer, i * 44 + 4); - float z = BitConverter.ToSingle(byteBuffer, i * 44 + 8); - - // Grab UV - float u = BitConverter.ToSingle(byteBuffer, i * 44 + 20); - float v = BitConverter.ToSingle(byteBuffer, i * 44 + 24); - - // Grab Normal - int vertexNormal = BitConverter.ToInt32(byteBuffer, i * 44 + 36); + // Read Struct + var gfxVertex = ByteUtil.BytesToStruct(byteBuffer, i * 44); // Create new SEModel Vertex - vertices[i] = new SEModelVertex() + vertices[i] = new Vertex() { // Set offset Position = new Vector3( - x, - y, - z), + gfxVertex.X, + gfxVertex.Y, + gfxVertex.Z), // Decode and set normal (from DTZxPorter - Wraith, same as XModels) - VertexNormal = new Vector3( - (float)(((vertexNormal & 0x3FF) / 1023.0) * 2.0 - 1.0), - (float)((((vertexNormal >> 10) & 0x3FF) / 1023.0) * 2.0 - 1.0), - (float)((((vertexNormal >> 20) & 0x3FF) / 1023.0) * 2.0 - 1.0)), + Normal = VertexNormal.UnpackB(gfxVertex.Normal), + // Set UV + UV = new Vector2(gfxVertex.U, 1 - gfxVertex.V) }; - - // Set UV - vertices[i].UVSets.Add(new Vector2(u, v)); } // Done return vertices; } + + /// + /// Reads a material for the given surface and its associated images + /// + public static WavefrontOBJ.Material ReadMaterial(ProcessReader reader, long address) + { + // Read Material + var material = reader.ReadStruct(address); + // Create new OBJ Image + var objMaterial = new WavefrontOBJ.Material(Path.GetFileNameWithoutExtension(reader.ReadNullTerminatedString(reader.ReadInt64(address)).Replace("*", ""))); + // Loop over images + for (byte i = 0; i < material.ImageCount; i++) + { + // Read Material Image + var materialImage = reader.ReadStruct(material.ImageTablePointer + i * Marshal.SizeOf()); + // Check for color map for now + if (materialImage.SemanticHash == 0xA0AB1041) + objMaterial.DiffuseMap = "_images\\\\" + reader.ReadNullTerminatedString(reader.ReadInt64(materialImage.ImagePointer + 96)) + ".png"; + } + // Done + return objMaterial; + } } } \ No newline at end of file diff --git a/src/Husky/Husky/Games/InfiniteWarfare.cs b/src/Husky/Husky/Games/InfiniteWarfare.cs deleted file mode 100644 index 6527029..0000000 --- a/src/Husky/Husky/Games/InfiniteWarfare.cs +++ /dev/null @@ -1,222 +0,0 @@ -using PhilLibX; -using PhilLibX.IO; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using SELib; -using SELib.Utilities; -using System.Diagnostics; - - -namespace Husky -{ - class InfiniteWarfare - { - /// - /// Entity Map Keys - /// - public static Dictionary EntityMapKeys = new Dictionary() - { - { "170", "classname" }, - { "738", "origin" }, - { "65", "angles" }, - { "1395", "_color" }, - { "1188", "target" }, - { "1190", "targetname" }, - { "668", "model" }, - { "22554", "model_scale" }, - }; - - /// - /// Reads Modern Warfare RM BSP Data - /// - public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress) - { - // Found her - Printer.WriteLine("INFO", "Found supported game: Call of Duty: Ghosts"); - - // Validate by XModel Name - if (reader.ReadNullTerminatedString(reader.ReadInt64(reader.ReadInt64(assetPoolsAddress + 0x20) + 8)) == "void") - { - // Load BSP Pools (they only have a size of 1 so we don't care about reading more than 1) - var gfxMapAsset = reader.ReadStruct(reader.ReadInt64(assetPoolsAddress + 0xD0)); - - // Name - string gfxMapName = reader.ReadNullTerminatedString(gfxMapAsset.NamePointer); - string mapName = reader.ReadNullTerminatedString(gfxMapAsset.MapNamePointer); - - // Verify a BSP is actually loaded (if in base menu, etc, no map is loaded) - if (String.IsNullOrWhiteSpace(gfxMapName)) - { - Printer.WriteLine("ERROR", "No BSP loaded. Enter Main Menu or a Map to load in the required assets.", ConsoleColor.DarkRed); - } - else - { - // Print Info - Printer.WriteLine("INFO", String.Format("Loaded Gfx Map - {0}", gfxMapName)); - Printer.WriteLine("INFO", String.Format("Loaded Map - {0}", mapName)); - Printer.WriteLine("INFO", String.Format("Vertex Count - {0}", gfxMapAsset.GfxVertexCount)); - Printer.WriteLine("INFO", String.Format("Indices Count - {0}", gfxMapAsset.GfxIndicesCount)); - Printer.WriteLine("INFO", String.Format("Surface Count - {0}", gfxMapAsset.SurfaceCount)); - - // Stop watch - var stopWatch = Stopwatch.StartNew(); - - // Read Vertices - Printer.WriteLine("INFO", "Parsing vertex data...."); - var vertices = ReadGfxVertices(reader, gfxMapAsset.GfxVerticesPointer, (int)gfxMapAsset.GfxVertexCount); - Printer.WriteLine("INFO", String.Format("Parsed vertex data in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); - - // Reset timer - stopWatch.Restart(); - - // Read Indices - Printer.WriteLine("INFO", "Parsing surface indices...."); - var indices = ReadGfxIndices(reader, gfxMapAsset.GfxIndicesPointer, (int)gfxMapAsset.GfxIndicesCount); - Printer.WriteLine("INFO", String.Format("Parsed indices in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); - - // Reset timer - stopWatch.Restart(); - - // Read Indices - Printer.WriteLine("INFO", "Parsing surfaces...."); - var surfaces = ReadGfxSufaces(reader, gfxMapAsset.GfxSurfacesPointer, gfxMapAsset.SurfaceCount); - Printer.WriteLine("INFO", String.Format("Parsed surfaces in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); - - // Reset timer - stopWatch.Restart(); - - // Write SEModel - Printer.WriteLine("INFO", "Converting to OBJ...."); - - // Create Dir - Directory.CreateDirectory(Path.GetDirectoryName(gfxMapName)); - - // Create OBJ output - using (StreamWriter writer = new StreamWriter(Path.ChangeExtension(gfxMapName, ".obj"))) - { - // Dump vertex data - foreach (var vertex in vertices) - { - writer.WriteLine("v {0} {1} {2}", vertex.Position.X, vertex.Position.Y, vertex.Position.Z); - writer.WriteLine("vn {0} {1} {2}", vertex.VertexNormal.X, vertex.VertexNormal.Y, vertex.VertexNormal.Z); - writer.WriteLine("vt {0} {1}", vertex.UVSets[0].X, vertex.UVSets[0].Y); - } - - // Dump Surfaces - foreach (var surface in surfaces) - { - // Get Material Name, purge any prefixes and Auto-Gen star characters - var materialName = Path.GetFileNameWithoutExtension(reader.ReadNullTerminatedString(reader.ReadInt64(surface.MaterialPointer)).Replace("*", "")); - - // Write MTL and Group - writer.WriteLine("g {0}", materialName); - writer.WriteLine("usemtl {0}", materialName); - // Add points - for (ushort i = 0; i < surface.FaceCount; i++) - { - // Face Indices - var faceIndex1 = indices[i * 3 + surface.FaceIndex] + surface.VertexIndex + 1; - var faceIndex2 = indices[i * 3 + surface.FaceIndex + 1] + surface.VertexIndex + 1; - var faceIndex3 = indices[i * 3 + surface.FaceIndex + 2] + surface.VertexIndex + 1; - - // Validate unique points, and write to OBJ - if (faceIndex1 != faceIndex2 && faceIndex1 != faceIndex3 && faceIndex2 != faceIndex3) - writer.WriteLine("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}", - faceIndex1, - faceIndex3, - faceIndex2); - } - } - } - - // Done - Printer.WriteLine("INFO", String.Format("Converted to OBJ in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); - } - - } - else - { - Printer.WriteLine("ERROR", "Call of Duty: Ghosts is supported, but this EXE is not.", ConsoleColor.Red); - } - } - - /// - /// Reads Gfx Surfaces - /// - public static GfxSurfaceGhosts[] ReadGfxSufaces(ProcessReader reader, long address, int count) - { - // Preallocate short array - GfxSurfaceGhosts[] surfaces = new GfxSurfaceGhosts[count]; - - // Loop number of indices we have - for (int i = 0; i < count; i++) - // Add it - surfaces[i] = reader.ReadStruct(address + i * 40); - - // Done - return surfaces; - } - - - /// - /// Reads Gfx Vertex Indices - /// - public static ushort[] ReadGfxIndices(ProcessReader reader, long address, int count) - { - // Preallocate short array - ushort[] indices = new ushort[count]; - - // Loop number of indices we have - for (int i = 0; i < count; i++) - // Add 16bit index - indices[i] = reader.ReadUInt16(address + i * 2); - - // Done - return indices; - } - - /// - /// Reads Gfx Vertices from MWR - /// - public static SEModelVertex[] ReadGfxVertices(ProcessReader reader, long address, int count) - { - // Preallocate vertex array - SEModelVertex[] vertices = new SEModelVertex[count]; - - // Loop number of vertices we have - for (int i = 0; i < count; i++) - { - // Read Gfx Vertex - var gfxVertex = reader.ReadStruct(address + i * 44); - - // Create new SEModel Vertex - vertices[i] = new SEModelVertex() - { - // Set offset - Position = new Vector3( - gfxVertex.X, - gfxVertex.Y, - gfxVertex.Z), - // Decode and set normal (from DTZxPorter - Wraith, same as XModels) - VertexNormal = new Vector3( - (float)(((gfxVertex.Normal & 0x3FF) / 1023.0) * 2.0 - 1.0), - (float)((((gfxVertex.Normal >> 10) & 0x3FF) / 1023.0) * 2.0 - 1.0), - (float)((((gfxVertex.Normal >> 20) & 0x3FF) / 1023.0) * 2.0 - 1.0)), - - }; - - // Set UV - vertices[i].UVSets.Add(new Vector2(gfxVertex.U, gfxVertex.V)); - } - - // Done - return vertices; - } - } -} \ No newline at end of file diff --git a/src/Husky/Husky/Games/ModernWarfare2.cs b/src/Husky/Husky/Games/ModernWarfare2.cs index b66439d..f60e09e 100644 --- a/src/Husky/Husky/Games/ModernWarfare2.cs +++ b/src/Husky/Husky/Games/ModernWarfare2.cs @@ -19,9 +19,9 @@ using PhilLibX.IO; using System; using System.IO; -using SELib; -using SELib.Utilities; using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Collections.Generic; namespace Husky { @@ -30,6 +30,146 @@ namespace Husky /// class ModernWarfare2 { + /// + /// MW2 GfxMap Asset (some pointers we skip over point to DirectX routines, etc. if that means anything to anyone) + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct GfxMap + { + /// + /// A pointer to the name of this GfxMap Asset + /// + public int NamePointer { get; set; } + + /// + /// A pointer to the name of the map + /// + public int MapNamePointer { get; set; } + + /// + /// Unknown Bytes (Possibly counts for other data we don't care about) + /// + public fixed byte Padding[8]; + + /// + /// Number of Surfaces + /// + public int SurfaceCount { get; set; } + + /// + /// Unknown Bytes (Possibly counts, pointers, etc. for other data we don't care about) + /// + public fixed byte Padding1[0x64]; + + /// + /// Number of Gfx Vertices (XYZ, etc.) + /// + public int GfxVertexCount { get; set; } + + /// + /// Pointer to the Gfx Vertex Data + /// + public int GfxVerticesPointer { get; set; } + + /// + /// Unknown Bytes (more BSP data we probably don't care for) + /// + public fixed byte Padding2[0x10]; + + /// + /// Number of Gfx Indices (for Faces) + /// + public int GfxIndicesCount { get; set; } + + /// + /// Pointer to the Gfx Index Data + /// + public int GfxIndicesPointer { get; set; } + + /// + /// Unknown Bytes (more BSP data we probably don't care for) + /// + public fixed byte Padding3[0x184]; + + /// + /// Pointer to the Gfx Index Data + /// + public int GfxSurfacesPointer { get; set; } + } + + /// + /// Gfx Map Surface + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct GfxSurface + { + /// + /// Unknown Int (I know which pointer in the GfxMap it correlates it, but doesn't seem to interest us) + /// + public int UnknownBaseIndex { get; set; } + + /// + /// Base Vertex Index (this is what allows the GfxMap to have 65k+ verts with only 2 byte indices) + /// + public int VertexIndex { get; set; } + + /// + /// Number of Vertices this surface has + /// + public ushort VertexCount { get; set; } + + /// + /// Number of Faces this surface has + /// + public ushort FaceCount { get; set; } + + /// + /// Base Face Index (this is what allows the GfxMap to have 65k+ faces with only 2 byte indices) + /// + public int FaceIndex { get; set; } + + /// + /// Pointer to the Material Asset of this Surface + /// + public int MaterialPointer { get; set; } + + /// + /// Unknown Bytes + /// + public fixed byte Padding[4]; + } + + /// + /// Call of Duty: Modern Warfare 2 Material Asset + /// + public unsafe struct Material + { + /// + /// A pointer to the name of this material + /// + public int NamePointer { get; set; } + + /// + /// Unknown Bytes (Flags, settings, etc.) + /// + public fixed byte UnknownBytes[0x44]; + + /// + /// Number of Images this Material has + /// + public byte ImageCount { get; set; } + + /// + /// Unknown Bytes (Flags, settings, etc.) + /// + public fixed byte UnknownBytes1[0xB]; + + /// + /// A pointer to this Material's Image table + /// + public int ImageTablePointer { get; set; } + } + /// /// Reads BSP Data /// @@ -41,7 +181,7 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l if (reader.ReadNullTerminatedString(reader.ReadInt32(reader.ReadInt32(assetPoolsAddress + 0x10) + 4)) == "void") { // Load BSP Pools (they only have a size of 1 so we have no free header) - var gfxMapAsset = reader.ReadStruct(reader.ReadInt32(assetPoolsAddress + 0x54)); + var gfxMapAsset = reader.ReadStruct(reader.ReadInt32(assetPoolsAddress + 0x54)); // Name string gfxMapName = reader.ReadNullTerminatedString(gfxMapAsset.NamePointer); @@ -88,50 +228,70 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l // Reset timer stopWatch.Restart(); - // Write SEModel + // Write OBJ Printer.WriteLine("INFO", "Converting to OBJ...."); - // Create Dir - Directory.CreateDirectory(Path.GetDirectoryName(gfxMapName)); + // Create new OBJ + var obj = new WavefrontOBJ(); - // Create OBJ output - using (StreamWriter writer = new StreamWriter(Path.ChangeExtension(gfxMapName, ".obj"))) + // Append Vertex Data + foreach (var vertex in vertices) { - // Dump vertex data - foreach (var vertex in vertices) - { - writer.WriteLine("v {0} {1} {2}", vertex.Position.X, vertex.Position.Y, vertex.Position.Z); - writer.WriteLine("vn {0} {1} {2}", vertex.VertexNormal.X, vertex.VertexNormal.Y, vertex.VertexNormal.Z); - writer.WriteLine("vt {0} {1}", vertex.UVSets[0].X, vertex.UVSets[0].Y); - } + obj.Vertices.Add(vertex.Position); + obj.Normals.Add(vertex.Normal); + obj.UVs.Add(vertex.UV); + } - // Dump Surfaces - foreach (var surface in surfaces) + // Image Names (for Search String) + HashSet imageNames = new HashSet(); + + // Append Faces + foreach (var surface in surfaces) + { + // Create new Material + var material = ReadMaterial(reader, surface.MaterialPointer); + // Add to images + imageNames.Add(material.DiffuseMap); + // Add it + obj.AddMaterial(material); + // Add points + for (ushort i = 0; i < surface.FaceCount; i++) { - // Get Material Name, purge any prefixes and Auto-Gen star characters - var materialName = Path.GetFileNameWithoutExtension(reader.ReadNullTerminatedString(reader.ReadInt32(surface.MaterialPointer)).Replace("*", "")); - - // Write MTL and Group - writer.WriteLine("g {0}", materialName); - writer.WriteLine("usemtl {0}", materialName); - // Add points - for (ushort i = 0; i < surface.FaceCount; i++) + // Face Indices + var faceIndex1 = indices[i * 3 + surface.FaceIndex] + surface.VertexIndex; + var faceIndex2 = indices[i * 3 + surface.FaceIndex + 1] + surface.VertexIndex; + var faceIndex3 = indices[i * 3 + surface.FaceIndex + 2] + surface.VertexIndex; + + // Validate unique points, and write to OBJ + if (faceIndex1 != faceIndex2 && faceIndex1 != faceIndex3 && faceIndex2 != faceIndex3) { - // Face Indices - var faceIndex1 = indices[i * 3 + surface.FaceIndex] + surface.VertexIndex + 1; - var faceIndex2 = indices[i * 3 + surface.FaceIndex + 1] + surface.VertexIndex + 1; - var faceIndex3 = indices[i * 3 + surface.FaceIndex + 2] + surface.VertexIndex + 1; - - // Validate unique points, and write to OBJ - if (faceIndex1 != faceIndex2 && faceIndex1 != faceIndex3 && faceIndex2 != faceIndex3) - writer.WriteLine("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}", - faceIndex1, - faceIndex3, - faceIndex2); + // new Obj Face + var objFace = new WavefrontOBJ.Face(material.Name); + + // Add points + objFace.Vertices[0] = new WavefrontOBJ.Face.Vertex(faceIndex1, faceIndex1, faceIndex1); + objFace.Vertices[2] = new WavefrontOBJ.Face.Vertex(faceIndex2, faceIndex2, faceIndex2); + objFace.Vertices[1] = new WavefrontOBJ.Face.Vertex(faceIndex3, faceIndex3, faceIndex3); + + // Add to OBJ + obj.Faces.Add(objFace); } } } + // Save it + obj.Save(Path.ChangeExtension(gfxMapName, ".obj")); + + // Build search strinmg + string searchString = ""; + + // Loop through images, and append each to the search string (for Wraith/Greyhound) + foreach (string imageName in imageNames) + searchString += String.Format("{0},", Path.GetFileNameWithoutExtension(imageName)); + + // Dump it + File.WriteAllText(Path.ChangeExtension(gfxMapName, ".txt"), searchString); + // Done Printer.WriteLine("INFO", String.Format("Converted to OBJ in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); } @@ -146,15 +306,15 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l /// /// Reads Gfx Surfaces /// - public static GfxSurfaceMW3[] ReadGfxSufaces(ProcessReader reader, long address, int count) + public static GfxSurface[] ReadGfxSufaces(ProcessReader reader, long address, int count) { // Preallocate short array - GfxSurfaceMW3[] surfaces = new GfxSurfaceMW3[count]; + GfxSurface[] surfaces = new GfxSurface[count]; // Loop number of indices we have for (int i = 0; i < count; i++) // Add it - surfaces[i] = reader.ReadStruct(address + i * 24); + surfaces[i] = reader.ReadStruct(address + i * 24); // Done return surfaces; @@ -178,52 +338,57 @@ public static ushort[] ReadGfxIndices(ProcessReader reader, long address, int co /// /// Reads Gfx Vertices /// - public static SEModelVertex[] ReadGfxVertices(ProcessReader reader, long address, int count) + public static Vertex[] ReadGfxVertices(ProcessReader reader, long address, int count) { // Preallocate vertex array - SEModelVertex[] vertices = new SEModelVertex[count]; + Vertex[] vertices = new Vertex[count]; // Read buffer var byteBuffer = reader.ReadBytes(address, count * 44); // Loop number of vertices we have for (int i = 0; i < count; i++) { - // Grab Offset - float x = BitConverter.ToSingle(byteBuffer, i * 44); - float y = BitConverter.ToSingle(byteBuffer, i * 44 + 4); - float z = BitConverter.ToSingle(byteBuffer, i * 44 + 8); - - // Grab UV - float u = BitConverter.ToSingle(byteBuffer, i * 44 + 20); - float v = BitConverter.ToSingle(byteBuffer, i * 44 + 24); - - // Grab Normal - int vertexNormal = BitConverter.ToInt32(byteBuffer, i * 44 + 36); - - // Decode the scale of the vector - float DecodeScale = (float)((float)((vertexNormal & 0xFF000000) >> 24) - -192.0) / 32385.0f; + // Read Struct + var gfxVertex = ByteUtil.BytesToStruct(byteBuffer, i * 44); // Create new SEModel Vertex - vertices[i] = new SEModelVertex() + vertices[i] = new Vertex() { // Set offset Position = new Vector3( - x, - y, - z), + gfxVertex.X, + gfxVertex.Y, + gfxVertex.Z), // Decode and set normal (from DTZxPorter - Wraith, same as XModels) - VertexNormal = new Vector3( - (float)((float)(vertexNormal & 0xFF) - 127.0) * DecodeScale, - (float)((float)((vertexNormal & 0xFF00) >> 8) - 127.0) * DecodeScale, - (float)((float)((vertexNormal & 0xFF0000) >> 16) - 127.0) * DecodeScale) - + Normal = VertexNormal.UnpackA(gfxVertex.Normal), + // Set UV + UV = new Vector2(gfxVertex.U, 1 - gfxVertex.V) }; - - // Set UV - vertices[i].UVSets.Add(new Vector2(u, v)); } // Done return vertices; } + + /// + /// Reads a material for the given surface and its associated images + /// + public static WavefrontOBJ.Material ReadMaterial(ProcessReader reader, long address) + { + // Read Material + var material = reader.ReadStruct(address); + // Create new OBJ Image + var objMaterial = new WavefrontOBJ.Material(Path.GetFileNameWithoutExtension(reader.ReadNullTerminatedString(reader.ReadInt32(address)).Replace("*", ""))); + // Loop over images + for (byte i = 0; i < material.ImageCount; i++) + { + // Read Material Image + var materialImage = reader.ReadStruct(material.ImageTablePointer + i * Marshal.SizeOf()); + // Check for color map for now + if (materialImage.SemanticHash == 0xA0AB1041) + objMaterial.DiffuseMap = "_images\\\\" + reader.ReadNullTerminatedString(reader.ReadInt32(materialImage.ImagePointer + 0x1C)) + ".png"; + } + // Done + return objMaterial; + } } } \ No newline at end of file diff --git a/src/Husky/Husky/Games/ModernWarfare3.cs b/src/Husky/Husky/Games/ModernWarfare3.cs index f9fc420..9b80b48 100644 --- a/src/Husky/Husky/Games/ModernWarfare3.cs +++ b/src/Husky/Husky/Games/ModernWarfare3.cs @@ -19,9 +19,9 @@ using PhilLibX.IO; using System; using System.IO; -using SELib; -using SELib.Utilities; using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Collections.Generic; namespace Husky { @@ -30,6 +30,104 @@ namespace Husky /// class ModernWarfare3 { + /// + /// MW3 GfxMap Asset (some pointers we skip over point to DirectX routines, etc. if that means anything to anyone) + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct GfxMap + { + /// + /// A pointer to the name of this GfxMap Asset + /// + public int NamePointer { get; set; } + + /// + /// A pointer to the name of the map + /// + public int MapNamePointer { get; set; } + + /// + /// Unknown Bytes (Possibly counts for other data we don't care about) + /// + public fixed byte Padding[8]; + + /// + /// Number of Surfaces + /// + public int SurfaceCount { get; set; } + + /// + /// Unknown Bytes (Possibly counts, pointers, etc. for other data we don't care about) + /// + public fixed byte Padding1[0x70]; + + /// + /// Number of Gfx Vertices (XYZ, etc.) + /// + public int GfxVertexCount { get; set; } + + /// + /// Pointer to the Gfx Vertex Data + /// + public int GfxVerticesPointer { get; set; } + + /// + /// Unknown Bytes (more BSP data we probably don't care for) + /// + public fixed byte Padding2[0x10]; + + /// + /// Number of Gfx Indices (for Faces) + /// + public int GfxIndicesCount { get; set; } + + /// + /// Pointer to the Gfx Index Data + /// + public int GfxIndicesPointer { get; set; } + + /// + /// Unknown Bytes (more BSP data we probably don't care for) + /// + public fixed byte Padding3[0x184]; + + /// + /// Pointer to the Gfx Index Data + /// + public int GfxSurfacesPointer { get; set; } + } + + /// + /// Call of Duty: Modern Warfare 3 Material Asset + /// + public unsafe struct Material + { + /// + /// A pointer to the name of this material + /// + public int NamePointer { get; set; } + + /// + /// Unknown Bytes (Flags, settings, etc.) + /// + public fixed byte UnknownBytes[0x4A]; + + /// + /// Number of Images this Material has + /// + public byte ImageCount { get; set; } + + /// + /// Unknown Bytes (Flags, settings, etc.) + /// + public fixed byte UnknownBytes1[9]; + + /// + /// A pointer to this Material's Image table + /// + public int ImageTablePointer { get; set; } + } + /// /// Reads BSP Data /// @@ -41,7 +139,7 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l if (reader.ReadNullTerminatedString(reader.ReadInt32(reader.ReadInt32(assetPoolsAddress + 0x10) + 4)) == "void") { // Load BSP Pools (they only have a size of 1 so we have no free header) - var gfxMapAsset = reader.ReadStruct(reader.ReadInt32(assetPoolsAddress + 0x54)); + var gfxMapAsset = reader.ReadStruct(reader.ReadInt32(assetPoolsAddress + 0x54)); // Name string gfxMapName = reader.ReadNullTerminatedString(gfxMapAsset.NamePointer); @@ -88,52 +186,70 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l // Reset timer stopWatch.Restart(); - // Write SEModel + // Write OBJ Printer.WriteLine("INFO", "Converting to OBJ...."); - // Create Dir - Directory.CreateDirectory(Path.GetDirectoryName(gfxMapName)); + // Create new OBJ + var obj = new WavefrontOBJ(); - // Create OBJ output - using (StreamWriter writer = new StreamWriter(Path.ChangeExtension(gfxMapName, ".obj"))) + // Append Vertex Data + foreach (var vertex in vertices) { - // Dump vertex data - foreach (var vertex in vertices) - { - writer.WriteLine("v {0} {1} {2}", vertex.Position.X, vertex.Position.Y, vertex.Position.Z); - writer.WriteLine("vn {0} {1} {2}", vertex.VertexNormal.X, vertex.VertexNormal.Y, vertex.VertexNormal.Z); - writer.WriteLine("vt {0} {1}", vertex.UVSets[0].X, vertex.UVSets[0].Y); - } + obj.Vertices.Add(vertex.Position); + obj.Normals.Add(vertex.Normal); + obj.UVs.Add(vertex.UV); + } - // Dump Surfaces - foreach (var surface in surfaces) + // Image Names (for Search String) + HashSet imageNames = new HashSet(); + + // Append Faces + foreach (var surface in surfaces) + { + // Create new Material + var material = ReadMaterial(reader, surface.MaterialPointer); + // Add to images + imageNames.Add(material.DiffuseMap); + // Add it + obj.AddMaterial(material); + // Add points + for (ushort i = 0; i < surface.FaceCount; i++) { - // Get Material Name, purge any prefixes and Auto-Gen star characters - var materialName = Path.GetFileNameWithoutExtension(reader.ReadNullTerminatedString(reader.ReadInt32(surface.MaterialPointer)).Replace("*", "")); - - // Write MTL and Group - writer.WriteLine("g {0}", materialName); - writer.WriteLine("usemtl {0}", materialName); - // Add points - for (ushort i = 0; i < surface.FaceCount; i++) - { + // Face Indices + var faceIndex1 = indices[i * 3 + surface.FaceIndex] + surface.VertexIndex; + var faceIndex2 = indices[i * 3 + surface.FaceIndex + 1] + surface.VertexIndex; + var faceIndex3 = indices[i * 3 + surface.FaceIndex + 2] + surface.VertexIndex; + // Validate unique points, and write to OBJ + if (faceIndex1 != faceIndex2 && faceIndex1 != faceIndex3 && faceIndex2 != faceIndex3) + { + // new Obj Face + var objFace = new WavefrontOBJ.Face(material.Name); - // Face Indices - var faceIndex1 = indices[i * 3 + surface.FaceIndex] + surface.VertexIndex + 1; - var faceIndex2 = indices[i * 3 + surface.FaceIndex + 1] + surface.VertexIndex + 1; - var faceIndex3 = indices[i * 3 + surface.FaceIndex + 2] + surface.VertexIndex + 1; + // Add points + objFace.Vertices[0] = new WavefrontOBJ.Face.Vertex(faceIndex1, faceIndex1, faceIndex1); + objFace.Vertices[2] = new WavefrontOBJ.Face.Vertex(faceIndex2, faceIndex2, faceIndex2); + objFace.Vertices[1] = new WavefrontOBJ.Face.Vertex(faceIndex3, faceIndex3, faceIndex3); - // Validate unique points, and write to OBJ - if (faceIndex1 != faceIndex2 && faceIndex1 != faceIndex3 && faceIndex2 != faceIndex3) - writer.WriteLine("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}", - faceIndex1, - faceIndex3, - faceIndex2); + // Add to OBJ + obj.Faces.Add(objFace); } } } + // Save it + obj.Save(Path.ChangeExtension(gfxMapName, ".obj")); + + // Build search strinmg + string searchString = ""; + + // Loop through images, and append each to the search string (for Wraith/Greyhound) + foreach (string imageName in imageNames) + searchString += String.Format("{0},", Path.GetFileNameWithoutExtension(imageName)); + + // Dump it + File.WriteAllText(Path.ChangeExtension(gfxMapName, ".txt"), searchString); + // Done Printer.WriteLine("INFO", String.Format("Converted to OBJ in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); } @@ -148,14 +264,14 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l /// /// Reads Gfx Surfaces /// - public static GfxSurfaceMW3[] ReadGfxSufaces(ProcessReader reader, long address, int count) + public static ModernWarfare2.GfxSurface[] ReadGfxSufaces(ProcessReader reader, long address, int count) { // Preallocate short array - GfxSurfaceMW3[] surfaces = new GfxSurfaceMW3[count]; + ModernWarfare2.GfxSurface[] surfaces = new ModernWarfare2.GfxSurface[count]; // Loop number of indices we have for (int i = 0; i < count; i++) // Add it - surfaces[i] = reader.ReadStruct(address + i * 24); + surfaces[i] = reader.ReadStruct(address + i * 24); // Done return surfaces; } @@ -179,52 +295,57 @@ public static ushort[] ReadGfxIndices(ProcessReader reader, long address, int co /// /// Reads Gfx Vertices /// - public static SEModelVertex[] ReadGfxVertices(ProcessReader reader, long address, int count) + public static Vertex[] ReadGfxVertices(ProcessReader reader, long address, int count) { // Preallocate vertex array - SEModelVertex[] vertices = new SEModelVertex[count]; + Vertex[] vertices = new Vertex[count]; // Read buffer var byteBuffer = reader.ReadBytes(address, count * 44); // Loop number of vertices we have for (int i = 0; i < count; i++) { - // Grab Offset - float x = BitConverter.ToSingle(byteBuffer, i * 44); - float y = BitConverter.ToSingle(byteBuffer, i * 44 + 4); - float z = BitConverter.ToSingle(byteBuffer, i * 44 + 8); - - // Grab UV - float u = BitConverter.ToSingle(byteBuffer, i * 44 + 20); - float v = BitConverter.ToSingle(byteBuffer, i * 44 + 24); - - // Grab Normal - int vertexNormal = BitConverter.ToInt32(byteBuffer, i * 44 + 36); - - // Decode the scale of the vector - float DecodeScale = (float)((float)((vertexNormal & 0xFF000000) >> 24) - -192.0) / 32385.0f; + // Read Struct + var gfxVertex = ByteUtil.BytesToStruct(byteBuffer, i * 44); // Create new SEModel Vertex - vertices[i] = new SEModelVertex() + vertices[i] = new Vertex() { // Set offset Position = new Vector3( - x, - y, - z), + gfxVertex.X, + gfxVertex.Y, + gfxVertex.Z), // Decode and set normal (from DTZxPorter - Wraith, same as XModels) - VertexNormal = new Vector3( - (float)((float)(vertexNormal & 0xFF) - 127.0) * DecodeScale, - (float)((float)((vertexNormal & 0xFF00) >> 8) - 127.0) * DecodeScale, - (float)((float)((vertexNormal & 0xFF0000) >> 16) - 127.0) * DecodeScale) - + Normal = VertexNormal.UnpackA(gfxVertex.Normal), + // Set UV + UV = new Vector2(gfxVertex.U, 1 - gfxVertex.V) }; - - // Set UV - vertices[i].UVSets.Add(new Vector2(u, v)); } // Done return vertices; } + + /// + /// Reads a material for the given surface and its associated images + /// + public static WavefrontOBJ.Material ReadMaterial(ProcessReader reader, long address) + { + // Read Material + var material = reader.ReadStruct(address); + // Create new OBJ Image + var objMaterial = new WavefrontOBJ.Material(Path.GetFileNameWithoutExtension(reader.ReadNullTerminatedString(reader.ReadInt32(address)).Replace("*", ""))); + // Loop over images + for (byte i = 0; i < material.ImageCount; i++) + { + // Read Material Image + var materialImage = reader.ReadStruct(material.ImageTablePointer + i * Marshal.SizeOf()); + // Check for color map for now + if (materialImage.SemanticHash == 0xA0AB1041) + objMaterial.DiffuseMap = "_images\\\\" + reader.ReadNullTerminatedString(reader.ReadInt32(materialImage.ImagePointer + 0x1C)) + ".png"; + } + // Done + return objMaterial; + } } } \ No newline at end of file diff --git a/src/Husky/Husky/Games/ModernWarfareRM.cs b/src/Husky/Husky/Games/ModernWarfareRM.cs index 2ac2df7..c880018 100644 --- a/src/Husky/Husky/Games/ModernWarfareRM.cs +++ b/src/Husky/Husky/Games/ModernWarfareRM.cs @@ -19,9 +19,9 @@ using PhilLibX.IO; using System; using System.IO; -using SELib; -using SELib.Utilities; using System.Diagnostics; +using System.Collections.Generic; +using System.Runtime.InteropServices; namespace Husky { @@ -30,6 +30,181 @@ namespace Husky /// public class ModernWarfareRM { + /// + /// MWR GfxMap Asset (some pointers we skip over point to DirectX routines, etc. if that means anything to anyone) + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct GfxMap + { + /// + /// A pointer to the name of this GfxMap Asset + /// + public long NamePointer { get; set; } + + /// + /// A pointer to the name of the map + /// + public long MapNamePointer { get; set; } + + /// + /// Unknown Bytes (Possibly counts for other data we don't care about) + /// + public fixed byte Padding[0xC]; + + /// + /// Number of Surfaces + /// + public int SurfaceCount { get; set; } + + /// + /// Unknown Bytes (Possibly counts, pointers, etc. for other data we don't care about) + /// + public fixed byte Padding1[0x110]; + + /// + /// Number of Gfx Vertices (XYZ, etc.) + /// + public long GfxVertexCount { get; set; } + + /// + /// Pointer to the Gfx Vertex Data + /// + public long GfxVerticesPointer { get; set; } + + /// + /// Unknown Bytes (more BSP data we probably don't care for) + /// + public fixed byte Padding2[0x30]; + + /// + /// Number of Gfx Indices (for Faces) + /// + public long GfxIndicesCount { get; set; } + + /// + /// Pointer to the Gfx Index Data + /// + public long GfxIndicesPointer { get; set; } + + /// + /// Points, etc. + /// + public fixed byte Padding3[0x5C8]; + + /// + /// Number of Static Models + /// + public long StaticModelsCount { get; set; } + + /// + /// Unknown Bytes (more BSP data we probably don't care for) + /// + public fixed byte Padding4[0x290]; + + /// + /// Pointer to the Gfx Index Data + /// + public long GfxSurfacesPointer { get; set; } + } + + /// + /// Gfx Map Surface + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct GfxSurface + { + /// + /// Unknown Int (I know which pointer in the GfxMap it correlates it, but doesn't seem to interest us) + /// + public int UnknownBaseIndex { get; set; } + + /// + /// Base Vertex Index (this is what allows the GfxMap to have 65k+ verts with only 2 byte indices) + /// + public int VertexIndex { get; set; } + + /// + /// Unknown Bytes (Possibly color? And vertex count, along with some float that might be size) + /// + public fixed byte Padding[8]; + + /// + /// Number of Vertices this surface has + /// + public ushort VertexCount { get; set; } + + /// + /// Number of Faces this surface has + /// + public ushort FaceCount { get; set; } + + /// + /// Base Face Index (this is what allows the GfxMap to have 65k+ faces with only 2 byte indices) + /// + public int FaceIndex { get; set; } + + /// + /// Pointer to the Material Asset of this Surface + /// + public long MaterialPointer { get; set; } + + /// + /// Unknown Bytes + /// + public fixed byte Padding2[8]; + } + + /// + /// Material Asset Info + /// + public unsafe struct Material + { + /// + /// A pointer to the name of this material + /// + public long NamePointer { get; set; } + + /// + /// Unknown Bytes (Flags, settings, etc.) + /// + public fixed byte UnknownBytes[0x118]; + + /// + /// Number of Images this Material has + /// + public byte ImageCount { get; set; } + + /// + /// Unknown Bytes (Flags, settings, etc.) + /// + public fixed byte UnknownBytes1[0x7]; + + /// + /// A pointer to the Tech Set this Material uses + /// + public long TechniqueSetPointer { get; set; } + + /// + /// A pointer to this Material's Image table + /// + public long ImageTablePointer { get; set; } + + /// + /// Null Bytes + /// + public long Padding { get; set; } + + /// + /// UnknownPointer (Probably settings that changed based off TechSet) + /// + public long UnknownPointer { get; set; } + + /// + /// Unknown Bytes (Flags, settings, etc.) + /// + public fixed byte UnknownBytes2[0x108]; + } + /// /// Reads BSP Data /// @@ -42,7 +217,7 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l if (reader.ReadNullTerminatedString(reader.ReadInt64(reader.ReadInt64(reader.GetBaseAddress() + assetPoolsAddress + 0x38) + 8)) == "fx") { // Load BSP Pools (they only have a size of 1 so we don't care about reading more than 1) - var gfxMapAsset = reader.ReadStruct(reader.ReadInt64(reader.GetBaseAddress() + assetPoolsAddress + 0xF8)); + var gfxMapAsset = reader.ReadStruct(reader.ReadInt64(reader.GetBaseAddress() + assetPoolsAddress + 0xF8)); // Name string gfxMapName = reader.ReadNullTerminatedString(gfxMapAsset.NamePointer); @@ -89,51 +264,70 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l // Reset timer stopWatch.Restart(); - // Write SEModel + // Write OBJ Printer.WriteLine("INFO", "Converting to OBJ...."); - // Create Dir - Directory.CreateDirectory(Path.GetDirectoryName(gfxMapName)); + // Create new OBJ + var obj = new WavefrontOBJ(); - // Create OBJ output - using (StreamWriter writer = new StreamWriter(Path.ChangeExtension(gfxMapName, ".obj"))) + // Append Vertex Data + foreach (var vertex in vertices) { - // Dump vertex data - foreach (var vertex in vertices) - { - writer.WriteLine("v {0} {1} {2}", vertex.Position.X, vertex.Position.Y, vertex.Position.Z); - writer.WriteLine("vn {0} {1} {2}", vertex.VertexNormal.X, vertex.VertexNormal.Y, vertex.VertexNormal.Z); - writer.WriteLine("vt {0} {1}", vertex.UVSets[0].X, vertex.UVSets[0].Y); - } + obj.Vertices.Add(vertex.Position); + obj.Normals.Add(vertex.Normal); + obj.UVs.Add(vertex.UV); + } - // Dump Surfaces - foreach(var surface in surfaces) - { - // Get Material Name - var materialName = Path.GetFileNameWithoutExtension(reader.ReadNullTerminatedString(reader.ReadInt64(surface.MaterialPointer)).Replace("*", "")); + // Image Names (for Search String) + HashSet imageNames = new HashSet(); - // Write MTL and Group - writer.WriteLine("g {0}", materialName); - writer.WriteLine("usemtl {0}", materialName); + // Append Faces + foreach (var surface in surfaces) + { + // Create new Material + var material = ReadMaterial(reader, surface.MaterialPointer); + // Add to images + imageNames.Add(material.DiffuseMap); + // Add it + obj.AddMaterial(material); + // Add points + for (ushort i = 0; i < surface.FaceCount; i++) + { + // Face Indices + var faceIndex1 = indices[i * 3 + surface.FaceIndex] + surface.VertexIndex; + var faceIndex2 = indices[i * 3 + surface.FaceIndex + 1] + surface.VertexIndex; + var faceIndex3 = indices[i * 3 + surface.FaceIndex + 2] + surface.VertexIndex; - // Add points - for (ushort i = 0; i < surface.FaceCount; i++) + // Validate unique points, and write to OBJ + if (faceIndex1 != faceIndex2 && faceIndex1 != faceIndex3 && faceIndex2 != faceIndex3) { - // Face Indices - var faceIndex1 = indices[i * 3 + surface.FaceIndex] + surface.VertexIndex + 1; - var faceIndex2 = indices[i * 3 + surface.FaceIndex + 1] + surface.VertexIndex + 1; - var faceIndex3 = indices[i * 3 + surface.FaceIndex + 2] + surface.VertexIndex + 1; - - // Validate unique points, and write to OBJ - if(faceIndex1 != faceIndex2 && faceIndex1 != faceIndex3 && faceIndex2 != faceIndex3) - writer.WriteLine("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}", - faceIndex1, - faceIndex3, - faceIndex2); + // new Obj Face + var objFace = new WavefrontOBJ.Face(material.Name); + + // Add points + objFace.Vertices[0] = new WavefrontOBJ.Face.Vertex(faceIndex1, faceIndex1, faceIndex1); + objFace.Vertices[2] = new WavefrontOBJ.Face.Vertex(faceIndex2, faceIndex2, faceIndex2); + objFace.Vertices[1] = new WavefrontOBJ.Face.Vertex(faceIndex3, faceIndex3, faceIndex3); + + // Add to OBJ + obj.Faces.Add(objFace); } } } + // Save it + obj.Save(Path.ChangeExtension(gfxMapName, ".obj")); + + // Build search strinmg + string searchString = ""; + + // Loop through images, and append each to the search string (for Wraith/Greyhound) + foreach (string imageName in imageNames) + searchString += String.Format("{0},", Path.GetFileNameWithoutExtension(imageName)); + + // Dump it + File.WriteAllText(Path.ChangeExtension(gfxMapName, ".txt"), searchString); + // Done Printer.WriteLine("INFO", String.Format("Converted to OBJ in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); } @@ -148,15 +342,15 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l /// /// Reads Gfx Surfaces /// - public static GfxSurfaceMWR[] ReadGfxSufaces(ProcessReader reader, long address, int count) + public static GfxSurface[] ReadGfxSufaces(ProcessReader reader, long address, int count) { // Preallocate array - GfxSurfaceMWR[] surfaces = new GfxSurfaceMWR[count]; + GfxSurface[] surfaces = new GfxSurface[count]; // Loop number of indices we have for (int i = 0; i < count; i++) // Add it - surfaces[i] = reader.ReadStruct(address + i * 40); + surfaces[i] = reader.ReadStruct(address + i * 40); // Done return surfaces; @@ -181,48 +375,57 @@ public static ushort[] ReadGfxIndices(ProcessReader reader, long address, int co /// /// Reads Gfx Vertices /// - public static SEModelVertex[] ReadGfxVertices(ProcessReader reader, long address, int count) + public static Vertex[] ReadGfxVertices(ProcessReader reader, long address, int count) { // Preallocate vertex array - SEModelVertex[] vertices = new SEModelVertex[count]; + Vertex[] vertices = new Vertex[count]; // Read buffer var byteBuffer = reader.ReadBytes(address, count * 44); // Loop number of vertices we have for (int i = 0; i < count; i++) { - // Grab Offset - float x = BitConverter.ToSingle(byteBuffer, i * 44); - float y = BitConverter.ToSingle(byteBuffer, i * 44 + 4); - float z = BitConverter.ToSingle(byteBuffer, i * 44 + 8); - - // Grab UV - float u = BitConverter.ToSingle(byteBuffer, i * 44 + 20); - float v = BitConverter.ToSingle(byteBuffer, i * 44 + 24); - - // Grab Normal - int vertexNormal = BitConverter.ToInt32(byteBuffer, i * 44 + 36); + // Read Struct + var gfxVertex = ByteUtil.BytesToStruct(byteBuffer, i * 44); // Create new SEModel Vertex - vertices[i] = new SEModelVertex() + vertices[i] = new Vertex() { // Set offset Position = new Vector3( - x, - y, - z), + gfxVertex.X, + gfxVertex.Y, + gfxVertex.Z), // Decode and set normal (from DTZxPorter - Wraith, same as XModels) - VertexNormal = new Vector3( - (float)(((vertexNormal & 0x3FF) / 1023.0) * 2.0 - 1.0), - (float)((((vertexNormal >> 10) & 0x3FF) / 1023.0) * 2.0 - 1.0), - (float)((((vertexNormal >> 20) & 0x3FF) / 1023.0) * 2.0 - 1.0)), + Normal = VertexNormal.UnpackB(gfxVertex.Normal), + // Set UV + UV = new Vector2(gfxVertex.U, 1 - gfxVertex.V) }; - - // Set UV - vertices[i].UVSets.Add(new Vector2(u, v)); } // Done return vertices; } + + /// + /// Reads a material for the given surface and its associated images + /// + public static WavefrontOBJ.Material ReadMaterial(ProcessReader reader, long address) + { + // Read Material + var material = reader.ReadStruct(address); + // Create new OBJ Image + var objMaterial = new WavefrontOBJ.Material(Path.GetFileNameWithoutExtension(reader.ReadNullTerminatedString(reader.ReadInt64(address)).Replace("*", ""))); + // Loop over images + for (byte i = 0; i < material.ImageCount; i++) + { + // Read Material Image + var materialImage = reader.ReadStruct(material.ImageTablePointer + i * Marshal.SizeOf()); + // Check for color map for now + if (materialImage.SemanticHash == 0xA0AB1041) + objMaterial.DiffuseMap = "_images\\\\" + reader.ReadNullTerminatedString(reader.ReadInt64(materialImage.ImagePointer + 96)) + ".png"; + } + // Done + return objMaterial; + } } } diff --git a/src/Husky/Husky/Games/WorldAtWar.cs b/src/Husky/Husky/Games/WorldAtWar.cs index 36806bc..dfdd20d 100644 --- a/src/Husky/Husky/Games/WorldAtWar.cs +++ b/src/Husky/Husky/Games/WorldAtWar.cs @@ -19,9 +19,9 @@ using PhilLibX.IO; using System; using System.IO; -using SELib; -using SELib.Utilities; using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Collections.Generic; namespace Husky { @@ -30,6 +30,156 @@ namespace Husky /// class WorldatWar { + /// + /// WaW GfxMap Asset (some pointers we skip over point to DirectX routines, etc. if that means anything to anyone) + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct GfxMap + { + /// + /// A pointer to the name of this GfxMap Asset + /// + public int NamePointer { get; set; } + + /// + /// A pointer to the name of the map + /// + public int MapNamePointer { get; set; } + + /// + /// Unknown Bytes (Possibly counts for other data we don't care about) + /// + public fixed byte Padding[8]; + + /// + /// Number of Gfx Indices (for Faces) + /// + public int GfxIndicesCount { get; set; } + + /// + /// Pointer to the Gfx Index Data + /// + public int GfxIndicesPointer { get; set; } + + /// + /// Number of Surfaces + /// + public int SurfaceCount { get; set; } + + /// + /// Unknown Bytes (Possibly counts, pointers, etc. for other data we don't care about) + /// + public fixed byte Padding1[0x18]; + + /// + /// Number of Gfx Vertices (XYZ, etc.) + /// + public int GfxVertexCount { get; set; } + + /// + /// Pointer to the Gfx Vertex Data + /// + public int GfxVerticesPointer { get; set; } + + /// + /// Unknown Bytes (more BSP data we probably don't care for) + /// + public fixed byte Padding2[0x26C]; + + /// + /// Pointer to the Gfx Index Data + /// + public int GfxSurfacesPointer { get; set; } + } + + /// + /// Gfx Map Surface + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct GfxSurface + { + /// + /// Unknown Int (I know which pointer in the GfxMap it correlates it, but doesn't seem to interest us) + /// + public int UnknownBaseIndex { get; set; } + + /// + /// Base Vertex Index (this is what allows the GfxMap to have 65k+ verts with only 2 byte indices) + /// + public int VertexIndex { get; set; } + + /// + /// Number of Vertices this surface has + /// + public ushort VertexCount { get; set; } + + /// + /// Number of Faces this surface has + /// + public ushort FaceCount { get; set; } + + /// + /// Base Face Index (this is what allows the GfxMap to have 65k+ faces with only 2 byte indices) + /// + public int FaceIndex { get; set; } + + /// + /// Always 0xFFFFFFFF? + /// + public int Padding1 { get; set; } + + /// + /// Pointer to the Material Asset of this Surface + /// + public int MaterialPointer { get; set; } + + /// + /// Unknown Bytes + /// + public fixed byte Padding[0x1C]; + } + + /// + /// Call of Duty: World at War Material Asset + /// + public unsafe struct Material + { + /// + /// A pointer to the name of this material + /// + public int NamePointer { get; set; } + + /// + /// Unknown Bytes (Flags, settings, etc.) + /// + public fixed byte UnknownBytes[0x57]; + + /// + /// Number of Images this Material has + /// + public byte ImageCount { get; set; } + + /// + /// Unknown Bytes (Flags, settings, etc.) + /// + public fixed byte UnknownBytes1[0x3]; + + /// + /// A pointer to the Tech Set this Material uses + /// + public int TechniqueSetPointer { get; set; } + + /// + /// A pointer to this Material's Image table + /// + public int ImageTablePointer { get; set; } + + /// + /// Padding and Unknown Pointer + /// + public long Padding { get; set; } + } + /// /// Reads BSP Data /// @@ -45,7 +195,7 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l if (firstXModelName == "void" || firstXModelName == "defaultactor" || firstXModelName == "defaultweapon") { // Load BSP Pools (they only have a size of 1 so we have no free header) - var gfxMapAsset = reader.ReadStruct(reader.ReadInt32(assetPoolsAddress + 0x44)); + var gfxMapAsset = reader.ReadStruct(reader.ReadInt32(assetPoolsAddress + 0x44)); // Name string gfxMapName = reader.ReadNullTerminatedString(gfxMapAsset.NamePointer); @@ -92,50 +242,70 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l // Reset timer stopWatch.Restart(); - // Write SEModel + // Write OBJ Printer.WriteLine("INFO", "Converting to OBJ...."); - // Create Dir - Directory.CreateDirectory(Path.GetDirectoryName(gfxMapName)); + // Create new OBJ + var obj = new WavefrontOBJ(); - // Create OBJ output - using (StreamWriter writer = new StreamWriter(Path.ChangeExtension(gfxMapName, ".obj"))) + // Append Vertex Data + foreach (var vertex in vertices) { - // Dump vertex data - foreach (var vertex in vertices) - { - writer.WriteLine("v {0} {1} {2}", vertex.Position.X, vertex.Position.Y, vertex.Position.Z); - writer.WriteLine("vn {0} {1} {2}", vertex.VertexNormal.X, vertex.VertexNormal.Y, vertex.VertexNormal.Z); - writer.WriteLine("vt {0} {1}", vertex.UVSets[0].X, vertex.UVSets[0].Y); - } + obj.Vertices.Add(vertex.Position); + obj.Normals.Add(vertex.Normal); + obj.UVs.Add(vertex.UV); + } + + // Image Names (for Search String) + HashSet imageNames = new HashSet(); - // Dump Surfaces - foreach (var surface in surfaces) + // Append Faces + foreach (var surface in surfaces) + { + // Create new Material + var material = ReadMaterial(reader, surface.MaterialPointer); + // Add to images + imageNames.Add(material.DiffuseMap); + // Add it + obj.AddMaterial(material); + // Add points + for (ushort i = 0; i < surface.FaceCount; i++) { - // Get Material Name, purge any prefixes and Auto-Gen star characters - var materialName = Path.GetFileNameWithoutExtension(reader.ReadNullTerminatedString(reader.ReadInt32(surface.MaterialPointer)).Replace("*", "")); - - // Write MTL and Group - writer.WriteLine("g {0}", materialName); - writer.WriteLine("usemtl {0}", materialName); - // Add points - for (ushort i = 0; i < surface.FaceCount; i++) + // Face Indices + var faceIndex1 = indices[i * 3 + surface.FaceIndex] + surface.VertexIndex; + var faceIndex2 = indices[i * 3 + surface.FaceIndex + 1] + surface.VertexIndex; + var faceIndex3 = indices[i * 3 + surface.FaceIndex + 2] + surface.VertexIndex; + + // Validate unique points, and write to OBJ + if (faceIndex1 != faceIndex2 && faceIndex1 != faceIndex3 && faceIndex2 != faceIndex3) { - // Face Indices - var faceIndex1 = indices[i * 3 + surface.FaceIndex] + surface.VertexIndex + 1; - var faceIndex2 = indices[i * 3 + surface.FaceIndex + 1] + surface.VertexIndex + 1; - var faceIndex3 = indices[i * 3 + surface.FaceIndex + 2] + surface.VertexIndex + 1; - - // Validate unique points, and write to OBJ - if (faceIndex1 != faceIndex2 && faceIndex1 != faceIndex3 && faceIndex2 != faceIndex3) - writer.WriteLine("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}", - faceIndex1, - faceIndex3, - faceIndex2); + // new Obj Face + var objFace = new WavefrontOBJ.Face(material.Name); + + // Add points + objFace.Vertices[0] = new WavefrontOBJ.Face.Vertex(faceIndex1, faceIndex1, faceIndex1); + objFace.Vertices[2] = new WavefrontOBJ.Face.Vertex(faceIndex2, faceIndex2, faceIndex2); + objFace.Vertices[1] = new WavefrontOBJ.Face.Vertex(faceIndex3, faceIndex3, faceIndex3); + + // Add to OBJ + obj.Faces.Add(objFace); } } } + // Save it + obj.Save(Path.ChangeExtension(gfxMapName, ".obj")); + + // Build search strinmg + string searchString = ""; + + // Loop through images, and append each to the search string (for Wraith/Greyhound) + foreach (string imageName in imageNames) + searchString += String.Format("{0},", Path.GetFileNameWithoutExtension(imageName)); + + // Dump it + File.WriteAllText(Path.ChangeExtension(gfxMapName, ".txt"), searchString); + // Done Printer.WriteLine("INFO", String.Format("Converted to OBJ in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); } @@ -150,19 +320,15 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l /// /// Reads Gfx Surfaces /// - public static GfxSurfaceWaW[] ReadGfxSufaces(ProcessReader reader, long address, int count) + public static GfxSurface[] ReadGfxSufaces(ProcessReader reader, long address, int count) { // Preallocate short array - GfxSurfaceWaW[] surfaces = new GfxSurfaceWaW[count]; + GfxSurface[] surfaces = new GfxSurface[count]; // Loop number of indices we have for (int i = 0; i < count; i++) - { // Add it - surfaces[i] = reader.ReadStruct(address + i * 52); - - } - + surfaces[i] = reader.ReadStruct(address + i * 52); // Done return surfaces; } @@ -186,52 +352,57 @@ public static ushort[] ReadGfxIndices(ProcessReader reader, long address, int co /// /// Reads Gfx Vertices /// - public static SEModelVertex[] ReadGfxVertices(ProcessReader reader, long address, int count) + public static Vertex[] ReadGfxVertices(ProcessReader reader, long address, int count) { // Preallocate vertex array - SEModelVertex[] vertices = new SEModelVertex[count]; + Vertex[] vertices = new Vertex[count]; // Read buffer var byteBuffer = reader.ReadBytes(address, count * 44); // Loop number of vertices we have for (int i = 0; i < count; i++) { - // Grab Offset - float x = BitConverter.ToSingle(byteBuffer, i * 44); - float y = BitConverter.ToSingle(byteBuffer, i * 44 + 4); - float z = BitConverter.ToSingle(byteBuffer, i * 44 + 8); - - // Grab UV - float u = BitConverter.ToSingle(byteBuffer, i * 44 + 20); - float v = BitConverter.ToSingle(byteBuffer, i * 44 + 24); - - // Grab Normal - int vertexNormal = BitConverter.ToInt32(byteBuffer, i * 44 + 36); - - // Decode the scale of the vector - float DecodeScale = (float)((float)((vertexNormal & 0xFF000000) >> 24) - -192.0) / 32385.0f; + // Read Struct + var gfxVertex = ByteUtil.BytesToStruct(byteBuffer, i * 44); // Create new SEModel Vertex - vertices[i] = new SEModelVertex() + vertices[i] = new Vertex() { // Set offset Position = new Vector3( - x, - y, - z), + gfxVertex.X, + gfxVertex.Y, + gfxVertex.Z), // Decode and set normal (from DTZxPorter - Wraith, same as XModels) - VertexNormal = new Vector3( - (float)((float)(vertexNormal & 0xFF) - 127.0) * DecodeScale, - (float)((float)((vertexNormal & 0xFF00) >> 8) - 127.0) * DecodeScale, - (float)((float)((vertexNormal & 0xFF0000) >> 16) - 127.0) * DecodeScale) - + Normal = VertexNormal.UnpackA(gfxVertex.Normal), + // Set UV + UV = new Vector2(gfxVertex.U, 1 - gfxVertex.V) }; - - // Set UV - vertices[i].UVSets.Add(new Vector2(u, v)); } // Done return vertices; } + + /// + /// Reads a material for the given surface and its associated images + /// + public static WavefrontOBJ.Material ReadMaterial(ProcessReader reader, long address) + { + // Read Material + var material = reader.ReadStruct(address); + // Create new OBJ Image + var objMaterial = new WavefrontOBJ.Material(Path.GetFileNameWithoutExtension(reader.ReadNullTerminatedString(reader.ReadInt32(address)).Replace("*", ""))); + // Loop over images + for(byte i = 0; i < material.ImageCount; i++) + { + // Read Material Image + var materialImage = reader.ReadStruct(material.ImageTablePointer + i * Marshal.SizeOf()); + // Check for color map for now + if(materialImage.SemanticHash == 0xA0AB1041) + objMaterial.DiffuseMap = "_images\\\\" + reader.ReadNullTerminatedString(reader.ReadInt32(materialImage.ImagePointer + 0x20)) + ".png"; + } + // Done + return objMaterial; + } } } \ No newline at end of file diff --git a/src/Husky/Husky/Husky.csproj b/src/Husky/Husky/Husky.csproj index 2cb4a98..56e329e 100644 --- a/src/Husky/Husky/Husky.csproj +++ b/src/Husky/Husky/Husky.csproj @@ -53,7 +53,8 @@ - + + @@ -62,7 +63,11 @@ + + + + @@ -73,10 +78,6 @@ {8f5c1ba4-88c1-4177-b91b-dd093dc849b9} PhilLibX - - {e03da150-718d-4ea9-afca-d3e17a7df3e8} - SELib - diff --git a/src/Husky/Husky/Program.cs b/src/Husky/Husky/Program.cs index d691734..c50eb93 100644 --- a/src/Husky/Husky/Program.cs +++ b/src/Husky/Husky/Program.cs @@ -112,6 +112,7 @@ static void LoadGame() } catch(Exception e) { + Console.WriteLine(e); Printer.WriteException(e, "ERROR", "An unhandled exception has occured:"); } } diff --git a/src/Husky/Husky/Properties/AssemblyInfo.cs b/src/Husky/Husky/Properties/AssemblyInfo.cs index fd08c90..3824c85 100644 --- a/src/Husky/Husky/Properties/AssemblyInfo.cs +++ b/src/Husky/Husky/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following @@ -8,7 +7,7 @@ [assembly: AssemblyTitle("Husky - Call of Duty BSP Extractor")] [assembly: AssemblyDescription("Extracts BSP Data from Call of Duty")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] +[assembly: AssemblyCompany("Scobalula")] [assembly: AssemblyProduct("Husky")] [assembly: AssemblyCopyright("Copyright © Scobalula 2018")] [assembly: AssemblyTrademark("")] @@ -32,5 +31,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.1.0.0")] -[assembly: AssemblyFileVersion("0.1.0.0")] +[assembly: AssemblyVersion("0.2.0.0")] +[assembly: AssemblyFileVersion("0.2.0.0")] diff --git a/src/Husky/Husky/Utility/Color.cs b/src/Husky/Husky/Utility/Color.cs new file mode 100644 index 0000000..32f3f10 --- /dev/null +++ b/src/Husky/Husky/Utility/Color.cs @@ -0,0 +1,6 @@ +namespace Husky +{ + public class Color + { + } +} diff --git a/src/Husky/Husky/Utility/Vectors.cs b/src/Husky/Husky/Utility/Vectors.cs new file mode 100644 index 0000000..bf6d584 --- /dev/null +++ b/src/Husky/Husky/Utility/Vectors.cs @@ -0,0 +1,109 @@ +namespace Husky +{ + /// + /// A class to hold a 2-D Vector + /// + public class Vector2 + { + /// + /// X Value + /// + public double X { get; set; } + + /// + /// Y Value + /// + public double Y { get; set; } + + /// + /// Initializes a 2-D Vector at 0 + /// + public Vector2() + { + X = 0.0; + Y = 0.0; + } + + /// + /// Initializes a 2-D Vector with the given values + /// + /// X Value + /// Y Value + public Vector2(double x, double y) + { + X = x; + Y = y; + } + } + + /// + /// A class to hold a 3-D Vector + /// + public class Vector3 : Vector2 + { + /// + /// Z Value + /// + public double Z { get; set; } + + /// + /// Initializes a 3-D Vector at 0 + /// + public Vector3() + { + X = 0.0; + Y = 0.0; + Z = 0.0; + } + + /// + /// Initializes a 3-D Vector with the given values + /// + /// X Value + /// Y Value + /// Z Value + public Vector3(double x, double y, double z) + { + X = x; + Y = y; + Z = z; + } + } + + /// + /// A class to hold a 4-D Vector + /// + public class Vector4 : Vector3 + { + /// + /// W Value + /// + public double W { get; set; } + + /// + /// Initializes a 4-D Vector at 0 + /// + public Vector4() + { + X = 0.0; + Y = 0.0; + Z = 0.0; + W = 0.0; + } + + /// + /// Initializes a 4-D Vector with the given values + /// + /// X Value + /// Y Value + /// Z Value + /// W Value + public Vector4(double x, double y, double z, double w) + { + X = x; + Y = y; + Z = z; + W = w; + } + } +} diff --git a/src/Husky/Husky/Utility/Vertex.cs b/src/Husky/Husky/Utility/Vertex.cs new file mode 100644 index 0000000..bcd41f5 --- /dev/null +++ b/src/Husky/Husky/Utility/Vertex.cs @@ -0,0 +1,28 @@ +namespace Husky +{ + /// + /// Vertex Class (Position, Offset, etc.) + /// + public class Vertex + { + /// + /// Vertex Position + /// + public Vector3 Position { get; set; } + + /// + /// Vertex Normal + /// + public Vector3 Normal { get; set; } + + /// + /// Vertex UV/Texture Coordinates + /// + public Vector2 UV { get; set; } + + /// + /// Vertex Color + /// + public Color Color { get; set; } + } +} diff --git a/src/Husky/Husky/Utility/VertexNormal.cs b/src/Husky/Husky/Utility/VertexNormal.cs new file mode 100644 index 0000000..6d22f86 --- /dev/null +++ b/src/Husky/Husky/Utility/VertexNormal.cs @@ -0,0 +1,39 @@ +namespace Husky +{ + /// + /// Vertex Normal Unpacking Methods + /// + class VertexNormal + { + /// + /// Unpacks a Vertex Normal from: WaW, MW2, MW3 + /// + /// Packed 4 byte Vertex Normal + /// Resulting Vertex Normal + public static Vector3 UnpackA(PackedUnitVector packedNormal) + { + // Decode the scale of the vector + float decodeScale = ((float)(packedNormal.Byte4 - -192.0) / 32385.0f); + + // Return decoded vector + return new Vector3( + (float)(packedNormal.Byte1 - 127.0) * decodeScale, + (float)(packedNormal.Byte2 - 127.0) * decodeScale, + (float)(packedNormal.Byte3 - 127.0) * decodeScale); + } + + /// + /// Unpacks a Vertex Normal from: Ghosts, AW, MWR + /// + /// Packed 4 byte Vertex Normal + /// Resulting Vertex Normal + public static Vector3 UnpackB(PackedUnitVector packedNormal) + { + // Return decoded vector + return new Vector3( + (float)(((packedNormal.Value & 0x3FF) / 1023.0) * 2.0 - 1.0), + (float)((((packedNormal.Value >> 10) & 0x3FF) / 1023.0) * 2.0 - 1.0), + (float)((((packedNormal.Value >> 20) & 0x3FF) / 1023.0) * 2.0 - 1.0)); + } + } +} diff --git a/src/Husky/PhilLibX/ByteUtil.cs b/src/Husky/PhilLibX/ByteUtil.cs index 9c0ffb5..afa9b93 100644 --- a/src/Husky/PhilLibX/ByteUtil.cs +++ b/src/Husky/PhilLibX/ByteUtil.cs @@ -23,7 +23,9 @@ // File: ByteUtil.cs // Author: Philip/Scobalula // Description: Utilities for working with Bytes and Bits +using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Text; namespace PhilLibX @@ -59,5 +61,25 @@ public static byte GetBit(long input, int bit) { return (byte)((input >> bit) & 1); } + + public static T BytesToStruct(byte[] data, int startIndex) + { + // Size of Struct + int size = Marshal.SizeOf(); + // Create new byte array + byte[] buffer = new byte[size]; + // Copy it + Buffer.BlockCopy(data, startIndex, buffer, 0, size); + // Return result + return BytesToStruct(buffer); + } + + public static T BytesToStruct(byte[] data) + { + GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); + T theStructure = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); + handle.Free(); + return theStructure; + } } } diff --git a/src/Husky/PhilLibX/IO/ProcessWriter.cs b/src/Husky/PhilLibX/IO/ProcessWriter.cs index 51274a5..41e1e1c 100644 --- a/src/Husky/PhilLibX/IO/ProcessWriter.cs +++ b/src/Husky/PhilLibX/IO/ProcessWriter.cs @@ -1,11 +1,7 @@ using System; -using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; -using System.Linq; using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; namespace PhilLibX.IO { diff --git a/src/Husky/SELib/Properties/AssemblyInfo.cs b/src/Husky/SELib/Properties/AssemblyInfo.cs deleted file mode 100644 index 0127505..0000000 --- a/src/Husky/SELib/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("SELib")] -[assembly: AssemblyDescription("A library for reading and writing SE Formats")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("DTZxPorter")] -[assembly: AssemblyProduct("SELib")] -[assembly: AssemblyCopyright("Copyright © 2017 DTZxPorter")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("fdd4ffe1-b3dd-4999-90ad-f45725f195fc")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.0.2.0")] -[assembly: AssemblyFileVersion("2.0.2.0")] diff --git a/src/Husky/SELib/SEAnim.cs b/src/Husky/SELib/SEAnim.cs deleted file mode 100644 index 41aed8d..0000000 --- a/src/Husky/SELib/SEAnim.cs +++ /dev/null @@ -1,1443 +0,0 @@ -using SELib.Utilities; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -/// -/// SEAnim.cs -/// Author: DTZxPorter -/// Written for the SE Format Project -/// Follows SEAnim specification v1.1 -/// https://github.com/SE2Dev/SEAnim-Docs/blob/master/spec.md -/// - -namespace SELib -{ - #region SEAnim Enums - - /// - /// Specifies how the data is interpreted by the importer - /// - public enum AnimationType : byte - { - /// - /// Animation translations are set to this exact value each frame - /// - Absolute = 0, - /// - /// This animation is applied to existing animation data in the scene - /// - Additive = 1, - /// - /// Animation translations are based on rest position in scene - /// - Relative = 2, - /// - /// This animation is relative and contains delta data (Whole model movement) Delta tag name must be set! - /// - Delta = 3 - } - - /// - /// Specifies the data present for each frame of every bone (Internal use only, matches specification v1.0.1) - /// - internal enum SEAnim_DataPresenceFlags : byte - { - // These describe what type of keyframe data is present for the bones - SEANIM_BONE_LOC = 1 << 0, - SEANIM_BONE_ROT = 1 << 1, - SEANIM_BONE_SCALE = 1 << 2, - - // If any of the above flags are set, then bone keyframe data is present, thus this comparing against this mask will return true - SEANIM_PRESENCE_BONE = SEANIM_BONE_LOC | SEANIM_BONE_ROT | SEANIM_BONE_SCALE, - - // The file contains notetrack data - SEANIM_PRESENCE_NOTE = 1 << 6, - // The file contains a custom data block - SEANIM_PRESENCE_CUSTOM = 1 << 7, - } - - #endregion - - #region SEAnim Key - - /// - /// Contains information for a specific keyframe - /// - public class SEAnimFrame - { - /// - /// Get or set the frame of this animation key - /// - public int Frame { get; set; } - /// - /// Get or set the frame data for this animation key - /// - public KeyData Data { get; set; } - } - - #endregion - - /// - /// Represents a SEAnim file, allows for reading and writing animation data - /// - public class SEAnim - { - - /* Animation key data */ - - /// - /// A list of animation keys, by bone, for positions - /// - public Dictionary> AnimationPositionKeys { get; private set; } - /// - /// A list of animation keys, by bone, for rotations - /// - public Dictionary> AnimationRotationKeys { get; private set; } - /// - /// A list of animation keys, by bone, for scales - /// - public Dictionary> AnimationScaleKeys { get; private set; } - /// - /// A list of animation keys, for notetracks - /// - public Dictionary> AnimationNotetracks { get; private set; } - /// - /// A list of animation modifiers, by bone - /// - public Dictionary AnimationBoneModifiers { get; private set; } - - /* Animation properties */ - - /// - /// The count of frames in the animation, this is automatically updated - /// - public int FrameCount { get { return CalculateFrameCount(); } } - /// - /// The count of bones in the animation, this is automatically updated - /// - public int BoneCount { get { return CalculateBoneCount(); } } - /// - /// A list of bones currently being used in this animation, this is automatically updated - /// - public List Bones { get { return BuildUniqueOrderedBoneMap(); } } - /// - /// The count of notifications in the animation, this is automatically updated - /// - public int NotificationCount { get { return CalculateNotetracksCount(); } } - /// - /// The animation type for this animation - /// - public AnimationType AnimType { get; set; } - /// - /// Whether or not the animation should loop - /// - public bool Looping { get; set; } - /// - /// The name of the delta tag of which to treat as the delta bone (If any) - /// - public string DeltaTagName; - /// - /// The framerate of the animation as a float (Defaults to 30.0) - /// - public float FrameRate { get; set; } - /// - /// Gets the SEAnim specification version this library supports - /// - public string APIVersion { get { return "v1.0.1"; } } - - /// - /// Creates a new SEAnim using default settings - /// - public SEAnim() - { - // Setup defaults - AnimationPositionKeys = new Dictionary>(); - AnimationRotationKeys = new Dictionary>(); - AnimationScaleKeys = new Dictionary>(); - AnimationNotetracks = new Dictionary>(); - AnimationBoneModifiers = new Dictionary(); - // Default type - AnimType = AnimationType.Absolute; - // Non-looping - Looping = false; - // No delta - DeltaTagName = string.Empty; - // Frame fps - FrameRate = 30.0f; - } - - /* Functions and utilities */ - - #region Utilities - - /// - /// Remove an existing notification and it's keyframes - /// - /// The notetrack name to remove - public void RemoveNotetrack(string NoteName) - { - // Check if we have it - if (AnimationNotetracks.ContainsKey(NoteName)) - { - // Remove it - AnimationNotetracks.Remove(NoteName); - } - } - - /// - /// Renames an existing notetrack, changing all it's keyframes - /// - /// The existing name to change - /// The new notetrack name - /// True on success, false on failure if the new name exists - public bool RenameNotetrack(string ExistingNote, string NewNote) - { - // Check if the new one exists - if (AnimationNotetracks.ContainsKey(NewNote)) - { - // Failed, already exists - return false; - } - // Check if we have it - if (AnimationNotetracks.ContainsKey(ExistingNote)) - { - // Grab keys - var tempKeys = AnimationNotetracks[ExistingNote]; - // Remove it - AnimationNotetracks.Remove(ExistingNote); - // Add it back - AnimationNotetracks.Add(NewNote, tempKeys); - } - // Worked - return true; - } - - /// - /// Removes a bone and all existing keyframes for it - /// - /// The bone name to remove - public void RemoveBone(string BoneName) - { - // Check for and remove existing keyframes - if (AnimationPositionKeys.ContainsKey(BoneName)) - { - // Remove - AnimationPositionKeys.Remove(BoneName); - } - if (AnimationRotationKeys.ContainsKey(BoneName)) - { - // Remove - AnimationRotationKeys.Remove(BoneName); - } - if (AnimationScaleKeys.ContainsKey(BoneName)) - { - // Remove - AnimationScaleKeys.Remove(BoneName); - } - // Check bone modifiers - if (AnimationBoneModifiers.ContainsKey(BoneName)) - { - // Remove - AnimationBoneModifiers.Remove(BoneName); - } - // Check delta tag name - if (DeltaTagName == BoneName) - { - // Reset it - DeltaTagName = string.Empty; - } - } - - /// - /// Renames a bone tag to a new name, changing all of the keys with it - /// - /// The existing bone name - /// The new name to change it to - /// True on success, false if the tag already exists - public bool RenameBone(string ExistingName, string NewName) - { - // Make sure the new name does NOT exist - var ExistingBones = Bones; - // Check - if (ExistingBones.Contains(NewName)) - { - // Fail because the tag exists - return false; - } - // Prepare to rename the bone, checking for existing keys first - if (AnimationPositionKeys.ContainsKey(ExistingName)) - { - // Grab the keys - var tempKeys = AnimationPositionKeys[ExistingName]; - // Remove - AnimationPositionKeys.Remove(ExistingName); - // Add back - AnimationPositionKeys.Add(NewName, tempKeys); - } - if (AnimationRotationKeys.ContainsKey(ExistingName)) - { - // Grab the keys - var tempKeys = AnimationRotationKeys[ExistingName]; - // Remove - AnimationRotationKeys.Remove(ExistingName); - // Add back - AnimationRotationKeys.Add(NewName, tempKeys); - } - if (AnimationScaleKeys.ContainsKey(ExistingName)) - { - // Grab the keys - var tempKeys = AnimationScaleKeys[ExistingName]; - // Remove - AnimationScaleKeys.Remove(ExistingName); - // Add back - AnimationScaleKeys.Add(NewName, tempKeys); - } - // Check bone modifiers - if (AnimationBoneModifiers.ContainsKey(ExistingName)) - { - // Grab value - var tempMod = AnimationBoneModifiers[ExistingName]; - // Remove - AnimationBoneModifiers.Remove(ExistingName); - // Add back - AnimationBoneModifiers.Add(NewName, tempMod); - } - // Check delta tag name - if (DeltaTagName == ExistingName) - { - // Rename - DeltaTagName = NewName; - } - // Worked - return true; - } - - #endregion - - #region Reading - - /// - /// Reads a SEAnim from a stream - /// - /// The stream to read from - /// A SEAnim if successful, otherwise throws an error and returns null - public static SEAnim Read(Stream Stream) - { - // Create a new anim - var anim = new SEAnim(); - // Setup a new reader - using (ExtendedBinaryReader readFile = new ExtendedBinaryReader(Stream)) - { - // Magic - var Magic = readFile.ReadChars(6); - // Version - var Version = readFile.ReadInt16(); - // Header size - var HeaderSize = readFile.ReadInt16(); - // Check magic - if (!Magic.SequenceEqual(new char[] { 'S', 'E', 'A', 'n', 'i', 'm' })) - { - // Bad file - throw new Exception("Bad SEAnim file, magic was invalid"); - } - // Read animation type - anim.AnimType = (AnimationType)readFile.ReadByte(); - // Read anim flags - var AnimFlags = readFile.ReadByte(); - // Check flags - { - // Looping flag - anim.Looping = Convert.ToBoolean(AnimFlags & (byte)(1 << 0)); - } - // Read data present - var DataPresentFlags = readFile.ReadByte(); - // Read data property flags - var DataPropertyFlags = readFile.ReadByte(); - // Skip over 2 bytes reserved - readFile.BaseStream.Position += 2; - // Read framerate - anim.FrameRate = readFile.ReadSingle(); - // Read numframes - var NumFrames = readFile.ReadInt32(); - // Read numbones - var NumBones = readFile.ReadInt32(); - // Read nummods - var NumMods = readFile.ReadByte(); - // Skip 3 reserved bytes - readFile.BaseStream.Position += 3; - // Read numnotes - var NumNotes = readFile.ReadInt32(); - // Loop and read bone names - List BoneNames = new List(); - // Loop - for (int i = 0; i < NumBones; i++) - { - BoneNames.Add(readFile.ReadNullTermString()); - } - // If we're delta, set delta name - if (anim.AnimType == AnimationType.Delta) - { - // Set it - if (BoneNames.Count > 0) - { - anim.DeltaTagName = BoneNames[0]; - } - } - // Loop and read bone modifiers - for (int i = 0; i < NumMods; i++) - { - // Check bone count buffer and read the bone index - var BoneIndex = (NumBones <= 0xFF ? readFile.ReadByte() : readFile.ReadUInt16()); - // Read modifier and add - anim.AnimationBoneModifiers.Add(BoneNames[BoneIndex], (AnimationType)readFile.ReadByte()); - } - // We must read the data per bone, in the bone names order - foreach (string Bone in BoneNames) - { - // Read bone flags (unused) - var BoneFlags = readFile.ReadByte(); - // Read translations if any - #region Translation Keys - - { - // Check if we have translations - if (Convert.ToBoolean(DataPresentFlags & (byte)SEAnim_DataPresenceFlags.SEANIM_BONE_LOC)) - { - // We have translations, read count based on frame count - var NumTranslations = 0; - // Check framecount - if (NumFrames <= 0xFF) - { - // Read as byte - NumTranslations = readFile.ReadByte(); - } - else if (NumFrames <= 0xFFFF) - { - // Read as ushort - NumTranslations = readFile.ReadUInt16(); - } - else - { - // Read as int - NumTranslations = readFile.ReadInt32(); - } - // Loop and read translations - for (int i = 0; i < NumTranslations; i++) - { - var KeyFrame = 0; - // Check framecount - if (NumFrames <= 0xFF) - { - // Read as byte - KeyFrame = readFile.ReadByte(); - } - else if (NumFrames <= 0xFFFF) - { - // Read as ushort - KeyFrame = readFile.ReadUInt16(); - } - else - { - // Read as int - KeyFrame = readFile.ReadInt32(); - } - // Read the vector, check precision flags - double X = 0.0, Y = 0.0, Z = 0.0; - // Check precision flags - if (Convert.ToBoolean(DataPropertyFlags & (1 << 0))) - { - // Read as doubles - X = readFile.ReadDouble(); - Y = readFile.ReadDouble(); - Z = readFile.ReadDouble(); - } - else - { - // Read as floats - X = readFile.ReadSingle(); - Y = readFile.ReadSingle(); - Z = readFile.ReadSingle(); - } - // Add the key - anim.AddTranslationKey(Bone, KeyFrame, X, Y, Z); - } - } - } - - #endregion - // Read rotations if any - #region Rotation Keys - - { - // Check if we have rotations - if (Convert.ToBoolean(DataPresentFlags & (byte)SEAnim_DataPresenceFlags.SEANIM_BONE_ROT)) - { - // We have rotations, read count based on frame count - var NumRotations = 0; - // Check framecount - if (NumFrames <= 0xFF) - { - // Read as byte - NumRotations = readFile.ReadByte(); - } - else if (NumFrames <= 0xFFFF) - { - // Read as ushort - NumRotations = readFile.ReadUInt16(); - } - else - { - // Read as int - NumRotations = readFile.ReadInt32(); - } - // Loop and read rotations - for (int i = 0; i < NumRotations; i++) - { - var KeyFrame = 0; - // Check framecount - if (NumFrames <= 0xFF) - { - // Read as byte - KeyFrame = readFile.ReadByte(); - } - else if (NumFrames <= 0xFFFF) - { - // Read as ushort - KeyFrame = readFile.ReadUInt16(); - } - else - { - // Read as int - KeyFrame = readFile.ReadInt32(); - } - // Read the quat, check precision flags - double X = 0.0, Y = 0.0, Z = 0.0, W = 0.0; - // Check precision flags - if (Convert.ToBoolean(DataPropertyFlags & (1 << 0))) - { - // Read as doubles - X = readFile.ReadDouble(); - Y = readFile.ReadDouble(); - Z = readFile.ReadDouble(); - W = readFile.ReadDouble(); - } - else - { - // Read as floats - X = readFile.ReadSingle(); - Y = readFile.ReadSingle(); - Z = readFile.ReadSingle(); - W = readFile.ReadSingle(); - } - // Add the key - anim.AddRotationKey(Bone, KeyFrame, X, Y, Z, W); - } - } - } - - #endregion - // Read scales if any - #region Scale Keys - - { - // Check if we have scales - if (Convert.ToBoolean(DataPresentFlags & (byte)SEAnim_DataPresenceFlags.SEANIM_BONE_SCALE)) - { - // We have scales, read count based on frame count - var NumScales = 0; - // Check framecount - if (NumFrames <= 0xFF) - { - // Read as byte - NumScales = readFile.ReadByte(); - } - else if (NumFrames <= 0xFFFF) - { - // Read as ushort - NumScales = readFile.ReadUInt16(); - } - else - { - // Read as int - NumScales = readFile.ReadInt32(); - } - // Loop and read scales - for (int i = 0; i < NumScales; i++) - { - var KeyFrame = 0; - // Check framecount - if (NumFrames <= 0xFF) - { - // Read as byte - KeyFrame = readFile.ReadByte(); - } - else if (NumFrames <= 0xFFFF) - { - // Read as ushort - KeyFrame = readFile.ReadUInt16(); - } - else - { - // Read as int - KeyFrame = readFile.ReadInt32(); - } - // Read the vector, check precision flags - double X = 0.0, Y = 0.0, Z = 0.0; - // Check precision flags - if (Convert.ToBoolean(DataPropertyFlags & (1 << 0))) - { - // Read as doubles - X = readFile.ReadDouble(); - Y = readFile.ReadDouble(); - Z = readFile.ReadDouble(); - } - else - { - // Read as floats - X = readFile.ReadSingle(); - Y = readFile.ReadSingle(); - Z = readFile.ReadSingle(); - } - // Add the key - anim.AddScaleKey(Bone, KeyFrame, X, Y, Z); - } - } - } - - #endregion - } - // Read notifications, if any - for (int i = 0; i < NumNotes; i++) - { - // Get the keyframe for the notification based on the framecount - var KeyFrame = 0; - // Check framecount - if (NumFrames <= 0xFF) - { - // Read as byte - KeyFrame = readFile.ReadByte(); - } - else if (NumFrames <= 0xFFFF) - { - // Read as ushort - KeyFrame = readFile.ReadUInt16(); - } - else - { - // Read as int - KeyFrame = readFile.ReadInt32(); - } - // Read the name and add - anim.AddNoteTrack(readFile.ReadNullTermString(), KeyFrame); - } - } - // Return result - return anim; - } - - /// - /// Reads a SEAnim file, following the current specification - /// - /// The file name to open - /// A SEAnim if successful, otherwise throws an error and returns null - public static SEAnim Read(string FileName) - { - // Proxy off - return Read(File.OpenRead(FileName)); - } - - #endregion - - #region Writing - - /// - /// Saves the SEAnim to a stream, following the current specification version, using the provided data - /// - /// The file stream to write to - /// Whether or not to use doubles or floats (Defaults to floats) - public void Write(Stream Stream, bool HighPrecision) - { - // Open up a binary writer - using (ExtendedBinaryWriter writeFile = new ExtendedBinaryWriter(Stream)) - { - // Write magic - writeFile.Write(new char[] { 'S', 'E', 'A', 'n', 'i', 'm' }); - // Write version - writeFile.Write((short)0x1); - // Write header size - writeFile.Write((short)0x1C); - // Write animation type - writeFile.Write((byte)AnimType); - // Write flags (Looped is the only flag for now) - writeFile.Write((byte)(Looping ? (1 << 0) : 0)); - - // Build data present flags - { - // Buffer - byte DataPresentFlags = 0x0; - // Check for translations - if (AnimationPositionKeys.Count > 0) - { - DataPresentFlags |= (byte)SEAnim_DataPresenceFlags.SEANIM_BONE_LOC; - } - // Check for rotations - if (AnimationRotationKeys.Count > 0) - { - DataPresentFlags |= (byte)SEAnim_DataPresenceFlags.SEANIM_BONE_ROT; - } - // Check for scales - if (AnimationScaleKeys.Count > 0) - { - DataPresentFlags |= (byte)SEAnim_DataPresenceFlags.SEANIM_BONE_SCALE; - } - // Check for notetracks - if (AnimationNotetracks.Count > 0) - { - DataPresentFlags |= (byte)SEAnim_DataPresenceFlags.SEANIM_PRESENCE_NOTE; - } - // Write it - writeFile.Write((byte)DataPresentFlags); - } - - // Write data property flags (Precision is the only one for now) - writeFile.Write((byte)(HighPrecision ? (1 << 0) : 0)); - - // Write reserved bytes - writeFile.Write(new byte[2] { 0x0, 0x0 }); - - // Write framerate - writeFile.Write((float)FrameRate); - - // The framecount buffer - int FrameCountBuffer = FrameCount; - // The bonecount buffer - int BoneCountBuffer = BoneCount; - // The notification buffer - int NotificationBuffer = NotificationCount; - // Write count of frames - writeFile.Write((int)FrameCountBuffer); - // Write count of bones - writeFile.Write((int)BoneCountBuffer); - // Write modifier count - writeFile.Write((byte)AnimationBoneModifiers.Count); - - // Write 3 reserved bytes - writeFile.Write(new byte[3] { 0x0, 0x0, 0x0 }); - - // Write notification count - writeFile.Write((int)NotificationBuffer); - - // Build unique tag data, in the order we need - { - // Get the bone tags in the order we need them - List BoneTags = BuildUniqueOrderedBoneMap(); - // Loop and write tags - foreach (string Tag in BoneTags) - { - // Write - writeFile.WriteNullTermString(Tag); - } - // Loop through modifiers - foreach (KeyValuePair Modifier in AnimationBoneModifiers) - { - // Write the modifier - if (BoneCountBuffer <= 0xFF) - { - // Write as byte - writeFile.Write((byte)BoneTags.IndexOf(Modifier.Key)); - } - else - { - // Write as short - writeFile.Write((short)BoneTags.IndexOf(Modifier.Key)); - } - // Write modifier value - writeFile.Write((byte)Modifier.Value); - } - // We must write the key info in the order of bonetags - foreach (string Bone in BoneTags) - { - // Write bone flags (0 for now) - writeFile.Write((byte)0x0); - // Write translation keys first (if we have any) - #region Translation Keys - - { - // Check if we have any - if (AnimationPositionKeys.Count > 0) - { - // Check if this bone has any translations - if (AnimationPositionKeys.ContainsKey(Bone)) - { - // Check length of frames to write count - if (FrameCountBuffer <= 0xFF) - { - // Write as byte - writeFile.Write((byte)AnimationPositionKeys[Bone].Count); - } - else if (FrameCountBuffer <= 0xFFFF) - { - // Write as short - writeFile.Write((short)AnimationPositionKeys[Bone].Count); - } - else - { - // Write as int - writeFile.Write((int)AnimationPositionKeys[Bone].Count); - } - // Output keys - foreach (SEAnimFrame Key in AnimationPositionKeys[Bone]) - { - // Output frame number based on frame count - if (FrameCountBuffer <= 0xFF) - { - // Write as byte - writeFile.Write((byte)Key.Frame); - } - else if (FrameCountBuffer <= 0xFFFF) - { - // Write as short - writeFile.Write((short)Key.Frame); - } - else - { - // Write as int - writeFile.Write((int)Key.Frame); - } - // Output the vector - if (HighPrecision) - { - writeFile.Write((double)((Vector3)Key.Data).X); - writeFile.Write((double)((Vector3)Key.Data).Y); - writeFile.Write((double)((Vector3)Key.Data).Z); - } - else - { - writeFile.Write((float)((Vector3)Key.Data).X); - writeFile.Write((float)((Vector3)Key.Data).Y); - writeFile.Write((float)((Vector3)Key.Data).Z); - } - } - } - else - { - // Check length of frames to write count (of 0 because no frames) - if (FrameCountBuffer <= 0xFF) - { - // Write as byte - writeFile.Write((byte)0x0); - } - else if (FrameCountBuffer <= 0xFFFF) - { - // Write as short - writeFile.Write((short)0x0); - } - else - { - // Write as int - writeFile.Write((int)0x0); - } - } - } - } - - #endregion - // Write rotation keys next (if we have any) - #region Rotation Keys - - { - // Check if we have any - if (AnimationRotationKeys.Count > 0) - { - // Check if this bone has any rotations - if (AnimationRotationKeys.ContainsKey(Bone)) - { - // Check length of frames to write count - if (FrameCountBuffer <= 0xFF) - { - // Write as byte - writeFile.Write((byte)AnimationRotationKeys[Bone].Count); - } - else if (FrameCountBuffer <= 0xFFFF) - { - // Write as short - writeFile.Write((short)AnimationRotationKeys[Bone].Count); - } - else - { - // Write as int - writeFile.Write((int)AnimationRotationKeys[Bone].Count); - } - // Output keys - foreach (SEAnimFrame Key in AnimationRotationKeys[Bone]) - { - // Output frame number based on frame count - if (FrameCountBuffer <= 0xFF) - { - // Write as byte - writeFile.Write((byte)Key.Frame); - } - else if (FrameCountBuffer <= 0xFFFF) - { - // Write as short - writeFile.Write((short)Key.Frame); - } - else - { - // Write as int - writeFile.Write((int)Key.Frame); - } - // Output the quat - if (HighPrecision) - { - writeFile.Write((double)((Quaternion)Key.Data).X); - writeFile.Write((double)((Quaternion)Key.Data).Y); - writeFile.Write((double)((Quaternion)Key.Data).Z); - writeFile.Write((double)((Quaternion)Key.Data).W); - } - else - { - writeFile.Write((float)((Quaternion)Key.Data).X); - writeFile.Write((float)((Quaternion)Key.Data).Y); - writeFile.Write((float)((Quaternion)Key.Data).Z); - writeFile.Write((float)((Quaternion)Key.Data).W); - } - } - } - else - { - // Check length of frames to write count (of 0 because no frames) - if (FrameCountBuffer <= 0xFF) - { - // Write as byte - writeFile.Write((byte)0x0); - } - else if (FrameCountBuffer <= 0xFFFF) - { - // Write as short - writeFile.Write((short)0x0); - } - else - { - // Write as int - writeFile.Write((int)0x0); - } - } - } - } - - #endregion - // Write scale keys next (if we have any) - #region Scale Keys - - { - // Check if we have any - if (AnimationScaleKeys.Count > 0) - { - // Check if this bone has any scales - if (AnimationScaleKeys.ContainsKey(Bone)) - { - // Check length of frames to write count - if (FrameCountBuffer <= 0xFF) - { - // Write as byte - writeFile.Write((byte)AnimationScaleKeys[Bone].Count); - } - else if (FrameCountBuffer <= 0xFFFF) - { - // Write as short - writeFile.Write((short)AnimationScaleKeys[Bone].Count); - } - else - { - // Write as int - writeFile.Write((int)AnimationScaleKeys[Bone].Count); - } - // Output keys - foreach (SEAnimFrame Key in AnimationScaleKeys[Bone]) - { - // Output frame number based on frame count - if (FrameCountBuffer <= 0xFF) - { - // Write as byte - writeFile.Write((byte)Key.Frame); - } - else if (FrameCountBuffer <= 0xFFFF) - { - // Write as short - writeFile.Write((short)Key.Frame); - } - else - { - // Write as int - writeFile.Write((int)Key.Frame); - } - // Output the vector - if (HighPrecision) - { - writeFile.Write((double)((Vector3)Key.Data).X); - writeFile.Write((double)((Vector3)Key.Data).Y); - writeFile.Write((double)((Vector3)Key.Data).Z); - } - else - { - writeFile.Write((float)((Vector3)Key.Data).X); - writeFile.Write((float)((Vector3)Key.Data).Y); - writeFile.Write((float)((Vector3)Key.Data).Z); - } - } - } - else - { - // Check length of frames to write count (of 0 because no frames) - if (FrameCountBuffer <= 0xFF) - { - // Write as byte - writeFile.Write((byte)0x0); - } - else if (FrameCountBuffer <= 0xFFFF) - { - // Write as short - writeFile.Write((short)0x0); - } - else - { - // Write as int - writeFile.Write((int)0x0); - } - } - } - } - - #endregion - } - } - // Output notetracks, if any - if (AnimationNotetracks.Count > 0) - { - // We have notifications - foreach (KeyValuePair> Note in AnimationNotetracks) - { - // Write them - foreach (SEAnimFrame Key in Note.Value) - { - // Write the frame itself based on framecount - if (FrameCountBuffer <= 0xFF) - { - // Write as byte - writeFile.Write((byte)Key.Frame); - } - else if (FrameCountBuffer <= 0xFFFF) - { - // Write as short - writeFile.Write((short)Key.Frame); - } - else - { - // Write as int - writeFile.Write((int)Key.Frame); - } - // Write flag name - writeFile.WriteNullTermString(Note.Key); - } - } - } - } - } - - /// - /// Saves the SEAnim to a file (Overwriting if exists), following the current specification version, using the provided data - /// - /// The file name to save the animation to - /// Whether or not to use doubles or floats (Defaults to floats) - public void Write(string FileName, bool HighPrecision = false) - { - // Proxy off - Write(File.Create(FileName), HighPrecision); - } - - #endregion - - #region Adding Keys - - /// - /// Add a translation key for the specified bone, on the given frame, with a vector3, (Data must be in CM scale!) - /// - /// The bone tag to animate - /// The frame index to key on - /// X part of a vector (Normalized) - /// Y part of a vector (Normalized) - /// Z part of a vector (Normalized) - public void AddTranslationKey(string Bone, int Frame, double X, double Y, double Z) - { - // We should add this key - if (!AnimationPositionKeys.ContainsKey(Bone)) - { - // Set it up - AnimationPositionKeys.Add(Bone, new List()); - } - // Add the key itself - AnimationPositionKeys[Bone].Add(new SEAnimFrame() { Frame = Frame, Data = new Vector3() { X = X, Y = Y, Z = Z } }); - } - - /// - /// Add a rotation key for the specified bone, on the given frame, with a quaternion - /// - /// The bone tag to animate - /// The frame index to key on - /// X part of a quaternion (Normalized) - /// Y part of a quaternion (Normalized) - /// Z part of a quaternion (Normalized) - /// W part of a quaternion (Normalized) - public void AddRotationKey(string Bone, int Frame, double X, double Y, double Z, double W) - { - // We should add this key - if (!AnimationRotationKeys.ContainsKey(Bone)) - { - // Set it up - AnimationRotationKeys.Add(Bone, new List()); - } - // Add the key itself - AnimationRotationKeys[Bone].Add(new SEAnimFrame() { Frame = Frame, Data = new Quaternion() { X = X, Y = Y, Z = Z, W = W } }); - } - - /// - /// Add a scale key for the specified bone, on the given frame, with a vector - /// - /// The bone tag to animate - /// The frame index to key on - /// - /// - /// - public void AddScaleKey(string Bone, int Frame, double X, double Y, double Z) - { - // We should add this key - if (!AnimationScaleKeys.ContainsKey(Bone)) - { - // Set it up - AnimationScaleKeys.Add(Bone, new List()); - } - // Add the key itself - AnimationScaleKeys[Bone].Add(new SEAnimFrame() { Frame = Frame, Data = new Vector3() { X = X, Y = Y, Z = Z } }); - } - - /// - /// Adds a notification at the specified frame - /// - /// The notification name - /// The frame index to key on - public void AddNoteTrack(string Notification, int Frame) - { - // We should add this key - if (!AnimationNotetracks.ContainsKey(Notification)) - { - // Set it up - AnimationNotetracks.Add(Notification, new List()); - } - // Add the key itself - AnimationNotetracks[Notification].Add(new SEAnimFrame() { Frame = Frame, Data = null }); - } - - /// - /// Adds a bone modifier for this bone, overwriting if existing - /// - /// The bone tag to modify - /// The new animation type for children - public void AddBoneModifier(string Bone, AnimationType Modifier) - { - // Check if it exists - if (AnimationBoneModifiers.ContainsKey(Bone)) - { - // Set it - AnimationBoneModifiers[Bone] = Modifier; - } - else - { - // Add it - AnimationBoneModifiers.Add(Bone, Modifier); - } - } - - #endregion - - #region Removing Keys - - /// - /// Remove a specific keyframe from a bone - /// - /// The bone name to remove the key from - /// The frame of which to remove - public void RemoveTranslationKey(string Bone, int Frame) - { - // Make sure bone exists first - if (AnimationPositionKeys.ContainsKey(Bone)) - { - // It exists, loop and check for the key - for (int i = 0; i < AnimationPositionKeys[Bone].Count; i++) - { - // Check - if (AnimationPositionKeys[Bone][i].Frame == Frame) - { - // Remove and end - AnimationPositionKeys[Bone].RemoveAt(i); - // End - break; - } - } - } - } - - /// - /// Remove a specific keyframe from a bone - /// - /// The bone name to remove the key from - /// The frame of which to remove - public void RemoveRotationKey(string Bone, int Frame) - { - // Make sure bone exists first - if (AnimationRotationKeys.ContainsKey(Bone)) - { - // It exists, loop and check for the key - for (int i = 0; i < AnimationRotationKeys[Bone].Count; i++) - { - // Check - if (AnimationRotationKeys[Bone][i].Frame == Frame) - { - // Remove and end - AnimationRotationKeys[Bone].RemoveAt(i); - // End - break; - } - } - } - } - - /// - /// Remove a specific keyframe from a bone - /// - /// The bone name to remove the key from - /// The frame of which to remove - public void RemoveScaleKey(string Bone, int Frame) - { - // Make sure bone exists first - if (AnimationScaleKeys.ContainsKey(Bone)) - { - // It exists, loop and check for the key - for (int i = 0; i < AnimationScaleKeys[Bone].Count; i++) - { - // Check - if (AnimationScaleKeys[Bone][i].Frame == Frame) - { - // Remove and end - AnimationScaleKeys[Bone].RemoveAt(i); - // End - break; - } - } - } - } - - /// - /// Remove a specific notification frame - /// - /// The notetrack name to remove the key from - /// The frame of which to remove - public void RemoveNotetrack(string Notification, int Frame) - { - // Make sure notification exists - if (AnimationNotetracks.ContainsKey(Notification)) - { - // Exists, loop and check for key - for (int i = 0; i < AnimationNotetracks[Notification].Count; i++) - { - // Check - if (AnimationNotetracks[Notification][i].Frame == Frame) - { - // Remove and end - AnimationNotetracks[Notification].RemoveAt(i); - // End - break; - } - } - } - } - - #endregion - - #region Calculations - - /// - /// Calculates a unique bone map ordering, moving the delta bone if need be to the front - /// - /// - private List BuildUniqueOrderedBoneMap() - { - // Build the list - List BoneMap = new List(); - // Loop and add all of the bone tags - #region Iterate - - foreach (string Bone in AnimationPositionKeys.Keys) - { - // Add it, we don't care if it fails - if (!BoneMap.Contains(Bone)) - { - BoneMap.Add(Bone); - } - } - foreach (string Bone in AnimationRotationKeys.Keys) - { - // Add it, we don't care if it fails - if (!BoneMap.Contains(Bone)) - { - BoneMap.Add(Bone); - } - } - foreach (string Bone in AnimationScaleKeys.Keys) - { - // Add it, we don't care if it fails - if (!BoneMap.Contains(Bone)) - { - BoneMap.Add(Bone); - } - } - foreach (string Bone in AnimationBoneModifiers.Keys) - { - // Add it, we don't care if it fails - if (!BoneMap.Contains(Bone)) - { - BoneMap.Add(Bone); - } - } - - #endregion - // Check delta tag - if (AnimType == AnimationType.Delta) - { - // Check for and remove the tag, insert back at the top - if (BoneMap.Contains(DeltaTagName)) - { - // Remove - BoneMap.Remove(DeltaTagName); - // Insert - BoneMap.Insert(0, DeltaTagName); - } - } - // Return result - return BoneMap; - } - - /// - /// Calculates the notification count in accordance with the specifications - /// - private int CalculateNotetracksCount() - { - // The total count - int TotalCount = 0; - // Loop for all keys and add - foreach (List NotificationList in AnimationNotetracks.Values) - { - // Append - TotalCount += NotificationList.Count; - } - // Return result - return TotalCount; - } - - /// - /// Calculates the frame count in accordance with the specifications - /// - private int CalculateFrameCount() - { - // The maximum frame index - int MaxFrame = 0; - // We must iterate through all key types and compare the max keyed frame against the current max frame - #region Key Iteration - - foreach (List Frames in AnimationPositionKeys.Values) - { - // Iterate - foreach (SEAnimFrame Frame in Frames) - { - // Compare - MaxFrame = Math.Max(MaxFrame, Frame.Frame); - } - } - foreach (List Frames in AnimationRotationKeys.Values) - { - // Iterate - foreach (SEAnimFrame Frame in Frames) - { - // Compare - MaxFrame = Math.Max(MaxFrame, Frame.Frame); - } - } - foreach (List Frames in AnimationScaleKeys.Values) - { - // Iterate - foreach (SEAnimFrame Frame in Frames) - { - // Compare - MaxFrame = Math.Max(MaxFrame, Frame.Frame); - } - } - foreach (List Frames in AnimationNotetracks.Values) - { - // Iterate - foreach (SEAnimFrame Frame in Frames) - { - // Compare - MaxFrame = Math.Max(MaxFrame, Frame.Frame); - } - } - - #endregion - // Frame count represents the length of the animation in frames - // Since all animations start at frame 0, we grab the max number and - // Add 1 to the result - return MaxFrame + 1; - } - - /// - /// Calculates the bone count in accordance with the specifications - /// - private int CalculateBoneCount() - { - // Make a hashset of unique bone names - HashSet UniqueBoneNames = new HashSet(); - // We must append bone names from all bone type animation keys, then simply return the result of the hash set - #region Key Iteration - - foreach (string Bone in AnimationPositionKeys.Keys) - { - // Add it, we don't care if it fails - UniqueBoneNames.Add(Bone); - } - foreach (string Bone in AnimationRotationKeys.Keys) - { - // Add it, we don't care if it fails - UniqueBoneNames.Add(Bone); - } - foreach (string Bone in AnimationScaleKeys.Keys) - { - // Add it, we don't care if it fails - UniqueBoneNames.Add(Bone); - } - foreach (string Bone in AnimationBoneModifiers.Keys) - { - // Add it, we don't care if it fails - UniqueBoneNames.Add(Bone); - } - - #endregion - // Return result - return UniqueBoneNames.Count; - } - - #endregion - } -} diff --git a/src/Husky/SELib/SELib.csproj b/src/Husky/SELib/SELib.csproj deleted file mode 100644 index 5181e4f..0000000 --- a/src/Husky/SELib/SELib.csproj +++ /dev/null @@ -1,59 +0,0 @@ - - - - - Debug - AnyCPU - {E03DA150-718D-4EA9-AFCA-D3E17A7DF3E8} - Library - Properties - SELib - SELib - v4.5 - 512 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Husky/SELib/SEModel.cs b/src/Husky/SELib/SEModel.cs deleted file mode 100644 index f1c3376..0000000 --- a/src/Husky/SELib/SEModel.cs +++ /dev/null @@ -1,1019 +0,0 @@ -using SELib.Utilities; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -/// -/// SEModel.cs -/// Author: DTZxPorter -/// Written for the SE Format Project -/// Follows SEModel specification v1.0 -/// https://github.com/dtzxporter/SEModel-Docs/blob/master/spec.md -/// - -namespace SELib -{ - #region SEModel Enums - - /// - /// Specifies what type of bone positioning to use in the model - /// - public enum ModelBoneSupportTypes : byte - { - /// - /// The model will be saved with local bone matricies - /// - SupportsLocals, - /// - /// The model will be saved with global bone matricies - /// - SupportsGlobals, - /// - /// The model will be saved with both global and local matricies - /// - SupportsBoth - } - - /// - /// Specifies the data present for the model - /// - internal enum SEModel_DataPresenceFlags : byte - { - // Whether or not this model contains a bone block - SEMODEL_PRESENCE_BONE = 1 << 0, - // Whether or not this model contains submesh blocks - SEMODEL_PRESENCE_MESH = 1 << 1, - // Whether or not this model contains inline material blocks - SEMODEL_PRESENCE_MATERIALS = 1 << 2, - - // The file contains a custom data block - SEMODEL_PRESENCE_CUSTOM = 1 << 7, - } - - /// - /// Specifies the data present for each bone in the model - /// - internal enum SEModel_BoneDataPresenceFlags : byte - { - // Whether or not bones contain global-space matricies - SEMODEL_PRESENCE_GLOBAL_MATRIX = 1 << 0, - // Whether or not bones contain local-space matricies - SEMODEL_PRESENCE_LOCAL_MATRIX = 1 << 1, - - // Whether or not bones contain scales - SEMODEL_PRESENCE_SCALES = 1 << 2, - } - - /// - /// Specifies the data present for each vertex in the model - /// - internal enum SEModel_MeshDataPresenceFlags : byte - { - // Whether or not meshes contain at least 1 uv map - SEMODEL_PRESENCE_UVSET = 1 << 0, - - // Whether or not meshes contain vertex normals - SEMODEL_PRESENCE_NORMALS = 1 << 1, - - // Whether or not meshes contain vertex colors (RGBA) - SEMODEL_PRESENCE_COLOR = 1 << 2, - - // Whether or not meshes contain at least 1 weighted skin - SEMODEL_PRESENCE_WEIGHTS = 1 << 3, - } - - #endregion - - #region SEModel Bone - - /// - /// Contains information for a specific bone - /// - public class SEModelBone - { - /// - /// Whether or not this bone is a root bone (No parent) - /// - public bool RootBone { get { return (BoneParent <= -1); } } - - /// - /// Get or set the name of this bone - /// - public string BoneName { get; set; } - /// - /// Get or set the parent index of this bone, -1 for a root bone - /// - public int BoneParent { get; set; } - - /// - /// Get or set the global position of this bone - /// - public Vector3 GlobalPosition { get; set; } - /// - /// Get or set the global rotation of this bone - /// - public Quaternion GlobalRotation { get; set; } - - /// - /// Get or set the local position of this bone - /// - public Vector3 LocalPosition { get; set; } - /// - /// Get or set the local rotation of this bone - /// - public Quaternion LocalRotation { get; set; } - - /// - /// Get or set the scale of this bone - /// - public Vector3 Scale { get; set; } - - /// - /// Creates a new SEModelBone with the default settings - /// - public SEModelBone() - { - BoneName = string.Empty; - BoneParent = -1; - GlobalPosition = new Vector3() { X = 0, Y = 0, Z = 0 }; - LocalPosition = new Vector3() { X = 0, Y = 0, Z = 0 }; - GlobalRotation = new Quaternion() { X = 0, Y = 0, Z = 0, W = 1 }; - LocalRotation = new Quaternion() { X = 0, Y = 0, Z = 0, W = 1 }; - Scale = new Vector3() { X = 1, Y = 1, Z = 1 }; - } - } - - #endregion - - #region SEModel Mesh - - public class SEModelMesh - { - /// - /// Returns the count of verticies in the mesh - /// - public uint VertexCount { get { return (uint)Verticies.Count; } } - /// - /// Returns the count of faces in the mesh - /// - public uint FaceCount { get { return (uint)Faces.Count; } } - - /// - /// A list of verticies in the mesh - /// - public List Verticies { get; set; } - /// - /// A list of faces in the mesh, faces match D3DPT_TRIANGLELIST (DirectX) and VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST (Vulkan) - /// - public List Faces { get; set; } - /// - /// A list of material indicies per each UVLayer, -1 indicates no material assigned... - /// - public List MaterialReferenceIndicies { get; set; } - - /// - /// Creates a new mesh with default settings - /// - public SEModelMesh() - { - Verticies = new List(); - Faces = new List(); - MaterialReferenceIndicies = new List(); - } - - /// - /// Adds the given vertex to the mesh - /// - /// The vertex to add to the mesh - public void AddVertex(SEModelVertex Vertex) - { - // Add it - Verticies.Add(Vertex); - } - - /// - /// Adds a new face to the mesh with the given indicies - /// - public void AddFace(uint Index1, uint Index2, uint Index3) - { - // Add new face - Faces.Add(new SEModelFace(Index1, Index2, Index3)); - } - - /// - /// Adds a new material index - /// - /// The index of the material in the model, or -1 for null - public void AddMaterialIndex(int Index) - { - // Add new index - MaterialReferenceIndicies.Add(Index); - } - } - - public class SEModelFace - { - /* Vertex indicies for this face */ - - public uint FaceIndex1; - public uint FaceIndex2; - public uint FaceIndex3; - - /// - /// Creates a new face with default settings - /// - public SEModelFace(uint Index1, uint Index2, uint Index3) - { - FaceIndex1 = Index1; - FaceIndex2 = Index2; - FaceIndex3 = Index3; - } - } - - public class SEModelVertex - { - /// - /// Returns the amount of UVSets for this vertex - /// - public uint UVSetCount { get { return (uint)UVSets.Count; } } - /// - /// Returns the amount of skin influences for this vertex - /// - public uint WeightCount { get { return (uint)Weights.Count; } } - - /// - /// The position of the vertex - /// - public Vector3 Position { get; set; } - - /// - /// The uv sets for this vertex - /// - public List UVSets { get; set; } - - /// - /// The vertex normal - /// - public Vector3 VertexNormal { get; set; } - /// - /// The vertex coloring - /// - public Color VertexColor { get; set; } - - /// - /// A list of skin weights for this vertex - /// - public List Weights { get; set; } - - /// - /// Creates a new SEModelVertex using default settings - /// - public SEModelVertex() - { - Position = Vector3.Zero; - UVSets = new List(); - VertexNormal = Vector3.Zero; - VertexColor = Color.White; - Weights = new List(); - } - } - - public class SEModelWeight - { - /// - /// The bone index for this weight - /// - public uint BoneIndex { get; set; } - /// - /// The weight value, from 0.0 to 1.0 for this weight - /// - public float BoneWeight { get; set; } - - /// - /// Creates a new SEModelWeight with default settings - /// - public SEModelWeight() { BoneIndex = 0; BoneWeight = 1.0f; } - } - - public class SEModelUVSet - { - /// - /// The UV coords for this UVSet - /// - public Vector2 UVCoord { get; set; } - /// - /// The material index of the UV, the index is within the models materials - /// - public uint MaterialIndex { get; set; } - - /// - /// Creates a new SEModelUVSet with default settings - /// - public SEModelUVSet() { UVCoord = new Vector2(); MaterialIndex = 0; } - } - - #endregion - - #region SEModel Material - - public class SEModelMaterial - { - /// - /// The name of the material - /// - public string Name { get; set; } - /// - /// The material data, determined by type - /// - public object MaterialData { get; set; } - } - - public class SEModelSimpleMaterial - { - public string DiffuseMap { get; set; } - public string NormalMap { get; set; } - public string SpecularMap { get; set; } - } - - #endregion - - /// - /// Represents a SEModel file, allows for reading and writing model data - /// - public class SEModel - { - /* Model data */ - - /// - /// A list of bones, in order by index - /// - public List Bones { get; private set; } - /// - /// A list of meshes, in order - /// - public List Meshes { get; private set; } - /// - /// A list of materials, in order - /// - public List Materials { get; private set; } - - /// - /// Gets the SEModel specification version this library supports - /// - public string APIVersion { get { return "v1.0.0"; } } - - /* Model properties */ - - /// - /// Returns the number of bones in this model, this is automatically updated - /// - public uint BoneCount { get { return (uint)Bones.Count; } } - /// - /// Returns the number of meshes in this model, this is automatically updated - /// - public uint MeshCount { get { return (uint)Meshes.Count; } } - /// - /// Returns the number of materials in this model, this is automatically updated - /// - public uint MaterialCount { get { return (uint)Materials.Count; } } - - /// - /// Creates a new SEModel using default settings - /// - public SEModel() - { - Bones = new List(); - Meshes = new List(); - Materials = new List(); - } - - /* Functions and utilities */ - - #region Writing - - /// - /// Saves the SEModel to a stream, following the current specification version, using the provided data - /// - /// The file stream to write to - public void Write(Stream Stream) - { - // Open up a binary writer - using (ExtendedBinaryWriter writeFile = new ExtendedBinaryWriter(Stream)) - { - // Write magic - writeFile.Write(new char[] { 'S', 'E', 'M', 'o', 'd', 'e', 'l' }); - // Write version - writeFile.Write((short)0x1); - // Write header size - writeFile.Write((short)0x14); - - // Build data present flags - { - // Buffer - byte DataPresentFlags = 0x0; - // Check for bones - if (Bones.Count > 0) - { - DataPresentFlags |= (byte)SEModel_DataPresenceFlags.SEMODEL_PRESENCE_BONE; - } - // Check for meshes - if (Meshes.Count > 0) - { - DataPresentFlags |= (byte)SEModel_DataPresenceFlags.SEMODEL_PRESENCE_MESH; - } - // Check for materials - if (Materials.Count > 0) - { - DataPresentFlags |= (byte)SEModel_DataPresenceFlags.SEMODEL_PRESENCE_MATERIALS; - } - // Write it - writeFile.Write((byte)DataPresentFlags); - } - - // Dynamic properties - bool HasScales = false, HasLocals = true, HasGlobals = false; - - // Build bone data present flags - { - // Buffer - byte DataPresentFlags = 0x0; - - // Check for non-default bone data - foreach (var Bone in Bones) - { - if (Bone.Scale != Vector3.One) - HasScales = true; - if (Bone.LocalPosition != Vector3.Zero || Bone.LocalRotation != Quaternion.Identity) - HasLocals = true; - if (Bone.GlobalPosition != Vector3.Zero || Bone.GlobalRotation != Quaternion.Identity) - HasGlobals = true; - - // Check to end - if (HasScales && HasLocals && HasGlobals) - break; - } - - // Check for bone types - if (HasScales) - DataPresentFlags |= (byte)SEModel_BoneDataPresenceFlags.SEMODEL_PRESENCE_SCALES; - if (HasLocals) - DataPresentFlags |= (byte)SEModel_BoneDataPresenceFlags.SEMODEL_PRESENCE_LOCAL_MATRIX; - if (HasGlobals) - DataPresentFlags |= (byte)SEModel_BoneDataPresenceFlags.SEMODEL_PRESENCE_GLOBAL_MATRIX; - - // Write it - writeFile.Write((byte)DataPresentFlags); - } - - // Dynamic properties - bool HasNormals = false, HasColors = false; - - // Build mesh data present flags - { - // Buffer - byte DataPresentFlags = 0x0; - - // Implied properties - bool HasUVSet = false, HasWeights = false; - - // Check for non-default properties - foreach (var Mesh in Meshes) - { - foreach (var Vertex in Mesh.Verticies) - { - if (Vertex.UVSets.Count > 0) - HasUVSet = true; - if (Vertex.Weights.Count > 0) - HasWeights = true; - if (Vertex.VertexColor != Color.White) - HasColors = true; - if (Vertex.VertexNormal != Vector3.Zero) - HasNormals = true; - - // Check to end - if (HasUVSet && HasWeights && HasNormals && HasColors) - break; - } - - // Check to end - if (HasUVSet && HasWeights && HasNormals && HasColors) - break; - } - - // Check for UVSets - if (HasUVSet) - { - DataPresentFlags |= (byte)SEModel_MeshDataPresenceFlags.SEMODEL_PRESENCE_UVSET; - } - // Check for normals - if (HasNormals) - { - DataPresentFlags |= (byte)SEModel_MeshDataPresenceFlags.SEMODEL_PRESENCE_NORMALS; - } - // Check for colors - if (HasColors) - { - DataPresentFlags |= (byte)SEModel_MeshDataPresenceFlags.SEMODEL_PRESENCE_COLOR; - } - // Check for weights - if (HasWeights) - { - DataPresentFlags |= (byte)SEModel_MeshDataPresenceFlags.SEMODEL_PRESENCE_WEIGHTS; - } - - // Write it - writeFile.Write((byte)DataPresentFlags); - } - - // The bonecount buffer - uint BoneCountBuffer = BoneCount; - // The meshcount buffer - uint MeshCountBuffer = MeshCount; - // The matcount buffer - uint MatCountBuffer = MaterialCount; - - // Write count of bones - writeFile.Write((uint)BoneCountBuffer); - // Write count of meshes - writeFile.Write((uint)MeshCountBuffer); - // Write count of mats - writeFile.Write((uint)MatCountBuffer); - - // Write 3 reserved bytes - writeFile.Write(new byte[3] { 0x0, 0x0, 0x0 }); - - // Write bone tagnames - foreach (var Bone in Bones) - writeFile.WriteNullTermString(Bone.BoneName); - - // Write bone data - foreach (var Bone in Bones) - { - // Write bone flags - writeFile.Write((byte)0x0); - - // Write parent index - writeFile.Write((int)Bone.BoneParent); - - // Write global matrix - if (HasGlobals) - { - writeFile.Write((float)Bone.GlobalPosition.X); - writeFile.Write((float)Bone.GlobalPosition.Y); - writeFile.Write((float)Bone.GlobalPosition.Z); - writeFile.Write((float)Bone.GlobalRotation.X); - writeFile.Write((float)Bone.GlobalRotation.Y); - writeFile.Write((float)Bone.GlobalRotation.Z); - writeFile.Write((float)Bone.GlobalRotation.W); - } - - // Write local matrix - if (HasLocals) - { - writeFile.Write((float)Bone.LocalPosition.X); - writeFile.Write((float)Bone.LocalPosition.Y); - writeFile.Write((float)Bone.LocalPosition.Z); - writeFile.Write((float)Bone.LocalRotation.X); - writeFile.Write((float)Bone.LocalRotation.Y); - writeFile.Write((float)Bone.LocalRotation.Z); - writeFile.Write((float)Bone.LocalRotation.W); - } - - // Write scales - if (HasScales) - { - writeFile.Write((float)Bone.Scale.X); - writeFile.Write((float)Bone.Scale.Y); - writeFile.Write((float)Bone.Scale.Z); - } - } - - // Write mesh data - foreach (var Mesh in Meshes) - { - // Write mesh flags - writeFile.Write((byte)0x0); - - // Buffers for counts - byte MatIndiciesCount = 0, MaxSkinInfluence = 0; - int VertexCount = Mesh.Verticies.Count, FaceCount = Mesh.Faces.Count; - - // Iterate and calculate indicies - foreach (var Vertex in Mesh.Verticies) - { - if (Vertex.UVSets.Count > MatIndiciesCount) - MatIndiciesCount = (byte)Vertex.UVSets.Count; - if (Vertex.Weights.Count > MaxSkinInfluence) - MaxSkinInfluence = (byte)Vertex.Weights.Count; - } - - // Write material indicies - writeFile.Write((byte)MatIndiciesCount); - // Write max skin influence - writeFile.Write((byte)MaxSkinInfluence); - // Write vertex count - writeFile.Write((int)VertexCount); - // Write face count - writeFile.Write((int)FaceCount); - - // Write positions - foreach (var Vertex in Mesh.Verticies) - { - writeFile.Write((float)Vertex.Position.X); - writeFile.Write((float)Vertex.Position.Y); - writeFile.Write((float)Vertex.Position.Z); - } - - // Write uvlayers - foreach (var Vertex in Mesh.Verticies) - { - for (int i = 0; i < MatIndiciesCount; i++) - { - var Layer = (i < Vertex.UVSets.Count) ? Vertex.UVSets[i] : Vector2.Zero; - - // Write it - writeFile.Write((float)Layer.X); - writeFile.Write((float)Layer.Y); - } - } - - // Write normals - if (HasNormals) - { - foreach (var Vertex in Mesh.Verticies) - { - writeFile.Write((float)Vertex.VertexNormal.X); - writeFile.Write((float)Vertex.VertexNormal.Y); - writeFile.Write((float)Vertex.VertexNormal.Z); - } - } - - // Write colors - if (HasColors) - { - foreach (var Vertex in Mesh.Verticies) - { - writeFile.Write((byte)Vertex.VertexColor.R); - writeFile.Write((byte)Vertex.VertexColor.G); - writeFile.Write((byte)Vertex.VertexColor.B); - writeFile.Write((byte)Vertex.VertexColor.A); - } - } - - // Write weights - foreach (var Vertex in Mesh.Verticies) - { - for (int i = 0; i < MaxSkinInfluence; i++) - { - var WeightID = (i < Vertex.Weights.Count) ? Vertex.Weights[i].BoneIndex : 0; - var WeightValue = (i < Vertex.Weights.Count) ? Vertex.Weights[i].BoneWeight : 0.0f; - - // Write ID based on count - if (BoneCountBuffer <= 0xFF) - writeFile.Write((byte)WeightID); - else if (BoneCountBuffer <= 0xFFFF) - writeFile.Write((ushort)WeightID); - else - writeFile.Write((uint)WeightID); - - // Write value - writeFile.Write((float)WeightValue); - } - } - - // Write faces - foreach (var Face in Mesh.Faces) - { - // Write face indicies based on total vertex count - if (VertexCount <= 0xFF) - { - writeFile.Write((byte)Face.FaceIndex1); - writeFile.Write((byte)Face.FaceIndex2); - writeFile.Write((byte)Face.FaceIndex3); - } - else if (VertexCount <= 0xFFFF) - { - writeFile.Write((ushort)Face.FaceIndex1); - writeFile.Write((ushort)Face.FaceIndex2); - writeFile.Write((ushort)Face.FaceIndex3); - } - else - { - writeFile.Write((uint)Face.FaceIndex1); - writeFile.Write((uint)Face.FaceIndex2); - writeFile.Write((uint)Face.FaceIndex3); - } - } - - // Write material indicies - for (int i = 0; i < MatIndiciesCount; i++) - { - var Index = (i < Mesh.MaterialReferenceIndicies.Count) ? Mesh.MaterialReferenceIndicies[i] : -1; - - // Write the index, or null for no reference - writeFile.Write((int)Index); - } - } - - // Write material data - foreach (var Mat in Materials) - { - // Write name - writeFile.WriteNullTermString(Mat.Name); - - // Check type - if (Mat.MaterialData is SEModelSimpleMaterial) - { - // Simple material - writeFile.Write((bool)true); - - // Write the image references - writeFile.WriteNullTermString(((SEModelSimpleMaterial)Mat.MaterialData).DiffuseMap); - writeFile.WriteNullTermString(((SEModelSimpleMaterial)Mat.MaterialData).NormalMap); - writeFile.WriteNullTermString(((SEModelSimpleMaterial)Mat.MaterialData).SpecularMap); - } - } - } - } - - /// - /// Saves the SEModel to a file (Overwriting if exists), following the current specification version, using the provided data - /// - /// The file name to save the model to - public void Write(string FileName) - { - // Proxy off - Write(File.Create(FileName)); - } - - #endregion - - #region Reading - - /// - /// Reads a SEAnim from a stream - /// - /// The stream to read from - /// A SEAnim if successful, otherwise throws an error and returns null - public static SEModel Read(Stream Stream) - { - // Create a new model - var model = new SEModel(); - // Setup a new reader - using (ExtendedBinaryReader readFile = new ExtendedBinaryReader(Stream)) - { - // Magic - var Magic = readFile.ReadChars(7); - // Version - var Version = readFile.ReadInt16(); - // Header size - var HeaderSize = readFile.ReadInt16(); - // Check magic - if (!Magic.SequenceEqual(new char[] { 'S', 'E', 'M', 'o', 'd', 'e', 'l' })) - { - // Bad file - throw new Exception("Bad SEModel file, magic was invalid"); - } - // Data present flags - var DataPresentFlags = readFile.ReadByte(); - // Bone data present flags - var BoneDataPresentFlags = readFile.ReadByte(); - // Mesh data present flags - var MeshDataPresentFlags = readFile.ReadByte(); - - // Read counts - var BoneCount = readFile.ReadInt32(); - var MeshCount = readFile.ReadInt32(); - var MatCount = readFile.ReadInt32(); - - // Skip 3 reserved bytes - readFile.BaseStream.Position += 3; - - // Read bone tag names - List BoneNames = new List(); - // Loop - for (int i = 0; i < BoneCount; i++) - { - BoneNames.Add(readFile.ReadNullTermString()); - } - - // Loop and read bones - for (int i = 0; i < BoneCount; i++) - { - // Read bone flags (unused) - var BoneFlags = readFile.ReadByte(); - - // Read bone index - var ParentIndex = readFile.ReadInt32(); - - // Check for global matricies - Vector3 GlobalPosition = Vector3.Zero; - Quaternion GlobalRotation = Quaternion.Identity; - // Check - if (Convert.ToBoolean(BoneDataPresentFlags & (byte)SEModel_BoneDataPresenceFlags.SEMODEL_PRESENCE_GLOBAL_MATRIX)) - { - GlobalPosition = new Vector3(readFile.ReadSingle(), readFile.ReadSingle(), readFile.ReadSingle()); - GlobalRotation = new Quaternion(readFile.ReadSingle(), readFile.ReadSingle(), readFile.ReadSingle(), readFile.ReadSingle()); - } - - // Check for local matricies - Vector3 LocalPosition = Vector3.Zero; - Quaternion LocalRotation = Quaternion.Identity; - // Check - if (Convert.ToBoolean(BoneDataPresentFlags & (byte)SEModel_BoneDataPresenceFlags.SEMODEL_PRESENCE_LOCAL_MATRIX)) - { - LocalPosition = new Vector3(readFile.ReadSingle(), readFile.ReadSingle(), readFile.ReadSingle()); - LocalRotation = new Quaternion(readFile.ReadSingle(), readFile.ReadSingle(), readFile.ReadSingle(), readFile.ReadSingle()); - } - - // Check for scales - Vector3 Scale = Vector3.One; - // Check - if (Convert.ToBoolean(BoneDataPresentFlags & (byte)SEModel_BoneDataPresenceFlags.SEMODEL_PRESENCE_SCALES)) - { - Scale = new Vector3(readFile.ReadSingle(), readFile.ReadSingle(), readFile.ReadSingle()); - } - - // Add the bone - model.AddBone(BoneNames[i], ParentIndex, GlobalPosition, GlobalRotation, LocalPosition, LocalRotation, Scale); - } - - // Loop and read meshes - for (int i = 0; i < MeshCount; i++) - { - // Make a new submesh - var mesh = new SEModelMesh(); - - // Read mesh flags (unused) - var MeshFlags = readFile.ReadByte(); - - // Read counts - var MatIndiciesCount = readFile.ReadByte(); - var MaxSkinInfluenceCount = readFile.ReadByte(); - var VertexCount = readFile.ReadInt32(); - var FaceCount = readFile.ReadInt32(); - - // Loop and read positions - for (int v = 0; v < VertexCount; v++) - mesh.AddVertex(new SEModelVertex() { Position = new Vector3(readFile.ReadSingle(), readFile.ReadSingle(), readFile.ReadSingle()) }); - - // Read uvlayers - if (Convert.ToBoolean(MeshDataPresentFlags & (byte)SEModel_MeshDataPresenceFlags.SEMODEL_PRESENCE_UVSET)) - { - for (int v = 0; v < VertexCount; v++) - { - for (int l = 0; l < MatIndiciesCount; l++) - mesh.Verticies[v].UVSets.Add(new Vector2(readFile.ReadSingle(), readFile.ReadSingle())); - } - } - - // Read normals - if (Convert.ToBoolean(MeshDataPresentFlags & (byte)SEModel_MeshDataPresenceFlags.SEMODEL_PRESENCE_NORMALS)) - { - // Loop and read vertex normals - for (int v = 0; v < VertexCount; v++) - mesh.Verticies[v].VertexNormal = new Vector3(readFile.ReadSingle(), readFile.ReadSingle(), readFile.ReadSingle()); - } - - // Read colors - if (Convert.ToBoolean(MeshDataPresentFlags & (byte)SEModel_MeshDataPresenceFlags.SEMODEL_PRESENCE_COLOR)) - { - // Loop and read colors - for (int v = 0; v < VertexCount; v++) - mesh.Verticies[v].VertexColor = new Color(readFile.ReadByte(), readFile.ReadByte(), readFile.ReadByte(), readFile.ReadByte()); - } - - // Read weights - if (Convert.ToBoolean(MeshDataPresentFlags & (byte)SEModel_MeshDataPresenceFlags.SEMODEL_PRESENCE_WEIGHTS)) - { - for (int v = 0; v < VertexCount; v++) - { - // Read IDs and Values - for (int l = 0; l < MaxSkinInfluenceCount; l++) - { - if (BoneCount <= 0xFF) - mesh.Verticies[v].Weights.Add(new SEModelWeight() { BoneIndex = readFile.ReadByte(), BoneWeight = readFile.ReadSingle() }); - else if (BoneCount <= 0xFFFF) - mesh.Verticies[v].Weights.Add(new SEModelWeight() { BoneIndex = readFile.ReadUInt16(), BoneWeight = readFile.ReadSingle() }); - else - mesh.Verticies[v].Weights.Add(new SEModelWeight() { BoneIndex = readFile.ReadUInt32(), BoneWeight = readFile.ReadSingle() }); - } - } - } - - // Loop and read faces - for (int f = 0; f < FaceCount; f++) - { - if (VertexCount <= 0xFF) - mesh.AddFace(readFile.ReadByte(), readFile.ReadByte(), readFile.ReadByte()); - else if (VertexCount <= 0xFFFF) - mesh.AddFace(readFile.ReadUInt16(), readFile.ReadUInt16(), readFile.ReadUInt16()); - else - mesh.AddFace(readFile.ReadUInt32(), readFile.ReadUInt32(), readFile.ReadUInt32()); - } - - // Read material reference indicies - for (int f = 0; f < MatIndiciesCount; f++) - mesh.AddMaterialIndex(readFile.ReadInt32()); - - // Add the mesh - model.AddMesh(mesh); - } - - // Loop and read materials - for (int m = 0; m < MatCount; m++) - { - var mat = new SEModelMaterial(); - - // Read the name - mat.Name = readFile.ReadNullTermString(); - // Read IsSimpleMaterial - var IsSimpleMaterial = readFile.ReadBoolean(); - - // Read the material - if (IsSimpleMaterial) - { - mat.MaterialData = new SEModelSimpleMaterial() - { - DiffuseMap = readFile.ReadNullTermString(), - NormalMap = readFile.ReadNullTermString(), - SpecularMap = readFile.ReadNullTermString() - }; - } - - // Add the material - model.AddMaterial(mat); - } - } - // Return result - return model; - } - - /// - /// Reads a SEModel file, following the current specification - /// - /// The file name to open - /// A SEModel if successful, otherwise throws an error and returns null - public static SEModel Read(string FileName) - { - // Proxy off - return Read(File.OpenRead(FileName)); - } - - #endregion - - #region Adding Data - - /// - /// Adds a new bone to the model with the given information - /// - /// The tag name of this bone - /// The parent index of this bone, -1 for a root bone - /// The global space position of this bone - /// The global space rotation of this bone - /// The local parent space position of this bone - /// The local parent space rotation of this bone - /// The scale of this bone, 1.0 is default - public void AddBone(string Name, int ParentIndex, Vector3 GlobalPosition, Quaternion GlobalRotation, Vector3 LocalPosition, Quaternion LocalRotation, Vector3 Scale) - { - var Bone = new SEModelBone() { BoneName = Name, BoneParent = ParentIndex }; - - // Set matricies - Bone.GlobalPosition = GlobalPosition; - Bone.GlobalRotation = GlobalRotation; - Bone.LocalPosition = LocalPosition; - Bone.LocalRotation = LocalRotation; - - // Set scale - Bone.Scale = Scale; - - // Add - Bones.Add(Bone); - } - - /// - /// Adds the given mesh to the model - /// - /// The mesh to add - public void AddMesh(SEModelMesh Mesh) - { - // Add it - Meshes.Add(Mesh); - } - - /// - /// Adds the given material to the model - /// - /// The material to add - public void AddMaterial(SEModelMaterial Material) - { - // Add it - Materials.Add(Material); - } - - #endregion - } -} diff --git a/src/Husky/SELib/Utilities/ExtendedBinaryReader.cs b/src/Husky/SELib/Utilities/ExtendedBinaryReader.cs deleted file mode 100644 index 8c5ed7e..0000000 --- a/src/Husky/SELib/Utilities/ExtendedBinaryReader.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -/// -/// ExtendedBinaryReader.cs -/// Author: DTZxPorter -/// Written for the SE Format Project -/// - -namespace SELib.Utilities -{ - /// - /// Supports custom methods for manipulating data between c++ streams and .net ones - /// - internal class ExtendedBinaryReader : BinaryReader - { - public ExtendedBinaryReader(Stream stream) - : base(stream) - { - } - - /// - /// Reads a null-terminated string from the stream - /// - /// The resulting string - public string ReadNullTermString() - { - // Buffer - string StringBuffer = ""; - // Char buffer - char CharBuffer; - // Read until null-term - while ((int)(CharBuffer = this.ReadChar()) != 0) - { - StringBuffer = StringBuffer + CharBuffer; - } - // Result - return StringBuffer; - } - } -} diff --git a/src/Husky/SELib/Utilities/ExtendedBinaryWriter.cs b/src/Husky/SELib/Utilities/ExtendedBinaryWriter.cs deleted file mode 100644 index b278e71..0000000 --- a/src/Husky/SELib/Utilities/ExtendedBinaryWriter.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -/// -/// ExtendedBinaryWriter.cs -/// Author: DTZxPorter -/// Written for the SE Format Project -/// - -namespace SELib.Utilities -{ - /// - /// Supports custom methods for manipulating data between c++ streams and .net ones - /// - internal class ExtendedBinaryWriter : BinaryWriter - { - public ExtendedBinaryWriter(Stream stream) - : base(stream) - { - } - - /// - /// Writes a null-terminated string to the stream - /// - /// The string to write - public void WriteNullTermString(string value) - { - // Check value - if (string.IsNullOrEmpty(value)) - { - value = string.Empty; - } - // Write to file - Write(Encoding.ASCII.GetBytes(value)); - // Write the null-char - Write((byte)0); - } - } -} diff --git a/src/Husky/SELib/Utilities/ExtendedMath.cs b/src/Husky/SELib/Utilities/ExtendedMath.cs deleted file mode 100644 index 62263ab..0000000 --- a/src/Husky/SELib/Utilities/ExtendedMath.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SELib.Utilities -{ - /// - /// Generic interface for all key data - /// - public interface KeyData - { - // A generic interface for a container - } - - /// - /// A container for a vector (XY) (Normalized) - /// - public class Vector2 : KeyData - { - public double X { get; set; } - public double Y { get; set; } - - public Vector2() { X = 0; Y = 0; } - public Vector2(float XCoord, float YCoord) { X = XCoord; Y = YCoord; } - - public override bool Equals(object obj) - { - Vector2 vec = (Vector2)obj; - return (vec.X == X && vec.Y == Y); - } - - public override int GetHashCode() - { - return X.GetHashCode() ^ Y.GetHashCode(); - } - - public static readonly Vector2 Zero = new Vector2() { X = 0, Y = 0 }; - public static readonly Vector2 One = new Vector2() { X = 1, Y = 1 }; - } - - /// - /// A container for a vector (XYZ) (Normalized) - /// - public class Vector3 : Vector2 - { - public double Z { get; set; } - - public Vector3() { X = 0; Y = 0; Z = 0; } - public Vector3(float XCoord, float YCoord, float ZCoord) { X = XCoord; Y = YCoord; Z = ZCoord; } - - public override bool Equals(object obj) - { - Vector3 vec = (Vector3)obj; - return (vec.X == X && vec.Y == Y && vec.Z == Z); - } - - public override int GetHashCode() - { - return base.GetHashCode() ^ Z.GetHashCode(); - } - - new public static readonly Vector3 Zero = new Vector3() { X = 0, Y = 0, Z = 0 }; - new public static readonly Vector3 One = new Vector3() { X = 1, Y = 1, Z = 1 }; - } - - /// - /// A container for a color (RGBA) - /// - public class Color - { - public byte R { get; set; } - public byte G { get; set; } - public byte B { get; set; } - public byte A { get; set; } - - public Color() { R = 255; G = 255; B = 255; A = 255; } - public Color(byte Red, byte Green, byte Blue, byte Alpha) { R = Red; G = Green; B = Blue; A = Alpha; } - - public override bool Equals(object obj) - { - Color vec = (Color)obj; - return (vec.R == R && vec.G == G && vec.B == B && vec.A == A); - } - - public override int GetHashCode() - { - return R.GetHashCode() ^ G.GetHashCode() ^ B.GetHashCode() ^ A.GetHashCode(); - } - - public static readonly Color White = new Color(255, 255, 255, 255); - } - - /// - /// A container for a quaternion rotation (XYZW) (Normalized) - /// - public class Quaternion : Vector3 - { - public double W { get; set; } - - public Quaternion() { X = 0; Y = 0; Z = 0; W = 1; } - public Quaternion(float XCoord, float YCoord, float ZCoord, float WCoord) { X = XCoord; Y = YCoord; Z = ZCoord; W = WCoord; } - - public override bool Equals(object obj) - { - Quaternion vec = (Quaternion)obj; - return (vec.X == X && vec.Y == Y && vec.Z == Z && vec.W == W); - } - - public override int GetHashCode() - { - return base.GetHashCode() ^ W.GetHashCode(); - } - - public static readonly Quaternion Identity = new Quaternion() { X = 0, Y = 0, Z = 0, W = 1 }; - } -}