summaryrefslogtreecommitdiff
path: root/NW4RTools/BrresReader.cs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--NW4RTools/BrresReader.cs442
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;
+ }
+ }
+}
+