Created
February 4, 2019 15:21
-
-
Save barncastle/4e2dedb824e4cd2f80d7c84175cb4b74 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Numerics; | |
using System.Runtime.InteropServices; | |
namespace ConsoleApp1 | |
{ | |
class FileNameExtractor | |
{ | |
public HashSet<uint> TextureIDs; | |
public HashSet<string> TextureNames; | |
public FileNameExtractor(string filename) | |
{ | |
TextureIDs = new HashSet<uint>(); | |
TextureNames = new HashSet<string>(); | |
using (var fs = File.OpenRead(filename)) | |
using (var br = new BinaryReader(fs)) | |
{ | |
switch (Path.GetExtension(filename).ToLower()) | |
{ | |
case ".m2": | |
ProcessM2(br); | |
break; | |
case ".wmo": // this should be the root not group WMO | |
ProcessWMO(br); | |
break; | |
case ".adt": | |
ProcessADT(br); | |
break; | |
} | |
} | |
} | |
/// <summary> | |
/// Scan an M2 for a TXID and any inline filenames | |
/// </summary> | |
/// <param name="br"></param> | |
private void ProcessM2(BinaryReader br) | |
{ | |
uint token = br.ReadUInt32(); | |
uint MD20offset = 0; | |
// if MD21 read TXID if it exists | |
if (token == 0x3132444D) | |
{ | |
MD20offset = 8; // MD20 is within the MD21 chunk | |
// scan for TXID - this is an array if FileDataIds | |
br.BaseStream.Position = 0; // reset stream pos | |
if (SeekChunk(br, 0x44495854, out int size)) | |
AddRange(TextureIDs, MemoryMarshal.Cast<byte, uint>(br.ReadBytes(size)).ToArray()); | |
} | |
// read the MD20 header | |
br.BaseStream.Position = MD20offset; | |
var header = MemoryMarshal.Read<M2Header>(br.ReadBytes(Marshal.SizeOf<M2Header>())); | |
// check if any filenames exist | |
if (header.NTextures > 0) | |
{ | |
br.BaseStream.Position = header.OfsTextures; | |
// read the M2Texture segments | |
int textureSize = Marshal.SizeOf<M2Texture>() * header.NTextures; | |
M2Texture[] textures = MemoryMarshal.Cast<byte, M2Texture>(br.ReadBytes(textureSize)).ToArray(); | |
// iterate the offsets and read the names - ignore \0 suffix | |
foreach (var tex in textures) | |
{ | |
// 0 indicates a reference to TXID so no string available | |
if (tex.FileNameSize == 0) | |
continue; | |
br.BaseStream.Position = tex.FileNameOffset; | |
TextureNames.Add(System.Text.Encoding.ASCII.GetString(br.ReadBytes(tex.FileNameSize - 1))); | |
} | |
} | |
} | |
/// <summary> | |
/// Scan a WMO for inline filenames fallingback to reading MOMT FileDataIds | |
/// </summary> | |
/// <param name="br"></param> | |
private void ProcessWMO(BinaryReader br) | |
{ | |
bool hasMOTX; // if MOTX then ignore MOMT | |
// scan for MOTX | |
if (hasMOTX = SeekChunk(br, 0x4D4F5458, out int size)) | |
{ | |
string textures = System.Text.Encoding.ASCII.GetString(br.ReadBytes(size)); | |
AddRange(TextureNames, textures.Split('\0', StringSplitOptions.RemoveEmptyEntries)); | |
} | |
// scan for MOMT if no MOTX chunk | |
br.BaseStream.Position = 0; | |
if (!hasMOTX && SeekChunk(br, 0x4D4F4D54, out size)) | |
{ | |
// BfA MOMT indices into MOTX are FileDataIds | |
var MOMTs = MemoryMarshal.Cast<byte, MOMT>(br.ReadBytes(size)); | |
foreach (var momt in MOMTs) | |
{ | |
if (momt.DiffuseNameIndex > 0) | |
TextureIDs.Add(momt.DiffuseNameIndex); | |
if (momt.EnvNameIndex > 0) | |
TextureIDs.Add(momt.EnvNameIndex); | |
if (momt.Unk_Texture > 0) | |
TextureIDs.Add(momt.Unk_Texture); | |
} | |
} | |
} | |
/// <summary> | |
/// Scan an ADT for inline filenames and FileDataIds | |
/// <para>MTEX, MDID, MHID, MMDX, MWMO</para> | |
/// </summary> | |
/// <param name="br"></param> | |
private void ProcessADT(BinaryReader br) | |
{ | |
// MTEX | |
br.BaseStream.Position = 0; | |
if (SeekChunk(br, 0x5845544D, out int size)) | |
{ | |
string textures = System.Text.Encoding.ASCII.GetString(br.ReadBytes(size)); | |
AddRange(TextureNames, textures.Split('\0', StringSplitOptions.RemoveEmptyEntries)); | |
} | |
// MDID | |
br.BaseStream.Position = 0; | |
if (SeekChunk(br, 0x4449444D, out size)) | |
AddRange(TextureIDs, MemoryMarshal.Cast<byte, uint>(br.ReadBytes(size)).ToArray()); | |
// MHID | |
br.BaseStream.Position = 0; | |
if (SeekChunk(br, 0x4449484d, out size)) | |
AddRange(TextureIDs, MemoryMarshal.Cast<byte, uint>(br.ReadBytes(size)).ToArray()); | |
// MMDX | |
br.BaseStream.Position = 0; | |
if (SeekChunk(br, 0x58444D4D, out size)) | |
{ | |
string textures = System.Text.Encoding.ASCII.GetString(br.ReadBytes(size)); | |
AddRange(TextureNames, textures.Split('\0', StringSplitOptions.RemoveEmptyEntries)); | |
} | |
// MWMO | |
if (SeekChunk(br, 0x4F4D574D, out size)) | |
{ | |
string textures = System.Text.Encoding.ASCII.GetString(br.ReadBytes(size)); | |
AddRange(TextureNames, textures.Split('\0', StringSplitOptions.RemoveEmptyEntries)); | |
} | |
} | |
/// <summary> | |
/// Scans for a specific token in the file | |
/// </summary> | |
/// <param name="br"></param> | |
/// <param name="token"></param> | |
/// <param name="size"></param> | |
/// <returns></returns> | |
private bool SeekChunk(BinaryReader br, uint token, out int size) | |
{ | |
long length = br.BaseStream.Length; | |
uint ctoken = br.ReadUInt32(); | |
size = br.ReadInt32(); | |
while (br.BaseStream.Position + size < length) | |
{ | |
br.BaseStream.Position += size; | |
ctoken = br.ReadUInt32(); | |
size = br.ReadInt32(); | |
if (ctoken == token) | |
return true; | |
} | |
return false; | |
} | |
/// <summary> | |
/// Lazy sugar syntax for hashset addrange cba making an extension method | |
/// </summary> | |
/// <typeparam name="T"></typeparam> | |
/// <param name="hashset"></param> | |
/// <param name="values"></param> | |
private void AddRange<T>(HashSet<T> hashset, IEnumerable<T> values) | |
{ | |
foreach (var v in values) | |
hashset.Add(v); | |
} | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
struct MOMT | |
{ | |
public uint flags; | |
public int Shader; | |
public int BlendMode; | |
public uint DiffuseNameIndex; | |
public uint SidnColor; | |
public uint FrameSidnColor; | |
public uint EnvNameIndex; | |
public uint DiffColor; | |
public uint GroundType; | |
public uint Unk_Texture; | |
public uint Unk_Color; | |
public uint Unk_Ref; | |
private readonly int RunTimeData1; | |
private readonly int RunTimeData2; | |
private readonly int RunTimeData3; | |
private readonly int RunTimeData4; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
struct M2Texture | |
{ | |
public uint Type; | |
public uint Flags; | |
public int FileNameSize; // M2ArrayHeader ... | |
public uint FileNameOffset; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
struct M2Header | |
{ | |
public int Magic; | |
public int Version; | |
public int LenName; | |
public int OfsName; | |
public int GlobalFlags; | |
public int NGlobalSequences; | |
public int OfsGlobalSequences; | |
public int NAnimations; | |
public int OfsAnimations; | |
public int NAnimLookup; | |
public int OfsAnimLookup; | |
public int NBones; | |
public int OfsBones; | |
public int NKeyBoneLookup; | |
public int OfsKeyBoneLookup; | |
public int NVertices; | |
public int OfsVertices; | |
public int NViews; | |
public int NSubmeshAnimations; | |
public int OfsSubmeshAnimations; | |
public int NTextures; | |
public int OfsTextures; | |
public int NTransparencies; | |
public int OfsTransparencies; | |
public int NUvAnimation; | |
public int OfsUvAnimation; | |
public int NTexReplace; | |
public int OfsTexReplace; | |
public int NRenderFlags; | |
public int OfsRenderFlags; | |
public int NBoneLookupTable; | |
public int OfsBoneLookupTable; | |
public int NTexLookup; | |
public int OfsTexLookup; | |
public int NTexUnits; | |
public int OfsTexUnits; | |
public int NTransLookup; | |
public int OfsTransLookup; | |
public int NUvAnimLookup; | |
public int OfsUvAnimLookup; | |
public Vector3 VertexBoxMin; | |
public Vector3 VertexBoxMax; | |
public float VertexRadius; | |
public Vector3 BoundingBoxMin; | |
public Vector3 BoundingBoxMax; | |
public float BoundingRadius; | |
public int NBoundingTriangles; | |
public int OfsBoundingTriangles; | |
public int NBoundingVertices; | |
public int OfsBoundingVertices; | |
public int NBoundingNormals; | |
public int OfsBoundingNormals; | |
public int NAttachments; | |
public int OfsAttachments; | |
public int NAttachLookup; | |
public int OfsAttachLookup; | |
public int NEvents; | |
public int OfsEvents; | |
public int NLights; | |
public int OfsLights; | |
public int NCameras; | |
public int OfsCameras; | |
public int NCameraLookup; | |
public int OfsCameraLookup; | |
public int NRibbonEmitters; | |
public int OfsRibbonEmitters; | |
public int NParticleEmitters; | |
public int OfsParticleEmitters; | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment