summaryrefslogtreecommitdiff
path: root/NW4RTools/BrresWriter.cs
diff options
context:
space:
mode:
authorTreeki <treeki@gmail.com>2011-03-03 04:53:56 +0100
committerTreeki <treeki@gmail.com>2011-03-03 04:53:56 +0100
commitab340fe4dc87086336643b6b616df6efdfac796b (patch)
treeaa01265000e80c8348631f3ae67cc6ded16d90bb /NW4RTools/BrresWriter.cs
parent6b14bc71cb699b72ca6cf164b7b800add414dec6 (diff)
downloadnw4rtools-ab340fe4dc87086336643b6b616df6efdfac796b.tar.gz
nw4rtools-ab340fe4dc87086336643b6b616df6efdfac796b.zip
working (but not 100% tested) model and texture writing. a few other fixes, etc
Diffstat (limited to 'NW4RTools/BrresWriter.cs')
-rw-r--r--NW4RTools/BrresWriter.cs979
1 files changed, 959 insertions, 20 deletions
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);
}
}