コード例 #1
0
ファイル: ModernWarfare2.cs プロジェクト: sheilan102/C2M
        /// <summary>
        /// Reads a material for the given surface and its associated images
        /// </summary>
        public static WavefrontOBJ.Material ReadMaterial(ProcessReader reader, long address)
        {
            // Read Material
            var material = reader.ReadStruct <Material>(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 <MaterialImage32B>(material.ImageTablePointer + i * Marshal.SizeOf <MaterialImage32B>());
                // Check for color map for now
                if (materialImage.SemanticHash == 0xA0AB1041)
                {
                    objMaterial.DiffuseMap = reader.ReadNullTerminatedString(reader.ReadInt32(materialImage.ImagePointer + 0x1C));
                }
                else if (materialImage.SemanticHash == 0x59D30D0F)
                {
                    objMaterial.NormalMap = reader.ReadNullTerminatedString(reader.ReadInt32(materialImage.ImagePointer + 0x1C));
                }
                else if (materialImage.SemanticHash == 0x34ECCCB3)
                {
                    objMaterial.SpecularMap = reader.ReadNullTerminatedString(reader.ReadInt32(materialImage.ImagePointer + 0x1C));
                }
            }
            // Done
            return(objMaterial);
        }
コード例 #2
0
        public static void ExportLuaFIles(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress, string gameType)
        {
            // Found the game
            Console.WriteLine("Found supported game: Call of Duty: Black Ops 4");

            // Get Base Address for ASLR and Scans
            long baseAddress = reader.GetBaseAddress();

            // Validate by XModel Name
            if (reader.ReadUInt64(reader.ReadStruct <AssetPool>(baseAddress + assetPoolsAddress + 0x20 * 0x4).PoolPointer) == 0x04647533e968c910)
            {
                AssetPool LuaPoolData = reader.ReadStruct <AssetPool>(baseAddress + assetPoolsAddress + 0x20 * 0x67);

                long address    = LuaPoolData.PoolPointer;
                long endAddress = LuaPoolData.PoolSize * LuaPoolData.AssetSize + address;

                Directory.CreateDirectory("t8_luafiles");
                int filesExported = 0;
                for (int i = 0; i < LuaPoolData.PoolSize; i++)
                {
                    var data = reader.ReadStruct <LuaFile>(address + (i * LuaPoolData.AssetSize));

                    if (data.NullPointer != 0 || data.Hash == 0)
                    {
                        continue;
                    }
                    if (data.Hash >= address && data.Hash <= endAddress)
                    {
                        continue;
                    }

                    filesExported++;
                    var    RawData = reader.ReadBytes(data.startLocation, data.DataSize);
                    ulong  assetHash = (ulong)data.Hash & 0xFFFFFFFFFFFFFFF;
                    string HashString, fileName;
                    //if (!Program.AssetNameCache.Entries.TryGetValue(assetHash, out HashString))
                    fileName = String.Format("t8_luafiles\\LuaFile_{0:x}.lua", assetHash);
                    //else
                    //fileName = String.Format("t8_luafiles\\{0}.lua", HashString);
                    if (File.Exists(fileName) && new FileInfo(fileName).Length == data.DataSize)
                    {
                        continue;
                    }
                    File.WriteAllBytes(fileName, RawData);
                }

                Console.WriteLine("Exported {0} files", filesExported);
            }
            else
            {
                Console.WriteLine("Unsupported version of game found");
            }
        }
