using System; using System.Collections; using System.Collections.Generic; using NW4RTools.Models; namespace NW4RTools { public class BrresReader { public static ResFile LoadFile(byte[] data) { return new BrresReader().Load(new InputStream(data, ByteEndian.BigEndian)); } private class RawStreamResDict : ResDict { } private ResFile File; private Logger Debug; private SortedDictionary OffsetMap; private BrresReader() { Debug = new Logger(); OffsetMap = new SortedDictionary(); } public ResFile Load(InputStream ins) { File = new ResFile(); // Read BRRES header byte[] magic = ins.ReadBytes(4); UInt16 endian = ins.ReadUInt16(); UInt16 version = ins.ReadUInt16(); UInt32 fileSize = ins.ReadUInt32(); UInt16 headerSize = ins.ReadUInt16(); UInt16 blockCount = ins.ReadUInt16(); File.version = version; // Read first block header byte[] blkMagic = ins.ReadBytes(4); UInt32 blkSize = ins.ReadUInt32(); using (var c = Debug.Push("Root Dictionary")) { ReadAndParseDict(ins, ParseRootEntry); } // DONE! using (var c = Debug.Push("Offset Map")) { foreach (var e in OffsetMap) { Debug.Send("0x{0:X} : {1}", e.Key, e.Value); } } return File; } private void ParseRootEntry(string name, InputStream ins) { using (var c = Debug.Push(name)) { switch (name) { case "3DModels(NW4R)": File.Add(name, ReadAndConvertDict(ins, ConvertModelResource)); break; default: Debug.Send("Not implemented"); return; } } } private Model ConvertModelResource(string name, InputStream ins) { using (var c = Debug.Push(name)) { Model mdl = new Model(); int startPos = ins.Position; OffsetMap.Add(startPos, "Model: " + name); byte[] magic = ins.ReadBytes(4); UInt32 size = ins.ReadUInt32(); UInt32 version = ins.ReadUInt32(); Debug.Send("Offset: 0x{0:X}; Size: 0x{1:X}; Version: {2}", startPos, size, version); Int32 resFileOffset = ins.ReadInt32(); Int32 bytecodeOffset = ins.ReadInt32(); Int32 nodeOffset = ins.ReadInt32(); Int32 vtxPosOffset = ins.ReadInt32(); Int32 vtxNrmOffset = ins.ReadInt32(); Int32 vtxClrOffset = ins.ReadInt32(); Int32 texCoordOffset = ins.ReadInt32(); Int32 vtxFurVecOffset = ins.ReadInt32(); Int32 vtxFurPosOffset = ins.ReadInt32(); Int32 materialOffset = ins.ReadInt32(); Int32 shaderOffset = ins.ReadInt32(); Int32 shapeOffset = ins.ReadInt32(); Int32 textureOffset = ins.ReadInt32(); Int32 paletteOffset = ins.ReadInt32(); Int32 unkOffset = ins.ReadInt32(); Int32 nameOffset = ins.ReadInt32(); int infoStructPos = ins.Position; OffsetMap.Add(infoStructPos, "Model Info Struct for: " + name); UInt32 infoSize = ins.ReadUInt32(); // should be 0x40 Int32 mdlOffset = ins.ReadInt32(); mdl.ScaleMode = (Model.ScaleModeType)ins.ReadUInt32(); mdl.TexMatrixMode = (Model.TexMatrixModeType)ins.ReadUInt32(); UInt32 vtxCount = ins.ReadUInt32(); UInt32 triCount = ins.ReadUInt32(); UInt32 unk = ins.ReadUInt32(); UInt32 nodeCount = ins.ReadUInt32(); mdl.UsesNrmMtxArray = (bool)(ins.ReadByte() != 0); mdl.UsesTexMtxArray = (bool)(ins.ReadByte() != 0); ins.Skip(2); Int32 mtxDataOffset = ins.ReadInt32(); mdl.Minimum = ins.ReadVec3(); mdl.Maximum = ins.ReadVec3(); ins.Seek(infoStructPos + mtxDataOffset); UInt32 mtxDataCount = ins.ReadUInt32(); mdl.MatrixIDtoNodeID = new int[mtxDataCount]; for (int i = 0; i < mtxDataCount; i++) { mdl.MatrixIDtoNodeID[i] = ins.ReadInt32(); } // Load bytecode using (var c2 = Debug.Push("Bytecode")) mdl.Bytecode = ReadAndConvertDict(ins.At(startPos + bytecodeOffset), ConvertModelBytecode); // Load nodes and fix up pointers StartNodeLoading(); using (var c2 = Debug.Push("Nodes")) mdl.Nodes = ReadAndConvertDict(ins.At(startPos + nodeOffset), ConvertModelNode); FinishNodeLoading(); // Load vertex data using (var c2 = Debug.Push("Vertex Position Data")) mdl.VtxPosData = ReadAndConvertDict(ins.At(startPos + vtxPosOffset), ConvertVtxPosData); using (var c2 = Debug.Push("Vertex Normal Data")) mdl.VtxNrmData = ReadAndConvertDict(ins.At(startPos + vtxNrmOffset), ConvertVtxNrmData); using (var c2 = Debug.Push("Vertex Colour Data")) mdl.VtxClrData = ReadAndConvertDict(ins.At(startPos + vtxClrOffset), ConvertVtxClrData); using (var c2 = Debug.Push("Vertex TexCoord Data")) mdl.VtxTexCoordData = ReadAndConvertDict(ins.At(startPos + texCoordOffset), ConvertVtxTexCoordData); return mdl; } } private Models.ByteCode ConvertModelBytecode(string name, InputStream ins) { var bc = new Models.ByteCode(); OffsetMap.Add(ins.Position, "ByteCode: " + name); while (true) { ByteCode.OpType op = (ByteCode.OpType)ins.ReadByte(); switch (op) { case ByteCode.OpType.Done: bc.Instructions.Add(new ByteCode.DoneInstruction()); return bc; case ByteCode.OpType.AssignNodeToParentMtx: var insn2 = new ByteCode.AssignNodeToParentMtxInstruction(); insn2.NodeID = ins.ReadUInt16(); insn2.ParentMatrixID = ins.ReadUInt16(); bc.Instructions.Add(insn2); break; case ByteCode.OpType.BlendMatrices: var insn3 = new ByteCode.BlendMatricesInstruction(); insn3.MatrixID = ins.ReadUInt16(); insn3.BlendedMatrices = new ByteCode.BlendMatricesInstruction.BlendedMatrix[ins.ReadByte()]; for (int i = 0; i < insn3.BlendedMatrices.Length; i++) { insn3.BlendedMatrices[i].MatrixID = ins.ReadUInt16(); insn3.BlendedMatrices[i].Ratio = ins.ReadFloat(); } bc.Instructions.Add(insn3); break; case ByteCode.OpType.DrawShape: var insn4 = new ByteCode.DrawShapeInstruction(); insn4.MaterialID = ins.ReadUInt16(); insn4.ShapeID = ins.ReadUInt16(); insn4.NodeID = ins.ReadUInt16(); ins.Skip(1); bc.Instructions.Add(insn4); break; case ByteCode.OpType.AssignMtxToNode: var insn5 = new ByteCode.AssignMtxToNodeInstruction(); insn5.MatrixID = ins.ReadUInt16(); insn5.NodeID = ins.ReadUInt16(); bc.Instructions.Add(insn5); break; default: bc.Instructions.Add(new ByteCode.Instruction()); break; } } } private class NodeLoadInfo { public Models.Node Node; public string Name; public int Position; public Int32 ParentOffset; public Int32 ChildOffset; public Int32 NextOffset; public Int32 PrevOffset; } private Dictionary NodeLoadData; private void StartNodeLoading() { NodeLoadData = new Dictionary(); } private Models.Node ConvertModelNode(string name, InputStream ins) { var n = new Models.Node(); OffsetMap.Add(ins.Position, "Node: " + name); int startPos = ins.Position; UInt32 size = ins.ReadUInt32(); Int32 mdlOffset = ins.ReadInt32(); Int32 nameOffset = ins.ReadInt32(); n.Index = ins.ReadUInt32(); n.MatrixID = ins.ReadUInt32(); n.Flags = ins.ReadUInt32(); n.BillboardMode = (Node.BillboardType)ins.ReadUInt32(); UInt32 unk = ins.ReadUInt32(); // might be swapped with bbmode, check asm to confirm n.Scale = ins.ReadVec3(); n.Rotation = ins.ReadVec3(); n.Translation = ins.ReadVec3(); n.BoxMin = ins.ReadVec3(); n.BoxMax = ins.ReadVec3(); NodeLoadInfo loadInfo = new NodeLoadInfo(); loadInfo.ParentOffset = ins.ReadInt32(); loadInfo.ChildOffset = ins.ReadInt32(); loadInfo.NextOffset = ins.ReadInt32(); loadInfo.PrevOffset = ins.ReadInt32(); loadInfo.Position = startPos; loadInfo.Node = n; loadInfo.Name = ins.At(startPos + nameOffset - 4).ReadName(); NodeLoadData[startPos] = loadInfo; n.NodeMatrix = ins.ReadMatrix(); n.NodeInvMatrix = ins.ReadMatrix(); return n; } private void FinishNodeLoading() { foreach (var dictEntry in NodeLoadData) { var entry = dictEntry.Value; var node = entry.Node; if (entry.ParentOffset == 0) { node.Parent = null; } else { NodeLoadInfo n = NodeLoadData[entry.Position + entry.ParentOffset]; node.Parent = n.Node; Debug.Send("Set {0}'s parent to {1}", entry.Name, n.Name); } if (entry.ChildOffset == 0) node.FirstChild = null; else node.FirstChild = NodeLoadData[entry.Position + entry.ChildOffset].Node; if (entry.NextOffset == 0) node.Next = null; else node.Next = NodeLoadData[entry.Position + entry.NextOffset].Node; if (entry.PrevOffset == 0) node.Previous = null; else node.Previous = NodeLoadData[entry.Position + entry.PrevOffset].Node; } NodeLoadData = null; } private void LoadVertexDataBase(InputStream ins, Models.VertexDataBase n) { UInt32 size = ins.ReadUInt32(); Int32 mdlOffset = ins.ReadInt32(); Int32 dataOffset = ins.ReadInt32(); Int32 nameOffset = ins.ReadInt32(); n.Index = ins.ReadUInt32(); n.ComponentCount = ins.ReadUInt32(); n.ComponentType = ins.ReadUInt32(); n.Fraction = ins.ReadByte(); n.EntrySize = ins.ReadByte(); n.EntryCount = ins.ReadUInt16(); n.Data = ins.ReadBytes(n.EntrySize * n.EntryCount); } private Models.VertexPosData ConvertVtxPosData(string name, InputStream ins) { var n = new Models.VertexPosData(); OffsetMap.Add(ins.Position, "VertexPosData: " + name); LoadVertexDataBase(ins, n); n.Minimum = ins.ReadVec3(); n.Maximum = ins.ReadVec3(); return n; } private Models.VertexNrmData ConvertVtxNrmData(string name, InputStream ins) { var n = new Models.VertexNrmData(); OffsetMap.Add(ins.Position, "VertexNrmData: " + name); LoadVertexDataBase(ins, n); return n; } private Models.VertexClrData ConvertVtxClrData(string name, InputStream ins) { var n = new Models.VertexClrData(); OffsetMap.Add(ins.Position, "VertexClrData: " + name); LoadVertexDataBase(ins, n); return n; } private Models.VertexTexCoordData ConvertVtxTexCoordData(string name, InputStream ins) { var n = new Models.VertexTexCoordData(); OffsetMap.Add(ins.Position, "VertexTexCoordData: " + name); LoadVertexDataBase(ins, n); n.Minimum = ins.ReadVec2(); n.Maximum = ins.ReadVec2(); return n; } private RawStreamResDict ReadDict(InputStream ins) { RawStreamResDict output = new RawStreamResDict(); int dictPos = ins.Position; UInt32 dataSize = ins.ReadUInt32(); UInt32 entryCount = ins.ReadUInt32(); for (int i = 0; i <= entryCount; i++) { UInt16 eRef = ins.ReadUInt16(); UInt16 flag = ins.ReadUInt16(); UInt16 iLeft = ins.ReadUInt16(); UInt16 iRight = ins.ReadUInt16(); Int32 nameOffset = ins.ReadInt32(); Int32 dataOffset = ins.ReadInt32(); //Debug.Send("Position: {6:X}, eRef: {0:X}, flag: {1:X}, iLeft: {2:X}, iRight: {3:X}, nameOffset: {4:X}, dataOffset: {5:X}", eRef, flag, iLeft, iRight, nameOffset, dataOffset, ins.Position); if (nameOffset != 0 && dataOffset != 0) { string name = ins.At(dictPos + nameOffset - 4).ReadName(); Debug.Send("Entry: {0} at offset 0x{1:X}", name, dictPos + dataOffset); InputStream entryStream = ins.At(dictPos + dataOffset); output.Add(name, entryStream); } } return output; } private delegate void ParseResourceDelegate(string name, InputStream ins); private delegate TValue ConvertResourceDelegate(string name, InputStream ins); private void ReadAndParseDict(InputStream ins, ParseResourceDelegate func) { ReadAndParseDict(ins, func, "Unnamed"); } private void ReadAndParseDict(InputStream ins, ParseResourceDelegate func, string dictName) { int dictPos = ins.Position; RawStreamResDict theDict = ReadDict(ins); OffsetMap.Add(dictPos, string.Format("ResDict: {0} [Data ends at 0x{1:X}]", dictName, ins.Position)); foreach (var entry in theDict) { func(entry.Key, entry.Value); } } private ResDict ReadAndConvertDict(InputStream ins, ConvertResourceDelegate func) { int dictPos = ins.Position; RawStreamResDict theDict = ReadDict(ins); var outDict = new ResDict(); // Hack to get the type name string valueTypeName = outDict.GetType().GetGenericArguments()[0].ToString(); OffsetMap.Add(dictPos, string.Format("ResDict: {0} [Data ends at 0x{1:X}]", valueTypeName, ins.Position)); foreach (var entry in theDict) { TValue returnedObject = func(entry.Key, entry.Value); outDict.Add(entry.Key, returnedObject); } return outDict; } } }