summaryrefslogtreecommitdiff
path: root/NW4RTools
diff options
context:
space:
mode:
Diffstat (limited to 'NW4RTools')
-rw-r--r--NW4RTools/BrresReader.cs104
-rw-r--r--NW4RTools/BrresWriter.cs979
-rw-r--r--NW4RTools/Models/Animation/CharacterAnim.cs8
-rw-r--r--NW4RTools/Models/Animation/ColorAnim.cs8
-rw-r--r--NW4RTools/Models/Animation/TextureSRTAnim.cs8
-rw-r--r--NW4RTools/Models/Material.cs2
-rw-r--r--NW4RTools/Models/Model.cs2
-rw-r--r--NW4RTools/Models/Shader.cs19
-rw-r--r--NW4RTools/Models/Shape.cs2
-rw-r--r--NW4RTools/NW4RTools.csproj4
-rw-r--r--NW4RTools/NW4RTools.pidbbin546082 -> 554210 bytes
-rw-r--r--NW4RTools/OutputStream.cs30
-rw-r--r--NW4RTools/ResFile.cs2
-rw-r--r--NW4RTools/Texture.cs195
-rwxr-xr-xNW4RTools/bin/Debug/NW4RTools.dllbin189952 -> 206336 bytes
-rw-r--r--NW4RTools/bin/Debug/NW4RTools.dll.mdbbin97520 -> 102677 bytes
16 files changed, 1289 insertions, 74 deletions
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
index 7330f88..39a94e4 100644
--- a/NW4RTools/NW4RTools.pidb
+++ b/NW4RTools/NW4RTools.pidb
Binary files differ
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
index 13eb22e..aa8cfdd 100755
--- a/NW4RTools/bin/Debug/NW4RTools.dll
+++ b/NW4RTools/bin/Debug/NW4RTools.dll
Binary files differ
diff --git a/NW4RTools/bin/Debug/NW4RTools.dll.mdb b/NW4RTools/bin/Debug/NW4RTools.dll.mdb
index a2acbbc..02e5217 100644
--- a/NW4RTools/bin/Debug/NW4RTools.dll.mdb
+++ b/NW4RTools/bin/Debug/NW4RTools.dll.mdb
Binary files differ