Skip to content

Commit

Permalink
New Games + Static Models for all Games
Browse files Browse the repository at this point in the history
  • Loading branch information
Scobalula committed Nov 19, 2018
1 parent 1d823f5 commit 41bef25
Show file tree
Hide file tree
Showing 16 changed files with 1,662 additions and 96 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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).
Expand Down
4 changes: 2 additions & 2 deletions src/Husky/Husky/FileFormats/IWMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
22 changes: 13 additions & 9 deletions src/Husky/Husky/Games/AdvancedWarfare.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ public unsafe struct GfxStaticModel
/// <summary>
/// Reads BSP Data
/// </summary>
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");
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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
Expand Down
136 changes: 127 additions & 9 deletions src/Husky/Husky/Games/BlackOps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,32 @@ public unsafe struct GfxMap
/// <summary>
/// Unknown Bytes (more BSP data we probably don't care for)
/// </summary>
public fixed byte Padding3[0x184];
public fixed byte Padding3[0x12C];

/// <summary>
/// Number of Static Models
/// </summary>
public int GfxStaticModelsCount { get; set; }

/// <summary>
/// Unknown Bytes (more BSP data we probably don't care for)
/// </summary>
public fixed byte Padding4[0x54];

/// <summary>
/// Pointer to the Gfx Index Data
/// </summary>
public int GfxSurfacesPointer { get; set; }

/// <summary>
/// Null Padding
/// </summary>
public int Padding5 { get; set; }

/// <summary>
/// Pointer to the Gfx Static Models
/// </summary>
public int GfxStaticModelsPointer { get; set; }
}

/// <summary>
Expand Down Expand Up @@ -149,6 +169,7 @@ public unsafe struct GfxSurface
public fixed byte Padding3[0x1C];
}


/// <summary>
/// Call of Duty: Black Ops Material Asset
/// </summary>
Expand Down Expand Up @@ -180,10 +201,57 @@ public unsafe struct Material
public int ImageTablePointer { get; set; }
}

/// <summary>
/// Gfx Static Model
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct GfxStaticModel
{
/// <summary>
/// Null Padding
/// </summary>
public int Padding { get; set; }

/// <summary>
/// X Origin
/// </summary>
public float X { get; set; }

/// <summary>
/// Y Origin
/// </summary>
public float Y { get; set; }

/// <summary>
/// Z Origin
/// </summary>
public float Z { get; set; }

/// <summary>
/// 3x3 Rotation Matrix
/// </summary>
public fixed float Matrix[9];

/// <summary>
/// Model Scale
/// </summary>
public float ModelScale { get; set; }

/// <summary>
/// Pointer to the XModel Asset
/// </summary>
public int ModelPointer { get; set; }

/// <summary>
/// Unknown Bytes
/// </summary>
public fixed byte UnknownBytes2[0x10];
}

/// <summary>
/// Reads BSP Data
/// </summary>
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");
Expand All @@ -208,27 +276,34 @@ 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
stopWatch.Restart();

// 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
Expand Down Expand Up @@ -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 = "";
Expand All @@ -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));
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -405,5 +484,44 @@ public static WavefrontOBJ.Material ReadMaterial(ProcessReader reader, long addr
// Done
return objMaterial;
}

/// <summary>
/// Reads Static Models
/// </summary>
public unsafe static List<IWMap.Entity> ReadStaticModels(ProcessReader reader, long address, int count)
{
// Resulting Entities
List<IWMap.Entity> entities = new List<IWMap.Entity>(count);
// Read buffer
var byteBuffer = reader.ReadBytes(address, count * Marshal.SizeOf<GfxStaticModel>());
// Loop number of models we have
for (int i = 0; i < count; i++)
{
// Read Struct
var staticModel = ByteUtil.BytesToStruct<GfxStaticModel>(byteBuffer, i * Marshal.SizeOf<GfxStaticModel>());
// 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;
}
}
}
Loading

0 comments on commit 41bef25

Please sign in to comment.