Last active
April 15, 2019 13:48
-
-
Save barncastle/21cde4ae9bba3c850c3271b1d7636d5e 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 TXIDReplacer | |
{ | |
class Program | |
{ | |
static Dictionary<uint, string> FileNameLookup; | |
static void Main(string[] args) | |
{ | |
LoadLookup(); | |
if (!Directory.Exists(args[0])) | |
throw new ArgumentException("Invalid directory provided"); | |
var files = Directory.EnumerateFiles(args[0], "*.m2", SearchOption.AllDirectories); | |
foreach (var file in files) | |
ProcessFile(file); | |
Console.ReadLine(); | |
} | |
static void ProcessFile(string file) | |
{ | |
Console.WriteLine("Processing " + Path.GetFileName(file)); | |
using (var fsIn = File.OpenRead(file)) | |
using (var br = new BinaryReader(fsIn)) | |
using (var fsOut = new MemoryStream((int)fsIn.Length)) | |
using (var bw = new BinaryWriter(fsOut)) | |
{ | |
// get the MD20 size | |
br.ReadInt32(); | |
int md20Size = br.ReadInt32(); | |
br.BaseStream.Position = 0; | |
// scan for TXID | |
if (!SeekChunk(br, 0x44495854, out int size)) | |
{ | |
Console.WriteLine($"Skipping {Path.GetFileName(file)} : No TXID"); | |
return; | |
} | |
// grab the TXIDs | |
var txids = MemoryMarshal.Cast<byte, uint>(br.ReadBytes(size)); | |
// copy the MD21 chunk to the output stream | |
byte[] buffer = new byte[8 + md20Size]; | |
fsIn.Position = 0; | |
fsIn.Read(buffer); | |
fsOut.Write(buffer); | |
// write c string filenames at the end of the MD20 datas | |
// and store their lengths and offsets | |
// unknown textures are presumed to be in the base directory and named <fildataid>.blp | |
br.BaseStream.Position = br.BaseStream.Length; | |
var textureLookup = new M2ArrayHeader[txids.Length]; | |
for (int i = 0; i < txids.Length; i++) | |
{ | |
if (!FileNameLookup.TryGetValue(txids[i], out string filename)) | |
{ | |
filename = txids[i] + ".blp"; | |
Console.WriteLine($"Unknown filename {txids[i]}"); | |
} | |
textureLookup[i] = new M2ArrayHeader() | |
{ | |
Size = filename.Length + 1, | |
Offset = (uint)br.BaseStream.Position | |
}; | |
fsOut.Write(System.Text.Encoding.UTF8.GetBytes(filename + "\0")); | |
} | |
// update the MD20 size field | |
fsIn.Position = 4; | |
bw.Write((uint)(bw.BaseStream.Length - 8)); | |
// parse the MD20 header | |
br.BaseStream.Position = 8; | |
M2Header header = MemoryMarshal.Read<M2Header>(br.ReadBytes(Marshal.SizeOf<M2Header>())); | |
// jump to the textures and update the texture name M2Array header | |
bw.BaseStream.Position = header.OfsTextures; | |
buffer = new byte[8]; | |
for (int i = 0; i < textureLookup.Length; i++) | |
{ | |
bw.BaseStream.Position += 8; | |
MemoryMarshal.Write(buffer, ref textureLookup[i]); | |
fsOut.Write(buffer); | |
} | |
// copy the other chunks | |
br.BaseStream.Position = md20Size + 8; | |
CopyChunks(br, bw, 0x44495854); | |
// overwrite the input file | |
fsIn.Close(); | |
using (var fsFinal = File.Create(file)) | |
fsOut.WriteTo(fsFinal); | |
} | |
} | |
private static bool SeekChunk(BinaryReader br, uint token, out int size) | |
{ | |
uint ctoken = 0; | |
size = 0; | |
while (br.BaseStream.Position < br.BaseStream.Length) | |
{ | |
ctoken = br.ReadUInt32(); | |
size = br.ReadInt32(); | |
if (ctoken == token) | |
return true; | |
br.BaseStream.Position += size; | |
} | |
return false; | |
} | |
private static void CopyChunks(BinaryReader br, BinaryWriter bw, params uint[] ignore) | |
{ | |
uint token; | |
int size; | |
while (br.BaseStream.Position < br.BaseStream.Length) | |
{ | |
token = br.ReadUInt32(); | |
size = br.ReadInt32(); | |
if (Array.IndexOf(ignore, token) > -1) | |
{ | |
br.BaseStream.Position += size; | |
} | |
else | |
{ | |
bw.Write(token); | |
bw.Write(size); | |
bw.Write(br.ReadBytes(size)); | |
} | |
} | |
} | |
private static void LoadLookup() | |
{ | |
FileNameLookup = new Dictionary<uint, string>(); | |
string[] parts; | |
using (var sr = new StreamReader("listfile.csv")) | |
{ | |
while (!sr.EndOfStream) | |
{ | |
parts = sr.ReadLine().Split(';', 2); | |
if (!parts[1].EndsWith("blp")) | |
continue; | |
FileNameLookup[uint.Parse(parts[0])] = parts[1]; | |
} | |
} | |
} | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
struct M2ArrayHeader | |
{ | |
public int Size; | |
public uint Offset; | |
} | |
[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