コード例 #3
0
        public static void ExportLuaFIles(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress, string gameType)
        {
            // Found the game
            Console.WriteLine("Found supported game: Call of Duty: Modern Warfare Remastered");

            // Get Base Address for ASLR and Scans
            long baseAddress = reader.GetBaseAddress();

            // Validate by XModel Name
            if (reader.ReadNullTerminatedString(reader.ReadInt64(reader.ReadInt64(baseAddress + assetPoolsAddress + 0x38) + 8)) == "fx")
            {
                var AssetPoolOffset = reader.ReadStruct <Int64>(baseAddress + assetPoolsAddress + (8 * 61));
                var poolSize        = reader.ReadStruct <UInt32>(baseAddress + assetSizesAddress + (4 * 61));

                Directory.CreateDirectory("h1_luafiles");

                int filesExported = 0;
                for (int i = 0; i < poolSize; i++)
                {
                    var data = reader.ReadStruct <LuaFile>(AssetPoolOffset + 8 + (i * 24));

                    if (!(data.DataSize != 0 && data.DataSize2 == 2))
                    {
                        continue;
                    }

                    filesExported++;

                    var RawData = reader.ReadBytes(data.startLocation, data.DataSize);

                    string exportName = Path.Combine("h1_luafiles", reader.ReadNullTerminatedString(data.NamePtr));

                    if (File.Exists(exportName) && new FileInfo(exportName).Length == data.DataSize)
                    {
                        continue;
                    }

                    Directory.CreateDirectory(Path.GetDirectoryName(exportName));

                    File.WriteAllBytes(exportName, RawData);
                }

                Console.WriteLine("Exported {0} files", filesExported);
            }
            else
            {
                Console.WriteLine("Unsupported version of game found");
            }
        }
コード例 #4
0
        public static void ExportLuaFIles(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress, string gameType)
        {
            // Found the game
            Console.WriteLine("Found supported game: Call of Duty: Black Ops 3");

            // Get Base Address for ASLR and Scans
            long baseAddress = reader.GetBaseAddress();

            // Validate by XModel Name
            if (reader.ReadNullTerminatedString(reader.ReadInt64(reader.ReadInt64(baseAddress + assetPoolsAddress + 0x80))) == "void")
            {
                AssetPool LuaPoolData = reader.ReadStruct <AssetPool>(baseAddress + assetPoolsAddress + 0x20 * 47);

                long address    = LuaPoolData.PoolPointer;
                long endAddress = LuaPoolData.PoolSize * LuaPoolData.AssetSize + address;

                Directory.CreateDirectory("t7_luafiles");
                int filesExported = 0;

                for (int i = 0; i < LuaPoolData.PoolSize; i++)
                {
                    var data = reader.ReadStruct <LuaFile>(address + (i * LuaPoolData.AssetSize));

                    if (!(data.AssetSize != 0))
                    {
                        continue;
                    }

                    filesExported++;
                    var RawData = reader.ReadBytes(data.RawDataPtr, data.AssetSize);

                    string exportName = Path.Combine("t7_luafiles", reader.ReadNullTerminatedString(data.NamePtr));

                    if (Path.GetExtension(exportName) != ".lua" || File.Exists(exportName) && new FileInfo(exportName).Length == data.AssetSize)
                    {
                        continue;
                    }
                    Directory.CreateDirectory(Path.GetDirectoryName(exportName));

                    File.WriteAllBytes(exportName, RawData);
                }

                Console.WriteLine("Exported {0} files", filesExported);
            }
            else
            {
                Console.WriteLine("Unsupported version of game found");
            }
        }
コード例 #5
0
        public static void ExportLuaFIles(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress, string gameType)
        {
            // Found the game
            Console.WriteLine("Found supported game: Call of Duty: Black Ops 2");

            // Validate by XModel Name
            if (reader.ReadNullTerminatedString(reader.ReadInt32(reader.ReadInt32(assetPoolsAddress + 0x14) + 4)) == "defaultvehicle")
            {
                var AssetPoolOffset = reader.ReadStruct <Int32>(assetPoolsAddress + (4 * 41)) + 4;
                var poolSize        = reader.ReadStruct <Int32>(assetSizesAddress + (4 * 41));

                Directory.CreateDirectory("t6_luafiles");

                int filesExported = 0;

                for (int i = 0; i < poolSize; i++)
                {
                    var data = reader.ReadStruct <LuaFile>(AssetPoolOffset + (i * 12));

                    if (!(data.AssetSize != 0))
                    {
                        continue;
                    }

                    filesExported++;
                    var RawData = reader.ReadBytes(data.RawDataPtr, data.AssetSize);

                    string exportName = Path.Combine("t6_luafiles", reader.ReadNullTerminatedString(data.NamePtr));

                    if (Path.GetExtension(exportName) != ".lua" || File.Exists(exportName) && new FileInfo(exportName).Length == data.AssetSize)
                    {
                        continue;
                    }
                    Directory.CreateDirectory(Path.GetDirectoryName(exportName));

                    File.WriteAllBytes(exportName, RawData);
                }

                Console.WriteLine("Exported {0} files", filesExported);
            }
            else
            {
                Console.WriteLine("Unsupported version of game found");
            }
        }
