diff options
author | Treeki <treeki@gmail.com> | 2011-02-04 15:40:12 +0100 |
---|---|---|
committer | Treeki <treeki@gmail.com> | 2011-02-04 15:40:12 +0100 |
commit | db8fc350b2bffda63d27797bfe7ae4afb4af327e (patch) | |
tree | a0a89cd83615e7794d9507d10c329e30c4226670 /NW4RTools/BrresReader.cs | |
download | nw4rtools-db8fc350b2bffda63d27797bfe7ae4afb4af327e.tar.gz nw4rtools-db8fc350b2bffda63d27797bfe7ae4afb4af327e.zip |
Initial commit of NW4RTools.
Supports BRRES reading for: Model Bytecode, Model Nodes, Model Vertex Data,
and incomplete Materials. Writing is completely unimplemented so far.
Diffstat (limited to '')
-rw-r--r-- | NW4RTools/BrresReader.cs | 442 |
1 files changed, 442 insertions, 0 deletions
diff --git a/NW4RTools/BrresReader.cs b/NW4RTools/BrresReader.cs new file mode 100644 index 0000000..a157fe9 --- /dev/null +++ b/NW4RTools/BrresReader.cs @@ -0,0 +1,442 @@ +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<InputStream> { + } + + + + private ResFile File; + private Logger Debug; + private SortedDictionary<int, string> OffsetMap; + + private BrresReader() { + Debug = new Logger(); + OffsetMap = new SortedDictionary<int, string>(); + } + + 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<Model>(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<ByteCode>(ins.At(startPos + bytecodeOffset), ConvertModelBytecode); + + // Load nodes and fix up pointers + StartNodeLoading(); + + using (var c2 = Debug.Push("Nodes")) + mdl.Nodes = ReadAndConvertDict<Node>(ins.At(startPos + nodeOffset), ConvertModelNode); + + FinishNodeLoading(); + + // Load vertex data + using (var c2 = Debug.Push("Vertex Position Data")) + mdl.VtxPosData = ReadAndConvertDict<VertexPosData>(ins.At(startPos + vtxPosOffset), ConvertVtxPosData); + using (var c2 = Debug.Push("Vertex Normal Data")) + mdl.VtxNrmData = ReadAndConvertDict<VertexNrmData>(ins.At(startPos + vtxNrmOffset), ConvertVtxNrmData); + using (var c2 = Debug.Push("Vertex Colour Data")) + mdl.VtxClrData = ReadAndConvertDict<VertexClrData>(ins.At(startPos + vtxClrOffset), ConvertVtxClrData); + using (var c2 = Debug.Push("Vertex TexCoord Data")) + mdl.VtxTexCoordData = ReadAndConvertDict<VertexTexCoordData>(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<int, NodeLoadInfo> NodeLoadData; + + private void StartNodeLoading() { + NodeLoadData = new Dictionary<int, NodeLoadInfo>(); + } + + 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<TValue>(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<TValue> ReadAndConvertDict<TValue>(InputStream ins, ConvertResourceDelegate<TValue> func) { + int dictPos = ins.Position; + RawStreamResDict theDict = ReadDict(ins); + var outDict = new ResDict<TValue>(); + + // 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; + } + } +} + |