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 // COMMENTED OUT TO MAKE DEUBG OUTPUT CLEARER FOR NOW /* 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); */ // Load materials using (var c2 = Debug.Push("Materials")) mdl.Materials = ReadAndConvertDict(ins.At(startPos + materialOffset), ConvertModelMaterial); using (var c2 = Debug.Push("Shaders")) mdl.Shaders = ReadAndConvertDict(ins.At(startPos + shaderOffset), ConvertModelShader); 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 Models.Material ConvertModelMaterial(string name, InputStream ins) { Debug.Send("Reading {0}...", name); var m = new Models.Material(); int startPos = ins.Position; OffsetMap.Add(ins.Position, "Material: " + name); UInt32 size = ins.ReadUInt32(); Int32 mdlOffset = ins.ReadInt32(); Int32 nameOffset = ins.ReadInt32(); m.Index = ins.ReadUInt32(); m.Flags = ins.ReadUInt32(); // ResGenMode m.TexCoordGenCount = ins.ReadByte(); m.ChanCount = ins.ReadByte(); m.TevStageCount = ins.ReadByte(); m.IndStageCount = ins.ReadByte(); m.CullMode = ins.ReadUInt32(); // ResMatMisc m.ZCompLoc = ins.ReadByte(); m.LightSetID = ins.ReadByte(); m.FogID = ins.ReadByte(); m.IndirectTexMtxCalcMethod1 = ins.ReadBytes(4); m.IndirectTexMtxCalcMethod2 = ins.ReadBytes(4); ins.Skip(1); // Other stuff // Shader offset points to an entry in the Shaders group, must fix this up later // like I did with Node classes Int32 shaderOffset = ins.ReadInt32(); //Debug.Send("Shader offset for {0}: {1:X} [dest {2:X}]", name, shaderOffset, startPos + shaderOffset); UInt32 boundTexCount = ins.ReadUInt32(); Int32 boundTexOffset = ins.ReadInt32(); Int32 furOffset = ins.ReadInt32(); Debug.Send("Fur offset for {0}: {1:X} [dest {2:X}]", name, furOffset, startPos + furOffset); Int32 unkOffset = ins.ReadInt32(); Debug.Send("Unknown offset for {0}: {1:X} [dest {2:X}]", name, unkOffset, startPos + unkOffset); // Need to implement DLs... Int32 dlOffset = ins.ReadInt32(); Debug.Send("DL offset for {0}: {1:X} [dest {2:X}]", name, dlOffset, startPos + dlOffset); // ResTexObj int ResTexObjPos = ins.Position; OffsetMap.Add(ins.Position, "Material ResTexObj: " + name); UInt32 textureFlag = ins.ReadUInt32(); m.TexObj = new byte[8][]; for (int i = 0; i < 8; i++) { if ((textureFlag & (1 << i)) != 0) { m.TexObj[i] = ins.ReadBytes(0x20); } } ins.Seek(ResTexObjPos + 0x104); // ResTlutObj int ResTlutObjPos = ins.Position; OffsetMap.Add(ins.Position, "Material ResTlutObj: " + name); UInt32 tlutFlag = ins.ReadUInt32(); if ((tlutFlag & 0xFC) == 0) { m.TlutObj = ins.ReadBytes(0x20); } else if ((tlutFlag & 0xE0) == 0) { m.TlutObj = ins.ReadBytes(0x40); } else { m.TlutObj = ins.ReadBytes(0x60); } ins.Seek(ResTlutObjPos + 0x64); // ResTexSrt int ResTexSrtPos = ins.Position; OffsetMap.Add(ins.Position, "Material ResTexSrt: " + name); UInt32 srtFlag = ins.ReadUInt32(); m.TexMatrixType = ins.ReadUInt32(); m.SRTSettings = new SRTSettingInfo[8]; InputStream texSrtStream = ins.At(ins.Position); InputStream texSetStream = ins.At(ins.Position + (0x14 * 8)); for (int i = 0; i < 8; i++) { if ((srtFlag & (0xF << (i * 4))) != 0) { var srtInfo = m.SRTSettings[i] = new SRTSettingInfo(); srtInfo.ScaleX = texSrtStream.ReadFloat(); srtInfo.ScaleY = texSrtStream.ReadFloat(); srtInfo.Rotate = texSrtStream.ReadFloat(); srtInfo.TranslateX = texSrtStream.ReadFloat(); srtInfo.TranslateY = texSrtStream.ReadFloat(); srtInfo.CameraID = texSetStream.ReadByte(); srtInfo.LightID = texSetStream.ReadByte(); srtInfo.MapType = texSetStream.ReadByte(); srtInfo.Flags = texSetStream.ReadByte(); srtInfo.TexMatrix = texSetStream.ReadMatrix(); } } ins.Seek(ResTexSrtPos + 8 + (0x14 * 8) + (0x34 * 8)); // ResMatChan OffsetMap.Add(ins.Position, "Material ResMatChan: " + name); m.ChanCtrls = new ChanCtrl[2]; for (int i = 0; i < 2; i++) { var chanInfo = m.ChanCtrls[i] = new ChanCtrl(); chanInfo.Flags = ins.ReadUInt32(); chanInfo.MatColor = ins.ReadColor(); chanInfo.AmbColor = ins.ReadColor(); chanInfo.FlagC = ins.ReadUInt32(); chanInfo.FlagA = ins.ReadUInt32(); } // Bound Textures if (boundTexOffset != 0) OffsetMap.Add(startPos + boundTexOffset, "Material Bound Textures: " + name); ins.Seek(startPos + boundTexOffset); m.BoundTextures = new List((int)boundTexCount); for (int i = 0; i < boundTexCount; i++) { //Debug.Send("Reading {0} bound texture at {1:X}", i, ins.Position); int bPos = ins.Position; var bTex = new BoundTextureInfo(); m.BoundTextures.Add(bTex); Int32 texOffs = ins.ReadInt32(); bTex.TextureName = texOffs == 0 ? null : ins.At(bPos + texOffs - 4).ReadName(); Int32 palOffs = ins.ReadInt32(); bTex.PaletteName = palOffs == 0 ? null : ins.At(bPos + palOffs - 4).ReadName(); //Debug.Send("<{0}> <{1}>", bTex.TextureName, bTex.PaletteName); // placeholder pointers, don't need these ins.Skip(8); bTex.TexMapID = ins.ReadUInt32(); bTex.TlutID = ins.ReadUInt32(); bTex.WrapS = ins.ReadUInt32(); bTex.WrapT = ins.ReadUInt32(); bTex.MinFilt = ins.ReadUInt32(); bTex.MagFilt = ins.ReadUInt32(); bTex.LODBias = ins.ReadFloat(); bTex.MaxAniso = ins.ReadUInt32(); bTex.BiasClamp = (ins.ReadByte() != 0); bTex.DoEdgeLOD = (ins.ReadByte() != 0); ins.Skip(2); } return m; } private Models.Shader ConvertModelShader(string name, InputStream ins) { var s = new Models.Shader(); //Debug.Send("{0} @ {1:X}", name, ins.Position); int startPos = ins.Position; // Note: duplicate shader entries seem to be common, so OffsetMap.Add can't be used // Looks like Nintendo's converter checks for them when writing and optimises them // into one entry. That'll sure be fun to implement here... probably requiring // messing around with IEqualityComparer or whatever it's called. I don't even know // if I'm making sense or not. Too tired to look it up, it's 4:51am... OffsetMap[ins.Position] = "Shader: " + name; return s; } 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; } } }