コード例 #6
0
        /// <summary>
        /// Reads a material for the given surface and its associated images
        /// </summary>
        public static WavefrontOBJ.Material ReadMaterial(ProcessReader reader, long address)
        {
            // Read Material
            var material = reader.ReadStruct <Material>(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 <MaterialImage64A>(material.ImageTablePointer + i * Marshal.SizeOf <MaterialImage64A>());
                // Check for color map for now
                if (materialImage.SemanticHash == 0xA0AB1041)
                {
                    objMaterial.DiffuseMap = "_images\\\\" + reader.ReadNullTerminatedString(reader.ReadInt64(materialImage.ImagePointer + 96)) + ".png";
                }
            }
            // Done
            return(objMaterial);
        }
コード例 #7
0
 /// <summary>
 /// Reads Gfx Surfaces
 /// </summary>
 public static ModernWarfare2.GfxSurface[] ReadGfxSufaces(ProcessReader reader, long address, int count)
 {
     // Preallocate short array
     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 <ModernWarfare2.GfxSurface>(address + i * 24);
     }
     // Done
     return(surfaces);
 }
コード例 #8
0
ファイル: BlackOps.cs プロジェクト: sheilan102/C2M
        /// <summary>
        /// Reads BSP Data
        /// </summary>
        public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress, string gameType, Action <object> printCallback = null)
        {
            // Found her
            printCallback?.Invoke("Found supported game: Call of Duty: Black Ops");

            // Get XModel Name
            var firstXModelName = reader.ReadNullTerminatedString(reader.ReadInt32(reader.ReadInt32(assetPoolsAddress + 0x14) + 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 <GfxMap>(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))
                {
                    printCallback?.Invoke("No BSP loaded. Enter Main Menu or a Map to load in the required assets.");
                }
                else
                {
                    // New IW Map
                    var mapFile = new IWMap();
                    // Print Info
                    printCallback?.Invoke(String.Format("\n--------------------------\n"));
                    printCallback?.Invoke(String.Format("Loaded Gfx Map     -   {0}", gfxMapName));
                    printCallback?.Invoke(String.Format("Loaded Map         -   {0}", mapName));
                    printCallback?.Invoke(String.Format("Vertex Count       -   {0}", gfxMapAsset.GfxVertexCount));
                    printCallback?.Invoke(String.Format("Indices Count      -   {0}", gfxMapAsset.GfxIndicesCount));
                    printCallback?.Invoke(String.Format("Surface Count      -   {0}", gfxMapAsset.SurfaceCount));
                    printCallback?.Invoke(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
                    printCallback?.Invoke("Parsing vertex data....");
                    var vertices = ReadGfxVertices(reader, gfxMapAsset.GfxVerticesPointer, gfxMapAsset.GfxVertexCount);
                    printCallback?.Invoke(String.Format("Parsed vertex data in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0));

                    // Reset timer
                    stopWatch.Restart();

                    // Read Indices
                    printCallback?.Invoke("Parsing surface indices....");
                    var indices = ReadGfxIndices(reader, gfxMapAsset.GfxIndicesPointer, gfxMapAsset.GfxIndicesCount);
                    printCallback?.Invoke(String.Format("Parsed indices in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0));

                    // Reset timer
                    stopWatch.Restart();

                    // Read Indices
                    printCallback?.Invoke("Parsing surfaces....");
                    var surfaces = ReadGfxSufaces(reader, gfxMapAsset.GfxSurfacesPointer, gfxMapAsset.SurfaceCount);
                    printCallback?.Invoke(String.Format("Parsed surfaces in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0));

                    // Reset timer
                    stopWatch.Restart();

                    // Write OBJ
                    printCallback?.Invoke("Generating map files...");

                    // 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 <string> imageNames = new HashSet <string>();

                    // 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));
                    }

                    // Create .JSON with XModel Data
                    List <IDictionary> ModelData  = CreateXModelDictionary(reader, gfxMapAsset.GfxStaticModelsPointer, (int)gfxMapAsset.GfxStaticModelsCount);
                    string             xmodeljson = JToken.FromObject(ModelData).ToString(Formatting.Indented);
                    File.WriteAllText(outputName + "_xmodels.json", xmodeljson);

                    // Loop through xmodels, and append each to the search string (for Wraith/Greyhound)
                    List <string> xmodelList = CreateXModelList(ModelData);

                    // Dump it
                    File.WriteAllText(outputName + "_search_string.txt", searchString);
                    File.WriteAllText(outputName + "_xmodelList.txt", String.Join(",", xmodelList.ToArray()));

                    // Read entities and dump to map
                    mapFile.Entities.AddRange(ReadStaticModels(reader, gfxMapAsset.GfxStaticModelsPointer, (int)gfxMapAsset.GfxStaticModelsCount));
                    mapFile.DumpToMap(outputName + ".map");

                    // Done
                    printCallback?.Invoke(String.Format("Generated files in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0));
                }
            }
            else
            {
                printCallback?.Invoke("Call of Duty: Black Ops is supported, but this EXE is not.");
            }
        }
コード例 #9
0
        /// <summary>
        /// Reads BSP Data
        /// </summary>
        public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress, string gameType, Action <object> printCallback = null)
        {
            // Found her
            printCallback?.Invoke("Found supported game: Call of Duty: Modern Warfare Remastered");

            // Validate by XModel Name
            var baseAddr = reader.GetBaseAddress();
            var si       = reader.ReadInt64(reader.ReadInt64(baseAddr + assetPoolsAddress + 0x38) + 8);
            var po       = reader.ReadNullTerminatedString(si) == "fx";

            if (po)
            {
                // Load BSP Pools (they only have a size of 1 so we don't care about reading more than 1)
                var gfxMapAsset = reader.ReadStruct <GfxMap>(reader.ReadInt64(reader.GetBaseAddress() + assetPoolsAddress + 0xF8));

                // 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))
                {
                    printCallback?.Invoke("No BSP loaded. Enter Main Menu or a Map to load in the required assets.");
                }
                else
                {
                    // New IW Map
                    var mapFile = new IWMap();
                    // Print Info
                    printCallback?.Invoke(String.Format("Loaded Gfx Map     -   {0}", gfxMapName));
                    printCallback?.Invoke(String.Format("Loaded Map         -   {0}", mapName));
                    printCallback?.Invoke(String.Format("Vertex Count       -   {0}", gfxMapAsset.GfxVertexCount));
                    printCallback?.Invoke(String.Format("Indices Count      -   {0}", gfxMapAsset.GfxIndicesCount));
                    printCallback?.Invoke(String.Format("Surface Count      -   {0}", gfxMapAsset.SurfaceCount));
                    printCallback?.Invoke(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();

                    // Read Vertices
                    printCallback?.Invoke("Parsing vertex data....");
                    var vertices = ReadGfxVertices(reader, gfxMapAsset.GfxVerticesPointer, (int)gfxMapAsset.GfxVertexCount);
                    printCallback?.Invoke(String.Format("Parsed vertex data in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0));

                    // Reset timer
                    stopWatch.Restart();

                    // Read Indices
                    printCallback?.Invoke("Parsing surface indices....");
                    var indices = ReadGfxIndices(reader, gfxMapAsset.GfxIndicesPointer, (int)gfxMapAsset.GfxIndicesCount);
                    printCallback?.Invoke(String.Format("Parsed indices in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0));

                    // Reset timer
                    stopWatch.Restart();

                    // Read Indices
                    printCallback?.Invoke("Parsing surfaces....");
                    var surfaces = ReadGfxSufaces(reader, gfxMapAsset.GfxSurfacesPointer, gfxMapAsset.SurfaceCount);
                    printCallback?.Invoke(String.Format("Parsed surfaces in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0));

                    // Reset timer
                    stopWatch.Restart();

                    // Write OBJ
                    printCallback?.Invoke("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 <string> imageNames = new HashSet <string>();

                    // 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, (int)gfxMapAsset.GfxStaticModelsCount));
                    mapFile.DumpToMap(outputName + ".map");

                    // Done
                    printCallback?.Invoke(String.Format("Converted to OBJ in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0));
                }
            }
            else
            {
                printCallback?.Invoke("Call of Duty: Modern Warfare Remastered is supported, but this EXE is not.");
            }
        }
コード例 #10
0
        /// <summary>
        /// Reads BSP Data
        /// </summary>
        public static void ExportBSPData(ProcessReader reader, long assetPoolsAddress, long assetSizesAddress, string gameType, Action <object> printCallback = null)
        {
            // Found her
            printCallback?.Invoke("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 <GfxMap>(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))
                {
                    printCallback?.Invoke("No BSP loaded. Enter Main Menu or a Map to load in the required assets.");
                }
                else
                {
                    // New IW Map
                    var mapFile = new IWMap();
                    // Print Info
                    printCallback?.Invoke(String.Format("Loaded Gfx Map     -   {0}", gfxMapName));
                    printCallback?.Invoke(String.Format("Loaded Map         -   {0}", mapName));
                    printCallback?.Invoke(String.Format("Vertex Count       -   {0}", gfxMapAsset.GfxVertexCount));
                    printCallback?.Invoke(String.Format("Indices Count      -   {0}", gfxMapAsset.GfxIndicesCount));
                    printCallback?.Invoke(String.Format("Surface Count      -   {0}", gfxMapAsset.SurfaceCount));
                    printCallback?.Invoke(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
                    printCallback?.Invoke("Parsing vertex data....");
                    var vertexBuffer = reader.ReadBytes(gfxMapAsset.GfxVerticesPointer, gfxMapAsset.GfxVertexBufferSize);
                    printCallback?.Invoke(String.Format("Parsed vertex data in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0));

                    // Reset timer
                    stopWatch.Restart();

                    // Read Indices
                    printCallback?.Invoke("Parsing surface indices....");
                    var indices = ReadGfxIndices(reader, gfxMapAsset.GfxIndicesPointer, gfxMapAsset.GfxIndicesCount);
                    printCallback?.Invoke(String.Format("Parsed indices in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0));

                    // Reset timer
                    stopWatch.Restart();

                    // Read Indices
                    printCallback?.Invoke("Parsing surfaces....");
                    var surfaces = ReadGfxSufaces(reader, gfxMapAsset.GfxSurfacesPointer, gfxMapAsset.SurfaceCount);
                    printCallback?.Invoke(String.Format("Parsed surfaces in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0));

                    // Reset timer
                    stopWatch.Restart();

                    // Write OBJ
                    printCallback?.Invoke("Converting to OBJ....");

                    // Create new OBJ
                    var obj = new WavefrontOBJ();

                    // Image Names (for Search String)
                    HashSet <string> imageNames = new HashSet <string>();

                    // 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 <GfxVertex>(vertexBuffer, surface.VertexBufferOffset + (faceIndex1 * 36)));
                                var vertex2 = UnpackVertex(ByteUtil.BytesToStruct <GfxVertex>(vertexBuffer, surface.VertexBufferOffset + (faceIndex2 * 36)));
                                var vertex3 = UnpackVertex(ByteUtil.BytesToStruct <GfxVertex>(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
                    printCallback?.Invoke(String.Format("Converted to OBJ in {0:0.00} seconds.", stopWatch.ElapsedMilliseconds / 1000.0));
                }
            }
            else
            {
                printCallback?.Invoke("Call of Duty: Black Ops 2 is supported, but this EXE is not.");
            }
        }