From 41bef25e5639c608dfcb7e2c142bf0a8f4e34adc Mon Sep 17 00:00:00 2001 From: Scobalula Date: Mon, 19 Nov 2018 01:42:10 +0000 Subject: [PATCH] New Games + Static Models for all Games --- README.md | 10 +- src/Husky/Husky/FileFormats/IWMap.cs | 4 +- src/Husky/Husky/Games/AdvancedWarfare.cs | 22 +- src/Husky/Husky/Games/BlackOps.cs | 136 ++++- src/Husky/Husky/Games/BlackOps2.cs | 611 +++++++++++++++++++++ src/Husky/Husky/Games/Ghosts.cs | 22 +- src/Husky/Husky/Games/ModernWarfare.cs | 508 +++++++++++++++++ src/Husky/Husky/Games/ModernWarfare2.cs | 49 +- src/Husky/Husky/Games/ModernWarfare3.cs | 130 ++++- src/Husky/Husky/Games/ModernWarfareRM.cs | 18 +- src/Husky/Husky/Games/WorldAtWar.cs | 135 ++++- src/Husky/Husky/Husky.csproj | 3 + src/Husky/Husky/Program.cs | 62 ++- src/Husky/Husky/Properties/AssemblyInfo.cs | 4 +- src/Husky/Husky/Utility/FloatToInt.cs | 25 + src/Husky/Husky/Utility/VertexNormal.cs | 19 + 16 files changed, 1662 insertions(+), 96 deletions(-) create mode 100644 src/Husky/Husky/Games/BlackOps2.cs create mode 100644 src/Husky/Husky/Games/ModernWarfare.cs create mode 100644 src/Husky/Husky/Utility/FloatToInt.cs diff --git a/README.md b/README.md index bd69ab7..8b94470 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,11 @@ Husky is a BSP Extractor for Call of Duty. It can rip the raw vertex/face data t ### Supported Games * Call of Duty: World at War -* Call of Duty: Modern Warfare 3 +* Call of Duty: Black Ops +* Call of Duty: Black Ops 2 +* Call of Duty: Modern Warfare * Call of Duty: Modern Warfare 2 +* Call of Duty: Modern Warfare 3 * Call of Duty: Advanced Warfare * Call of Duty: Ghosts * Call of Duty: Modern Warfare Remastered @@ -16,16 +19,13 @@ Husky is a BSP Extractor for Call of Duty. It can rip the raw vertex/face data t To download Husky, go to the [Releases](https://github.com/Scobalula/Husky/releases) and download the latest build. -To use Husky, simply run the game, load the map you want to extract, and run Husky. +To use Husky, simply run the game, load the map you want to extract, and run Husky. In some cases you may need to run Husky as an administator. 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) - -For Ghosts, AW, and MWR: - * **mapname**.map - Map file with **static** model locations and rotations 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). diff --git a/src/Husky/Husky/FileFormats/IWMap.cs b/src/Husky/Husky/FileFormats/IWMap.cs index f99b50d..94e4c2b 100644 --- a/src/Husky/Husky/FileFormats/IWMap.cs +++ b/src/Husky/Husky/FileFormats/IWMap.cs @@ -40,8 +40,8 @@ public static Entity CreateMiscModel(string modelName, Vector3 origin, Vector3 a var result = new Entity("misc_model"); // Add properties result.KeyValuePairs["model"] = modelName; - result.KeyValuePairs["origin"] = String.Format("{0} {1} {2}", origin.X, origin.Y, origin.Z); - result.KeyValuePairs["angles"] = String.Format("{1} {2} {0}", angles.X, angles.Y, angles.Z); + result.KeyValuePairs["origin"] = String.Format("{0:0.0000} {1:0.0000} {2:0.0000}", origin.X, origin.Y, origin.Z); + result.KeyValuePairs["angles"] = String.Format("{1:0.0000} {2:0.0000} {0:0.0000}", angles.X, angles.Y, angles.Z); result.KeyValuePairs["modelscale"] = modelScale.ToString(); // Ship her back return result; diff --git a/src/Husky/Husky/Games/AdvancedWarfare.cs b/src/Husky/Husky/Games/AdvancedWarfare.cs index 0204d4b..841b1ad 100644 --- a/src/Husky/Husky/Games/AdvancedWarfare.cs +++ b/src/Husky/Husky/Games/AdvancedWarfare.cs @@ -270,7 +270,7 @@ public unsafe struct GfxStaticModel /// /// Reads BSP Data /// - public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress) + public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress, string gameType) { // Found her Printer.WriteLine("INFO", "Found supported game: Call of Duty: Advanced Warfare"); @@ -302,6 +302,10 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l Printer.WriteLine("INFO", String.Format("Surface Count - {0}", gfxMapAsset.SurfaceCount)); Printer.WriteLine("INFO", String.Format("Model Count - {0}", gfxMapAsset.GfxStaticModelsCount)); + // Build output Folder + string outputName = Path.Combine("exported_maps", "advanced_warfare", gameType, mapName, mapName); + Directory.CreateDirectory(Path.GetDirectoryName(outputName)); + // Stop watch var stopWatch = Stopwatch.StartNew(); @@ -381,21 +385,21 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l } // Save it - obj.Save(Path.ChangeExtension(gfxMapName, ".obj")); + obj.Save(outputName + ".obj"); // Build search strinmg string searchString = ""; // Loop through images, and append each to the search string (for Wraith/Greyhound) - foreach(string imageName in imageNames) + foreach (string imageName in imageNames) searchString += String.Format("{0},", Path.GetFileNameWithoutExtension(imageName)); // Dump it - File.WriteAllText(Path.ChangeExtension(gfxMapName, ".txt"), searchString); + File.WriteAllText(outputName + "_search_string.txt", searchString); // Read entities and dump to map - mapFile.Entities.AddRange(ReadStaticModels(reader, gfxMapAsset.GfxStaticModelsPointer, gfxMapAsset.GfxStaticModelsCount)); - mapFile.DumpToMap(Path.ChangeExtension(gfxMapName, ".map")); + mapFile.Entities.AddRange(ReadStaticModels(reader, gfxMapAsset.GfxStaticModelsPointer, (int)gfxMapAsset.GfxStaticModelsCount)); + mapFile.DumpToMap(outputName + ".map"); // Done Printer.WriteLine("INFO", String.Format("Converted to OBJ in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); @@ -461,9 +465,9 @@ public static Vertex[] ReadGfxVertices(ProcessReader reader, long address, int c { // Set offset Position = new Vector3( - gfxVertex.X, - gfxVertex.Y, - gfxVertex.Z), + gfxVertex.X * 2.54, + gfxVertex.Y * 2.54, + gfxVertex.Z * 2.54), // Decode and set normal (from DTZxPorter - Wraith, same as XModels) Normal = VertexNormal.UnpackB(gfxVertex.Normal), // Set UV diff --git a/src/Husky/Husky/Games/BlackOps.cs b/src/Husky/Husky/Games/BlackOps.cs index a76570f..f3d2b38 100644 --- a/src/Husky/Husky/Games/BlackOps.cs +++ b/src/Husky/Husky/Games/BlackOps.cs @@ -89,12 +89,32 @@ public unsafe struct GfxMap /// /// Unknown Bytes (more BSP data we probably don't care for) /// - public fixed byte Padding3[0x184]; + public fixed byte Padding3[0x12C]; + + /// + /// Number of Static Models + /// + public int GfxStaticModelsCount { get; set; } + + /// + /// Unknown Bytes (more BSP data we probably don't care for) + /// + public fixed byte Padding4[0x54]; /// /// Pointer to the Gfx Index Data /// public int GfxSurfacesPointer { get; set; } + + /// + /// Null Padding + /// + public int Padding5 { get; set; } + + /// + /// Pointer to the Gfx Static Models + /// + public int GfxStaticModelsPointer { get; set; } } /// @@ -149,6 +169,7 @@ public unsafe struct GfxSurface public fixed byte Padding3[0x1C]; } + /// /// Call of Duty: Black Ops Material Asset /// @@ -180,10 +201,57 @@ public unsafe struct Material public int ImageTablePointer { get; set; } } + /// + /// Gfx Static Model + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct GfxStaticModel + { + /// + /// Null Padding + /// + public int Padding { get; set; } + + /// + /// X Origin + /// + public float X { get; set; } + + /// + /// Y Origin + /// + public float Y { get; set; } + + /// + /// Z Origin + /// + public float Z { get; set; } + + /// + /// 3x3 Rotation Matrix + /// + public fixed float Matrix[9]; + + /// + /// Model Scale + /// + public float ModelScale { get; set; } + + /// + /// Pointer to the XModel Asset + /// + public int ModelPointer { get; set; } + + /// + /// Unknown Bytes + /// + public fixed byte UnknownBytes2[0x10]; + } + /// /// Reads BSP Data /// - public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress) + public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress, string gameType) { // Found her Printer.WriteLine("INFO", "Found supported game: Call of Duty: Black Ops"); @@ -208,19 +276,26 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l } else { + // New IW Map + var mapFile = new IWMap(); // 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)); + Printer.WriteLine("INFO", String.Format("Model Count - {0}", gfxMapAsset.GfxStaticModelsCount)); + + // Build output Folder + string outputName = Path.Combine("exported_maps", "black_ops", gameType, mapName, mapName); + Directory.CreateDirectory(Path.GetDirectoryName(outputName)); // Stop watch var stopWatch = Stopwatch.StartNew(); // Read Vertices Printer.WriteLine("INFO", "Parsing vertex data...."); - var vertices = ReadGfxVertices(reader, gfxMapAsset.GfxVerticesPointer, (int)gfxMapAsset.GfxVertexCount); + var vertices = ReadGfxVertices(reader, gfxMapAsset.GfxVerticesPointer, gfxMapAsset.GfxVertexCount); Printer.WriteLine("INFO", String.Format("Parsed vertex data in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); // Reset timer @@ -228,7 +303,7 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l // Read Indices Printer.WriteLine("INFO", "Parsing surface indices...."); - var indices = ReadGfxIndices(reader, gfxMapAsset.GfxIndicesPointer, (int)gfxMapAsset.GfxIndicesCount); + var indices = ReadGfxIndices(reader, gfxMapAsset.GfxIndicesPointer, gfxMapAsset.GfxIndicesCount); Printer.WriteLine("INFO", String.Format("Parsed indices in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); // Reset timer @@ -294,7 +369,7 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l } // Save it - obj.Save(Path.ChangeExtension(gfxMapName, ".obj")); + obj.Save(outputName + ".obj"); // Build search strinmg string searchString = ""; @@ -304,7 +379,11 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l searchString += String.Format("{0},", Path.GetFileNameWithoutExtension(imageName)); // Dump it - File.WriteAllText(Path.ChangeExtension(gfxMapName, ".txt"), searchString); + File.WriteAllText(outputName + "_search_string.txt", searchString); + + // Read entities and dump to map + mapFile.Entities.AddRange(ReadStaticModels(reader, gfxMapAsset.GfxStaticModelsPointer, gfxMapAsset.GfxStaticModelsCount)); + mapFile.DumpToMap(outputName + ".map"); // Done Printer.WriteLine("INFO", String.Format("Converted to OBJ in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); @@ -370,9 +449,9 @@ public static Vertex[] ReadGfxVertices(ProcessReader reader, long address, int c { // Set offset Position = new Vector3( - gfxVertex.X, - gfxVertex.Y, - gfxVertex.Z), + gfxVertex.X * 2.54, + gfxVertex.Y * 2.54, + gfxVertex.Z * 2.54), // Decode and set normal (from DTZxPorter - Wraith, same as XModels) Normal = VertexNormal.UnpackA(gfxVertex.Normal), // Set UV @@ -405,5 +484,44 @@ public static WavefrontOBJ.Material ReadMaterial(ProcessReader reader, long addr // Done return objMaterial; } + + /// + /// Reads Static Models + /// + public unsafe static List ReadStaticModels(ProcessReader reader, long address, int count) + { + // Resulting Entities + List entities = new List(count); + // Read buffer + var byteBuffer = reader.ReadBytes(address, count * Marshal.SizeOf()); + // Loop number of models we have + for (int i = 0; i < count; i++) + { + // Read Struct + var staticModel = ByteUtil.BytesToStruct(byteBuffer, i * Marshal.SizeOf()); + // Model Name + var modelName = reader.ReadNullTerminatedString(reader.ReadInt32(staticModel.ModelPointer)); + // New Matrix + var matrix = new Rotation.Matrix(); + // Copy X Values + matrix.Values[0] = staticModel.Matrix[0]; + matrix.Values[1] = staticModel.Matrix[1]; + matrix.Values[2] = staticModel.Matrix[2]; + // Copy Y Values + matrix.Values[4] = staticModel.Matrix[3]; + matrix.Values[5] = staticModel.Matrix[4]; + matrix.Values[6] = staticModel.Matrix[5]; + // Copy Z Values + matrix.Values[8] = staticModel.Matrix[6]; + matrix.Values[9] = staticModel.Matrix[7]; + matrix.Values[10] = staticModel.Matrix[8]; + // Convert to Euler + var euler = matrix.ToEuler(); + // Add it + entities.Add(IWMap.Entity.CreateMiscModel(modelName, new Vector3(staticModel.X, staticModel.Y, staticModel.Z), Rotation.ToDegrees(euler), staticModel.ModelScale)); + } + // Done + return entities; + } } } \ No newline at end of file diff --git a/src/Husky/Husky/Games/BlackOps2.cs b/src/Husky/Husky/Games/BlackOps2.cs new file mode 100644 index 0000000..db25869 --- /dev/null +++ b/src/Husky/Husky/Games/BlackOps2.cs @@ -0,0 +1,611 @@ +// ------------------------------------------------------------------------ +// 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 . +// ------------------------------------------------------------------------ +using PhilLibX; +using PhilLibX.IO; +using System; +using System.IO; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Collections.Generic; + +namespace Husky +{ + /// + /// Bo2 Logic + /// + class BlackOps2 + { + /// + /// Bo2 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[0x194]; + + /// + /// Number of Gfx Vertices (XYZ, etc.) + /// + public int GfxVertexCount { get; set; } + + /// + /// Raw size of the Gfx Vertex Buffer + /// + public int GfxVertexBufferSize { 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 Padding3[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 Padding4[0x144]; + + /// + /// Number of Static Models + /// + public int GfxStaticModelsCount { get; set; } + + /// + /// Unknown Bytes (more BSP data we probably don't care for) + /// + public fixed byte Padding5[0x54]; + + /// + /// Pointer to the Gfx Index Data + /// + public int GfxSurfacesPointer { get; set; } + + /// + /// Pointer to the Gfx Static Models + /// + public int GfxStaticModelsPointer { get; set; } + } + + /// + /// Gfx Map Surface + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct GfxSurface + { + /// + /// Unknown Bytes + /// + public fixed byte Padding[0xC]; + + /// + /// offset of the vertex chunk within the vertex buffer + /// + public int VertexBufferOffset { get; set; } + + /// + /// Unknown Bytes + /// + public fixed byte Padding1[0x10]; + + /// + /// Base Vertex Index (this is what allows the GfxMap to have 65k+ verts with only 2 byte indices) + /// + public int VertexIndex { get; set; } + + /// + /// Null Padding + /// + public int Padding2 { 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 Padding4[0x1C]; + } + + /// + /// 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 ushort U { get; set; } + + /// + /// V Texture Position + /// + public ushort V { get; set; } + + /// + /// Packed Vertex Normal (same as XModels) + /// + public PackedUnitVector Normal { get; set; } + + /// + /// Unknown Bytes (Possibly Tangent) + /// + public int Padding2 { get; set; } + + /// + /// Unknown Bytes (Possibly Tangent) + /// + public int Padding3 { get; set; } + } + + /// + /// Gfx Static Model + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct GfxStaticModel + { + /// + /// Null Padding + /// + public int Padding { get; set; } + + /// + /// X Origin + /// + public float X { get; set; } + + /// + /// Y Origin + /// + public float Y { get; set; } + + /// + /// Z Origin + /// + public float Z { get; set; } + + /// + /// 3x3 Rotation Matrix + /// + public fixed float Matrix[9]; + + /// + /// Model Scale + /// + public float ModelScale { get; set; } + + /// + /// Pointer to the XModel Asset + /// + public int ModelPointer { get; set; } + + /// + /// Unknown Bytes + /// + public fixed byte UnknownBytes2[0x5C]; + } + + /// + /// Call of Duty: Black Ops 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[0x50]; + + /// + /// 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 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 + /// + public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress, string gameType) + { + // Found her + Printer.WriteLine("INFO", "Found supported game: Call of Duty: Black Ops 2"); + // Validate by XModel Name + if (reader.ReadNullTerminatedString(reader.ReadInt32(reader.ReadInt32(assetPoolsAddress + 0x14) + 4)) == "defaultvehicle") + { + // Load BSP Pools (they only have a size of 1 so we have no free header) + var gfxMapAsset = reader.ReadStruct(reader.ReadInt32(assetPoolsAddress + 0x44)); + + // 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 + { + // New IW Map + var mapFile = new IWMap(); + // 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)); + Printer.WriteLine("INFO", String.Format("Model Count - {0}", gfxMapAsset.GfxStaticModelsCount)); + + // Build output Folder + string outputName = Path.Combine("exported_maps", "black_ops_2", gameType, mapName, mapName); + Directory.CreateDirectory(Path.GetDirectoryName(outputName)); + + // Stop watch + var stopWatch = Stopwatch.StartNew(); + + // Read Vertices + Printer.WriteLine("INFO", "Parsing vertex data...."); + var vertexBuffer = ReadGfxVertices(reader, gfxMapAsset.GfxVerticesPointer, gfxMapAsset.GfxVertexBufferSize); + 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, 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 OBJ + Printer.WriteLine("INFO", "Converting to OBJ...."); + + // Create new OBJ + var obj = new WavefrontOBJ(); + + // Image Names (for Search String) + HashSet imageNames = new HashSet(); + + // Vertex Index Tracker + int vertexIndex = 0; + + // 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]; + var faceIndex2 = indices[i * 3 + surface.FaceIndex + 1]; + var faceIndex3 = indices[i * 3 + surface.FaceIndex + 2]; + + // Validate unique points, and write to OBJ + if (faceIndex1 != faceIndex2 && faceIndex1 != faceIndex3 && faceIndex2 != faceIndex3) + { + // new Obj Face + var objFace = new WavefrontOBJ.Face(material.Name); + + // Unpack vertices + var vertex1 = UnpackVertex(ByteUtil.BytesToStruct(vertexBuffer, surface.VertexBufferOffset + (faceIndex1 * 36))); + var vertex2 = UnpackVertex(ByteUtil.BytesToStruct(vertexBuffer, surface.VertexBufferOffset + (faceIndex2 * 36))); + var vertex3 = UnpackVertex(ByteUtil.BytesToStruct(vertexBuffer, surface.VertexBufferOffset + (faceIndex3 * 36))); + + // Add Offsets + obj.Vertices.Add(vertex1.Position); + obj.Vertices.Add(vertex2.Position); + obj.Vertices.Add(vertex3.Position); + + // Add Normals + obj.Normals.Add(vertex1.Normal); + obj.Normals.Add(vertex2.Normal); + obj.Normals.Add(vertex3.Normal); + + // Add UVs + obj.UVs.Add(vertex1.UV); + obj.UVs.Add(vertex2.UV); + obj.UVs.Add(vertex3.UV); + + // Add points + objFace.Vertices[0] = new WavefrontOBJ.Face.Vertex(vertexIndex, vertexIndex, vertexIndex); + objFace.Vertices[2] = new WavefrontOBJ.Face.Vertex(vertexIndex + 1, vertexIndex + 1, vertexIndex + 1); + objFace.Vertices[1] = new WavefrontOBJ.Face.Vertex(vertexIndex + 2, vertexIndex + 2, vertexIndex + 2); + + // Add to OBJ + obj.Faces.Add(objFace); + + vertexIndex += 3; + } + } + } + + + // Save it + obj.Save(outputName + ".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(outputName + "_search_string.txt", searchString); + + // Read entities and dump to map + mapFile.Entities.AddRange(ReadStaticModels(reader, gfxMapAsset.GfxStaticModelsPointer, gfxMapAsset.GfxStaticModelsCount)); + mapFile.DumpToMap(outputName + ".map"); + + // 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: Black Ops 2 is supported, but this EXE is not.", ConsoleColor.DarkRed); + } + } + + /// + /// Reads Gfx Surfaces + /// + public static GfxSurface[] ReadGfxSufaces(ProcessReader reader, long address, int count) + { + // Preallocate short array + 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 * 80); + + // 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]; + // Read buffer + var byteBuffer = reader.ReadBytes(address, count * 2); + // Copy buffer + Buffer.BlockCopy(byteBuffer, 0, indices, 0, byteBuffer.Length); + // Done + return indices; + } + + /// + /// Reads Gfx Vertices + /// + public static byte[] ReadGfxVertices(ProcessReader reader, long address, int size) + { + // Done + return reader.ReadBytes(address, size); + } + + /// + /// Unpacks a vertex + /// + /// Vertex to unpack + /// + public static Vertex UnpackVertex(GfxVertex packedVertex) + { + return new Vertex() + { + // Set offset + Position = new Vector3( + packedVertex.X * 2.54, + packedVertex.Y * 2.54, + packedVertex.Z * 2.54), + // Decode and set normal (from DTZxPorter - Wraith, same as XModels) + Normal = VertexNormal.UnpackC(packedVertex.Normal), + // Set UV + UV = new Vector2(packedVertex.U, 1 - packedVertex.V) + }; + } + + /// + /// 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("*", "").Replace(")", "").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 + 0x48)) + ".png"; + } + // Done + return objMaterial; + } + + /// + /// Reads Static Models + /// + public unsafe static List ReadStaticModels(ProcessReader reader, long address, int count) + { + // Resulting Entities + List entities = new List(count); + // Read buffer + var byteBuffer = reader.ReadBytes(address, count * Marshal.SizeOf()); + // Loop number of models we have + for (int i = 0; i < count; i++) + { + // Read Struct + var staticModel = ByteUtil.BytesToStruct(byteBuffer, i * Marshal.SizeOf()); + // Model Name + var modelName = reader.ReadNullTerminatedString(reader.ReadInt32(staticModel.ModelPointer)); + // New Matrix + var matrix = new Rotation.Matrix(); + // Copy X Values + matrix.Values[0] = staticModel.Matrix[0]; + matrix.Values[1] = staticModel.Matrix[1]; + matrix.Values[2] = staticModel.Matrix[2]; + // Copy Y Values + matrix.Values[4] = staticModel.Matrix[3]; + matrix.Values[5] = staticModel.Matrix[4]; + matrix.Values[6] = staticModel.Matrix[5]; + // Copy Z Values + matrix.Values[8] = staticModel.Matrix[6]; + matrix.Values[9] = staticModel.Matrix[7]; + matrix.Values[10] = staticModel.Matrix[8]; + // Convert to Euler + var euler = matrix.ToEuler(); + // Add it + entities.Add(IWMap.Entity.CreateMiscModel(modelName, new Vector3(staticModel.X, staticModel.Y, staticModel.Z), Rotation.ToDegrees(euler), staticModel.ModelScale)); + } + // Done + return entities; + } + } +} \ No newline at end of file diff --git a/src/Husky/Husky/Games/Ghosts.cs b/src/Husky/Husky/Games/Ghosts.cs index 16c35f0..130e231 100644 --- a/src/Husky/Husky/Games/Ghosts.cs +++ b/src/Husky/Husky/Games/Ghosts.cs @@ -270,7 +270,7 @@ public unsafe struct GfxStaticModel /// /// Reads BSP Data /// - public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress) + public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress, string gameType) { // Found her Printer.WriteLine("INFO", "Found supported game: Call of Duty: Ghosts"); @@ -302,16 +302,16 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l Printer.WriteLine("INFO", String.Format("Surface Count - {0}", gfxMapAsset.SurfaceCount)); Printer.WriteLine("INFO", String.Format("Model Count - {0}", gfxMapAsset.GfxStaticModelsCount)); - // Read entities and dump to map - mapFile.Entities.AddRange(ReadStaticModels(reader, gfxMapAsset.GfxStaticModelsPointer, gfxMapAsset.GfxStaticModelsCount)); - mapFile.DumpToMap(Path.ChangeExtension(gfxMapName, ".map")); + // Build output Folder + string outputName = Path.Combine("exported_maps", "ghosts", gameType, mapName, mapName); + Directory.CreateDirectory(Path.GetDirectoryName(outputName)); // Stop watch var stopWatch = Stopwatch.StartNew(); // Read Vertices Printer.WriteLine("INFO", "Parsing vertex data...."); - var vertices = ReadGfxVertices(reader, gfxMapAsset.GfxVerticesPointer, (int)gfxMapAsset.GfxVertexCount); + var vertices = ReadGfxVertices(reader, gfxMapAsset.GfxVerticesPointer, gfxMapAsset.GfxVertexCount); Printer.WriteLine("INFO", String.Format("Parsed vertex data in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); // Reset timer @@ -385,7 +385,7 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l } // Save it - obj.Save(Path.ChangeExtension(gfxMapName, ".obj")); + obj.Save(outputName + ".obj"); // Build search strinmg string searchString = ""; @@ -395,11 +395,11 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l searchString += String.Format("{0},", Path.GetFileNameWithoutExtension(imageName)); // Dump it - File.WriteAllText(Path.ChangeExtension(gfxMapName, ".txt"), searchString); + File.WriteAllText(outputName + "_search_string.txt", searchString); // Read entities and dump to map mapFile.Entities.AddRange(ReadStaticModels(reader, gfxMapAsset.GfxStaticModelsPointer, gfxMapAsset.GfxStaticModelsCount)); - mapFile.DumpToMap(Path.ChangeExtension(gfxMapName, ".map")); + mapFile.DumpToMap(outputName + ".map"); // Done Printer.WriteLine("INFO", String.Format("Converted to OBJ in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); @@ -465,9 +465,9 @@ public static Vertex[] ReadGfxVertices(ProcessReader reader, long address, int c { // Set offset Position = new Vector3( - gfxVertex.X, - gfxVertex.Y, - gfxVertex.Z), + gfxVertex.X * 2.54, + gfxVertex.Y * 2.54, + gfxVertex.Z * 2.54), // Decode and set normal (from DTZxPorter - Wraith, same as XModels) Normal = VertexNormal.UnpackB(gfxVertex.Normal), // Set UV diff --git a/src/Husky/Husky/Games/ModernWarfare.cs b/src/Husky/Husky/Games/ModernWarfare.cs new file mode 100644 index 0000000..7fe6ea1 --- /dev/null +++ b/src/Husky/Husky/Games/ModernWarfare.cs @@ -0,0 +1,508 @@ +// ------------------------------------------------------------------------ +// 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 . +// ------------------------------------------------------------------------ +using PhilLibX; +using PhilLibX.IO; +using System; +using System.IO; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Collections.Generic; + +namespace Husky +{ + /// + /// MW Logic + /// + class ModernWarfare + { + /// + /// MW 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[0x14]; + + /// + /// 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[0x220]; + + /// + /// Number of Static Models + /// + public int GfxStaticModelsCount { get; set; } + + /// + /// Unknown Bytes (more BSP data we probably don't care for) + /// + public fixed byte Padding3[0x38]; + + /// + /// Pointer to the Gfx Index Data + /// + public int GfxSurfacesPointer { get; set; } + + /// + /// Null Padding + /// + public int Padding4 { get; set; } + + /// + /// Pointer to the Gfx Static Models + /// + public int GfxStaticModelsPointer { 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[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[0x36]; + + /// + /// 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; } + } + + /// + /// Gfx Static Model + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct GfxStaticModel + { + /// + /// Null Padding + /// + public int Padding { get; set; } + + /// + /// X Origin + /// + public float X { get; set; } + + /// + /// Y Origin + /// + public float Y { get; set; } + + /// + /// Z Origin + /// + public float Z { get; set; } + + /// + /// 3x3 Rotation Matrix + /// + public fixed float Matrix[9]; + + /// + /// Model Scale + /// + public float ModelScale { get; set; } + + /// + /// Pointer to the XModel Asset + /// + public int ModelPointer { get; set; } + + /// + /// Unknown Bytes + /// + public fixed byte UnknownBytes2[0x10]; + } + + /// + /// Reads BSP Data + /// + public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress, string gameType) + { + // Found her + Printer.WriteLine("INFO", "Found supported game: Call of Duty: Modern Warfare"); + // Get XModel Name + var firstXModelName = reader.ReadNullTerminatedString(reader.ReadInt32(reader.ReadInt32(assetPoolsAddress + 0xC) + 4)); + // Validate by XModel Name + 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 + 0x40)); + + // 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 + { + // New IW Map + var mapFile = new IWMap(); + // 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)); + Printer.WriteLine("INFO", String.Format("Model Count - {0}", gfxMapAsset.GfxStaticModelsCount)); + + // Build output Folder + string outputName = Path.Combine("exported_maps", "modern_warfare", gameType, mapName, mapName); + Directory.CreateDirectory(Path.GetDirectoryName(outputName)); + + // Stop watch + var stopWatch = Stopwatch.StartNew(); + + // Read Vertices + Printer.WriteLine("INFO", "Parsing vertex data...."); + var vertices = ReadGfxVertices(reader, gfxMapAsset.GfxVerticesPointer, 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, 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 OBJ + Printer.WriteLine("INFO", "Converting to OBJ...."); + + // Create new OBJ + var obj = new WavefrontOBJ(); + + // Append Vertex Data + foreach (var vertex in vertices) + { + obj.Vertices.Add(vertex.Position); + obj.Normals.Add(vertex.Normal); + obj.UVs.Add(vertex.UV); + } + + // 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++) + { + // 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); + + // 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(outputName + ".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(outputName + "_search_string.txt", searchString); + + // Read entities and dump to map + mapFile.Entities.AddRange(ReadStaticModels(reader, gfxMapAsset.GfxStaticModelsPointer, gfxMapAsset.GfxStaticModelsCount)); + mapFile.DumpToMap(outputName + ".map"); + + // 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: Modern Warfare is supported, but this EXE is not.", ConsoleColor.DarkRed); + } + } + + /// + /// Reads Gfx Surfaces + /// + public static GfxSurface[] ReadGfxSufaces(ProcessReader reader, long address, int count) + { + // Preallocate short array + 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 * 48); + // 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]; + // Read buffer + var byteBuffer = reader.ReadBytes(address, count * 2); + // Copy buffer + Buffer.BlockCopy(byteBuffer, 0, indices, 0, byteBuffer.Length); + // Done + return indices; + } + + /// + /// Reads Gfx Vertices + /// + public static Vertex[] ReadGfxVertices(ProcessReader reader, long address, int count) + { + // Preallocate vertex array + 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++) + { + // Read Struct + var gfxVertex = ByteUtil.BytesToStruct(byteBuffer, i * 44); + + // Create new SEModel Vertex + vertices[i] = new Vertex() + { + // Set offset + Position = new Vector3( + gfxVertex.X * 2.54, + gfxVertex.Y * 2.54, + gfxVertex.Z * 2.54), + // Decode and set normal (from DTZxPorter - Wraith, same as XModels) + Normal = VertexNormal.UnpackA(gfxVertex.Normal), + // Set UV + UV = new Vector2(gfxVertex.U, 1 - gfxVertex.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; + } + + /// + /// Reads Static Models + /// + public unsafe static List ReadStaticModels(ProcessReader reader, long address, int count) + { + // Resulting Entities + List entities = new List(count); + // Read buffer + var byteBuffer = reader.ReadBytes(address, count * Marshal.SizeOf()); + // Loop number of models we have + for (int i = 0; i < count; i++) + { + // Read Struct + var staticModel = ByteUtil.BytesToStruct(byteBuffer, i * Marshal.SizeOf()); + // Model Name + var modelName = reader.ReadNullTerminatedString(reader.ReadInt32(staticModel.ModelPointer)); + // New Matrix + var matrix = new Rotation.Matrix(); + // Copy X Values + matrix.Values[0] = staticModel.Matrix[0]; + matrix.Values[1] = staticModel.Matrix[1]; + matrix.Values[2] = staticModel.Matrix[2]; + // Copy Y Values + matrix.Values[4] = staticModel.Matrix[3]; + matrix.Values[5] = staticModel.Matrix[4]; + matrix.Values[6] = staticModel.Matrix[5]; + // Copy Z Values + matrix.Values[8] = staticModel.Matrix[6]; + matrix.Values[9] = staticModel.Matrix[7]; + matrix.Values[10] = staticModel.Matrix[8]; + // Convert to Euler + var euler = matrix.ToEuler(); + // Add it + entities.Add(IWMap.Entity.CreateMiscModel(modelName, new Vector3(staticModel.X, staticModel.Y, staticModel.Z), Rotation.ToDegrees(euler), staticModel.ModelScale)); + } + // Done + return entities; + } + } +} \ No newline at end of file diff --git a/src/Husky/Husky/Games/ModernWarfare2.cs b/src/Husky/Husky/Games/ModernWarfare2.cs index f60e09e..ed22727 100644 --- a/src/Husky/Husky/Games/ModernWarfare2.cs +++ b/src/Husky/Husky/Games/ModernWarfare2.cs @@ -89,12 +89,32 @@ public unsafe struct GfxMap /// /// Unknown Bytes (more BSP data we probably don't care for) /// - public fixed byte Padding3[0x184]; + public fixed byte Padding3[0x130]; + + /// + /// Number of Static Models + /// + public int GfxStaticModelsCount { get; set; } + + /// + /// Unknown Bytes (more BSP data we probably don't care for) + /// + public fixed byte Padding4[0x50]; /// /// Pointer to the Gfx Index Data /// public int GfxSurfacesPointer { get; set; } + + /// + /// Null Padding + /// + public int Padding5 { get; set; } + + /// + /// Pointer to the Gfx Static Models + /// + public int GfxStaticModelsPointer { get; set; } } /// @@ -173,7 +193,7 @@ public unsafe struct Material /// /// Reads BSP Data /// - public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress) + public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress, string gameType) { // Found her Printer.WriteLine("INFO", "Found supported game: Call of Duty: Modern Warfare 2"); @@ -194,19 +214,26 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l } else { + // New IW Map + var mapFile = new IWMap(); // 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)); + Printer.WriteLine("INFO", String.Format("Model Count - {0}", gfxMapAsset.GfxStaticModelsCount)); + + // Build output Folder + string outputName = Path.Combine("exported_maps", "modern_warfare_2", gameType, mapName, mapName); + Directory.CreateDirectory(Path.GetDirectoryName(outputName)); // Stop watch var stopWatch = Stopwatch.StartNew(); // Read Vertices Printer.WriteLine("INFO", "Parsing vertex data...."); - var vertices = ReadGfxVertices(reader, gfxMapAsset.GfxVerticesPointer, (int)gfxMapAsset.GfxVertexCount); + var vertices = ReadGfxVertices(reader, gfxMapAsset.GfxVerticesPointer, gfxMapAsset.GfxVertexCount); Printer.WriteLine("INFO", String.Format("Parsed vertex data in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); // Reset timer @@ -214,7 +241,7 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l // Read Indices Printer.WriteLine("INFO", "Parsing surface indices...."); - var indices = ReadGfxIndices(reader, gfxMapAsset.GfxIndicesPointer, (int)gfxMapAsset.GfxIndicesCount); + var indices = ReadGfxIndices(reader, gfxMapAsset.GfxIndicesPointer, gfxMapAsset.GfxIndicesCount); Printer.WriteLine("INFO", String.Format("Parsed indices in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); // Reset timer @@ -280,7 +307,7 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l } // Save it - obj.Save(Path.ChangeExtension(gfxMapName, ".obj")); + obj.Save(outputName + ".obj"); // Build search strinmg string searchString = ""; @@ -290,7 +317,11 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l searchString += String.Format("{0},", Path.GetFileNameWithoutExtension(imageName)); // Dump it - File.WriteAllText(Path.ChangeExtension(gfxMapName, ".txt"), searchString); + File.WriteAllText(outputName + "_search_string.txt", searchString); + + // Read entities and dump to map + mapFile.Entities.AddRange(ModernWarfare3.ReadStaticModels(reader, gfxMapAsset.GfxStaticModelsPointer, gfxMapAsset.GfxStaticModelsCount)); + mapFile.DumpToMap(outputName + ".map"); // Done Printer.WriteLine("INFO", String.Format("Converted to OBJ in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); @@ -355,9 +386,9 @@ public static Vertex[] ReadGfxVertices(ProcessReader reader, long address, int c { // Set offset Position = new Vector3( - gfxVertex.X, - gfxVertex.Y, - gfxVertex.Z), + gfxVertex.X * 2.54, + gfxVertex.Y * 2.54, + gfxVertex.Z * 2.54), // Decode and set normal (from DTZxPorter - Wraith, same as XModels) Normal = VertexNormal.UnpackA(gfxVertex.Normal), // Set UV diff --git a/src/Husky/Husky/Games/ModernWarfare3.cs b/src/Husky/Husky/Games/ModernWarfare3.cs index 9b80b48..ba2e326 100644 --- a/src/Husky/Husky/Games/ModernWarfare3.cs +++ b/src/Husky/Husky/Games/ModernWarfare3.cs @@ -89,12 +89,32 @@ public unsafe struct GfxMap /// /// Unknown Bytes (more BSP data we probably don't care for) /// - public fixed byte Padding3[0x184]; + public fixed byte Padding3[0x130]; + + /// + /// Number of Static Models + /// + public int GfxStaticModelsCount { get; set; } + + /// + /// Unknown Bytes (more BSP data we probably don't care for) + /// + public fixed byte Padding4[0x50]; /// /// Pointer to the Gfx Index Data /// public int GfxSurfacesPointer { get; set; } + + /// + /// Null Padding + /// + public int Padding5 { get; set; } + + /// + /// Pointer to the Gfx Static Models + /// + public int GfxStaticModelsPointer { get; set; } } /// @@ -128,10 +148,52 @@ public unsafe struct Material public int ImageTablePointer { 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; } + + /// + /// 3x3 Rotation Matrix + /// + public fixed float Matrix[9]; + + /// + /// Model Scale + /// + public float ModelScale { get; set; } + + /// + /// Pointer to the XModel Asset + /// + public int ModelPointer { get; set; } + + /// + /// Unknown Bytes + /// + public fixed byte UnknownBytes2[0x14]; + } + /// /// Reads BSP Data /// - public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress) + public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress, string gameType) { // Found her Printer.WriteLine("INFO", "Found supported game: Call of Duty: Modern Warfare 3"); @@ -152,19 +214,26 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l } else { + // New IW Map + var mapFile = new IWMap(); // 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)); + Printer.WriteLine("INFO", String.Format("Model Count - {0}", gfxMapAsset.GfxStaticModelsCount)); + + // Build output Folder + string outputName = Path.Combine("exported_maps", "modern_warfare_3", gameType, mapName, mapName); + Directory.CreateDirectory(Path.GetDirectoryName(outputName)); // Stop watch var stopWatch = Stopwatch.StartNew(); // Read Vertices Printer.WriteLine("INFO", "Parsing vertex data...."); - var vertices = ReadGfxVertices(reader, gfxMapAsset.GfxVerticesPointer, (int)gfxMapAsset.GfxVertexCount); + var vertices = ReadGfxVertices(reader, gfxMapAsset.GfxVerticesPointer, gfxMapAsset.GfxVertexCount); Printer.WriteLine("INFO", String.Format("Parsed vertex data in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); // Reset timer @@ -172,7 +241,7 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l // Read Indices Printer.WriteLine("INFO", "Parsing surface indices...."); - var indices = ReadGfxIndices(reader, gfxMapAsset.GfxIndicesPointer, (int)gfxMapAsset.GfxIndicesCount); + var indices = ReadGfxIndices(reader, gfxMapAsset.GfxIndicesPointer, gfxMapAsset.GfxIndicesCount); Printer.WriteLine("INFO", String.Format("Parsed indices in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); // Reset timer @@ -238,7 +307,7 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l } // Save it - obj.Save(Path.ChangeExtension(gfxMapName, ".obj")); + obj.Save(outputName + ".obj"); // Build search strinmg string searchString = ""; @@ -248,7 +317,11 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l searchString += String.Format("{0},", Path.GetFileNameWithoutExtension(imageName)); // Dump it - File.WriteAllText(Path.ChangeExtension(gfxMapName, ".txt"), searchString); + File.WriteAllText(outputName + "_search_string.txt", searchString); + + // Read entities and dump to map + mapFile.Entities.AddRange(ReadStaticModels(reader, gfxMapAsset.GfxStaticModelsPointer, gfxMapAsset.GfxStaticModelsCount)); + mapFile.DumpToMap(outputName + ".map"); // Done Printer.WriteLine("INFO", String.Format("Converted to OBJ in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); @@ -312,9 +385,9 @@ public static Vertex[] ReadGfxVertices(ProcessReader reader, long address, int c { // Set offset Position = new Vector3( - gfxVertex.X, - gfxVertex.Y, - gfxVertex.Z), + gfxVertex.X * 2.54, + gfxVertex.Y * 2.54, + gfxVertex.Z * 2.54), // Decode and set normal (from DTZxPorter - Wraith, same as XModels) Normal = VertexNormal.UnpackA(gfxVertex.Normal), // Set UV @@ -347,5 +420,44 @@ public static WavefrontOBJ.Material ReadMaterial(ProcessReader reader, long addr // Done return objMaterial; } + + /// + /// Reads Static Models + /// + public unsafe static List ReadStaticModels(ProcessReader reader, long address, int count) + { + // Resulting Entities + List entities = new List(count); + // Read buffer + var byteBuffer = reader.ReadBytes(address, count * Marshal.SizeOf()); + // Loop number of models we have + for (int i = 0; i < count; i++) + { + // Read Struct + var staticModel = ByteUtil.BytesToStruct(byteBuffer, i * Marshal.SizeOf()); + // Model Name + var modelName = reader.ReadNullTerminatedString(reader.ReadInt32(staticModel.ModelPointer)); + // New Matrix + var matrix = new Rotation.Matrix(); + // Copy X Values + matrix.Values[0] = staticModel.Matrix[0]; + matrix.Values[1] = staticModel.Matrix[1]; + matrix.Values[2] = staticModel.Matrix[2]; + // Copy Y Values + matrix.Values[4] = staticModel.Matrix[3]; + matrix.Values[5] = staticModel.Matrix[4]; + matrix.Values[6] = staticModel.Matrix[5]; + // Copy Z Values + matrix.Values[8] = staticModel.Matrix[6]; + matrix.Values[9] = staticModel.Matrix[7]; + matrix.Values[10] = staticModel.Matrix[8]; + // Convert to Euler + var euler = matrix.ToEuler(); + // Add it + entities.Add(IWMap.Entity.CreateMiscModel(modelName, new Vector3(staticModel.X, staticModel.Y, staticModel.Z), Rotation.ToDegrees(euler), staticModel.ModelScale)); + } + // Done + return entities; + } } } \ No newline at end of file diff --git a/src/Husky/Husky/Games/ModernWarfareRM.cs b/src/Husky/Husky/Games/ModernWarfareRM.cs index 7d50017..6b7b481 100644 --- a/src/Husky/Husky/Games/ModernWarfareRM.cs +++ b/src/Husky/Husky/Games/ModernWarfareRM.cs @@ -266,7 +266,7 @@ public unsafe struct GfxStaticModel /// /// Reads BSP Data /// - public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress) + public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress, string gameType) { // Found her Printer.WriteLine("INFO", "Found supported game: Call of Duty: Modern Warfare Remastered"); @@ -298,6 +298,10 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l Printer.WriteLine("INFO", String.Format("Surface Count - {0}", gfxMapAsset.SurfaceCount)); Printer.WriteLine("INFO", String.Format("Model Count - {0}", gfxMapAsset.GfxStaticModelsCount)); + // Build output Folder + string outputName = Path.Combine("exported_maps", "modern_warfare_rm", gameType, mapName, mapName); + Directory.CreateDirectory(Path.GetDirectoryName(outputName)); + // Stop watch var stopWatch = Stopwatch.StartNew(); @@ -377,7 +381,7 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l } // Save it - obj.Save(Path.ChangeExtension(gfxMapName, ".obj")); + obj.Save(outputName + ".obj"); // Build search strinmg string searchString = ""; @@ -387,11 +391,11 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l searchString += String.Format("{0},", Path.GetFileNameWithoutExtension(imageName)); // Dump it - File.WriteAllText(Path.ChangeExtension(gfxMapName, ".txt"), searchString); + File.WriteAllText(outputName + "_search_string.txt", searchString); // Read entities and dump to map mapFile.Entities.AddRange(ReadStaticModels(reader, gfxMapAsset.GfxStaticModelsPointer, (int)gfxMapAsset.GfxStaticModelsCount)); - mapFile.DumpToMap(Path.ChangeExtension(gfxMapName, ".map")); + mapFile.DumpToMap(outputName + ".map"); // Done Printer.WriteLine("INFO", String.Format("Converted to OBJ in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); @@ -457,9 +461,9 @@ public static Vertex[] ReadGfxVertices(ProcessReader reader, long address, int c { // Set offset Position = new Vector3( - gfxVertex.X, - gfxVertex.Y, - gfxVertex.Z), + gfxVertex.X * 2.54, + gfxVertex.Y * 2.54, + gfxVertex.Z * 2.54), // Decode and set normal (from DTZxPorter - Wraith, same as XModels) Normal = VertexNormal.UnpackB(gfxVertex.Normal), // Set UV diff --git a/src/Husky/Husky/Games/WorldAtWar.cs b/src/Husky/Husky/Games/WorldAtWar.cs index dfdd20d..9bab2bd 100644 --- a/src/Husky/Husky/Games/WorldAtWar.cs +++ b/src/Husky/Husky/Games/WorldAtWar.cs @@ -84,12 +84,32 @@ public unsafe struct GfxMap /// /// Unknown Bytes (more BSP data we probably don't care for) /// - public fixed byte Padding2[0x26C]; + public fixed byte Padding2[0x220]; + + /// + /// Number of Static Models + /// + public int GfxStaticModelsCount { get; set; } + + /// + /// Unknown Bytes (more BSP data we probably don't care for) + /// + public fixed byte Padding3[0x48]; /// /// Pointer to the Gfx Index Data /// public int GfxSurfacesPointer { get; set; } + + /// + /// Null Padding + /// + public int Padding4 { get; set; } + + /// + /// Pointer to the Gfx Static Models + /// + public int GfxStaticModelsPointer { get; set; } } /// @@ -180,10 +200,57 @@ public unsafe struct Material public long Padding { get; set; } } + /// + /// Gfx Static Model + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct GfxStaticModel + { + /// + /// Null Padding + /// + public int Padding { get; set; } + + /// + /// X Origin + /// + public float X { get; set; } + + /// + /// Y Origin + /// + public float Y { get; set; } + + /// + /// Z Origin + /// + public float Z { get; set; } + + /// + /// 3x3 Rotation Matrix + /// + public fixed float Matrix[9]; + + /// + /// Model Scale + /// + public float ModelScale { get; set; } + + /// + /// Pointer to the XModel Asset + /// + public int ModelPointer { get; set; } + + /// + /// Unknown Bytes + /// + public fixed byte UnknownBytes2[0x20]; + } + /// /// Reads BSP Data /// - public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress) + public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress, string gameType) { // Found her Printer.WriteLine("INFO", "Found supported game: Call of Duty: World At War"); @@ -208,19 +275,26 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l } else { + // New IW Map + var mapFile = new IWMap(); // 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)); + Printer.WriteLine("INFO", String.Format("Model Count - {0}", gfxMapAsset.GfxStaticModelsCount)); + + // Build output Folder + string outputName = Path.Combine("exported_maps", "world_at_war", gameType, mapName, mapName); + Directory.CreateDirectory(Path.GetDirectoryName(outputName)); // Stop watch var stopWatch = Stopwatch.StartNew(); // Read Vertices Printer.WriteLine("INFO", "Parsing vertex data...."); - var vertices = ReadGfxVertices(reader, gfxMapAsset.GfxVerticesPointer, (int)gfxMapAsset.GfxVertexCount); + var vertices = ReadGfxVertices(reader, gfxMapAsset.GfxVerticesPointer, gfxMapAsset.GfxVertexCount); Printer.WriteLine("INFO", String.Format("Parsed vertex data in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); // Reset timer @@ -228,7 +302,7 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l // Read Indices Printer.WriteLine("INFO", "Parsing surface indices...."); - var indices = ReadGfxIndices(reader, gfxMapAsset.GfxIndicesPointer, (int)gfxMapAsset.GfxIndicesCount); + var indices = ReadGfxIndices(reader, gfxMapAsset.GfxIndicesPointer, gfxMapAsset.GfxIndicesCount); Printer.WriteLine("INFO", String.Format("Parsed indices in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); // Reset timer @@ -294,7 +368,7 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l } // Save it - obj.Save(Path.ChangeExtension(gfxMapName, ".obj")); + obj.Save(outputName + ".obj"); // Build search strinmg string searchString = ""; @@ -304,7 +378,11 @@ public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, l searchString += String.Format("{0},", Path.GetFileNameWithoutExtension(imageName)); // Dump it - File.WriteAllText(Path.ChangeExtension(gfxMapName, ".txt"), searchString); + File.WriteAllText(outputName + "_search_string.txt", searchString); + + // Read entities and dump to map + mapFile.Entities.AddRange(ReadStaticModels(reader, gfxMapAsset.GfxStaticModelsPointer, gfxMapAsset.GfxStaticModelsCount)); + mapFile.DumpToMap(outputName + ".map"); // Done Printer.WriteLine("INFO", String.Format("Converted to OBJ in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0)); @@ -369,9 +447,9 @@ public static Vertex[] ReadGfxVertices(ProcessReader reader, long address, int c { // Set offset Position = new Vector3( - gfxVertex.X, - gfxVertex.Y, - gfxVertex.Z), + gfxVertex.X * 2.54, + gfxVertex.Y * 2.54, + gfxVertex.Z * 2.54), // Decode and set normal (from DTZxPorter - Wraith, same as XModels) Normal = VertexNormal.UnpackA(gfxVertex.Normal), // Set UV @@ -404,5 +482,44 @@ public static WavefrontOBJ.Material ReadMaterial(ProcessReader reader, long addr // Done return objMaterial; } + + /// + /// Reads Static Models + /// + public unsafe static List ReadStaticModels(ProcessReader reader, long address, int count) + { + // Resulting Entities + List entities = new List(count); + // Read buffer + var byteBuffer = reader.ReadBytes(address, count * Marshal.SizeOf()); + // Loop number of models we have + for (int i = 0; i < count; i++) + { + // Read Struct + var staticModel = ByteUtil.BytesToStruct(byteBuffer, i * Marshal.SizeOf()); + // Model Name + var modelName = reader.ReadNullTerminatedString(reader.ReadInt32(staticModel.ModelPointer)); + // New Matrix + var matrix = new Rotation.Matrix(); + // Copy X Values + matrix.Values[0] = staticModel.Matrix[0]; + matrix.Values[1] = staticModel.Matrix[1]; + matrix.Values[2] = staticModel.Matrix[2]; + // Copy Y Values + matrix.Values[4] = staticModel.Matrix[3]; + matrix.Values[5] = staticModel.Matrix[4]; + matrix.Values[6] = staticModel.Matrix[5]; + // Copy Z Values + matrix.Values[8] = staticModel.Matrix[6]; + matrix.Values[9] = staticModel.Matrix[7]; + matrix.Values[10] = staticModel.Matrix[8]; + // Convert to Euler + var euler = matrix.ToEuler(); + // Add it + entities.Add(IWMap.Entity.CreateMiscModel(modelName, new Vector3(staticModel.X, staticModel.Y, staticModel.Z), Rotation.ToDegrees(euler), staticModel.ModelScale)); + } + // Done + return entities; + } } } \ No newline at end of file diff --git a/src/Husky/Husky/Husky.csproj b/src/Husky/Husky/Husky.csproj index 4e70cc8..b25c860 100644 --- a/src/Husky/Husky/Husky.csproj +++ b/src/Husky/Husky/Husky.csproj @@ -56,6 +56,7 @@ + @@ -63,9 +64,11 @@ + + diff --git a/src/Husky/Husky/Program.cs b/src/Husky/Husky/Program.cs index a5286b1..f83cc37 100644 --- a/src/Husky/Husky/Program.cs +++ b/src/Husky/Husky/Program.cs @@ -26,6 +26,11 @@ namespace Husky { + /// + /// Game Definition (AssetDB Address, Sizes Address, Game Type ID (MP, SP, ZM, etc.), Export Method + /// + using GameDefinition = Tuple>; + /// /// Main Program Class /// @@ -34,29 +39,36 @@ class Program /// /// Game Addresses & Methods (Asset DB and Asset Pool Sizes) (Some are relative due to ASLR) /// - static readonly Dictionary>> Games = new Dictionary>>() + static readonly Dictionary Games = new Dictionary() { // Call of Duty: World At War - { "CoDWaWmp", new Tuple>(0x8D0958, 0x8D06E8, WorldatWar.ExportBSPData) }, - { "CoDWaW", new Tuple>(0x8DC828, 0x8DC5D0, WorldatWar.ExportBSPData) }, + { "CoDWaWmp", new GameDefinition(0x8D0958, 0x8D06E8, "mp", WorldatWar.ExportBSPData) }, + { "CoDWaW", new GameDefinition(0x8DC828, 0x8DC5D0, "sp", WorldatWar.ExportBSPData) }, + // Call of Duty: Modern Warfare + { "iw3mp", new GameDefinition(0x7265E0, 0x7263A0, "mp", ModernWarfare.ExportBSPData) }, + { "iw3sp", new GameDefinition(0x7307F8, 0x730510, "sp", ModernWarfare.ExportBSPData) }, // Call of Duty: Modern Warfare 2 - { "iw4mp", new Tuple>(0x6F81D0, 0x6F7F08, ModernWarfare2.ExportBSPData) }, - { "iw4sp", new Tuple>(0x7307F8, 0x730510, ModernWarfare2.ExportBSPData) }, + { "iw4mp", new GameDefinition(0x6F81D0, 0x6F7F08, "mp", ModernWarfare2.ExportBSPData) }, + { "iw4sp", new GameDefinition(0x7307F8, 0x730510, "sp", ModernWarfare2.ExportBSPData) }, // Call of Duty: Modern Warfare 3 - { "iw5mp", new Tuple>(0x8AB258, 0x8AAF78, ModernWarfare3.ExportBSPData) }, - { "iw5sp", new Tuple>(0x92AD20, 0x92AA40, ModernWarfare3.ExportBSPData) }, + { "iw5mp", new GameDefinition(0x8AB258, 0x8AAF78, "mp", ModernWarfare3.ExportBSPData) }, + { "iw5sp", new GameDefinition(0x92AD20, 0x92AA40, "sp", ModernWarfare3.ExportBSPData) }, + // Call of Duty: Black Ops 2 + { "t6zm", new GameDefinition(0xD41240, 0xD40E80, "zm", BlackOps2.ExportBSPData) }, + { "t6mp", new GameDefinition(0xD4B340, 0xD4AF80, "mp", BlackOps2.ExportBSPData) }, + { "t6sp", new GameDefinition(0xBD46B8, 0xBD42F8, "sp", BlackOps2.ExportBSPData) }, // Call of Duty: Black Ops - { "BlackOps", new Tuple>(0xB741B8, 0xB73EF8, BlackOps.ExportBSPData) }, - { "BlackOpsMP", new Tuple>(0xBF2C30, 0xBF2970, BlackOps.ExportBSPData) }, + { "BlackOps", new GameDefinition(0xB741B8, 0xB73EF8, "sp", BlackOps.ExportBSPData) }, + { "BlackOpsMP", new GameDefinition(0xBF2C30, 0xBF2970, "mp", BlackOps.ExportBSPData) }, // Call of Duty: Ghosts - { "iw6mp64_ship", new Tuple>(0x1409E4F20, 0x1409E4E20, Ghosts.ExportBSPData) }, - { "iw6sp64_ship", new Tuple>(0x14086DCB0, 0x14086DBB0, Ghosts.ExportBSPData) }, + { "iw6mp64_ship", new GameDefinition(0x1409E4F20, 0x1409E4E20, "mp", Ghosts.ExportBSPData) }, + { "iw6sp64_ship", new GameDefinition(0x14086DCB0, 0x14086DBB0, "sp", Ghosts.ExportBSPData) }, // Call of Duty: Advanced Warfare - { "s1_mp64_ship", new Tuple>(0x1409B40D0, 0x1409B4B90, AdvancedWarfare.ExportBSPData) }, - { "s1_sp64_ship", new Tuple>(0x140804690, 0x140804140, AdvancedWarfare.ExportBSPData) }, + { "s1_mp64_ship", new GameDefinition(0x1409B40D0, 0x1409B4B90, "mp", AdvancedWarfare.ExportBSPData) }, + { "s1_sp64_ship", new GameDefinition(0x140804690, 0x140804140, "sp", AdvancedWarfare.ExportBSPData) }, // Call of Duty: Modern Warfare Remastered - { "h1_mp64_ship", new Tuple>(0x10B4460, 0x10B3C80, ModernWarfareRM.ExportBSPData) }, - { "h1_sp64_ship", new Tuple>(0xEC9FB0, 0xEC97D0, ModernWarfareRM.ExportBSPData) }, + { "h1_mp64_ship", new GameDefinition(0x10B4460, 0x10B3C80, "mp", ModernWarfareRM.ExportBSPData) }, + { "h1_sp64_ship", new GameDefinition(0xEC9FB0, 0xEC97D0, "sp", ModernWarfareRM.ExportBSPData) }, }; /// @@ -70,19 +82,20 @@ static void Main(string[] args) Console.Title = "Husky"; // Initial Info Printer.WriteLine("INIT", "────────────────────────────────────────────────────"); - Printer.WriteLine("INIT", "Husky - Exports some BSP Data from some CoD Games"); + Printer.WriteLine("INIT", "Husky - Call of Duty BSP Exporter"); Printer.WriteLine("INIT", "By Scobalula"); Printer.WriteLine("INIT", String.Format("Version: {0}", Assembly.GetExecutingAssembly().GetName().Version)); Printer.WriteLine("INIT", "────────────────────────────────────────────────────"); // Information Printer.WriteLine("INFO", "Currently supports:"); - Printer.WriteLine("INFO", " CoD: WAW - BSP Geometry"); - Printer.WriteLine("INFO", " CoD: BO1 - BSP Geometry"); - Printer.WriteLine("INFO", " CoD: MW2 - BSP Geometry"); - Printer.WriteLine("INFO", " CoD: MW3 - BSP Geometry"); - Printer.WriteLine("INFO", " CoD: AW - BSP Geometry, Static Models"); - Printer.WriteLine("INFO", " CoD: Ghosts - BSP Geometry, Static Models"); - Printer.WriteLine("INFO", " CoD: MWR - BSP Geometry, Static Models"); + Printer.WriteLine("INFO", " CoD: WAW"); + Printer.WriteLine("INFO", " CoD: BO1"); + Printer.WriteLine("INFO", " CoD: MW"); + Printer.WriteLine("INFO", " CoD: MW2"); + Printer.WriteLine("INFO", " CoD: MW3"); + Printer.WriteLine("INFO", " CoD: AW"); + Printer.WriteLine("INFO", " CoD: Ghosts"); + Printer.WriteLine("INFO", " CoD: MWR"); Printer.WriteLine("INFO", "Usage:"); Printer.WriteLine("INFO", " Run a supported game, then run Husky"); // Scanning @@ -111,7 +124,8 @@ static void LoadGame() if (Games.TryGetValue(process.ProcessName, out var game)) { // Export it - game.Item3(new ProcessReader(process), game.Item1, game.Item2); + game.Item4(new ProcessReader(process), game.Item1, game.Item2, game.Item3); + // Done return; } diff --git a/src/Husky/Husky/Properties/AssemblyInfo.cs b/src/Husky/Husky/Properties/AssemblyInfo.cs index 0424dd1..3d98de5 100644 --- a/src/Husky/Husky/Properties/AssemblyInfo.cs +++ b/src/Husky/Husky/Properties/AssemblyInfo.cs @@ -31,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.3.0.0")] -[assembly: AssemblyFileVersion("0.3.0.0")] +[assembly: AssemblyVersion("0.5.0.0")] +[assembly: AssemblyFileVersion("0.5.0.0")] diff --git a/src/Husky/Husky/Utility/FloatToInt.cs b/src/Husky/Husky/Utility/FloatToInt.cs new file mode 100644 index 0000000..c82e0e1 --- /dev/null +++ b/src/Husky/Husky/Utility/FloatToInt.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Husky +{ + [StructLayout(LayoutKind.Explicit)] + class FloatToInt + { + /// + /// Integer Value + /// + [FieldOffset(0)] + public uint Integer; + + /// + /// Floating Point Value + /// + [FieldOffset(0)] + public float Float; + } +} diff --git a/src/Husky/Husky/Utility/VertexNormal.cs b/src/Husky/Husky/Utility/VertexNormal.cs index c847b4a..29cf5d9 100644 --- a/src/Husky/Husky/Utility/VertexNormal.cs +++ b/src/Husky/Husky/Utility/VertexNormal.cs @@ -35,5 +35,24 @@ public static Vector3 UnpackB(PackedUnitVector packedNormal) (float)((((packedNormal.Value >> 10) & 0x3FF) / 1023.0) * 2.0 - 1.0), (float)((((packedNormal.Value >> 20) & 0x3FF) / 1023.0) * 2.0 - 1.0)); } + + /// + /// Unpacks a Vertex Normal from: Bo2 + /// + /// Packed 4 byte Vertex Normal + /// Resulting Vertex Normal + public static Vector3 UnpackC(PackedUnitVector packedNormal) + { + // Resulting values + var builtX = new FloatToInt { Integer = (uint)((packedNormal.Value & 0x3FF) - 2 * (packedNormal.Value & 0x200) + 0x40400000) }; + var builtY = new FloatToInt { Integer = (uint)((((packedNormal.Value >> 10) & 0x3FF) - 2 * ((packedNormal.Value >> 10) & 0x200) + 0x40400000)) }; + var builtZ = new FloatToInt { Integer = (uint)((((packedNormal.Value >> 20) & 0x3FF) - 2 * ((packedNormal.Value >> 20) & 0x200) + 0x40400000)) }; + + // Return decoded vector + return new Vector3( + (builtX.Float - 3.0) * 8208.0312f, + (builtY.Float - 3.0) * 8208.0312f, + (builtZ.Float - 3.0) * 8208.0312f); + } } }