From ab340fe4dc87086336643b6b616df6efdfac796b Mon Sep 17 00:00:00 2001 From: Treeki Date: Thu, 3 Mar 2011 04:53:56 +0100 Subject: working (but not 100% tested) model and texture writing. a few other fixes, etc --- NW4RTools/BrresWriter.cs | 979 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 959 insertions(+), 20 deletions(-) (limited to 'NW4RTools/BrresWriter.cs') diff --git a/NW4RTools/BrresWriter.cs b/NW4RTools/BrresWriter.cs index 1b103fc..b93a71d 100644 --- a/NW4RTools/BrresWriter.cs +++ b/NW4RTools/BrresWriter.cs @@ -29,10 +29,12 @@ namespace NW4RTools { using (var c = Debug.Push("Offset Calculation")) CalculateRoot(); - Output.WriteBytes(StringTableData.GetBuffer()); + using (var c = Debug.Push("Data")) + WriteRoot(); using (var c = Debug.Push("Offset Map")) { foreach (var e in OffsetMap) { + // Commented for easier output reading atm Debug.Send("0x{0:X} : {1}", e.Key, e.Value); } } @@ -53,18 +55,38 @@ namespace NW4RTools { private Dictionary VtxClrOffsets; private Dictionary VtxTexCoordOffsets; private Dictionary MaterialOffsets; + private Dictionary MaterialDLOffsets; private Dictionary ShaderOffsets; private Dictionary ShapeOffsets; + private Dictionary ShapeDL1Offsets; + private Dictionary ShapeDL2Offsets; + private Dictionary ShapeSizes; private Dictionary, int> PairingOffsets; + private Dictionary TextureInfoOffsets; + + // Models have lots of extra offset data attached + private class ModelCalcInfo { + public int MatrixIDtoNodeID, Bytecode, Nodes; + public int VtxPosData, VtxNrmData, VtxClrData, VtxTexCoordData; + public int Materials, Shaders, Shapes; + public int PairingLookupByTexture; + public int BlockSize; + // Duplicate shader structs are removed from the model, so they require special tracking + public List UniqueShaders; + } + private Dictionary ModelCalcInfos; private Dictionary TextureOffsets; private Dictionary TextureDataOffsets; - #endregion + private Dictionary RootDictOffsets; - #region Offset/String Table Calculation private int CurrentPos; + private int BlockCount; + private int RootBlockSize; + #endregion + #region Offset/String Table Calculation private void CalculateRoot() { // Where it all starts! InitialiseStringTable(); @@ -75,12 +97,21 @@ namespace NW4RTools { // First block, and ResDict CurrentPos += 8; CurrentPos += GetSizeForResDict(File.Count); + RootBlockSize = 8 + GetSizeForResDict(File.Count); + + // "root" block counts + BlockCount = 1; + + RootDictOffsets = new Dictionary(); // Now do each ResDict in the File - foreach (object dict in File.Values) { - // yay stupid hack - OffsetMap.Add(CurrentPos, "ResDict: " + dict.GetType().GetGenericArguments()[0].ToString()); - CurrentPos += GetSizeForResDict((dict as ICollection).Count); + foreach (var kv in File) { + OffsetMap.Add(CurrentPos, "ResDict: " + kv.Key); + RootDictOffsets[kv.Value] = CurrentPos; + + CurrentPos += GetSizeForResDict((kv.Value as ICollection).Count); + RootBlockSize += GetSizeForResDict((kv.Value as ICollection).Count); + BlockCount += (kv.Value as ICollection).Count; } // OK, so that's done. Process each type @@ -100,12 +131,17 @@ namespace NW4RTools { } // ... and done with that. Build the string table and go! + AlignCalcPos(4); + OffsetMap.Add(CurrentPos, "String Table"); CalculateStringTable(); } #region Model Calculation + private void CalculateModels() { ModelOffsets = new Dictionary(); + ModelCalcInfos = new Dictionary(); + BytecodeOffsets = new Dictionary(); NodeOffsets = new Dictionary(); VtxPosOffsets = new Dictionary(); @@ -113,59 +149,82 @@ namespace NW4RTools { VtxClrOffsets = new Dictionary(); VtxTexCoordOffsets = new Dictionary(); MaterialOffsets = new Dictionary(); + MaterialDLOffsets = new Dictionary(); ShaderOffsets = new Dictionary(); ShapeOffsets = new Dictionary(); + ShapeDL1Offsets = new Dictionary(); + ShapeDL2Offsets = new Dictionary(); + ShapeSizes = new Dictionary(); PairingOffsets = new Dictionary, int>(); + TextureInfoOffsets = new Dictionary(); - - var modelDict = File.GetGroup("3DModels(NW4R)"); - - foreach (var kv in modelDict) { + foreach (var kv in File.GetGroup("3DModels(NW4R)")) { AddString(kv.Key); AlignCalcPos(0x20); // 0x40? dunno Model model = kv.Value; + var calcInfo = ModelCalcInfos[model] = new BrresWriter.ModelCalcInfo(); + + // This is used later to calculate the MDL0 block size easily + int startPos = CurrentPos; + OffsetMap.Add(CurrentPos, "Model: " + kv.Key); + ModelOffsets.Add(model, CurrentPos); CurrentPos += 0x4C; OffsetMap.Add(CurrentPos, "Model Info Struct for: " + kv.Key); CurrentPos += 0x40; OffsetMap.Add(CurrentPos, "Matrix ID to Node ID Data for: " + kv.Key); + calcInfo.MatrixIDtoNodeID = CurrentPos; CurrentPos += 4 + (model.MatrixIDtoNodeID.Length * 4); OffsetMap.Add(CurrentPos, "ResDict: ByteCode"); + calcInfo.Bytecode = CurrentPos; CurrentPos += GetSizeForResDict(model.Bytecode.Count); + OffsetMap.Add(CurrentPos, "ResDict: Nodes"); + calcInfo.Nodes = CurrentPos; CurrentPos += GetSizeForResDict(model.Nodes.Count); + OffsetMap.Add(CurrentPos, "ResDict: VertexPosData"); + calcInfo.VtxPosData = CurrentPos; CurrentPos += GetSizeForResDict(model.VtxPosData.Count); if (model.VtxNrmData.Count > 0) { OffsetMap.Add(CurrentPos, "ResDict: VertexNrmData"); + calcInfo.VtxNrmData = CurrentPos; CurrentPos += GetSizeForResDict(model.VtxNrmData.Count); } if (model.VtxClrData.Count > 0) { OffsetMap.Add(CurrentPos, "ResDict: VertexClrData"); + calcInfo.VtxClrData = CurrentPos; CurrentPos += GetSizeForResDict(model.VtxClrData.Count); } if (model.VtxTexCoordData.Count > 0) { OffsetMap.Add(CurrentPos, "ResDict: VertexTexCoordData"); + calcInfo.VtxTexCoordData = CurrentPos; CurrentPos += GetSizeForResDict(model.VtxTexCoordData.Count); } OffsetMap.Add(CurrentPos, "ResDict: Materials"); + calcInfo.Materials = CurrentPos; CurrentPos += GetSizeForResDict(model.Materials.Count); + OffsetMap.Add(CurrentPos, "ResDict: Shaders"); + calcInfo.Shaders = CurrentPos; CurrentPos += GetSizeForResDict(model.Shaders.Count); + OffsetMap.Add(CurrentPos, "ResDict: Shapes"); + calcInfo.Shapes = CurrentPos; CurrentPos += GetSizeForResDict(model.Shapes.Count); if (model.PairingLookupByTexture.Count > 0) { OffsetMap.Add(CurrentPos, "ResDict: Texture Lookup"); + calcInfo.PairingLookupByTexture = CurrentPos; CurrentPos += GetSizeForResDict(model.PairingLookupByTexture.Count); } @@ -176,12 +235,16 @@ namespace NW4RTools { CalculateBytecode(model); CalculateNodes(model); CalculateMaterials(model); + calcInfo.UniqueShaders = new List(); CalculateShaders(model); CalculateShapes(model); CalculateVtxPosData(model); CalculateVtxNrmData(model); CalculateVtxClrData(model); CalculateVtxTexCoordData(model); + + // Here we go! + calcInfo.BlockSize = CurrentPos - startPos; } } @@ -275,23 +338,64 @@ namespace NW4RTools { if (kv.Value.TextureInfos.Count > 0) OffsetMap.Add(CurrentPos, "Material Texture Infos: " + kv.Key); - CurrentPos += (kv.Value.TextureInfos.Count * 0x34); + for (int i = 0; i < kv.Value.TextureInfos.Count; i++) { + TextureInfoOffsets[kv.Value.TextureInfos[i]] = CurrentPos; + CurrentPos += 0x34; + } // Display Lists AlignCalcPos(0x20); OffsetMap.Add(CurrentPos, "Material Display Lists: " + kv.Key); + MaterialDLOffsets[kv.Value] = CurrentPos; CurrentPos += 0x20 + 0x80 + 0x40 + 0xA0; } } private void CalculateShaders(Model m) { + // Oh great, now I need to optimise this by removing duplicate shaders. + // I'm doing it like this: I'll loop through every shader in the ResDict. + // I'll check each one against the UniqueShaders list. + // If it's not in the list, I'll add it there and add it to the resource data. + + var calcInfo = ModelCalcInfos[m]; + foreach (var kv in m.Shaders) { AddString(kv.Key); - OffsetMap.Add(CurrentPos, "Shader: " + kv.Key); - ShaderOffsets.Add(kv.Value, CurrentPos); + // Check to see if it's in the UniqueShaders list already + // This is made slightly difficult -- shaders can be different object instances + // yet still match, so simple reference comparison isn't good enough. + // So, I added a "DataMatches" method to the Shader class that checks the + // data in two Shaders and sees if they match. + + bool wasFound = false; + Shader match = null; + + foreach (var checkAgainst in calcInfo.UniqueShaders) { + if (checkAgainst.DataMatches(kv.Value)) { + wasFound = true; + match = checkAgainst; + break; + } + } + + if (wasFound) { + // It's already in there. + int positionOfMatch = ShaderOffsets[match]; + OffsetMap[positionOfMatch] = OffsetMap[positionOfMatch] + ", " + kv.Key; + + // Add it to ShaderOffsets so we can link it up to the shader ResDict + ShaderOffsets.Add(kv.Value, positionOfMatch); + + } else { + // This is a new shader, add it! + calcInfo.UniqueShaders.Add(kv.Value); + + OffsetMap.Add(CurrentPos, "Shader: " + kv.Key); + ShaderOffsets.Add(kv.Value, CurrentPos); - CurrentPos += 0x200; + CurrentPos += 0x200; + } } } @@ -304,14 +408,26 @@ namespace NW4RTools { CurrentPos += 0x68; + // extra data + CurrentPos += 4; + CurrentPos += (kv.Value.ExtraData.Length * 2); + + // display lists AlignCalcPos(0x20); - + + ShapeDL1Offsets.Add(kv.Value, CurrentPos); OffsetMap.Add(CurrentPos, "Shape DL 1: " + kv.Key); CurrentPos += (int)kv.Value.DLBufferSize1; AlignCalcPos(0x20); + ShapeDL2Offsets.Add(kv.Value, CurrentPos); OffsetMap.Add(CurrentPos, "Shape DL 2: " + kv.Key); CurrentPos += (int)kv.Value.DLBufferSize2; + + // Should this line be after the final alignment? + // Does it even matter? I'd assume DL sizes will be aligned to 0x20... + ShapeSizes[kv.Value] = CurrentPos - ShapeOffsets[kv.Value]; + AlignCalcPos(0x20); } } @@ -438,13 +554,832 @@ namespace NW4RTools { #endregion + #region Writing Data + private void WriteRoot() { + // magic "bres", endian, version, file size + Output.WriteUInt32(0x62726573); + Output.WriteUInt16(0xFEFF); + Output.WriteUInt16(File.Version); + Output.WriteUInt32((uint)(((CurrentPos + StringTableData.Position) + 0x3F) & ~0x3F)); + + // header size, block count + Output.WriteUInt16(0x10); + Output.WriteUInt16((ushort)BlockCount); + + // first block: magic "root", block size + Output.WriteUInt32(0x726F6F74); + Output.WriteUInt32((uint)RootBlockSize); + + // root dictionaries + WriteResDict(File, RootDictOffsets); + + using (var c = Debug.Push("Root Dictionaries")) { + foreach (var kv in File) { + Debug.Send(kv.Key); + + switch (kv.Key) { + case "3DModels(NW4R)": + WriteResDict(kv.Value as ResDict, ModelOffsets); + break; + case "Textures(NW4R)": + WriteResDict(kv.Value as ResDict, TextureOffsets); + break; + default: + // for testing, boo + Output.AddPadding(GetSizeForResDict((kv.Value as ICollection).Count)); + Debug.Send("UNHANDLED RESOURCE TYPE: {0}", kv.Key); + break; + } + } + } + + // now write the actual data + using (var c = Debug.Push("Root Data")) { + foreach (var kv in File) { + switch (kv.Key) { + case "3DModels(NW4R)": + WriteModels(); + break; + case "Textures(NW4R)": + WriteTextures(); + break; + default: + Debug.Send("UNHANDLED RESOURCE TYPE: {0}", kv.Key); + break; + } + } + } + + Output.AlignTo(4); + Output.WriteBytes(StringTableData.GetBuffer()); + + // before we finish. pad the file + Output.AlignTo(0x40); + } + + #region Model Writing + private void WriteModels() { + foreach (var kv in File.GetGroup("3DModels(NW4R)")) { + using (var c = Debug.Push("Model: {0}", kv.Key)) { + Output.AlignTo(0x20); + + int startPos = Output.Position; + Model model = kv.Value; + var calcInfo = ModelCalcInfos[model]; + + // Base struct: magic 'MDL0', block size, version [currently 11], resfile offset + Output.WriteUInt32(0x4D444C30); + Output.WriteUInt32((uint)calcInfo.BlockSize); + Output.WriteUInt32(11); + + // Note: "0 - startPos" *DOESN'T* work when compiling under Mono! + // Details: https://bugzilla.novell.com/show_bug.cgi?id=675777 + // So just use "-startPos". + Output.WriteInt32(-startPos); + + // More offsets for a ton of crap + Output.WriteInt32(calcInfo.Bytecode - startPos); + Output.WriteInt32(calcInfo.Nodes - startPos); + Output.WriteInt32(calcInfo.VtxPosData - startPos); + Output.WriteInt32((calcInfo.VtxNrmData == 0) ? 0 : (calcInfo.VtxNrmData - startPos)); + Output.WriteInt32((calcInfo.VtxClrData == 0) ? 0 : (calcInfo.VtxClrData - startPos)); + Output.WriteInt32((calcInfo.VtxTexCoordData == 0) ? 0 : (calcInfo.VtxTexCoordData - startPos)); + // VtxFurVec, VtxFurPos (not handled) + Output.WriteInt32(0); + Output.WriteInt32(0); + Output.WriteInt32(calcInfo.Materials - startPos); + Output.WriteInt32(calcInfo.Shaders - startPos); + Output.WriteInt32(calcInfo.Shapes - startPos); + Output.WriteInt32(calcInfo.PairingLookupByTexture - startPos); + // Pairing lookup by palette, unhandled atm + Output.WriteInt32(0); + // Unknown extra data + Output.WriteInt32(0); + + // Name offset + Output.WriteInt32(StringPositions[kv.Key] - startPos); + + // Model Info struct: struct size, model offset, some other stuff + int infoStructPos = Output.Position; + + Output.WriteUInt32(0x40); + Output.WriteInt32(startPos - infoStructPos); + + Output.WriteUInt32((uint)model.ScaleMode); + Output.WriteUInt32((uint)model.TexMatrixMode); + Output.WriteUInt32((uint)model.VertexCount); + Output.WriteUInt32((uint)model.TriangleCount); + Output.WriteUInt32(0); + Output.WriteUInt32((uint)model.MatrixIDtoNodeID.Length); + Output.WriteByte(model.UsesNrmMtxArray ? (byte)1 : (byte)0); + Output.WriteByte(model.UsesTexMtxArray ? (byte)1 : (byte)0); + // Padding + Output.WriteInt16(0); + Output.WriteInt32(calcInfo.MatrixIDtoNodeID - infoStructPos); + Output.WriteVec3(model.Minimum); + Output.WriteVec3(model.Maximum); + + // Matrix ID to Node ID data + Output.WriteUInt32((uint)model.MatrixIDtoNodeID.Length); + + for (int i = 0; i < model.MatrixIDtoNodeID.Length; i++) { + Output.WriteInt32(model.MatrixIDtoNodeID[i]); + } + + + // ResDicts + WriteResDict(model.Bytecode, BytecodeOffsets); + WriteResDict(model.Nodes, NodeOffsets); + WriteResDict(model.VtxPosData, VtxPosOffsets); + if (model.VtxNrmData.Count > 0) + WriteResDict(model.VtxNrmData, VtxNrmOffsets); + if (model.VtxClrData.Count > 0) + WriteResDict(model.VtxClrData, VtxClrOffsets); + if (model.VtxTexCoordData.Count > 0) + WriteResDict(model.VtxTexCoordData, VtxTexCoordOffsets); + WriteResDict(model.Materials, MaterialOffsets); + WriteResDict(model.Shaders, ShaderOffsets); + WriteResDict(model.Shapes, ShapeOffsets); + WriteResDict>(model.PairingLookupByTexture, PairingOffsets); + + // TODO: Palette pairing lookups + + WritePairings(model, model.PairingLookupByTexture); + WriteBytecode(model); + WriteNodes(model); + WriteMaterials(model); + WriteShaders(model); + WriteShapes(model); + WriteVertexData(model, model.VtxPosData); + WriteVertexData(model, model.VtxNrmData); + WriteVertexData(model, model.VtxClrData); + WriteVertexData(model, model.VtxTexCoordData); + } + } + } + + private void WritePairings(Model m, ResDict> dict) { + foreach (var kv in dict) { + int startPos = Output.Position; + Output.WriteUInt32((uint)kv.Value.Count); + + foreach (var pairing in kv.Value) { + // Material offset + Output.WriteInt32(MaterialOffsets[pairing.Material] - startPos); + + // Texture info offset, points to the TextureInfo struct in the Material data + Output.WriteInt32(TextureInfoOffsets[pairing.Texture] - startPos); + } + } + } + + private void WriteBytecode(Model m) { + foreach (var kv in m.Bytecode) { + ByteCode bc = kv.Value; + + foreach (var insn in bc.Instructions) { + Output.WriteByte((byte)insn.GetOp()); + + switch (insn.GetOp()) { + case ByteCode.OpType.None: + break; + case ByteCode.OpType.Done: + break; + + case ByteCode.OpType.AssignNodeToParentMtx: + var op2 = insn as ByteCode.AssignNodeToParentMtxInstruction; + Output.WriteUInt16(op2.NodeID); + Output.WriteUInt16(op2.ParentMatrixID); + break; + + case ByteCode.OpType.BlendMatrices: + var op3 = insn as ByteCode.BlendMatricesInstruction; + Output.WriteUInt16(op3.MatrixID); + Output.WriteByte((byte)op3.BlendedMatrices.Length); + + foreach (var bm in op3.BlendedMatrices) { + Output.WriteUInt16(bm.MatrixID); + Output.WriteFloat(bm.Ratio); + } + break; + + case ByteCode.OpType.DrawShape: + var op4 = insn as ByteCode.DrawShapeInstruction; + Output.WriteUInt16(op4.MaterialID); + Output.WriteUInt16(op4.ShapeID); + Output.WriteUInt16(op4.NodeID); + Output.WriteByte(0); + break; + + case ByteCode.OpType.AssignMtxToNode: + var op5 = insn as ByteCode.AssignMtxToNodeInstruction; + Output.WriteUInt16(op5.MatrixID); + Output.WriteUInt16(op5.NodeID); + break; + } + } + } + + Output.AlignTo(4); + } + + private void WriteNodes(Model m) { + int currentIndex = 0; + + foreach (var kv in m.Nodes) { + Node node = kv.Value; + int startPos = Output.Position; + + // Size, model offset, name offset, index + // Reminds me... TODO: remove "Index" from the Node/Material/... classes + Output.WriteUInt32(0xD0); + Output.WriteInt32(ModelOffsets[m] - startPos); + Output.WriteInt32(StringPositions[kv.Key] - startPos); + Output.WriteUInt32((uint)currentIndex); + + Output.WriteUInt32(node.MatrixID); + Output.WriteUInt32(node.Flags); + Output.WriteUInt32((uint)node.BillboardMode); + Output.WriteUInt32(0); + // TODO: might be swapped with bbmode, check asm to confirm + + Output.WriteVec3(node.Scale); + Output.WriteVec3(node.Rotation); + Output.WriteVec3(node.Translation); + Output.WriteVec3(node.BoxMin); + Output.WriteVec3(node.BoxMax); + + // node offsets: parent, child, next, previous + Output.WriteInt32((node.Parent == null) ? 0 : (NodeOffsets[node.Parent] - startPos)); + Output.WriteInt32((node.FirstChild == null) ? 0 : (NodeOffsets[node.FirstChild] - startPos)); + Output.WriteInt32((node.Next == null) ? 0 : (NodeOffsets[node.Next] - startPos)); + Output.WriteInt32((node.Previous == null) ? 0 : (NodeOffsets[node.Previous] - startPos)); + + // extra data offset (currently unhandled) + Output.WriteInt32(0); + + // matrices + Output.WriteMatrix(node.NodeMatrix); + Output.WriteMatrix(node.NodeInvMatrix); + + // done + currentIndex++; + } + } + + private void WriteMaterials(Model m) { + int currentIndex = 0; + + foreach (var kv in m.Materials) { + Material mat = kv.Value; + int startPos = Output.Position; + + // size, model offset, name offset, index + // is size static, or does it change? must verify + Output.WriteUInt32(0x5E8); + Output.WriteInt32(ModelOffsets[m] - startPos); + Output.WriteInt32(StringPositions[kv.Key] - startPos); + Output.WriteUInt32((uint)currentIndex); + + Output.WriteUInt32(mat.Flags); + + // ResGenMode + Output.WriteByte(mat.TexCoordGenCount); + Output.WriteByte(mat.ChanCount); + Output.WriteByte(mat.TevStageCount); + Output.WriteByte(mat.IndStageCount); + Output.WriteUInt32(mat.CullMode); + + // ResMatMisc + Output.WriteByte(mat.ZCompLoc); + Output.WriteByte(mat.LightSetID); + Output.WriteByte(mat.FogID); + + // are these even correct? maybe not + Output.WriteBytes(mat.IndirectTexMtxCalcMethod1); + Output.WriteBytes(mat.IndirectTexMtxCalcMethod2); + + // padding -- this is weird, the previous two might be incorrect + Output.WriteByte(0xFF); + + // more stuff: shader offset, texinfo count, texinfo offset, fur offset, unk offset, DL offset + Output.WriteInt32(ShaderOffsets[mat.ShaderRef] - startPos); + Output.WriteInt32(mat.TextureInfos.Count); + if (mat.TextureInfos.Count > 0) + Output.WriteInt32(TextureInfoOffsets[mat.TextureInfos[0]] - startPos); + else + Output.WriteInt32(0); + Output.WriteInt32(0); + Output.WriteInt32(0); + Output.WriteInt32(MaterialDLOffsets[mat] - startPos); + + // ResTexObj + UInt32 textureFlag = 0; + int texObjPadding = 0x100; + + // first calculate the flag + for (int i = 0; i < 8; i++) { + if (mat.TexObj[i] != null) { + textureFlag |= (uint)(1 << i); + texObjPadding -= 0x20; + } + } + + // now write it + Output.WriteUInt32(textureFlag); + for (int i = 0; i < 8; i++) { + if (mat.TexObj[i] != null) { + Output.WriteBytes(mat.TexObj[i]); + } + } + + Output.AddPadding(texObjPadding); + + // ResTlutObj + Output.WriteUInt32(mat.TlutFlag); + Output.WriteBytes(mat.TlutObj); + Output.AddPadding(0x60 - mat.TlutObj.Length); + + // ResTexSrt + // this one is a bit of a pain + UInt32 srtFlag = 0; + int unusedSlots = 8; + + // first, calculate the flag + for (int i = 0; i < 8; i++) { + byte thisFlag = 0; + + if (mat.SRTSettings[i] != null) { + thisFlag |= 1; + + if (mat.SRTSettings[i].ScaleX == 1.0f && mat.SRTSettings[i].ScaleY == 1.0f) + thisFlag |= 2; + + if (mat.SRTSettings[i].Rotate == 0.0f) + thisFlag |= 4; + + if (mat.SRTSettings[i].TranslateX == 0.0f && mat.SRTSettings[i].TranslateY == 0.0f) + thisFlag |= 8; + + unusedSlots--; + } + + srtFlag |= ((uint)thisFlag << (i * 4)); + } + + Output.WriteUInt32(srtFlag); + Output.WriteUInt32(mat.TexMatrixType); + + // now write out TexSrt + for (int i = 0; i < 8; i++) { + if (mat.SRTSettings[i] != null) { + Output.WriteFloat(mat.SRTSettings[i].ScaleX); + Output.WriteFloat(mat.SRTSettings[i].ScaleY); + Output.WriteFloat(mat.SRTSettings[i].Rotate); + Output.WriteFloat(mat.SRTSettings[i].TranslateX); + Output.WriteFloat(mat.SRTSettings[i].TranslateY); + } + } + + // frustratingly, the padding here is NOT a section of zeroes, but instead + // a blank section (scale=1.0 rotate=0.0 translate=0.0). + for (int i = 0; i < unusedSlots; i++) { + Output.WriteFloat(1.0f); + Output.WriteFloat(1.0f); + Output.WriteFloat(0.0f); + Output.WriteFloat(0.0f); + Output.WriteFloat(0.0f); + } + + // then write out TexSet + for (int i = 0; i < 8; i++) { + if (mat.SRTSettings[i] != null) { + Output.WriteByte(mat.SRTSettings[i].CameraID); + Output.WriteByte(mat.SRTSettings[i].LightID); + Output.WriteByte(mat.SRTSettings[i].MapType); + Output.WriteByte(mat.SRTSettings[i].Flags); + Output.WriteMatrix(mat.SRTSettings[i].TexMatrix); + } + } + + // and TexSet does the same thing for padding -_- + for (int i = 0; i < unusedSlots; i++) { + Output.WriteByte(0xFF); + Output.WriteByte(0xFF); + Output.WriteByte(0x00); + Output.WriteByte(0x01); + Output.WriteFloat(1.0f); + Output.WriteFloat(0.0f); + Output.WriteFloat(0.0f); + Output.WriteFloat(0.0f); + Output.WriteFloat(0.0f); + Output.WriteFloat(1.0f); + Output.WriteFloat(0.0f); + Output.WriteFloat(0.0f); + Output.WriteFloat(0.0f); + Output.WriteFloat(0.0f); + Output.WriteFloat(1.0f); + Output.WriteFloat(0.0f); + } + + // ResMatChan + for (int i = 0; i < 2; i++) { + Output.WriteUInt32(mat.ChanCtrls[i].Flags); + Output.WriteColor(mat.ChanCtrls[i].MatColor); + Output.WriteColor(mat.ChanCtrls[i].AmbColor); + Output.WriteUInt32(mat.ChanCtrls[i].FlagC); + Output.WriteUInt32(mat.ChanCtrls[i].FlagA); + } + + // Texture Infos + foreach (var texInfo in mat.TextureInfos) { + int texInfoPos = Output.Position; + + Output.WriteInt32(texInfo.TextureName == null ? 0 : (StringPositions[texInfo.TextureName] - texInfoPos)); + Output.WriteInt32(texInfo.PaletteName == null ? 0 : (StringPositions[texInfo.PaletteName] - texInfoPos)); + + // placeholder pointers + Output.WriteUInt32(0); + Output.WriteUInt32(0); + + Output.WriteUInt32(texInfo.TexMapID); + Output.WriteUInt32(texInfo.TlutID); + Output.WriteUInt32((uint)texInfo.WrapS); + Output.WriteUInt32((uint)texInfo.WrapT); + Output.WriteUInt32(texInfo.MinFilt); + Output.WriteUInt32(texInfo.MagFilt); + Output.WriteFloat(texInfo.LODBias); + Output.WriteUInt32(texInfo.MaxAniso); + Output.WriteBool(texInfo.BiasClamp); + Output.WriteBool(texInfo.DoEdgeLOD); + Output.AddPadding(2); + } + + // Display Lists + Output.AlignTo(0x20); + + Output.WriteBytes(mat.PixDL); + Output.WriteBytes(mat.TevColorDL); + Output.WriteBytes(mat.IndMtxAndScaleDL); + Output.WriteBytes(mat.TexCoordGenDL); + + // I can't believe this is done + currentIndex++; + } + } + + private void WriteShaders(Model m) { + int currentIndex = 0; + + foreach (var shader in ModelCalcInfos[m].UniqueShaders) { + int startPos = Output.Position; + + // Size, model offset, index + Output.WriteUInt32((uint)(0x20 + shader.DisplayList.Length)); + Output.WriteInt32(ModelOffsets[m] - startPos); + // What do I write here...? + Output.WriteUInt32((uint)currentIndex); + + Output.WriteByte(shader.TevStageCount); + Output.AddPadding(3); + + Output.WriteUInt32(shader.Unk1); + Output.WriteUInt32(shader.Unk2); + Output.AddPadding(8); + + Output.WriteBytes(shader.DisplayList); + + // done + currentIndex++; + } + } + + private void WriteShapes(Model m) { + int currentIndex = 0; + + foreach (var kv in m.Shapes) { + Shape shape = kv.Value; + + int startPos = Output.Position; + + // Size, model offset, matrix ID, unknown data + Output.WriteUInt32((uint)ShapeSizes[shape]); + Output.WriteInt32(ModelOffsets[m] - startPos); + Output.WriteInt32(shape.MatrixID); + + Output.WriteBytes(shape.Unk); + + // Display Lists + int dlBase1 = Output.Position; + Output.WriteUInt32(shape.DLBufferSize1); + Output.WriteUInt32((uint)shape.DisplayList1.Length); + Output.WriteInt32(ShapeDL1Offsets[shape] - dlBase1); + + int dlBase2 = Output.Position; + Output.WriteUInt32(shape.DLBufferSize2); + Output.WriteUInt32((uint)shape.DisplayList2.Length); + Output.WriteInt32(ShapeDL2Offsets[shape] - dlBase2); + + // Indexes and flags and other stuff + Output.WriteUInt32(shape.DataFlags); + Output.WriteUInt32(shape.Flags); + + Output.WriteInt32(StringPositions[kv.Key] - startPos); + Output.WriteUInt32((uint)currentIndex); + + Output.WriteUInt32(shape.VertexCount); + Output.WriteUInt32(shape.PolygonCount); + + Output.WriteInt16((short)m.VtxPosData.GetIndexForValue(shape.PosData)); + + if (shape.NrmData != null) + Output.WriteInt16((short)m.VtxNrmData.GetIndexForValue(shape.NrmData)); + else + Output.WriteInt16(-1); + + for (int i = 0; i < 2; i++) { + if (shape.ClrData[i] != null) + Output.WriteInt16((short)m.VtxClrData.GetIndexForValue(shape.ClrData[i])); + else + Output.WriteInt16(-1); + } + + for (int i = 0; i < 8; i++) { + if (shape.TexCoordData[i] != null) + Output.WriteInt16((short)m.VtxTexCoordData.GetIndexForValue(shape.TexCoordData[i])); + else + Output.WriteInt16(-1); + } + + // Indexes for VtxFurVec and VtxFurPos, not used here + Output.WriteInt16(-1); + Output.WriteInt16(-1); + + // Extra data offset (fixed) + Output.WriteInt32(0x68); + + // Extra data + Output.WriteInt32(shape.ExtraData.Length); + for (int i = 0; i < shape.ExtraData.Length; i++) + Output.WriteUInt16(shape.ExtraData[i]); + + // Display lists + // Padding is added to fill up the buffer (Display List data size can be smaller than the buffer size) + Output.AlignTo(0x20); + Output.WriteBytes(shape.DisplayList1); + Output.AddPadding((int)(shape.DLBufferSize1 - shape.DisplayList1.Length)); + Output.AlignTo(0x20); + Output.WriteBytes(shape.DisplayList2); + Output.AddPadding((int)(shape.DLBufferSize2 - shape.DisplayList2.Length)); + Output.AlignTo(0x20); + + // done! + currentIndex++; + } + } + + private void WriteVertexData(Model m, ResDict dict) where T : VertexDataBase { + int currentIndex = 0; + + foreach (var kv in dict) { + int startPos = Output.Position; + + int structSize = 0x20; + if (kv.Value is VertexPosData || kv.Value is VertexTexCoordData) { + // Min/max fields, plus alignment + structSize += 0x20; + } + + // Size, model offset, data offset, name offset, index + // For some reason, the raw data length added to the size is aligned... + Output.WriteUInt32((uint)(((structSize + kv.Value.RawData.Length) + 0x1F) & ~0x1F)); + Output.WriteInt32(ModelOffsets[m] - startPos); + Output.WriteInt32(structSize); + Output.WriteInt32(StringPositions[kv.Key] - startPos); + Output.WriteUInt32((uint)currentIndex); + + // Parameters + Output.WriteUInt32((uint)kv.Value.ComponentCount); + Output.WriteUInt32((uint)kv.Value.ComponentType); + + if (kv.Value is VertexClrData) { + Output.WriteByte(kv.Value.EntrySize); + Output.AddPadding(1); + } else { + Output.WriteByte(kv.Value.Fraction); + Output.WriteByte(kv.Value.EntrySize); + } + + Output.WriteUInt16(kv.Value.EntryCount); + + // Type-specific stuff + if (kv.Value is VertexPosData) { + var posData = kv.Value as VertexPosData; + Output.WriteVec3(posData.Minimum); + Output.WriteVec3(posData.Maximum); + + } else if (kv.Value is VertexTexCoordData) { + var tcData = kv.Value as VertexTexCoordData; + Output.WriteVec2(tcData.Minimum); + Output.WriteVec2(tcData.Maximum); + } + + Output.AlignTo(0x20); + Output.WriteBytes(kv.Value.RawData); + Output.AlignTo(0x20); + + // done! + currentIndex++; + } + } + #endregion + + #region Texture Writing + private void WriteTextures() { + foreach (var kv in File.GetGroup("Textures(NW4R)")) { + using (var c = Debug.Push("Texture: {0}", kv.Key)) { + Output.AlignTo(0x20); + + int startPos = Output.Position; + Texture tex = kv.Value; + + // Base struct: magic 'TEX0', block size, version [currently 11], resfile offset + Output.WriteUInt32(0x54455830); + Output.WriteUInt32((uint)(0x40 + tex.GetDataSize())); + Output.WriteUInt32(3); + Output.WriteInt32(-startPos); + + // Data offset, name offset + Output.WriteInt32(0x40); + Output.WriteInt32(StringPositions[kv.Key] - startPos); + + // Flags -- Stores nothing interesting, just an "is CI" flag (0x1) + // We don't handle that atm, so ignore it + Output.WriteUInt32(0); + + Output.WriteInt16((short)tex.Images[0].Width); + Output.WriteInt16((short)tex.Images[0].Height); + Output.WriteUInt32((uint)tex.Format); + + Output.WriteInt32(tex.Images.Length); + Output.WriteFloat(tex.MinLOD); + Output.WriteFloat(tex.MaxLOD); + + Output.AlignTo(0x20); + for (int i = 0; i < tex.Images.Length; i++) + Output.WriteBytes(tex.ExportData(i)); + } + } + } + #endregion + + #region ResDicts + private struct RawDictEntry { + public ushort Ref; + public ushort Unk; + public ushort ILeft; + public ushort IRight; + } + + private unsafe void WriteResDict(ResDict dict, Dictionary positions) { + int dictPos = Output.Position; + + // First, I've got to build an in-memory representation of the raw ResDict. + // Next, I've got to write it out. This'll be fun. + // This is partly based off the BrawlLib code. + + RawDictEntry[] rd = new RawDictEntry[dict.Count + 1]; + byte[][] encodedNames = new byte[dict.Count + 1][]; + + // Before the actual calculation, build that list + + rd[0].Ref = 0xFFFF; + rd[0].Unk = 0; + rd[0].ILeft = 0; + rd[0].IRight = 0; + + encodedNames[0] = new byte[] { }; + + for (int i = 1; i <= dict.Count; i++) { + rd[i].Ref = 0; + rd[i].Unk = 0; + rd[i].ILeft = 0; + rd[i].IRight = 0; + + // I wanted to store the encoded name as a member of RawDictEntry, but C# doesn't let me + // get a pointer to the struct if I do that -- see Compiler Error CS0208 on MSDN. + string theName = dict.GetKeyForIndex(i - 1); + encodedNames[i] = System.Text.Encoding.GetEncoding("Shift_JIS").GetBytes(theName); + } + + + // Now calculate indexes, etc + + for (ushort i = 1; i <= dict.Count; i++) { + // Using unsafe pointers for convenience + fixed (RawDictEntry* entry = &rd[i]) { + ushort prev = 0; + ushort current = rd[prev].ILeft; + + bool isRight = false; + + int strLen = encodedNames[i].Length; + byte[] pChar = encodedNames[i]; + byte[] sChar; + + int eIndex = strLen - 1; + int eBits = CompareBits(pChar[eIndex], 0); + int val; + + entry->Ref = (ushort)((eIndex << 3) | eBits); + entry->ILeft = (ushort)i; + entry->IRight = (ushort)i; + + while ((entry->Ref <= rd[current].Ref) && (rd[prev].Ref > rd[current].Ref)) { + if (entry->Ref == rd[current].Ref) { + sChar = encodedNames[current]; + + for (eIndex = strLen; (--eIndex > 0) && (pChar[eIndex] == sChar[eIndex]);); + + eBits = CompareBits(pChar[eIndex], sChar[eIndex]); + + entry->Ref = (ushort)((eIndex << 3) | eBits); + + if (((sChar[eIndex] >> eBits) & 1) != 0) { + entry->ILeft = (ushort)i; + entry->IRight = current; + } else { + entry->ILeft = current; + entry->IRight = (ushort)i; + } + } + + isRight = ((val = rd[current].Ref >> 3) < strLen) && (((pChar[val] >> (rd[current].Ref & 7)) & 1) != 0); + + prev = current; + current = isRight ? rd[current].IRight : rd[current].ILeft; + } + + sChar = encodedNames[current]; + val = sChar == null ? 0 : sChar.Length; + + if ((val == strLen) && (((sChar[eIndex] >> eBits) & 1) != 0)) + entry->IRight = current; + else + entry->ILeft = current; + + if (isRight) + rd[prev].IRight = i; + else + rd[prev].ILeft = i; + } + } + + + // Now write it + Output.WriteUInt32((uint)(8 + (rd.Length * 0x10))); + Output.WriteUInt32((uint)(rd.Length - 1)); + + for (int entryID = 0; entryID < rd.Length; entryID++) { + Output.WriteUInt16(rd[entryID].Ref); + Output.WriteUInt16(rd[entryID].Unk); + Output.WriteUInt16(rd[entryID].ILeft); + Output.WriteUInt16(rd[entryID].IRight); + + if (entryID == 0) { + Output.WriteInt32(0); + Output.WriteInt32(0); + } else { + // Name offset + Output.WriteInt32(StringPositions[dict.GetKeyForIndex(entryID - 1)] - dictPos); + + // Data offset + Output.WriteInt32(positions[dict[entryID - 1]] - dictPos); + } + } + } + + private static int CompareBits(byte b1, byte b2) { + int b = 0x80; + for (int i = 7; i != 0; i--) { + if ((b1 & b) != (b2 & b)) + return i; + b >>= 1; + } + return 0; + } + #endregion + + #endregion + + #region String Table Handling // Contains the strings while they're being pulled from the ResFile data private List StringTable; // Once every string used in the .brres is found, CalculateStringTable() is called, which // writes the table to an OutputStream and saves every offset - private Dictionary StringTableOffsets; + private Dictionary StringPositions; private OutputStream StringTableData; @@ -461,14 +1396,18 @@ namespace NW4RTools { } private void CalculateStringTable() { - StringTable.Sort(); + // Hmm... this doesn't work for whatever reason? Might be a Mono thing. + //StringTable.Sort(StringComparer.Create(System.Globalization.CultureInfo.InvariantCulture, false)); + StringTable.Sort(StringComparer.Ordinal); - StringTableOffsets = new Dictionary(); + StringPositions = new Dictionary(); StringTableData = new OutputStream(ByteEndian.BigEndian); + // Note: once all calculation is done, CurrentPos stores the address of the string table + foreach (var s in StringTable) { // Add 4 because the Names are referenced by the string data, and not by the ResName struct itself - StringTableOffsets[s] = StringTableData.Position + 4; + StringPositions[s] = CurrentPos + StringTableData.Position + 4; StringTableData.WriteName(s); } } -- cgit v1.2.3