diff options
23 files changed, 1302 insertions, 79 deletions
diff --git a/NW4RTools.userprefs b/NW4RTools.userprefs index 767aacd..a5003ad 100644 --- a/NW4RTools.userprefs +++ b/NW4RTools.userprefs @@ -2,10 +2,18 @@ <MonoDevelop.Ide.Workspace ActiveConfiguration="Debug" /> <MonoDevelop.Ide.Workbench ActiveDocument="NW4RTools/BrresWriter.cs"> <Files> - <File FileName="NW4RTools/BrresReader.cs" Line="844" Column="39" /> - <File FileName="NW4RTools/BrresWriter.cs" Line="106" Column="56" /> - <File FileName="TestApp/Main.cs" Line="43" Column="44" /> - <File FileName="NW4RTools/InputStream.cs" Line="1" Column="1" /> + <File FileName="NW4RTools/BrresReader.cs" Line="724" Column="48" /> + <File FileName="NW4RTools/BrresWriter.cs" Line="346" Column="30" /> + <File FileName="NW4RTools/Texture.cs" Line="291" Column="1" /> + <File FileName="TestApp/Main.cs" Line="27" Column="31" /> + <File FileName="NW4RTools/InputStream.cs" Line="136" Column="1" /> + <File FileName="NW4RTools/Models/Shape.cs" Line="8" Column="46" /> + <File FileName="NW4RTools/Models/OpenGL/GLTexture.cs" Line="37" Column="18" /> + <File FileName="NW4RTools/OutputStream.cs" Line="105" Column="36" /> + <File FileName="NW4RTools/Models/Model.cs" Line="33" Column="44" /> + <File FileName="NW4RTools/Models/Material.cs" Line="39" Column="3" /> + <File FileName="NW4RTools/Models/TextureInfo.cs" Line="1" Column="1" /> + <File FileName="TestApp/AssemblyInfo.cs" Line="1" Column="1" /> </Files> </MonoDevelop.Ide.Workbench> <MonoDevelop.Ide.DebuggingService.Breakpoints> diff --git a/NW4RTools/BrresReader.cs b/NW4RTools/BrresReader.cs index db5d7c2..e67581b 100644 --- a/NW4RTools/BrresReader.cs +++ b/NW4RTools/BrresReader.cs @@ -37,7 +37,7 @@ namespace NW4RTools { UInt16 headerSize = ins.ReadUInt16(); UInt16 blockCount = ins.ReadUInt16(); - File.version = version; + File.Version = version; // Read first block header byte[] blkMagic = ins.ReadBytes(4); @@ -67,8 +67,14 @@ namespace NW4RTools { case "Textures(NW4R)": File.Add(name, ReadAndConvertDict<Texture>(ins, ConvertTextureResource)); break; + case "AnmChr(NW4R)": + File.Add(name, ReadAndConvertDict<Models.Animation.CharacterAnim>(ins, ConvertAnmChrResource)); + break; + case "AnmClr(NW4R)": + File.Add(name, ReadAndConvertDict<Models.Animation.ColorAnim>(ins, ConvertAnmClrResource)); + break; case "AnmTexSrt(NW4R)": - File.Add(name, ReadAndConvertDict<Models.Animation.TextureSRT>(ins, ConvertAnmTexSRTResource)); + File.Add(name, ReadAndConvertDict<Models.Animation.TextureSRTAnim>(ins, ConvertAnmTexSRTResource)); break; default: Debug.Send("Not implemented"); @@ -125,16 +131,48 @@ namespace NW4RTools { - private Models.Animation.TextureSRT ConvertAnmTexSRTResource(string name, InputStream ins) { + private Models.Animation.CharacterAnim ConvertAnmChrResource(string name, InputStream ins) { using (var c = Debug.Push(name)) { - var anim = new Models.Animation.TextureSRT(); + var anim = new Models.Animation.CharacterAnim(); int startPos = ins.Position; - OffsetMap.Add(startPos, "Texture SRT Animation: " + name); + 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; } } @@ -170,6 +208,10 @@ namespace NW4RTools { 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; @@ -180,10 +222,10 @@ namespace NW4RTools { Int32 mdlOffset = ins.ReadInt32(); mdl.ScaleMode = (Model.ScaleModeType)ins.ReadUInt32(); mdl.TexMatrixMode = (Model.TexMatrixModeType)ins.ReadUInt32(); - UInt32 vtxCount = ins.ReadUInt32(); - UInt32 triCount = ins.ReadUInt32(); + mdl.VertexCount = ins.ReadUInt32(); + mdl.TriangleCount = ins.ReadUInt32(); UInt32 unk = ins.ReadUInt32(); - UInt32 nodeCount = ins.ReadUInt32(); + UInt32 matrixCount = ins.ReadUInt32(); mdl.UsesNrmMtxArray = (bool)(ins.ReadByte() != 0); mdl.UsesTexMtxArray = (bool)(ins.ReadByte() != 0); ins.Skip(2); @@ -216,8 +258,6 @@ namespace NW4RTools { FinishNodeLoading(); // Load vertex data - // COMMENTED OUT TO MAKE DEUBG OUTPUT CLEARER FOR NOW - VtxPosIndexLookup = new Dictionary<int, VertexPosData>(); VtxNrmIndexLookup = new Dictionary<int, VertexNrmData>(); VtxClrIndexLookup = new Dictionary<int, VertexClrData>(); @@ -259,12 +299,12 @@ namespace NW4RTools { } } - //*/ - // 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<int, Shader>(); MaterialOffsetRefs = new Dictionary<int, Material>(); + TextureInfoRefs = new Dictionary<int, TextureInfo>(); using (var c2 = Debug.Push("Shaders")) mdl.Shaders = ReadAndConvertDict<Shader>(ins.At(startPos + shaderOffset), ConvertModelShader); @@ -403,6 +443,10 @@ namespace NW4RTools { 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(); @@ -471,15 +515,10 @@ namespace NW4RTools { n.EntryCount = ins.ReadUInt16(); - int structEndPos = ins.Position; - - ins.Seek(startPos + dataOffset); - n.RawData = ins.ReadBytes(n.EntrySize * n.EntryCount); + n.RawData = ins.At(startPos + dataOffset).ReadBytes(n.EntrySize * n.EntryCount); n.Parse(); OffsetMap.Add(startPos + dataOffset, n.GetType().ToString() + " Data"); - - ins.Seek(structEndPos); } private Dictionary<int, Models.VertexPosData> VtxPosIndexLookup; @@ -544,6 +583,7 @@ namespace NW4RTools { private Dictionary<int, Models.Material> MaterialOffsetRefs; + private Dictionary<int, Models.TextureInfo> TextureInfoRefs; private Models.Material ConvertModelMaterial(string name, InputStream ins) { Debug.Send("Reading {0}...", name); @@ -611,10 +651,10 @@ namespace NW4RTools { int ResTlutObjPos = ins.Position; //OffsetMap.Add(ins.Position, "Material ResTlutObj: " + name); - UInt32 tlutFlag = ins.ReadUInt32(); - if ((tlutFlag & 0xFC) == 0) { + m.TlutFlag = ins.ReadUInt32(); + if ((m.TlutFlag & ~3) == 0) { m.TlutObj = ins.ReadBytes(0x20); - } else if ((tlutFlag & 0xE0) == 0) { + } else if ((m.TlutFlag & ~0x1F) == 0) { m.TlutObj = ins.ReadBytes(0x40); } else { m.TlutObj = ins.ReadBytes(0x60); @@ -678,7 +718,11 @@ namespace NW4RTools { for (int i = 0; i < texInfoCount; i++) { //Debug.Send("Reading {0} texture info at {1:X}", i, ins.Position); - m.TextureInfos.Add(ReadTextureInfo(ins)); + int texInfoPos = ins.Position; + var texInfo = ReadTextureInfo(ins); + + m.TextureInfos.Add(texInfo); + TextureInfoRefs[texInfoPos] = texInfo; } // Display Lists @@ -812,6 +856,16 @@ namespace NW4RTools { // 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; } @@ -827,7 +881,7 @@ namespace NW4RTools { for (int i = 0; i < count; i++) { var p = new TexMatPairing(); p.Material = MaterialOffsetRefs[startPos + ins.ReadInt32()]; - p.Texture = ReadTextureInfo(ins.At(startPos + ins.ReadInt32())); + p.Texture = TextureInfoRefs[startPos + ins.ReadInt32()]; list.Add(p); } @@ -840,11 +894,15 @@ namespace NW4RTools { 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); 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<VertexClrData, int> VtxClrOffsets; private Dictionary<VertexTexCoordData, int> VtxTexCoordOffsets; private Dictionary<Material, int> MaterialOffsets; + private Dictionary<Material, int> MaterialDLOffsets; private Dictionary<Shader, int> ShaderOffsets; private Dictionary<Shape, int> ShapeOffsets; + private Dictionary<Shape, int> ShapeDL1Offsets; + private Dictionary<Shape, int> ShapeDL2Offsets; + private Dictionary<Shape, int> ShapeSizes; private Dictionary<List<TexMatPairing>, int> PairingOffsets; + private Dictionary<TextureInfo, int> 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<Shader> UniqueShaders; + } + private Dictionary<Model, ModelCalcInfo> ModelCalcInfos; private Dictionary<Texture, int> TextureOffsets; private Dictionary<Texture, int> TextureDataOffsets; - #endregion + private Dictionary<object, int> 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<object, int>(); // 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<Model, int>(); + ModelCalcInfos = new Dictionary<Model, ModelCalcInfo>(); + BytecodeOffsets = new Dictionary<ByteCode, int>(); NodeOffsets = new Dictionary<Node, int>(); VtxPosOffsets = new Dictionary<VertexPosData, int>(); @@ -113,59 +149,82 @@ namespace NW4RTools { VtxClrOffsets = new Dictionary<VertexClrData, int>(); VtxTexCoordOffsets = new Dictionary<VertexTexCoordData, int>(); MaterialOffsets = new Dictionary<Material, int>(); + MaterialDLOffsets = new Dictionary<Material, int>(); ShaderOffsets = new Dictionary<Shader, int>(); ShapeOffsets = new Dictionary<Shape, int>(); + ShapeDL1Offsets = new Dictionary<Shape, int>(); + ShapeDL2Offsets = new Dictionary<Shape, int>(); + ShapeSizes = new Dictionary<Shape, int>(); PairingOffsets = new Dictionary<List<TexMatPairing>, int>(); + TextureInfoOffsets = new Dictionary<TextureInfo, int>(); - - var modelDict = File.GetGroup<Model>("3DModels(NW4R)"); - - foreach (var kv in modelDict) { + foreach (var kv in File.GetGroup<Model>("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<Shader>(); 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<object>(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<Model>(kv.Value as ResDict<Model>, ModelOffsets); + break; + case "Textures(NW4R)": + WriteResDict<Texture>(kv.Value as ResDict<Texture>, 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<Model>("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<ByteCode>(model.Bytecode, BytecodeOffsets); + WriteResDict<Node>(model.Nodes, NodeOffsets); + WriteResDict<VertexPosData>(model.VtxPosData, VtxPosOffsets); + if (model.VtxNrmData.Count > 0) + WriteResDict<VertexNrmData>(model.VtxNrmData, VtxNrmOffsets); + if (model.VtxClrData.Count > 0) + WriteResDict<VertexClrData>(model.VtxClrData, VtxClrOffsets); + if (model.VtxTexCoordData.Count > 0) + WriteResDict<VertexTexCoordData>(model.VtxTexCoordData, VtxTexCoordOffsets); + WriteResDict<Material>(model.Materials, MaterialOffsets); + WriteResDict<Shader>(model.Shaders, ShaderOffsets); + WriteResDict<Shape>(model.Shapes, ShapeOffsets); + WriteResDict<List<TexMatPairing>>(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<List<TexMatPairing>> 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<T>(Model m, ResDict<T> 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<Texture>("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<TValue>(ResDict<TValue> dict, Dictionary<TValue, int> 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<string> 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<string, int> StringTableOffsets; + private Dictionary<string, int> 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<string, int>(); + StringPositions = new Dictionary<string, int>(); 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); } } diff --git a/NW4RTools/Models/Animation/CharacterAnim.cs b/NW4RTools/Models/Animation/CharacterAnim.cs new file mode 100644 index 0000000..951969e --- /dev/null +++ b/NW4RTools/Models/Animation/CharacterAnim.cs @@ -0,0 +1,8 @@ +using System; +namespace NW4RTools.Models.Animation { + public class CharacterAnim { + public CharacterAnim() { + } + } +} + diff --git a/NW4RTools/Models/Animation/ColorAnim.cs b/NW4RTools/Models/Animation/ColorAnim.cs new file mode 100644 index 0000000..e1e14bc --- /dev/null +++ b/NW4RTools/Models/Animation/ColorAnim.cs @@ -0,0 +1,8 @@ +using System; +namespace NW4RTools.Models.Animation { + public class ColorAnim { + public ColorAnim() { + } + } +} + diff --git a/NW4RTools/Models/Animation/TextureSRTAnim.cs b/NW4RTools/Models/Animation/TextureSRTAnim.cs new file mode 100644 index 0000000..edc3593 --- /dev/null +++ b/NW4RTools/Models/Animation/TextureSRTAnim.cs @@ -0,0 +1,8 @@ +using System; +namespace NW4RTools.Models.Animation { + public class TextureSRTAnim { + public TextureSRTAnim() { + } + } +} + diff --git a/NW4RTools/Models/Material.cs b/NW4RTools/Models/Material.cs index 200c6e7..9e90348 100644 --- a/NW4RTools/Models/Material.cs +++ b/NW4RTools/Models/Material.cs @@ -36,6 +36,8 @@ namespace NW4RTools.Models { // ResTexObj and ResTlutObj public byte[][] TexObj; + + public UInt32 TlutFlag; public byte[] TlutObj; // ResTexSrt diff --git a/NW4RTools/Models/Model.cs b/NW4RTools/Models/Model.cs index c0297db..71edaf3 100644 --- a/NW4RTools/Models/Model.cs +++ b/NW4RTools/Models/Model.cs @@ -30,6 +30,8 @@ namespace NW4RTools.Models { public ScaleModeType ScaleMode; public TexMatrixModeType TexMatrixMode; + public UInt32 VertexCount, TriangleCount; + public bool UsesNrmMtxArray, UsesTexMtxArray; public Int32[] MatrixIDtoNodeID; diff --git a/NW4RTools/Models/Shader.cs b/NW4RTools/Models/Shader.cs index 767470e..6510110 100644 --- a/NW4RTools/Models/Shader.cs +++ b/NW4RTools/Models/Shader.cs @@ -10,6 +10,25 @@ namespace NW4RTools.Models { public Shader() { } + + + public bool DataMatches(Shader another) { + if (another.TevStageCount != TevStageCount) + return false; + + if (another.Unk1 != Unk1 || another.Unk2 != Unk2) + return false; + + if (another.DisplayList.Length != DisplayList.Length) + return false; + + for (int i = 0; i < DisplayList.Length; i++) { + if (another.DisplayList[i] != DisplayList[i]) + return false; + } + + return true; + } } } diff --git a/NW4RTools/Models/Shape.cs b/NW4RTools/Models/Shape.cs index 6bc7e44..87bf370 100644 --- a/NW4RTools/Models/Shape.cs +++ b/NW4RTools/Models/Shape.cs @@ -21,7 +21,7 @@ namespace NW4RTools.Models { public Int16 FurVecDataIndex; public Int16 FurPosDataIndex; - // todo: more + public UInt16[] ExtraData; public Shape() { } diff --git a/NW4RTools/NW4RTools.csproj b/NW4RTools/NW4RTools.csproj index 493cc46..68652ce 100644 --- a/NW4RTools/NW4RTools.csproj +++ b/NW4RTools/NW4RTools.csproj @@ -72,7 +72,9 @@ <Compile Include="Models\OpenGL\GLDisplayList.cs" /> <Compile Include="BrresWriter.cs" /> <Compile Include="OutputStream.cs" /> - <Compile Include="Models\Animation\TextureSRT.cs" /> + <Compile Include="Models\Animation\ColorAnim.cs" /> + <Compile Include="Models\Animation\CharacterAnim.cs" /> + <Compile Include="Models\Animation\TextureSRTAnim.cs" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <ItemGroup> diff --git a/NW4RTools/NW4RTools.pidb b/NW4RTools/NW4RTools.pidb Binary files differindex 7330f88..39a94e4 100644 --- a/NW4RTools/NW4RTools.pidb +++ b/NW4RTools/NW4RTools.pidb diff --git a/NW4RTools/OutputStream.cs b/NW4RTools/OutputStream.cs index 3f250a2..e934158 100644 --- a/NW4RTools/OutputStream.cs +++ b/NW4RTools/OutputStream.cs @@ -39,6 +39,25 @@ namespace NW4RTools { BaseStream.Position = pos; } + public void AlignTo(int num) { + if ((Position & (num - 1)) == 0) + return; + + for (int i = (num - (Position & (num - 1))); i != 0; i--) { + WriteByte(0); + } + } + + public void AddPadding(int count) { + if (count == 0) + return; + else if (count == 1) + WriteByte(0); + else + for (int i = count; i != 0; i--) + WriteByte(0); + } + public void WriteBytes(byte[] data) { BaseStream.Write(data, 0, data.Length); } @@ -82,6 +101,11 @@ namespace NW4RTools { WriteReversedBytes(BitConverter.GetBytes(val)); } + public void WriteBool(bool val) { + // TODO: ReadBool in InputStream + WriteByte(val ? (byte)1 : (byte)0); + } + public void WriteColor(Color col) { WriteByte(col.r); WriteByte(col.g); @@ -119,9 +143,11 @@ namespace NW4RTools { byte[] encoded = System.Text.Encoding.GetEncoding("Shift_JIS").GetBytes(name); WriteInt32(encoded.Length); WriteBytes(encoded); + WriteByte(0); - if ((encoded.Length & 3) != 0) { - for (int i = 4 - (encoded.Length & 3); i > 0; i--) { + // add 1 to the length to include the zero byte + if (((encoded.Length + 1) & 3) != 0) { + for (int i = 4 - ((encoded.Length + 1) & 3); i > 0; i--) { WriteByte(0); } } diff --git a/NW4RTools/ResFile.cs b/NW4RTools/ResFile.cs index 865db93..02919e5 100644 --- a/NW4RTools/ResFile.cs +++ b/NW4RTools/ResFile.cs @@ -2,7 +2,7 @@ using System; namespace NW4RTools { public class ResFile : ResDict<object> { - public UInt16 version; + public UInt16 Version; public ResDict<TValue> GetGroup<TValue>(string name) { return this[name] as ResDict<TValue>; diff --git a/NW4RTools/Texture.cs b/NW4RTools/Texture.cs index 0561435..078fa6b 100644 --- a/NW4RTools/Texture.cs +++ b/NW4RTools/Texture.cs @@ -82,8 +82,6 @@ namespace NW4RTools { width = Misc.AlignUp(width, info.TexelWidth); height = Misc.AlignUp(height, info.TexelHeight); - // SPECIAL CASE: - return width * height * info.NybblesPerPixel / 2; } @@ -100,46 +98,154 @@ namespace NW4RTools { return GetDataSize(Images[imageID].Width, Images[imageID].Height, Format); } - unsafe public void ImportData(int imageID, byte[] imgdata, int width, int height, TextureFormat format) { - var image = new Bitmap(width, height, PixelFormat.Format32bppArgb); - var bits = image.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, image.PixelFormat); - var info = TextureFormatInfo.GetFormat(format); + unsafe public byte[] ExportData(int imageID) { + var info = TextureFormatInfo.GetFormat(Format); int blkWidth = info.TexelWidth; int blkHeight = info.TexelHeight; - /*int blkNumY = 0; + int width = Images[imageID].Width; + int height = Images[imageID].Height; + var bits = Images[imageID].LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); - for (int blkY = 0; blkY < height; blkY += blkHeight) { - byte* pBlkY = (byte*)bits.Scan0 + (blkY * bits.Stride); + var data = new OutputStream(ByteEndian.BigEndian); - int blkNumX = 0; + switch (Format) { + case TextureFormat.RGB565: + case TextureFormat.RGB5A3: + case TextureFormat.I4: + case TextureFormat.I8: + case TextureFormat.IA4: + case TextureFormat.IA8: + // This won't be fun + bool alreadyHaveI4Nybble = false; + byte currentI4Byte = 0; - for (int blkX = 0; blkX < width; blkX += blkWidth) { - byte* pBlk = pBlkY + (blkX * sizeof(uint)); + for (int y = 0; y < height; y += blkHeight) { + int yMax = y + blkHeight; + if (yMax > height) + yMax = height; + + for (int x = 0; x < width; x += blkWidth) { + int xMax = x + blkWidth; + if (xMax > width) + xMax = width; + + for (int y1 = y; y1 < yMax; y1++) { + byte* pRow = (byte*)bits.Scan0 + (y1 * bits.Stride); + + for (int x1 = x; x1 < xMax; x1++) { + uint* pPixel = (uint*)pRow + x1; + + byte a = (byte)((*pPixel & 0xFF000000) >> 24); + byte r = (byte)((*pPixel & 0x00FF0000) >> 16); + byte g = (byte)((*pPixel & 0x0000FF00) >> 8); + byte b = (byte)(*pPixel & 0x000000FF); + + if (Format == TextureFormat.RGB565) { + ushort _r = (ushort)(r >> 3); + ushort _g = (ushort)(g >> 2); + ushort _b = (ushort)(b >> 3); + data.WriteUInt16((ushort)((_r << 11) | (_g << 5) | _b)); + + } else if (Format == TextureFormat.RGB5A3) { + if (a == 255) { + ushort _r = (ushort)(r >> 3); + ushort _g = (ushort)(g >> 3); + ushort _b = (ushort)(b >> 3); + data.WriteUInt16((ushort)(0x8000 | (_r << 10) | (_g << 5) | _b)); + } else { + ushort _a = (ushort)(a >> 5); + ushort _r = (ushort)(r >> 4); + ushort _g = (ushort)(g >> 4); + ushort _b = (ushort)(b >> 4); + data.WriteUInt16((ushort)((_a << 12) | (_r << 8) | (_g << 4) | _b)); + } - for (int y = 0; y < blkHeight; y++) { - if ((blkY + y) >= height) - break; + } else if (Format == TextureFormat.I4) { + byte _i = (byte)((r & g & b) >> 4); - byte* pRow = pBlk + (y * bits.Stride); + if (!alreadyHaveI4Nybble) { + currentI4Byte = (byte)(_i << 4); + } else { + data.WriteByte((byte)(currentI4Byte | _i)); + currentI4Byte = 0; + } - for (int x = 0; x < blkWidth; x++) { - if ((blkX + x) >= width) - break; + alreadyHaveI4Nybble = !alreadyHaveI4Nybble; + + } else if (Format == TextureFormat.I8) { + byte _i = (byte)(r & g & b); + data.WriteByte(_i); - uint* pPixel = (uint*)pRow + x; + } else if (Format == TextureFormat.IA4) { + byte _i = (byte)((r & g & b) >> 4); + byte _a = (byte)(a >> 4); + data.WriteByte((byte)((_i << 4) | _a)); + } else if (Format == TextureFormat.IA8) { + byte _i = (byte)(r & g & b); + data.WriteByte(_i); + data.WriteByte(a); + } + } } } + } + + break; + + case TextureFormat.RGBA8: + // this one is a pain because it stores an AR chunk, then a GB chunk, then ... + for (int y = 0; y < height; y += blkHeight) { + int yMax = y + blkHeight; + if (yMax > height) + yMax = height; + + for (int x = 0; x < width; x += blkWidth) { + int xMax = x + blkWidth; + if (xMax > width) + xMax = width; + + for (int blkType = 0; blkType < 2; blkType++) { + for (int y1 = y; y1 < yMax; y1++) { + byte* pRow = (byte*)bits.Scan0 + (y1 * bits.Stride); + + for (int x1 = x; x1 < xMax; x1++) { + uint* pPixel = (uint*)pRow + x1; + + ushort piece; + if (blkType == 0) { + piece = (ushort)((*pPixel & 0xFFFF0000) >> 16); + } else { + piece = (ushort)(*pPixel & 0x0000FFFF); + } - blkNumX++; + data.WriteUInt16(piece); + } + } + } + } } - blkNumY++; - }*/ + + break; + } + + Images[imageID].UnlockBits(bits); + return data.GetBuffer(); + } + - // What I'm missing overall: IA4, IA8, RGBA8, all the palette ones + unsafe public void ImportData(int imageID, byte[] imgdata, int width, int height, TextureFormat format) { + var image = new Bitmap(width, height, PixelFormat.Format32bppArgb); + var bits = image.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, image.PixelFormat); + + var info = TextureFormatInfo.GetFormat(format); + int blkWidth = info.TexelWidth; + int blkHeight = info.TexelHeight; + + // What I'm missing overall: all the palette ones // this thing probably breaks on textures which aren't multiples of the texel width/height, too // oh well @@ -168,10 +274,10 @@ namespace NW4RTools { xMax = width; for (int y1 = y; y1 < yMax; y1++) { - byte *pRow = (byte*)bits.Scan0 + (y1 * bits.Stride); + byte* pRow = (byte*)bits.Scan0 + (y1 * bits.Stride); for (int x1 = x; x1 < xMax; x1++) { - uint *pPixel = (uint*)pRow + x1; + uint* pPixel = (uint*)pRow + x1; if (format == TextureFormat.RGB565) { ushort val = data.ReadUInt16(); @@ -230,6 +336,43 @@ namespace NW4RTools { break; + case TextureFormat.RGBA8: + // this one is a pain because it stores an AR chunk, then a GB chunk, then ... + for (int y = 0; y < height; y += blkHeight) { + int yMax = y + blkHeight; + if (yMax > height) + yMax = height; + + for (int x = 0; x < width; x += blkWidth) { + int xMax = x + blkWidth; + if (xMax > width) + xMax = width; + + for (int blkType = 0; blkType < 2; blkType++) { + for (int y1 = y; y1 < yMax; y1++) { + byte* pRow = (byte*)bits.Scan0 + (y1 * bits.Stride); + + for (int x1 = x; x1 < xMax; x1++) { + uint* pPixel = (uint*)pRow + x1; + + uint val1 = data.ReadByte(); + uint val2 = data.ReadByte(); + + if (blkType == 0) { + // Alpha, Red + *pPixel = ((*pPixel & 0x0000FFFF) | (val1 << 24) | (val2 << 16)); + } else { + // Green, Blue + *pPixel = ((*pPixel & 0xFFFF0000) | (val1 << 8) | (val2 << 0)); + } + } + } + } + } + } + + break; + case TextureFormat.CMPR: // this code is messy diff --git a/NW4RTools/bin/Debug/NW4RTools.dll b/NW4RTools/bin/Debug/NW4RTools.dll Binary files differindex 13eb22e..aa8cfdd 100755 --- a/NW4RTools/bin/Debug/NW4RTools.dll +++ b/NW4RTools/bin/Debug/NW4RTools.dll diff --git a/NW4RTools/bin/Debug/NW4RTools.dll.mdb b/NW4RTools/bin/Debug/NW4RTools.dll.mdb Binary files differindex a2acbbc..02e5217 100644 --- a/NW4RTools/bin/Debug/NW4RTools.dll.mdb +++ b/NW4RTools/bin/Debug/NW4RTools.dll.mdb diff --git a/TestApp/Main.cs b/TestApp/Main.cs index 80687d1..b9a8f13 100644 --- a/TestApp/Main.cs +++ b/TestApp/Main.cs @@ -15,7 +15,7 @@ namespace TestApp { //string mdlName = "bgB_4502"; //string mdlName = "cobKoopaCastle"; string mdlName = "waterPlate_W4boss"; - mdlName = "CS_W9"; + mdlName = "test_lift"; string whatever = (mdlName == "CS_W2" || mdlName == "CS_W3" || mdlName == "CS_W6") ? "a" : ""; diff --git a/TestApp/TestApp.pidb b/TestApp/TestApp.pidb Binary files differindex c79a804..f604be0 100644 --- a/TestApp/TestApp.pidb +++ b/TestApp/TestApp.pidb diff --git a/TestApp/bin/Debug/NW4RTools.dll b/TestApp/bin/Debug/NW4RTools.dll Binary files differindex 13eb22e..aa8cfdd 100755 --- a/TestApp/bin/Debug/NW4RTools.dll +++ b/TestApp/bin/Debug/NW4RTools.dll diff --git a/TestApp/bin/Debug/NW4RTools.dll.mdb b/TestApp/bin/Debug/NW4RTools.dll.mdb Binary files differindex a2acbbc..02e5217 100644 --- a/TestApp/bin/Debug/NW4RTools.dll.mdb +++ b/TestApp/bin/Debug/NW4RTools.dll.mdb diff --git a/TestApp/bin/Debug/TestApp.exe b/TestApp/bin/Debug/TestApp.exe Binary files differindex 6d524d8..d0238aa 100755 --- a/TestApp/bin/Debug/TestApp.exe +++ b/TestApp/bin/Debug/TestApp.exe diff --git a/TestApp/bin/Debug/TestApp.exe.mdb b/TestApp/bin/Debug/TestApp.exe.mdb Binary files differindex c8a281a..2998706 100644 --- a/TestApp/bin/Debug/TestApp.exe.mdb +++ b/TestApp/bin/Debug/TestApp.exe.mdb |