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 ILogger Debug; private SortedDictionary OffsetMap; private BrresReader() { Debug = new ConsoleLogger(); 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, "Root Dictionary"); } // 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; case "Textures(NW4R)": File.Add(name, ReadAndConvertDict(ins, ConvertTextureResource)); break; case "AnmChr(NW4R)": File.Add(name, ReadAndConvertDict(ins, ConvertAnmChrResource)); break; case "AnmClr(NW4R)": File.Add(name, ReadAndConvertDict(ins, ConvertAnmClrResource)); break; case "AnmTexSrt(NW4R)": File.Add(name, ReadAndConvertDict(ins, ConvertAnmTexSRTResource)); break; default: Debug.Send("Not implemented"); return; } } } private Texture ConvertTextureResource(string name, InputStream ins) { using (var c = Debug.Push(name)) { Texture tex = new Texture(); int startPos = ins.Position; OffsetMap.Add(startPos, "Texture: " + 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 dataOffset = ins.ReadInt32(); Int32 nameOffset = ins.ReadInt32(); // Flags stores nothing interesting, just "is CI" flag (0x1) UInt32 flags = ins.ReadUInt32(); Int16 width = ins.ReadInt16(); Int16 height = ins.ReadInt16(); TextureFormat format = (TextureFormat)ins.ReadUInt32(); int mipmapCount = ins.ReadInt32(); tex.MinLOD = ins.ReadFloat(); tex.MaxLOD = ins.ReadFloat(); ins.Seek(startPos + dataOffset); OffsetMap.Add(ins.Position, String.Format("Texture Data for: {0} [{1}, {2}x{3}, {4} mipmaps]", name, format, width, height, mipmapCount)); // handle mipmaps tex.Images = new System.Drawing.Bitmap[mipmapCount]; for (int i = 0; i < mipmapCount; i++) { tex.ImportData(i, ins.ReadBytes(Texture.GetDataSize(width, height, format)), width, height, format); width /= 2; height /= 2; } return tex; } } private Models.Animation.CharacterAnim ConvertAnmChrResource(string name, InputStream ins) { using (var c = Debug.Push(name)) { var anim = new Models.Animation.CharacterAnim(); int startPos = ins.Position; OffsetMap.Add(startPos, "Character Animation: " + name); // TODO return anim; } } private Models.Animation.ColorAnim ConvertAnmClrResource(string name, InputStream ins) { using (var c = Debug.Push(name)) { var anim = new Models.Animation.ColorAnim(); int startPos = ins.Position; OffsetMap.Add(startPos, "Color Animation: " + name); // TODO return anim; } } private Models.Animation.TextureSRTAnim ConvertAnmTexSRTResource(string name, InputStream ins) { using (var c = Debug.Push(name)) { var anim = new Models.Animation.TextureSRTAnim(); int startPos = ins.Position; OffsetMap.Add(startPos, "Texture SRT Animation: " + name); // TODO return anim; } } 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(); if (unkOffset != 0) { Debug.Send("WARNING: Model {0} has unhandled unknown data", name); OffsetMap.Add(startPos + unkOffset, "Model Unknown Data: " + name); } 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(); mdl.VertexCount = ins.ReadUInt32(); mdl.TriangleCount = ins.ReadUInt32(); UInt32 unk = ins.ReadUInt32(); UInt32 matrixCount = 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); OffsetMap.Add(ins.Position, "Matrix ID to Node ID Data for: " + name); 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 VtxPosIndexLookup = new Dictionary(); VtxNrmIndexLookup = new Dictionary(); VtxClrIndexLookup = new Dictionary(); VtxTexCoordIndexLookup = new Dictionary(); using (var c2 = Debug.Push("Vertex Position Data")) { if (vtxPosOffset == 0) { Debug.Send("None (what?)"); mdl.VtxPosData = new ResDict(); } else { mdl.VtxPosData = ReadAndConvertDict(ins.At(startPos + vtxPosOffset), ConvertVtxPosData); } } using (var c2 = Debug.Push("Vertex Normal Data")) { if (vtxNrmOffset == 0) { Debug.Send("None"); } else { mdl.VtxNrmData = ReadAndConvertDict(ins.At(startPos + vtxNrmOffset), ConvertVtxNrmData); } } using (var c2 = Debug.Push("Vertex Colour Data")) { if (vtxClrOffset == 0) { Debug.Send("None"); } else { mdl.VtxClrData = ReadAndConvertDict(ins.At(startPos + vtxClrOffset), ConvertVtxClrData); } } using (var c2 = Debug.Push("Vertex TexCoord Data")) { if (texCoordOffset == 0) { Debug.Send("None"); } else { mdl.VtxTexCoordData = ReadAndConvertDict(ins.At(startPos + texCoordOffset), ConvertVtxTexCoordData); } } // Load materials and associated structs // Material classes refer to Shaders using an offset, so this is used to fix those up // Also, Pairings refer to Texture Info ShaderOffsetRefs = new Dictionary(); MaterialOffsetRefs = new Dictionary(); TextureInfoRefs = new Dictionary(); using (var c2 = Debug.Push("Shaders")) mdl.Shaders = ReadAndConvertDict(ins.At(startPos + shaderOffset), ConvertModelShader); using (var c2 = Debug.Push("Materials")) mdl.Materials = ReadAndConvertDict(ins.At(startPos + materialOffset), ConvertModelMaterial); using (var c2 = Debug.Push("Shapes")) mdl.Shapes = ReadAndConvertDict(ins.At(startPos + shapeOffset), ConvertModelShape); if (textureOffset != 0) { using (var c2 = Debug.Push("Texture Lookup for Texture Info/Material Pairing")) mdl.PairingLookupByTexture = ReadAndConvertDict>(ins.At(startPos + textureOffset), ConvertPairingList); } else { mdl.PairingLookupByTexture = new ResDict>(); Debug.Send("Texture Lookup for Texture Info/Material Pairing: none"); } if (paletteOffset != 0) { using (var c2 = Debug.Push("Palette Lookup for Texture Info/Material Pairing")) mdl.PairingLookupByPalette = ReadAndConvertDict>(ins.At(startPos + paletteOffset), ConvertPairingList); } else { mdl.PairingLookupByPalette = new ResDict>(); Debug.Send("Palette Lookup for Texture Info/Material Pairing: none"); } 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; Int32 extraDataOffset = ins.ReadInt32(); if (extraDataOffset != 0) { Debug.Send("WARNING: Node {0} has unhandled extra data!", name); OffsetMap.Add(startPos + extraDataOffset, "Node Extra Data: " + name); } 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) { int startPos = ins.Position; UInt32 size = ins.ReadUInt32(); Int32 mdlOffset = ins.ReadInt32(); Int32 dataOffset = ins.ReadInt32(); Int32 nameOffset = ins.ReadInt32(); // Note: we're relying on this value to be correct, for Shape mappings n.Index = ins.ReadUInt32(); n.ComponentCount = (VertexSettings.CompCount)ins.ReadUInt32(); n.ComponentType = (VertexSettings.CompType)ins.ReadUInt32(); // ridiculous and hacky. thanks for requiring this, Nintendo if (n is VertexClrData) { n.EntrySize = ins.ReadByte(); ins.Skip(1); } else { n.Fraction = ins.ReadByte(); n.EntrySize = ins.ReadByte(); } n.EntryCount = ins.ReadUInt16(); n.RawData = ins.At(startPos + dataOffset).ReadBytes(n.EntrySize * n.EntryCount); n.Parse(); OffsetMap.Add(startPos + dataOffset, n.GetType().ToString() + " Data"); } private Dictionary VtxPosIndexLookup; private Dictionary VtxNrmIndexLookup; private Dictionary VtxClrIndexLookup; private Dictionary VtxTexCoordIndexLookup; 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(); VtxPosIndexLookup[(int)n.Index] = n; return n; } private Models.VertexNrmData ConvertVtxNrmData(string name, InputStream ins) { var n = new Models.VertexNrmData(); OffsetMap.Add(ins.Position, "VertexNrmData: " + name); LoadVertexDataBase(ins, n); VtxNrmIndexLookup[(int)n.Index] = 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); VtxClrIndexLookup[(int)n.Index] = 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(); VtxTexCoordIndexLookup[(int)n.Index] = n; return n; } private Dictionary MaterialOffsetRefs; private Dictionary TextureInfoRefs; 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 Int32 shaderOffset = ins.ReadInt32(); m.ShaderRef = ShaderOffsetRefs[startPos + shaderOffset]; //Debug.Send("Shader offset for {0}: {1:X} [dest {2:X}]", name, shaderOffset, startPos + shaderOffset); UInt32 texInfoCount = ins.ReadUInt32(); Int32 texInfoOffset = 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); 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); m.TlutFlag = ins.ReadUInt32(); if ((m.TlutFlag & ~3) == 0) { m.TlutObj = ins.ReadBytes(0x20); } else if ((m.TlutFlag & ~0x1F) == 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(); } // Texture Info if (texInfoOffset != 0) OffsetMap.Add(startPos + texInfoOffset, "Material Texture Info: " + name); ins.Seek(startPos + texInfoOffset); m.TextureInfos = new List((int)texInfoCount); for (int i = 0; i < texInfoCount; i++) { //Debug.Send("Reading {0} texture info at {1:X}", i, ins.Position); int texInfoPos = ins.Position; var texInfo = ReadTextureInfo(ins); m.TextureInfos.Add(texInfo); TextureInfoRefs[texInfoPos] = texInfo; } // Display Lists OffsetMap.Add(startPos + dlOffset, "Material Display Lists: " + name); ins.Seek(startPos + dlOffset); m.PixDL = ins.ReadBytes(0x20); m.TevColorDL = ins.ReadBytes(0x80); m.IndMtxAndScaleDL = ins.ReadBytes(0x40); m.TexCoordGenDL = ins.ReadBytes(0xA0); MaterialOffsetRefs[startPos] = m; return m; } private Dictionary ShaderOffsetRefs; 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; UInt32 size = ins.ReadUInt32(); Int32 mdlOffset = ins.ReadInt32(); s.Index = ins.ReadUInt32(); s.TevStageCount = ins.ReadByte(); ins.Skip(3); s.Unk1 = ins.ReadUInt32(); s.Unk2 = ins.ReadUInt32(); ins.Skip(8); s.DisplayList = ins.ReadBytes(0x1E0); ShaderOffsetRefs[startPos] = s; return s; } private Models.Shape ConvertModelShape(string name, InputStream ins) { var s = new Models.Shape(); int startPos = ins.Position; OffsetMap.Add(ins.Position, "Shape: " + name); // 0x00 UInt32 size = ins.ReadUInt32(); // 0x04 Int32 mdlOffset = ins.ReadInt32(); // 0x08 s.MatrixID = ins.ReadInt32(); // 0x0C s.Unk = ins.ReadBytes(12); int dlBase1 = ins.Position; // 0x18 s.DLBufferSize1 = ins.ReadUInt32(); // 0x1C UInt32 dataSize1 = ins.ReadUInt32(); // 0x20 Int32 offset1 = ins.ReadInt32(); s.DisplayList1 = ins.At(dlBase1 + offset1).ReadBytes((int)dataSize1); OffsetMap.Add(dlBase1 + offset1, String.Format("Shape DL 1: {0} [Buffer Size: 0x{1:X}; Data Size: 0x{2:X}]", name, s.DLBufferSize1, dataSize1)); int dlBase2 = ins.Position; // 0x24 s.DLBufferSize2 = ins.ReadUInt32(); // 0x28 UInt32 dataSize2 = ins.ReadUInt32(); // 0x2C Int32 offset2 = ins.ReadInt32(); s.DisplayList2 = ins.At(dlBase2 + offset2).ReadBytes((int)dataSize2); OffsetMap.Add(dlBase2 + offset2, String.Format("Shape DL 2: {0} [Buffer Size: 0x{1:X}; Data Size: 0x{2:X}]", name, s.DLBufferSize2, dataSize2)); // 0x30 s.DataFlags = ins.ReadUInt32(); // 0x34 s.Flags = ins.ReadUInt32(); // 0x38 int nameOffset = ins.ReadInt32(); // 0x3C s.Index = ins.ReadUInt32(); // 0x40 s.VertexCount = ins.ReadUInt32(); // 0x44 s.PolygonCount = ins.ReadUInt32(); // 0x48 s.PosData = VtxPosIndexLookup[ins.ReadInt16()]; // 0x4A short nrmIndex = ins.ReadInt16(); s.NrmData = nrmIndex == -1 ? null : VtxNrmIndexLookup[nrmIndex]; // 0x4C s.ClrData = new VertexClrData[2]; for (int i = 0; i < 2; i++) { short clrIndex = ins.ReadInt16(); s.ClrData[i] = clrIndex == -1 ? null : VtxClrIndexLookup[clrIndex]; } // 0x50 s.TexCoordData = new VertexTexCoordData[8]; for (int i = 0; i < 8; i++) { short tcIndex = ins.ReadInt16(); s.TexCoordData[i] = tcIndex == -1 ? null : VtxTexCoordIndexLookup[tcIndex]; } // 0x60 s.FurVecDataIndex = ins.ReadInt16(); // 0x62 s.FurPosDataIndex = ins.ReadInt16(); // 0x64 Int32 extraDataOffset = ins.ReadInt32(); var ed = ins.At(startPos + extraDataOffset); UInt32 edCount = ed.ReadUInt32(); s.ExtraData = new ushort[edCount]; for (int i = 0; i < edCount; i++) { s.ExtraData[i] = ed.ReadUInt16(); } OffsetMap.Add(startPos + extraDataOffset, String.Format("Shape Extra Data: {0} [{1} entries]", name, edCount)); return s; } public List ConvertPairingList(string name, InputStream ins) { var list = new List(); int startPos = ins.Position; OffsetMap.Add(ins.Position, "Texture/Material Pairing List for: " + name); UInt32 count = ins.ReadUInt32(); for (int i = 0; i < count; i++) { var p = new TexMatPairing(); p.Material = MaterialOffsetRefs[startPos + ins.ReadInt32()]; p.Texture = TextureInfoRefs[startPos + ins.ReadInt32()]; list.Add(p); } return list; } public TextureInfo ReadTextureInfo(InputStream ins) { int bPos = ins.Position; var bTex = new Models.TextureInfo(); Debug.Send("Reading a texture info from {0:X}", bPos); 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("Was {0}", bTex.TextureName); // placeholder pointers, don't need these ins.Skip(8); bTex.TexMapID = ins.ReadUInt32(); bTex.TlutID = ins.ReadUInt32(); bTex.WrapS = (TextureWrapType)ins.ReadUInt32(); bTex.WrapT = (TextureWrapType)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 bTex; } 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; } } }