summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreeki <treeki@gmail.com>2011-02-04 15:40:12 +0100
committerTreeki <treeki@gmail.com>2011-02-04 15:40:12 +0100
commitdb8fc350b2bffda63d27797bfe7ae4afb4af327e (patch)
treea0a89cd83615e7794d9507d10c329e30c4226670
downloadnw4rtools-db8fc350b2bffda63d27797bfe7ae4afb4af327e.tar.gz
nw4rtools-db8fc350b2bffda63d27797bfe7ae4afb4af327e.zip
Initial commit of NW4RTools.
Supports BRRES reading for: Model Bytecode, Model Nodes, Model Vertex Data, and incomplete Materials. Writing is completely unimplemented so far.
-rw-r--r--NW4RTools.sln58
-rw-r--r--NW4RTools.userprefs17
-rw-r--r--NW4RTools/AssemblyInfo.cs27
-rw-r--r--NW4RTools/BrresReader.cs442
-rw-r--r--NW4RTools/InputStream.cs148
-rw-r--r--NW4RTools/Logger.cs81
-rw-r--r--NW4RTools/Models/ByteCode.cs74
-rw-r--r--NW4RTools/Models/Material.cs54
-rw-r--r--NW4RTools/Models/Model.cs37
-rw-r--r--NW4RTools/Models/Node.cs26
-rw-r--r--NW4RTools/Models/VertexData.cs46
-rw-r--r--NW4RTools/NW4RTools.csproj56
-rw-r--r--NW4RTools/NW4RTools.pidbbin0 -> 71720 bytes
-rw-r--r--NW4RTools/ResDict.cs8
-rw-r--r--NW4RTools/ResFile.cs12
-rw-r--r--NW4RTools/Types.cs137
-rw-r--r--NW4RTools/Util/IOrderedDictionary.cs59
-rw-r--r--NW4RTools/Util/OrderedDictionary.cs644
-rwxr-xr-xNW4RTools/bin/Debug/NW4RTools.dllbin0 -> 25600 bytes
-rw-r--r--NW4RTools/bin/Debug/NW4RTools.dll.mdbbin0 -> 8825 bytes
-rw-r--r--TestApp/AssemblyInfo.cs27
-rw-r--r--TestApp/Main.cs13
-rw-r--r--TestApp/TestApp.csproj46
-rw-r--r--TestApp/TestApp.pidbbin0 -> 3039 bytes
-rwxr-xr-xTestApp/bin/Debug/NW4RTools.dllbin0 -> 25600 bytes
-rw-r--r--TestApp/bin/Debug/NW4RTools.dll.mdbbin0 -> 8825 bytes
-rwxr-xr-xTestApp/bin/Debug/TestApp.exebin0 -> 3584 bytes
-rw-r--r--TestApp/bin/Debug/TestApp.exe.mdbbin0 -> 416 bytes
28 files changed, 2012 insertions, 0 deletions
diff --git a/NW4RTools.sln b/NW4RTools.sln
new file mode 100644
index 0000000..eb42975
--- /dev/null
+++ b/NW4RTools.sln
@@ -0,0 +1,58 @@
+
+Microsoft Visual Studio Solution File, Format Version 10.00
+# Visual Studio 2008
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NW4RTools", "NW4RTools\NW4RTools.csproj", "{A9C9FABD-0A5F-4DAB-979D-9F288F96866F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApp", "TestApp\TestApp.csproj", "{3A064CD8-CFAD-412D-986F-ED7D2D54CDB1}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {3A064CD8-CFAD-412D-986F-ED7D2D54CDB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3A064CD8-CFAD-412D-986F-ED7D2D54CDB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3A064CD8-CFAD-412D-986F-ED7D2D54CDB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3A064CD8-CFAD-412D-986F-ED7D2D54CDB1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A9C9FABD-0A5F-4DAB-979D-9F288F96866F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A9C9FABD-0A5F-4DAB-979D-9F288F96866F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A9C9FABD-0A5F-4DAB-979D-9F288F96866F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A9C9FABD-0A5F-4DAB-979D-9F288F96866F}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(MonoDevelopProperties) = preSolution
+ StartupItem = TestApp\TestApp.csproj
+ Policies = $0
+ $0.DotNetNamingPolicy = $1
+ $1.DirectoryNamespaceAssociation = None
+ $1.ResourceNamePolicy = FileFormatDefault
+ $0.TextStylePolicy = $2
+ $2.inheritsSet = null
+ $2.scope = text/x-csharp
+ $0.CSharpFormattingPolicy = $3
+ $3.NamespaceBraceStyle = EndOfLine
+ $3.ClassBraceStyle = EndOfLine
+ $3.InterfaceBraceStyle = EndOfLine
+ $3.StructBraceStyle = EndOfLine
+ $3.EnumBraceStyle = EndOfLine
+ $3.MethodBraceStyle = EndOfLine
+ $3.ConstructorBraceStyle = EndOfLine
+ $3.DestructorBraceStyle = EndOfLine
+ $3.BeforeMethodCallParentheses = False
+ $3.BeforeMethodDeclarationParentheses = False
+ $3.BeforeConstructorDeclarationParentheses = False
+ $3.BeforeDelegateDeclarationParentheses = False
+ $3.NewParentheses = False
+ $3.inheritsSet = Mono
+ $3.inheritsScope = text/x-csharp
+ $3.scope = text/x-csharp
+ $0.TextStylePolicy = $4
+ $4.FileWidth = 120
+ $4.TabWidth = 4
+ $4.RemoveTrailingWhitespace = True
+ $4.EolMarker = Unix
+ $4.inheritsSet = Mono
+ $4.inheritsScope = text/plain
+ $4.scope = text/plain
+ EndGlobalSection
+EndGlobal
diff --git a/NW4RTools.userprefs b/NW4RTools.userprefs
new file mode 100644
index 0000000..f308c35
--- /dev/null
+++ b/NW4RTools.userprefs
@@ -0,0 +1,17 @@
+<Properties>
+ <MonoDevelop.Ide.Workspace ActiveConfiguration="Debug" />
+ <MonoDevelop.Ide.Workbench ActiveDocument="NW4RTools/Models/Material.cs">
+ <Files>
+ <File FileName="NW4RTools/Types.cs" Line="11" Column="26" />
+ <File FileName="TestApp/Main.cs" Line="9" Column="44" />
+ <File FileName="NW4RTools/BrresReader.cs" Line="412" Column="1" />
+ <File FileName="NW4RTools/Models/Model.cs" Line="17" Column="54" />
+ <File FileName="NW4RTools/Models/Material.cs" Line="48" Column="16" />
+ <File FileName="NW4RTools/InputStream.cs" Line="94" Column="15" />
+ </Files>
+ </MonoDevelop.Ide.Workbench>
+ <MonoDevelop.Ide.DebuggingService.Breakpoints>
+ <BreakpointStore />
+ </MonoDevelop.Ide.DebuggingService.Breakpoints>
+ <MonoDevelop.Ide.DebuggingService.PinnedWatches />
+</Properties> \ No newline at end of file
diff --git a/NW4RTools/AssemblyInfo.cs b/NW4RTools/AssemblyInfo.cs
new file mode 100644
index 0000000..aa11f3f
--- /dev/null
+++ b/NW4RTools/AssemblyInfo.cs
@@ -0,0 +1,27 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+
+[assembly: AssemblyTitle("NW4RTools")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("")]
+[assembly: AssemblyCopyright("")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+
+[assembly: AssemblyVersion("1.0.*")]
+
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+
diff --git a/NW4RTools/BrresReader.cs b/NW4RTools/BrresReader.cs
new file mode 100644
index 0000000..a157fe9
--- /dev/null
+++ b/NW4RTools/BrresReader.cs
@@ -0,0 +1,442 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+using NW4RTools.Models;
+
+namespace NW4RTools {
+ public class BrresReader {
+ public static ResFile LoadFile(byte[] data) {
+ return new BrresReader().Load(new InputStream(data, ByteEndian.BigEndian));
+ }
+
+
+
+ private class RawStreamResDict : ResDict<InputStream> {
+ }
+
+
+
+ private ResFile File;
+ private Logger Debug;
+ private SortedDictionary<int, string> OffsetMap;
+
+ private BrresReader() {
+ Debug = new Logger();
+ OffsetMap = new SortedDictionary<int, string>();
+ }
+
+ public ResFile Load(InputStream ins) {
+ File = new ResFile();
+
+ // Read BRRES header
+ byte[] magic = ins.ReadBytes(4);
+ UInt16 endian = ins.ReadUInt16();
+ UInt16 version = ins.ReadUInt16();
+ UInt32 fileSize = ins.ReadUInt32();
+ UInt16 headerSize = ins.ReadUInt16();
+ UInt16 blockCount = ins.ReadUInt16();
+
+ File.version = version;
+
+ // Read first block header
+ byte[] blkMagic = ins.ReadBytes(4);
+ UInt32 blkSize = ins.ReadUInt32();
+
+ using (var c = Debug.Push("Root Dictionary")) {
+ ReadAndParseDict(ins, ParseRootEntry);
+ }
+
+
+ // DONE!
+ using (var c = Debug.Push("Offset Map")) {
+ foreach (var e in OffsetMap) {
+ Debug.Send("0x{0:X} : {1}", e.Key, e.Value);
+ }
+ }
+
+ return File;
+ }
+
+ private void ParseRootEntry(string name, InputStream ins) {
+ using (var c = Debug.Push(name)) {
+ switch (name) {
+ case "3DModels(NW4R)":
+ File.Add(name, ReadAndConvertDict<Model>(ins, ConvertModelResource));
+ break;
+ default:
+ Debug.Send("Not implemented");
+ return;
+ }
+ }
+ }
+
+ private Model ConvertModelResource(string name, InputStream ins) {
+ using (var c = Debug.Push(name)) {
+ Model mdl = new Model();
+
+ int startPos = ins.Position;
+
+ OffsetMap.Add(startPos, "Model: " + name);
+
+ byte[] magic = ins.ReadBytes(4);
+ UInt32 size = ins.ReadUInt32();
+ UInt32 version = ins.ReadUInt32();
+
+ Debug.Send("Offset: 0x{0:X}; Size: 0x{1:X}; Version: {2}", startPos, size, version);
+
+ Int32 resFileOffset = ins.ReadInt32();
+ Int32 bytecodeOffset = ins.ReadInt32();
+ Int32 nodeOffset = ins.ReadInt32();
+ Int32 vtxPosOffset = ins.ReadInt32();
+ Int32 vtxNrmOffset = ins.ReadInt32();
+ Int32 vtxClrOffset = ins.ReadInt32();
+ Int32 texCoordOffset = ins.ReadInt32();
+ Int32 vtxFurVecOffset = ins.ReadInt32();
+ Int32 vtxFurPosOffset = ins.ReadInt32();
+ Int32 materialOffset = ins.ReadInt32();
+ Int32 shaderOffset = ins.ReadInt32();
+ Int32 shapeOffset = ins.ReadInt32();
+ Int32 textureOffset = ins.ReadInt32();
+ Int32 paletteOffset = ins.ReadInt32();
+ Int32 unkOffset = ins.ReadInt32();
+ Int32 nameOffset = ins.ReadInt32();
+
+ int infoStructPos = ins.Position;
+ OffsetMap.Add(infoStructPos, "Model Info Struct for: " + name);
+ UInt32 infoSize = ins.ReadUInt32();
+ // should be 0x40
+
+ Int32 mdlOffset = ins.ReadInt32();
+ mdl.ScaleMode = (Model.ScaleModeType)ins.ReadUInt32();
+ mdl.TexMatrixMode = (Model.TexMatrixModeType)ins.ReadUInt32();
+ UInt32 vtxCount = ins.ReadUInt32();
+ UInt32 triCount = ins.ReadUInt32();
+ UInt32 unk = ins.ReadUInt32();
+ UInt32 nodeCount = ins.ReadUInt32();
+ mdl.UsesNrmMtxArray = (bool)(ins.ReadByte() != 0);
+ mdl.UsesTexMtxArray = (bool)(ins.ReadByte() != 0);
+ ins.Skip(2);
+ Int32 mtxDataOffset = ins.ReadInt32();
+ mdl.Minimum = ins.ReadVec3();
+ mdl.Maximum = ins.ReadVec3();
+
+
+ ins.Seek(infoStructPos + mtxDataOffset);
+ UInt32 mtxDataCount = ins.ReadUInt32();
+ mdl.MatrixIDtoNodeID = new int[mtxDataCount];
+
+ for (int i = 0; i < mtxDataCount; i++) {
+ mdl.MatrixIDtoNodeID[i] = ins.ReadInt32();
+ }
+
+
+
+ // Load bytecode
+ using (var c2 = Debug.Push("Bytecode"))
+ mdl.Bytecode = ReadAndConvertDict<ByteCode>(ins.At(startPos + bytecodeOffset), ConvertModelBytecode);
+
+ // Load nodes and fix up pointers
+ StartNodeLoading();
+
+ using (var c2 = Debug.Push("Nodes"))
+ mdl.Nodes = ReadAndConvertDict<Node>(ins.At(startPos + nodeOffset), ConvertModelNode);
+
+ FinishNodeLoading();
+
+ // Load vertex data
+ using (var c2 = Debug.Push("Vertex Position Data"))
+ mdl.VtxPosData = ReadAndConvertDict<VertexPosData>(ins.At(startPos + vtxPosOffset), ConvertVtxPosData);
+ using (var c2 = Debug.Push("Vertex Normal Data"))
+ mdl.VtxNrmData = ReadAndConvertDict<VertexNrmData>(ins.At(startPos + vtxNrmOffset), ConvertVtxNrmData);
+ using (var c2 = Debug.Push("Vertex Colour Data"))
+ mdl.VtxClrData = ReadAndConvertDict<VertexClrData>(ins.At(startPos + vtxClrOffset), ConvertVtxClrData);
+ using (var c2 = Debug.Push("Vertex TexCoord Data"))
+ mdl.VtxTexCoordData = ReadAndConvertDict<VertexTexCoordData>(ins.At(startPos + texCoordOffset), ConvertVtxTexCoordData);
+
+ return mdl;
+ }
+ }
+
+ private Models.ByteCode ConvertModelBytecode(string name, InputStream ins) {
+ var bc = new Models.ByteCode();
+
+ OffsetMap.Add(ins.Position, "ByteCode: " + name);
+
+ while (true) {
+ ByteCode.OpType op = (ByteCode.OpType)ins.ReadByte();
+
+ switch (op) {
+ case ByteCode.OpType.Done:
+ bc.Instructions.Add(new ByteCode.DoneInstruction());
+ return bc;
+
+ case ByteCode.OpType.AssignNodeToParentMtx:
+ var insn2 = new ByteCode.AssignNodeToParentMtxInstruction();
+ insn2.NodeID = ins.ReadUInt16();
+ insn2.ParentMatrixID = ins.ReadUInt16();
+ bc.Instructions.Add(insn2);
+ break;
+
+ case ByteCode.OpType.BlendMatrices:
+ var insn3 = new ByteCode.BlendMatricesInstruction();
+ insn3.MatrixID = ins.ReadUInt16();
+ insn3.BlendedMatrices = new ByteCode.BlendMatricesInstruction.BlendedMatrix[ins.ReadByte()];
+ for (int i = 0; i < insn3.BlendedMatrices.Length; i++) {
+ insn3.BlendedMatrices[i].MatrixID = ins.ReadUInt16();
+ insn3.BlendedMatrices[i].Ratio = ins.ReadFloat();
+ }
+ bc.Instructions.Add(insn3);
+ break;
+
+ case ByteCode.OpType.DrawShape:
+ var insn4 = new ByteCode.DrawShapeInstruction();
+ insn4.MaterialID = ins.ReadUInt16();
+ insn4.ShapeID = ins.ReadUInt16();
+ insn4.NodeID = ins.ReadUInt16();
+ ins.Skip(1);
+ bc.Instructions.Add(insn4);
+ break;
+
+ case ByteCode.OpType.AssignMtxToNode:
+ var insn5 = new ByteCode.AssignMtxToNodeInstruction();
+ insn5.MatrixID = ins.ReadUInt16();
+ insn5.NodeID = ins.ReadUInt16();
+ bc.Instructions.Add(insn5);
+ break;
+
+ default:
+ bc.Instructions.Add(new ByteCode.Instruction());
+ break;
+ }
+ }
+ }
+
+
+
+
+ private class NodeLoadInfo {
+ public Models.Node Node;
+ public string Name;
+ public int Position;
+ public Int32 ParentOffset;
+ public Int32 ChildOffset;
+ public Int32 NextOffset;
+ public Int32 PrevOffset;
+ }
+
+ private Dictionary<int, NodeLoadInfo> NodeLoadData;
+
+ private void StartNodeLoading() {
+ NodeLoadData = new Dictionary<int, NodeLoadInfo>();
+ }
+
+ private Models.Node ConvertModelNode(string name, InputStream ins) {
+ var n = new Models.Node();
+
+ OffsetMap.Add(ins.Position, "Node: " + name);
+
+ int startPos = ins.Position;
+
+ UInt32 size = ins.ReadUInt32();
+ Int32 mdlOffset = ins.ReadInt32();
+ Int32 nameOffset = ins.ReadInt32();
+
+ n.Index = ins.ReadUInt32();
+ n.MatrixID = ins.ReadUInt32();
+ n.Flags = ins.ReadUInt32();
+ n.BillboardMode = (Node.BillboardType)ins.ReadUInt32();
+ UInt32 unk = ins.ReadUInt32();
+ // might be swapped with bbmode, check asm to confirm
+
+ n.Scale = ins.ReadVec3();
+ n.Rotation = ins.ReadVec3();
+ n.Translation = ins.ReadVec3();
+ n.BoxMin = ins.ReadVec3();
+ n.BoxMax = ins.ReadVec3();
+
+ NodeLoadInfo loadInfo = new NodeLoadInfo();
+ loadInfo.ParentOffset = ins.ReadInt32();
+ loadInfo.ChildOffset = ins.ReadInt32();
+ loadInfo.NextOffset = ins.ReadInt32();
+ loadInfo.PrevOffset = ins.ReadInt32();
+ loadInfo.Position = startPos;
+ loadInfo.Node = n;
+ loadInfo.Name = ins.At(startPos + nameOffset - 4).ReadName();
+ NodeLoadData[startPos] = loadInfo;
+
+ n.NodeMatrix = ins.ReadMatrix();
+ n.NodeInvMatrix = ins.ReadMatrix();
+
+ return n;
+ }
+
+ private void FinishNodeLoading() {
+ foreach (var dictEntry in NodeLoadData) {
+ var entry = dictEntry.Value;
+ var node = entry.Node;
+
+ if (entry.ParentOffset == 0) {
+ node.Parent = null;
+ } else {
+ NodeLoadInfo n = NodeLoadData[entry.Position + entry.ParentOffset];
+ node.Parent = n.Node;
+ Debug.Send("Set {0}'s parent to {1}", entry.Name, n.Name);
+ }
+
+ if (entry.ChildOffset == 0)
+ node.FirstChild = null;
+ else
+ node.FirstChild = NodeLoadData[entry.Position + entry.ChildOffset].Node;
+
+ if (entry.NextOffset == 0)
+ node.Next = null;
+ else
+ node.Next = NodeLoadData[entry.Position + entry.NextOffset].Node;
+
+ if (entry.PrevOffset == 0)
+ node.Previous = null;
+ else
+ node.Previous = NodeLoadData[entry.Position + entry.PrevOffset].Node;
+ }
+
+ NodeLoadData = null;
+ }
+
+
+
+
+ private void LoadVertexDataBase(InputStream ins, Models.VertexDataBase n) {
+ UInt32 size = ins.ReadUInt32();
+
+ Int32 mdlOffset = ins.ReadInt32();
+ Int32 dataOffset = ins.ReadInt32();
+ Int32 nameOffset = ins.ReadInt32();
+
+ n.Index = ins.ReadUInt32();
+ n.ComponentCount = ins.ReadUInt32();
+ n.ComponentType = ins.ReadUInt32();
+ n.Fraction = ins.ReadByte();
+ n.EntrySize = ins.ReadByte();
+ n.EntryCount = ins.ReadUInt16();
+
+ n.Data = ins.ReadBytes(n.EntrySize * n.EntryCount);
+ }
+
+ private Models.VertexPosData ConvertVtxPosData(string name, InputStream ins) {
+ var n = new Models.VertexPosData();
+
+ OffsetMap.Add(ins.Position, "VertexPosData: " + name);
+
+ LoadVertexDataBase(ins, n);
+
+ n.Minimum = ins.ReadVec3();
+ n.Maximum = ins.ReadVec3();
+
+ return n;
+ }
+
+ private Models.VertexNrmData ConvertVtxNrmData(string name, InputStream ins) {
+ var n = new Models.VertexNrmData();
+
+ OffsetMap.Add(ins.Position, "VertexNrmData: " + name);
+
+ LoadVertexDataBase(ins, n);
+
+ return n;
+ }
+
+ private Models.VertexClrData ConvertVtxClrData(string name, InputStream ins) {
+ var n = new Models.VertexClrData();
+
+ OffsetMap.Add(ins.Position, "VertexClrData: " + name);
+
+ LoadVertexDataBase(ins, n);
+
+ return n;
+ }
+
+ private Models.VertexTexCoordData ConvertVtxTexCoordData(string name, InputStream ins) {
+ var n = new Models.VertexTexCoordData();
+
+ OffsetMap.Add(ins.Position, "VertexTexCoordData: " + name);
+
+ LoadVertexDataBase(ins, n);
+
+ n.Minimum = ins.ReadVec2();
+ n.Maximum = ins.ReadVec2();
+
+ return n;
+ }
+
+
+
+
+ private RawStreamResDict ReadDict(InputStream ins) {
+ RawStreamResDict output = new RawStreamResDict();
+ int dictPos = ins.Position;
+
+ UInt32 dataSize = ins.ReadUInt32();
+ UInt32 entryCount = ins.ReadUInt32();
+
+ for (int i = 0; i <= entryCount; i++) {
+ UInt16 eRef = ins.ReadUInt16();
+ UInt16 flag = ins.ReadUInt16();
+ UInt16 iLeft = ins.ReadUInt16();
+ UInt16 iRight = ins.ReadUInt16();
+ Int32 nameOffset = ins.ReadInt32();
+ Int32 dataOffset = ins.ReadInt32();
+
+ //Debug.Send("Position: {6:X}, eRef: {0:X}, flag: {1:X}, iLeft: {2:X}, iRight: {3:X}, nameOffset: {4:X}, dataOffset: {5:X}", eRef, flag, iLeft, iRight, nameOffset, dataOffset, ins.Position);
+
+ if (nameOffset != 0 && dataOffset != 0) {
+ string name = ins.At(dictPos + nameOffset - 4).ReadName();
+ Debug.Send("Entry: {0} at offset 0x{1:X}", name, dictPos + dataOffset);
+
+ InputStream entryStream = ins.At(dictPos + dataOffset);
+ output.Add(name, entryStream);
+ }
+ }
+
+ return output;
+ }
+
+
+
+ private delegate void ParseResourceDelegate(string name, InputStream ins);
+ private delegate TValue ConvertResourceDelegate<TValue>(string name, InputStream ins);
+
+ private void ReadAndParseDict(InputStream ins, ParseResourceDelegate func) {
+ ReadAndParseDict(ins, func, "Unnamed");
+ }
+
+ private void ReadAndParseDict(InputStream ins, ParseResourceDelegate func, string dictName) {
+ int dictPos = ins.Position;
+ RawStreamResDict theDict = ReadDict(ins);
+ OffsetMap.Add(dictPos, string.Format("ResDict: {0} [Data ends at 0x{1:X}]", dictName, ins.Position));
+
+ foreach (var entry in theDict) {
+ func(entry.Key, entry.Value);
+ }
+ }
+
+ private ResDict<TValue> ReadAndConvertDict<TValue>(InputStream ins, ConvertResourceDelegate<TValue> func) {
+ int dictPos = ins.Position;
+ RawStreamResDict theDict = ReadDict(ins);
+ var outDict = new ResDict<TValue>();
+
+ // Hack to get the type name
+ string valueTypeName = outDict.GetType().GetGenericArguments()[0].ToString();
+
+ OffsetMap.Add(dictPos, string.Format("ResDict: {0} [Data ends at 0x{1:X}]", valueTypeName, ins.Position));
+
+ foreach (var entry in theDict) {
+ TValue returnedObject = func(entry.Key, entry.Value);
+ outDict.Add(entry.Key, returnedObject);
+ }
+
+ return outDict;
+ }
+ }
+}
+
diff --git a/NW4RTools/InputStream.cs b/NW4RTools/InputStream.cs
new file mode 100644
index 0000000..3a776b4
--- /dev/null
+++ b/NW4RTools/InputStream.cs
@@ -0,0 +1,148 @@
+using System;
+using System.IO;
+
+namespace NW4RTools {
+ public class InputStream {
+ public readonly byte[] Data;
+ public readonly ByteEndian Endian;
+ private readonly bool MustReverseArrays;
+
+ public int Position {
+ get; private set;
+ }
+
+ public InputStream(byte[] data) {
+ Data = data;
+ Endian = ByteEndian.BigEndian;
+ Position = 0;
+ MustReverseArrays = BitConverter.IsLittleEndian;
+ }
+
+ public InputStream(byte[] data, ByteEndian endian) {
+ Data = data;
+ Endian = endian;
+ Position = 0;
+
+ if (Endian == ByteEndian.BigEndian)
+ MustReverseArrays = BitConverter.IsLittleEndian;
+ else
+ MustReverseArrays = !BitConverter.IsLittleEndian;
+ }
+
+ public void Seek(int pos) {
+ if (pos < 0 || pos >= Data.Length)
+ throw new ArgumentOutOfRangeException();
+
+ Position = pos;
+ }
+
+ public void Skip(int count) {
+ Seek(Position + count);
+ }
+
+ public byte[] ReadBytes(int count) {
+ byte[] ret = new byte[count];
+ Array.Copy(Data, Position, ret, 0, count);
+ Skip(count);
+ return ret;
+ }
+
+ private byte[] ReadReversedBytes(int count) {
+ byte[] buf = ReadBytes(count);
+ if (MustReverseArrays)
+ Array.Reverse(buf);
+ return buf;
+ }
+
+ public byte ReadByte() {
+ byte ret = Data[Position];
+ Skip(1);
+ return ret;
+ }
+
+ public Int16 ReadInt16() {
+ return BitConverter.ToInt16(ReadReversedBytes(2), 0);
+ }
+
+ public UInt16 ReadUInt16() {
+ return BitConverter.ToUInt16(ReadReversedBytes(2), 0);
+ }
+
+ public Int32 ReadInt32() {
+ return BitConverter.ToInt32(ReadReversedBytes(4), 0);
+ }
+
+ public UInt32 ReadUInt32() {
+ return BitConverter.ToUInt32(ReadReversedBytes(4), 0);
+ }
+
+ public float ReadFloat() {
+ return BitConverter.ToSingle(ReadReversedBytes(4), 0);
+ }
+
+ public double ReadDouble() {
+ return BitConverter.ToDouble(ReadReversedBytes(8), 0);
+ }
+
+ public Color ReadColor() {
+ var ret = new Color();
+ ret.r = Data[Position];
+ ret.g = Data[Position + 1];
+ ret.b = Data[Position + 2];
+ ret.a = Data[Position + 3];
+ Skip(4);
+ return ret;
+ }
+
+ public Vec2 ReadVec2() {
+ float x = ReadFloat();
+ float y = ReadFloat();
+ return new Vec2 { x = x, y = y };
+ }
+
+ public Vec3 ReadVec3() {
+ float x = ReadFloat();
+ float y = ReadFloat();
+ float z = ReadFloat();
+ return new Vec3 { x = x, y = y, z = z };
+ }
+
+ public Matrix ReadMatrix() {
+ var ret = new Matrix();
+ ret.v00 = ReadFloat();
+ ret.v01 = ReadFloat();
+ ret.v02 = ReadFloat();
+ ret.v03 = ReadFloat();
+ ret.v10 = ReadFloat();
+ ret.v11 = ReadFloat();
+ ret.v12 = ReadFloat();
+ ret.v13 = ReadFloat();
+ ret.v20 = ReadFloat();
+ ret.v21 = ReadFloat();
+ ret.v22 = ReadFloat();
+ ret.v23 = ReadFloat();
+ return ret;
+ }
+
+ public string ReadName() {
+ int length = ReadInt32();
+ string name = System.Text.Encoding.GetEncoding("Shift_JIS").GetString(ReadBytes(length));
+ if ((length & 3) != 0) {
+ Skip(4 - (length & 3));
+ }
+ return name;
+ }
+
+
+ public InputStream At(int pos) {
+ var ret = new InputStream(Data, Endian);
+ ret.Seek(pos);
+ return ret;
+ }
+
+ public InputStream Copy() {
+ return At(Position);
+ }
+ }
+}
+
diff --git a/NW4RTools/Logger.cs b/NW4RTools/Logger.cs
new file mode 100644
index 0000000..d5a76f3
--- /dev/null
+++ b/NW4RTools/Logger.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace NW4RTools {
+ public class LogContext : IDisposable {
+ // An awful, awful hack
+ private readonly Logger Owner;
+
+ public LogContext(Logger owner) {
+ Owner = owner;
+ }
+
+ void IDisposable.Dispose() {
+ Owner.Pop();
+ }
+ }
+
+ public class Logger {
+ private List<string> PrefixElements;
+ private List<string> Names;
+ private int BlockCount;
+ private string Prefix;
+
+ public Logger() {
+ Names = new List<string>();
+ Names.Add("root");
+
+ PrefixElements = new List<string>();
+ PrefixElements.Add("|");
+
+ RebuildPrefix();
+ }
+
+ private void RebuildPrefix() {
+ Prefix = string.Join(" ", PrefixElements.ToArray()) + "- ";
+ }
+
+ public void Send(string format, params object[] args) {
+ if (BlockCount > 0)
+ return;
+
+ Console.Write(Prefix);
+ Console.WriteLine(format, args);
+ }
+
+ public LogContext Push(string format, params object[] args) {
+ string n = string.Format(format, args);
+ Console.Write(Prefix);
+ Console.Write("=== ");
+ Console.WriteLine(n);
+
+ Names.Add(n);
+
+ PrefixElements.Add("-");
+ RebuildPrefix();
+
+ return new LogContext(this);
+ }
+
+ public void Pop() {
+ PrefixElements.RemoveAt(PrefixElements.Count - 1);
+ RebuildPrefix();
+
+ string n = Names[Names.Count - 1];
+ Names.RemoveAt(Names.Count - 1);
+ Console.Write(Prefix);
+ Console.Write("=/= end ");
+ Console.WriteLine(n);
+ }
+
+ public void Block() {
+ BlockCount += 1;
+ }
+
+ public void Unblock() {
+ BlockCount -= 1;
+ }
+ }
+}
+
diff --git a/NW4RTools/Models/ByteCode.cs b/NW4RTools/Models/ByteCode.cs
new file mode 100644
index 0000000..438c009
--- /dev/null
+++ b/NW4RTools/Models/ByteCode.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+
+namespace NW4RTools.Models {
+ public class ByteCode {
+ public enum OpType {
+ None, Done, AssignNodeToParentMtx, BlendMatrices, DrawShape, AssignMtxToNode
+ }
+
+ public class Instruction {
+ public virtual OpType GetOp() {
+ return OpType.None;
+ }
+ }
+
+ public class DoneInstruction : Instruction {
+ public override OpType GetOp() {
+ return OpType.Done;
+ }
+ }
+
+ public class AssignNodeToParentMtxInstruction : Instruction {
+ public override OpType GetOp() {
+ return OpType.AssignNodeToParentMtx;
+ }
+
+ public UInt16 NodeID;
+ public UInt16 ParentMatrixID;
+ }
+
+ public class BlendMatricesInstruction : Instruction {
+ public override OpType GetOp() {
+ return OpType.BlendMatrices;
+ }
+
+ public UInt16 MatrixID;
+ public BlendedMatrix[] BlendedMatrices;
+
+ public struct BlendedMatrix {
+ public UInt16 MatrixID;
+ public float Ratio;
+ }
+ }
+
+ public class DrawShapeInstruction : Instruction {
+ public override OpType GetOp() {
+ return OpType.DrawShape;
+ }
+
+ public UInt16 MaterialID;
+ public UInt16 ShapeID;
+ public UInt16 NodeID;
+ }
+
+ public class AssignMtxToNodeInstruction : Instruction {
+ public override OpType GetOp() {
+ return OpType.AssignMtxToNode;
+ }
+
+ public UInt16 MatrixID;
+ public UInt16 NodeID;
+ }
+
+
+
+
+ public List<Instruction> Instructions;
+
+ public ByteCode() {
+ Instructions = new List<Instruction>();
+ }
+ }
+}
+
diff --git a/NW4RTools/Models/Material.cs b/NW4RTools/Models/Material.cs
new file mode 100644
index 0000000..bb1b85a
--- /dev/null
+++ b/NW4RTools/Models/Material.cs
@@ -0,0 +1,54 @@
+using System;
+
+namespace NW4RTools.Models {
+ public class ChanCtrl {
+ public UInt32 Flags;
+ public Color MatColor, AmbColor;
+ public UInt32 FlagC, FlagA;
+ }
+
+ public class SRTSettingInfo {
+ public float ScaleX, ScaleY, Rotate, TranslateX, TranslateY;
+
+ public byte CameraID;
+ public byte LightID;
+ public byte MapType;
+ public byte Flags;
+ public Matrix TexMatrix;
+ }
+
+ public class BoundTextureInfo {
+ public string TextureName, PaletteName;
+ public UInt32 TexMapID, TlutID, WrapS, WrapT, MinFilt, MagFilt;
+ public float LODBias;
+ public UInt32 MaxAniso;
+ }
+
+
+
+ public class Material {
+ public UInt32 Index, Flags;
+
+ // ResGenMode
+ public byte TexCoordGenCount, ChanCount, TevStageCount, IndStageCount;
+ public UInt32 CullMode;
+
+ // ResMatMisc
+ public byte ZCompLoc, LightSetID, FogID;
+ public byte[] IndirectTexMtxCalcMethod1;
+ public byte[] IndirectTexMtxCalcMethod2;
+
+ // ResTexObj and ResTlutObj
+ public byte[][] TexObj;
+ public byte[] TlutObj;
+
+ // ResTexSrt
+ public SRTSettingInfo[] SRTSettings;
+
+ // UNFINISHED
+
+ public Material() {
+ }
+ }
+}
+
diff --git a/NW4RTools/Models/Model.cs b/NW4RTools/Models/Model.cs
new file mode 100644
index 0000000..ea95f4f
--- /dev/null
+++ b/NW4RTools/Models/Model.cs
@@ -0,0 +1,37 @@
+using System;
+namespace NW4RTools.Models {
+ public class Model {
+ public enum ScaleModeType {
+ Standard, SoftImage, Maya
+ }
+
+ public enum TexMatrixModeType {
+ Maya, SoftImage, Max
+ }
+
+ public ResDict<ByteCode> Bytecode;
+ public ResDict<Node> Nodes;
+ public ResDict<VertexPosData> VtxPosData;
+ public ResDict<VertexNrmData> VtxNrmData;
+ public ResDict<VertexClrData> VtxClrData;
+ public ResDict<VertexTexCoordData> VtxTexCoordData;
+
+ /*public ResDict Bytecode, Nodes, VtxPosData, VtsNrmData, VtxClrData, VtxTexCoordData;
+ public ResDict VtxFurVecData, VtxFurPosData, Materials, Shaders, Shapes;
+ public ResDict Textures, Palettes;*/
+
+ public ScaleModeType ScaleMode;
+ public TexMatrixModeType TexMatrixMode;
+
+ public bool UsesNrmMtxArray, UsesTexMtxArray;
+
+ public Int32[] MatrixIDtoNodeID;
+
+ public Vec3 Minimum, Maximum;
+
+
+ public Model() {
+ }
+ }
+}
+
diff --git a/NW4RTools/Models/Node.cs b/NW4RTools/Models/Node.cs
new file mode 100644
index 0000000..4031ae9
--- /dev/null
+++ b/NW4RTools/Models/Node.cs
@@ -0,0 +1,26 @@
+using System;
+
+namespace NW4RTools.Models {
+ public class Node {
+ public enum BillboardType {
+ None, Type1, Type2, Type3, Type4, Type5, Type6
+ }
+
+ public UInt32 Index;
+ public UInt32 MatrixID;
+ public UInt32 Flags;
+ public BillboardType BillboardMode;
+
+ public Vec3 Scale, Rotation, Translation;
+ public Vec3 BoxMin, BoxMax;
+
+ public Node Parent, FirstChild, Next, Previous;
+ public byte[] UserData;
+
+ public Matrix NodeMatrix, NodeInvMatrix;
+
+ public Node() {
+ }
+ }
+}
+
diff --git a/NW4RTools/Models/VertexData.cs b/NW4RTools/Models/VertexData.cs
new file mode 100644
index 0000000..d8733b3
--- /dev/null
+++ b/NW4RTools/Models/VertexData.cs
@@ -0,0 +1,46 @@
+using System;
+
+namespace NW4RTools.Models {
+ public class VertexDataBase {
+ public UInt32 Index;
+
+ public UInt32 ComponentCount, ComponentType; // todo: enums
+ public byte Fraction /* Not used for colours */, EntrySize;
+ public UInt16 EntryCount;
+
+ // Todo, decode data when reading
+ public byte[] Data;
+
+ public VertexDataBase() {
+ }
+ }
+
+
+ public class VertexPosData : VertexDataBase {
+ public Vec3 Minimum, Maximum;
+
+ public VertexPosData() {
+ }
+ }
+
+
+ public class VertexNrmData : VertexDataBase {
+ public VertexNrmData() {
+ }
+ }
+
+
+ public class VertexClrData : VertexDataBase {
+ public VertexClrData() {
+ }
+ }
+
+
+ public class VertexTexCoordData : VertexDataBase {
+ public Vec2 Minimum, Maximum;
+
+ public VertexTexCoordData() {
+ }
+ }
+}
+
diff --git a/NW4RTools/NW4RTools.csproj b/NW4RTools/NW4RTools.csproj
new file mode 100644
index 0000000..f8f89e8
--- /dev/null
+++ b/NW4RTools/NW4RTools.csproj
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="3.5" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.21022</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{A9C9FABD-0A5F-4DAB-979D-9F288F96866F}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <RootNamespace>NW4RTools</RootNamespace>
+ <AssemblyName>NW4RTools</AssemblyName>
+ <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug</OutputPath>
+ <DefineConstants>DEBUG</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>none</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Release</OutputPath>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="AssemblyInfo.cs" />
+ <Compile Include="Logger.cs" />
+ <Compile Include="InputStream.cs" />
+ <Compile Include="Types.cs" />
+ <Compile Include="ResDict.cs" />
+ <Compile Include="BrresReader.cs" />
+ <Compile Include="ResFile.cs" />
+ <Compile Include="Models\Model.cs" />
+ <Compile Include="Models\ByteCode.cs" />
+ <Compile Include="Models\Node.cs" />
+ <Compile Include="Util\IOrderedDictionary.cs" />
+ <Compile Include="Util\OrderedDictionary.cs" />
+ <Compile Include="Models\VertexData.cs" />
+ <Compile Include="Models\Material.cs" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+ <ItemGroup>
+ <Folder Include="Models\" />
+ <Folder Include="Util\" />
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/NW4RTools/NW4RTools.pidb b/NW4RTools/NW4RTools.pidb
new file mode 100644
index 0000000..726779f
--- /dev/null
+++ b/NW4RTools/NW4RTools.pidb
Binary files differ
diff --git a/NW4RTools/ResDict.cs b/NW4RTools/ResDict.cs
new file mode 100644
index 0000000..30e25e3
--- /dev/null
+++ b/NW4RTools/ResDict.cs
@@ -0,0 +1,8 @@
+using System;
+using System.Collections;
+
+namespace NW4RTools {
+ public class ResDict<TValue> : Util.OrderedDictionary<string, TValue> {
+ }
+}
+
diff --git a/NW4RTools/ResFile.cs b/NW4RTools/ResFile.cs
new file mode 100644
index 0000000..865db93
--- /dev/null
+++ b/NW4RTools/ResFile.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace NW4RTools {
+ public class ResFile : ResDict<object> {
+ public UInt16 version;
+
+ public ResDict<TValue> GetGroup<TValue>(string name) {
+ return this[name] as ResDict<TValue>;
+ }
+ }
+}
+
diff --git a/NW4RTools/Types.cs b/NW4RTools/Types.cs
new file mode 100644
index 0000000..7887f1c
--- /dev/null
+++ b/NW4RTools/Types.cs
@@ -0,0 +1,137 @@
+using System;
+
+namespace NW4RTools {
+ public enum ByteEndian {
+ LittleEndian,
+ BigEndian
+ }
+
+
+ public struct Color {
+ public byte r, g, b, a;
+ }
+
+ public struct Vec3 {
+ public float x, y, z;
+ }
+
+ public struct Vec2 {
+ public float x, y;
+ }
+
+ public struct Matrix {
+ public float v00, v01, v02, v03, v10, v11, v12, v13, v20, v21, v22, v23;
+
+ public float this[int x, int y] {
+ get {
+ switch (y) {
+ case 0:
+ switch (x) {
+ case 0:
+ return v00;
+ break;
+ case 1:
+ return v01;
+ break;
+ case 2:
+ return v02;
+ break;
+ case 3:
+ return v03;
+ break;
+ }
+ break;
+ case 1:
+ switch (x) {
+ case 0:
+ return v10;
+ break;
+ case 1:
+ return v11;
+ break;
+ case 2:
+ return v12;
+ break;
+ case 3:
+ return v13;
+ break;
+ }
+ break;
+ case 2:
+ switch (x) {
+ case 0:
+ return v20;
+ break;
+ case 1:
+ return v21;
+ break;
+ case 2:
+ return v22;
+ break;
+ case 3:
+ return v23;
+ break;
+ }
+ break;
+ }
+
+ throw new IndexOutOfRangeException();
+ }
+
+ set {
+ switch (y) {
+ case 0:
+ switch (x) {
+ case 0:
+ v00 = value;
+ break;
+ case 1:
+ v01 = value;
+ break;
+ case 2:
+ v02 = value;
+ break;
+ case 3:
+ v03 = value;
+ break;
+ }
+ break;
+ case 1:
+ switch (x) {
+ case 0:
+ v10 = value;
+ break;
+ case 1:
+ v11 = value;
+ break;
+ case 2:
+ v12 = value;
+ break;
+ case 3:
+ v13 = value;
+ break;
+ }
+ break;
+ case 2:
+ switch (x) {
+ case 0:
+ v20 = value;
+ break;
+ case 1:
+ v21 = value;
+ break;
+ case 2:
+ v22 = value;
+ break;
+ case 3:
+ v23 = value;
+ break;
+ }
+ break;
+ }
+
+ throw new IndexOutOfRangeException();
+ }
+ }
+ }
+}
diff --git a/NW4RTools/Util/IOrderedDictionary.cs b/NW4RTools/Util/IOrderedDictionary.cs
new file mode 100644
index 0000000..a574c7c
--- /dev/null
+++ b/NW4RTools/Util/IOrderedDictionary.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+
+// From http://www.codeproject.com/KB/recipes/GenericOrderedDictionary.aspx
+
+namespace NW4RTools.Util
+{
+ /// <summary>
+ /// Represents a generic collection of key/value pairs that are ordered independently of the key and value.
+ /// </summary>
+ /// <typeparam name="TKey">The type of the keys in the dictionary</typeparam>
+ /// <typeparam name="TValue">The type of the values in the dictionary</typeparam>
+ public interface IOrderedDictionary<TKey, TValue> : IOrderedDictionary, IDictionary<TKey, TValue>
+ {
+ /// <summary>
+ /// Adds an entry with the specified key and value into the <see cref="IOrderedDictionary{TKey,TValue}">IOrderedDictionary&lt;TKey,TValue&gt;</see> collection with the lowest available index.
+ /// </summary>
+ /// <param name="key">The key of the entry to add.</param>
+ /// <param name="value">The value of the entry to add.</param>
+ /// <returns>The index of the newly added entry</returns>
+ /// <remarks>
+ /// <para>You can also use the <see cref="P:System.Collections.Generic.IDictionary{TKey,TValue}.Item(TKey)"/> property to add new elements by setting the value of a key that does not exist in the <see cref="IOrderedDictionary{TKey,TValue}">IOrderedDictionary&lt;TKey,TValue&gt;</see> collection; however, if the specified key already exists in the <see cref="IOrderedDictionary{TKey,TValue}">IOrderedDictionary&lt;TKey,TValue&gt;</see>, setting the <see cref="P:Item(TKey)"/> property overwrites the old value. In contrast, the <see cref="M:Add"/> method does not modify existing elements.</para></remarks>
+ /// <exception cref="ArgumentException">An element with the same key already exists in the <see cref="IOrderedDictionary{TKey,TValue}">IOrderedDictionary&lt;TKey,TValue&gt;</see></exception>
+ /// <exception cref="NotSupportedException">The <see cref="IOrderedDictionary{TKey,TValue}">IOrderedDictionary&lt;TKey,TValue&gt;</see> is read-only.<br/>
+ /// -or-<br/>
+ /// The <see cref="IOrderedDictionary{TKey,TValue}">IOrderedDictionary&lt;TKey,TValue&gt;</see> has a fized size.</exception>
+ new int Add(TKey key, TValue value);
+
+ /// <summary>
+ /// Inserts a new entry into the <see cref="IOrderedDictionary{TKey,TValue}">IOrderedDictionary&lt;TKey,TValue&gt;</see> collection with the specified key and value at the specified index.
+ /// </summary>
+ /// <param name="index">The zero-based index at which the element should be inserted.</param>
+ /// <param name="key">The key of the entry to add.</param>
+ /// <param name="value">The value of the entry to add. The value can be <null/> if the type of the values in the dictionary is a reference type.</param>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0.<br/>
+ /// -or-<br/>
+ /// <paramref name="index"/> is greater than <see cref="System.Collections.ICollection.Count"/>.</exception>
+ /// <exception cref="ArgumentException">An element with the same key already exists in the <see cref="IOrderedDictionary{TKey,TValue}">IOrderedDictionary&lt;TKey,TValue&gt;</see>.</exception>
+ /// <exception cref="NotSupportedException">The <see cref="IOrderedDictionary{TKey,TValue}">IOrderedDictionary&lt;TKey,TValue&gt;</see> is read-only.<br/>
+ /// -or-<br/>
+ /// The <see cref="IOrderedDictionary{TKey,TValue}">IOrderedDictionary&lt;TKey,TValue&gt;</see> has a fized size.</exception>
+ void Insert(int index, TKey key, TValue value);
+
+ /// <summary>
+ /// Gets or sets the value at the specified index.
+ /// </summary>
+ /// <param name="index">The zero-based index of the value to get or set.</param>
+ /// <value>The value of the item at the specified index.</value>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0.<br/>
+ /// -or-<br/>
+ /// <paramref name="index"/> is equal to or greater than <see cref="System.Collections.ICollection.Count"/>.</exception>
+ new TValue this[int index]
+ {
+ get;
+ set;
+ }
+ }
+}
diff --git a/NW4RTools/Util/OrderedDictionary.cs b/NW4RTools/Util/OrderedDictionary.cs
new file mode 100644
index 0000000..3dd4fb4
--- /dev/null
+++ b/NW4RTools/Util/OrderedDictionary.cs
@@ -0,0 +1,644 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+
+// From http://www.codeproject.com/KB/recipes/GenericOrderedDictionary.aspx
+
+namespace NW4RTools.Util
+{
+ /// <summary>
+ /// Represents a generic collection of key/value pairs that are ordered independently of the key and value.
+ /// </summary>
+ /// <typeparam name="TKey">The type of the keys in the dictionary</typeparam>
+ /// <typeparam name="TValue">The type of the values in the dictionary</typeparam>
+ public class OrderedDictionary<TKey, TValue> : IOrderedDictionary<TKey, TValue>
+ {
+ private const int DefaultInitialCapacity = 0;
+
+ private static readonly string _keyTypeName = typeof(TKey).FullName;
+ private static readonly string _valueTypeName = typeof(TValue).FullName;
+ private static readonly bool _valueTypeIsReferenceType = !typeof(ValueType).IsAssignableFrom(typeof(TValue));
+
+ private Dictionary<TKey, TValue> _dictionary;
+ private List<KeyValuePair<TKey, TValue>> _list;
+ private IEqualityComparer<TKey> _comparer;
+ private object _syncRoot;
+ private int _initialCapacity;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> class.
+ /// </summary>
+ public OrderedDictionary()
+ : this(DefaultInitialCapacity, null)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> class using the specified initial capacity.
+ /// </summary>
+ /// <param name="capacity">The initial number of elements that the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> can contain.</param>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="capacity"/> is less than 0</exception>
+ public OrderedDictionary(int capacity)
+ : this(capacity, null)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> class using the specified comparer.
+ /// </summary>
+ /// <param name="comparer">The <see cref="IEqualityComparer{TKey}">IEqualityComparer&lt;TKey&gt;</see> to use when comparing keys, or <null/> to use the default <see cref="EqualityComparer{TKey}">EqualityComparer&lt;TKey&gt;</see> for the type of the key.</param>
+ public OrderedDictionary(IEqualityComparer<TKey> comparer)
+ : this(DefaultInitialCapacity, comparer)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> class using the specified initial capacity and comparer.
+ /// </summary>
+ /// <param name="capacity">The initial number of elements that the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection can contain.</param>
+ /// <param name="comparer">The <see cref="IEqualityComparer{TKey}">IEqualityComparer&lt;TKey&gt;</see> to use when comparing keys, or <null/> to use the default <see cref="EqualityComparer{TKey}">EqualityComparer&lt;TKey&gt;</see> for the type of the key.</param>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="capacity"/> is less than 0</exception>
+ public OrderedDictionary(int capacity, IEqualityComparer<TKey> comparer)
+ {
+ if(0 > capacity)
+ throw new ArgumentOutOfRangeException("capacity", "'capacity' must be non-negative");
+
+ _initialCapacity = capacity;
+ _comparer = comparer;
+ }
+
+ /// <summary>
+ /// Converts the object passed as a key to the key type of the dictionary
+ /// </summary>
+ /// <param name="keyObject">The key object to check</param>
+ /// <returns>The key object, cast as the key type of the dictionary</returns>
+ /// <exception cref="ArgumentNullException"><paramref name="keyObject"/> is <null/>.</exception>
+ /// <exception cref="ArgumentException">The key type of the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> is not in the inheritance hierarchy of <paramref name="keyObject"/>.</exception>
+ private static TKey ConvertToKeyType(object keyObject)
+ {
+ if(null == keyObject)
+ {
+ throw new ArgumentNullException("key");
+ }
+ else
+ {
+ if(keyObject is TKey)
+ return (TKey)keyObject;
+ }
+ throw new ArgumentException("'key' must be of type " + _keyTypeName, "key");
+ }
+
+ /// <summary>
+ /// Converts the object passed as a value to the value type of the dictionary
+ /// </summary>
+ /// <param name="value">The object to convert to the value type of the dictionary</param>
+ /// <returns>The value object, converted to the value type of the dictionary</returns>
+ /// <exception cref="ArgumentNullException"><paramref name="valueObject"/> is <null/>, and the value type of the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> is a value type.</exception>
+ /// <exception cref="ArgumentException">The value type of the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> is not in the inheritance hierarchy of <paramref name="valueObject"/>.</exception>
+ private static TValue ConvertToValueType(object value)
+ {
+ if(null == value)
+ {
+ if(_valueTypeIsReferenceType)
+ return default(TValue);
+ else
+ throw new ArgumentNullException("value");
+ }
+ else
+ {
+ if(value is TValue)
+ return (TValue)value;
+ }
+ throw new ArgumentException("'value' must be of type " + _valueTypeName, "value");
+ }
+
+ /// <summary>
+ /// Gets the dictionary object that stores the keys and values
+ /// </summary>
+ /// <value>The dictionary object that stores the keys and values for the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see></value>
+ /// <remarks>Accessing this property will create the dictionary object if necessary</remarks>
+ private Dictionary<TKey, TValue> Dictionary
+ {
+ get
+ {
+ if(null == _dictionary)
+ {
+ _dictionary = new Dictionary<TKey, TValue>(_initialCapacity, _comparer);
+ }
+ return _dictionary;
+ }
+ }
+
+ /// <summary>
+ /// Gets the list object that stores the key/value pairs.
+ /// </summary>
+ /// <value>The list object that stores the key/value pairs for the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see></value>
+ /// <remarks>Accessing this property will create the list object if necessary.</remarks>
+ private List<KeyValuePair<TKey, TValue>> List
+ {
+ get
+ {
+ if(null == _list)
+ {
+ _list = new List<KeyValuePair<TKey, TValue>>(_initialCapacity);
+ }
+ return _list;
+ }
+ }
+
+ IDictionaryEnumerator IOrderedDictionary.GetEnumerator()
+ {
+ return Dictionary.GetEnumerator();
+ }
+
+ IDictionaryEnumerator IDictionary.GetEnumerator()
+ {
+ return Dictionary.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return List.GetEnumerator();
+ }
+
+ IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey,TValue>>.GetEnumerator()
+ {
+ return List.GetEnumerator();
+ }
+
+ /// <summary>
+ /// Inserts a new entry into the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection with the specified key and value at the specified index.
+ /// </summary>
+ /// <param name="index">The zero-based index at which the element should be inserted.</param>
+ /// <param name="key">The key of the entry to add.</param>
+ /// <param name="value">The value of the entry to add. The value can be <null/> if the type of the values in the dictionary is a reference type.</param>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0.<br/>
+ /// -or-<br/>
+ /// <paramref name="index"/> is greater than <see cref="Count"/>.</exception>
+ /// <exception cref="ArgumentNullException"><paramref name="key"/> is <null/>.</exception>
+ /// <exception cref="ArgumentException">An element with the same key already exists in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>.</exception>
+ public void Insert(int index, TKey key, TValue value)
+ {
+ if(index > Count || index < 0)
+ throw new ArgumentOutOfRangeException("index");
+
+ Dictionary.Add(key, value);
+ List.Insert(index, new KeyValuePair<TKey, TValue>(key, value));
+ }
+
+ /// <summary>
+ /// Inserts a new entry into the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection with the specified key and value at the specified index.
+ /// </summary>
+ /// <param name="index">The zero-based index at which the element should be inserted.</param>
+ /// <param name="key">The key of the entry to add.</param>
+ /// <param name="value">The value of the entry to add. The value can be <null/> if the type of the values in the dictionary is a reference type.</param>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0.<br/>
+ /// -or-<br/>
+ /// <paramref name="index"/> is greater than <see cref="Count"/>.</exception>
+ /// <exception cref="ArgumentNullException"><paramref name="key"/> is <null/>.<br/>
+ /// -or-<br/>
+ /// <paramref name="value"/> is <null/>, and the value type of the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> is a value type.</exception>
+ /// <exception cref="ArgumentException">The key type of the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> is not in the inheritance hierarchy of <paramref name="key"/>.<br/>
+ /// -or-<br/>
+ /// The value type of the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> is not in the inheritance hierarchy of <paramref name="value"/>.<br/>
+ /// -or-<br/>
+ /// An element with the same key already exists in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>.</exception>
+ void IOrderedDictionary.Insert(int index, object key, object value)
+ {
+ Insert(index, ConvertToKeyType(key), ConvertToValueType(value));
+ }
+
+ /// <summary>
+ /// Removes the entry at the specified index from the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection.
+ /// </summary>
+ /// <param name="index">The zero-based index of the entry to remove.</param>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0.<br/>
+ /// -or-<br/>
+ /// index is equal to or greater than <see cref="Count"/>.</exception>
+ public void RemoveAt(int index)
+ {
+ if(index >= Count || index < 0)
+ throw new ArgumentOutOfRangeException("index", "'index' must be non-negative and less than the size of the collection");
+
+ TKey key = List[index].Key;
+
+ List.RemoveAt(index);
+ Dictionary.Remove(key);
+ }
+
+ /// <summary>
+ /// Gets or sets the value at the specified index.
+ /// </summary>
+ /// <param name="index">The zero-based index of the value to get or set.</param>
+ /// <value>The value of the item at the specified index.</value>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0.<br/>
+ /// -or-<br/>
+ /// index is equal to or greater than <see cref="Count"/>.</exception>
+ public TValue this[int index]
+ {
+ get
+ {
+ return List[index].Value;
+ }
+
+ set
+ {
+ if(index >= Count || index < 0)
+ throw new ArgumentOutOfRangeException("index", "'index' must be non-negative and less than the size of the collection");
+
+ TKey key = List[index].Key;
+
+ List[index] = new KeyValuePair<TKey, TValue>(key, value);
+ Dictionary[key] = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the value at the specified index.
+ /// </summary>
+ /// <param name="index">The zero-based index of the value to get or set.</param>
+ /// <value>The value of the item at the specified index.</value>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0.<br/>
+ /// -or-<br/>
+ /// index is equal to or greater than <see cref="Count"/>.</exception>
+ /// <exception cref="ArgumentNullException"><paramref name="valueObject"/> is a null reference, and the value type of the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> is a value type.</exception>
+ /// <exception cref="ArgumentException">The value type of the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> is not in the inheritance hierarchy of <paramref name="valueObject"/>.</exception>
+ object IOrderedDictionary.this[int index]
+ {
+ get
+ {
+ return this[index];
+ }
+
+ set
+ {
+ this[index] = ConvertToValueType(value);
+ }
+ }
+
+ /// <summary>
+ /// Adds an entry with the specified key and value into the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection with the lowest available index.
+ /// </summary>
+ /// <param name="key">The key of the entry to add.</param>
+ /// <param name="value">The value of the entry to add. This value can be <null/>.</param>
+ /// <remarks>A key cannot be <null/>, but a value can be.
+ /// <para>You can also use the <see cref="P:OrderedDictionary{TKey,TValue}.Item(TKey)"/> property to add new elements by setting the value of a key that does not exist in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection; however, if the specified key already exists in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>, setting the <see cref="P:OrderedDictionary{TKey,TValue}.Item(TKey)"/> property overwrites the old value. In contrast, the <see cref="M:Add"/> method does not modify existing elements.</para></remarks>
+ /// <exception cref="ArgumentNullException"><paramref name="key"/> is <null/></exception>
+ /// <exception cref="ArgumentException">An element with the same key already exists in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see></exception>
+ void IDictionary<TKey, TValue>.Add(TKey key, TValue value)
+ {
+ Add(key, value);
+ }
+
+ /// <summary>
+ /// Adds an entry with the specified key and value into the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection with the lowest available index.
+ /// </summary>
+ /// <param name="key">The key of the entry to add.</param>
+ /// <param name="value">The value of the entry to add. This value can be <null/>.</param>
+ /// <returns>The index of the newly added entry</returns>
+ /// <remarks>A key cannot be <null/>, but a value can be.
+ /// <para>You can also use the <see cref="P:OrderedDictionary{TKey,TValue}.Item(TKey)"/> property to add new elements by setting the value of a key that does not exist in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection; however, if the specified key already exists in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>, setting the <see cref="P:OrderedDictionary{TKey,TValue}.Item(TKey)"/> property overwrites the old value. In contrast, the <see cref="M:Add"/> method does not modify existing elements.</para></remarks>
+ /// <exception cref="ArgumentNullException"><paramref name="key"/> is <null/></exception>
+ /// <exception cref="ArgumentException">An element with the same key already exists in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see></exception>
+ public int Add(TKey key, TValue value)
+ {
+ Dictionary.Add(key, value);
+ List.Add(new KeyValuePair<TKey,TValue>(key, value));
+ return Count - 1;
+ }
+
+ /// <summary>
+ /// Adds an entry with the specified key and value into the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection with the lowest available index.
+ /// </summary>
+ /// <param name="key">The key of the entry to add.</param>
+ /// <param name="value">The value of the entry to add. This value can be <null/>.</param>
+ /// <exception cref="ArgumentNullException"><paramref name="key"/> is <null/>.<br/>
+ /// -or-<br/>
+ /// <paramref name="value"/> is <null/>, and the value type of the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> is a value type.</exception>
+ /// <exception cref="ArgumentException">The key type of the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> is not in the inheritance hierarchy of <paramref name="key"/>.<br/>
+ /// -or-<br/>
+ /// The value type of the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> is not in the inheritance hierarchy of <paramref name="value"/>.</exception>
+ void IDictionary.Add(object key, object value)
+ {
+ Add(ConvertToKeyType(key), ConvertToValueType(value));
+ }
+
+ /// <summary>
+ /// Removes all elements from the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection.
+ /// </summary>
+ /// <remarks>The capacity is not changed as a result of calling this method.</remarks>
+ public void Clear()
+ {
+ Dictionary.Clear();
+ List.Clear();
+ }
+
+ /// <summary>
+ /// Determines whether the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection contains a specific key.
+ /// </summary>
+ /// <param name="key">The key to locate in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection.</param>
+ /// <returns><see langword="true"/> if the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection contains an element with the specified key; otherwise, <see langword="false"/>.</returns>
+ /// <exception cref="ArgumentNullException"><paramref name="key"/> is <null/></exception>
+ public bool ContainsKey(TKey key)
+ {
+ return Dictionary.ContainsKey(key);
+ }
+ /// <summary>
+ /// Determines whether the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection contains a specific key.
+ /// </summary>
+ /// <param name="key">The key to locate in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection.</param>
+ /// <returns><see langword="true"/> if the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection contains an element with the specified key; otherwise, <see langword="false"/>.</returns>
+ /// <exception cref="ArgumentNullException"><paramref name="key"/> is <null/></exception>
+ /// <exception cref="ArgumentException">The key type of the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> is not in the inheritance hierarchy of <paramref name="key"/>.</exception>
+ bool IDictionary.Contains(object key)
+ {
+ return ContainsKey(ConvertToKeyType(key));
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> has a fixed size.
+ /// </summary>
+ /// <value><see langword="true"/> if the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> has a fixed size; otherwise, <see langword="false"/>. The default is <see langword="false"/>.</value>
+ bool IDictionary.IsFixedSize
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection is read-only.
+ /// </summary>
+ /// <value><see langword="true"/> if the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> is read-only; otherwise, <see langword="false"/>. The default is <see langword="false"/>.</value>
+ /// <remarks>
+ /// A collection that is read-only does not allow the addition, removal, or modification of elements after the collection is created.
+ /// <para>A collection that is read-only is simply a collection with a wrapper that prevents modification of the collection; therefore, if changes are made to the underlying collection, the read-only collection reflects those changes.</para>
+ /// </remarks>
+ public bool IsReadOnly
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Gets an <see cref="ICollection"/> object containing the keys in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>.
+ /// </summary>
+ /// <value>An <see cref="ICollection"/> object containing the keys in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>.</value>
+ /// <remarks>The returned <see cref="ICollection"/> object is not a static copy; instead, the collection refers back to the keys in the original <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>. Therefore, changes to the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> continue to be reflected in the key collection.</remarks>
+ ICollection IDictionary.Keys
+ {
+ get
+ {
+ return (ICollection)Keys;
+ }
+ }
+
+ /// <summary>
+ /// Returns the zero-based index of the specified key in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>
+ /// </summary>
+ /// <param name="key">The key to locate in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see></param>
+ /// <returns>The zero-based index of <paramref name="key"/>, if <paramref name="ley"/> is found in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>; otherwise, -1</returns>
+ /// <remarks>This method performs a linear search; therefore it has a cost of O(n) at worst.</remarks>
+ public int IndexOfKey(TKey key)
+ {
+ if(null == key)
+ throw new ArgumentNullException("key");
+
+ for(int index = 0; index < List.Count; index++)
+ {
+ KeyValuePair<TKey, TValue> entry = List[index];
+ TKey next = entry.Key;
+ if(null != _comparer)
+ {
+ if(_comparer.Equals(next, key))
+ {
+ return index;
+ }
+ }
+ else if(next.Equals(key))
+ {
+ return index;
+ }
+ }
+
+ return -1;
+ }
+
+ /// <summary>
+ /// Removes the entry with the specified key from the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection.
+ /// </summary>
+ /// <param name="key">The key of the entry to remove</param>
+ /// <returns><see langword="true"/> if the key was found and the corresponding element was removed; otherwise, <see langword="false"/></returns>
+ public bool Remove(TKey key)
+ {
+ if(null == key)
+ throw new ArgumentNullException("key");
+
+ int index = IndexOfKey(key);
+ if(index >= 0)
+ {
+ if(Dictionary.Remove(key))
+ {
+ List.RemoveAt(index);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Removes the entry with the specified key from the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection.
+ /// </summary>
+ /// <param name="key">The key of the entry to remove</param>
+ void IDictionary.Remove(object key)
+ {
+ Remove(ConvertToKeyType(key));
+ }
+
+ /// <summary>
+ /// Gets an <see cref="ICollection"/> object containing the values in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection.
+ /// </summary>
+ /// <value>An <see cref="ICollection"/> object containing the values in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection.</value>
+ /// <remarks>The returned <see cref="ICollection"/> object is not a static copy; instead, the <see cref="ICollection"/> refers back to the values in the original <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection. Therefore, changes to the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> continue to be reflected in the <see cref="ICollection"/>.</remarks>
+ ICollection IDictionary.Values
+ {
+ get
+ {
+ return (ICollection)Values;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the value with the specified key.
+ /// </summary>
+ /// <param name="key">The key of the value to get or set.</param>
+ /// <value>The value associated with the specified key. If the specified key is not found, attempting to get it returns <null/>, and attempting to set it creates a new element using the specified key.</value>
+ public TValue this[TKey key]
+ {
+ get
+ {
+ return Dictionary[key];
+ }
+ set
+ {
+ if(Dictionary.ContainsKey(key))
+ {
+ Dictionary[key] = value;
+ List[IndexOfKey(key)] = new KeyValuePair<TKey, TValue>(key, value);
+ }
+ else
+ {
+ Add(key, value);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the value with the specified key.
+ /// </summary>
+ /// <param name="key">The key of the value to get or set.</param>
+ /// <value>The value associated with the specified key. If the specified key is not found, attempting to get it returns <null/>, and attempting to set it creates a new element using the specified key.</value>
+ object IDictionary.this[object key]
+ {
+ get
+ {
+ return this[ConvertToKeyType(key)];
+ }
+ set
+ {
+ this[ConvertToKeyType(key)] = ConvertToValueType(value);
+ }
+ }
+
+ /// <summary>
+ /// Copies the elements of the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> elements to a one-dimensional Array object at the specified index.
+ /// </summary>
+ /// <param name="array">The one-dimensional <see cref="Array"/> object that is the destination of the <see cref="T:KeyValuePair`2>"/> objects copied from the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>. The <see cref="Array"/> must have zero-based indexing.</param>
+ /// <param name="index">The zero-based index in <paramref name="array"/> at which copying begins.</param>
+ /// <remarks>The <see cref="M:CopyTo"/> method preserves the order of the elements in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see></remarks>
+ void ICollection.CopyTo(Array array, int index)
+ {
+ ((ICollection)List).CopyTo(array, index);
+ }
+
+ /// <summary>
+ /// Gets the number of key/values pairs contained in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection.
+ /// </summary>
+ /// <value>The number of key/value pairs contained in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> collection.</value>
+ public int Count
+ {
+ get
+ {
+ return List.Count;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether access to the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> object is synchronized (thread-safe).
+ /// </summary>
+ /// <value>This method always returns false.</value>
+ bool ICollection.IsSynchronized
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Gets an object that can be used to synchronize access to the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> object.
+ /// </summary>
+ /// <value>An object that can be used to synchronize access to the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> object.</value>
+ object ICollection.SyncRoot
+ {
+ get
+ {
+ if(this._syncRoot == null)
+ {
+ System.Threading.Interlocked.CompareExchange(ref this._syncRoot, new object(), null);
+ }
+ return this._syncRoot;
+ }
+ }
+
+ /// <summary>
+ /// Gets an <see cref="T:System.Collections.Generic.ICollection{TKey}">ICollection&lt;TKey&gt;</see> object containing the keys in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>.
+ /// </summary>
+ /// <value>An <see cref="T:System.Collections.Generic.ICollection{TKey}">ICollection&lt;TKey&gt;</see> object containing the keys in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>.</value>
+ /// <remarks>The returned <see cref="T:System.Collections.Generic.ICollection{TKey}">ICollection&lt;TKey&gt;</see> object is not a static copy; instead, the collection refers back to the keys in the original <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>. Therefore, changes to the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> continue to be reflected in the key collection.</remarks>
+ public ICollection<TKey> Keys
+ {
+ get
+ {
+ return Dictionary.Keys;
+ }
+ }
+
+ /// <summary>
+ /// Gets the value associated with the specified key.
+ /// </summary>
+ /// <param name="key">The key of the value to get.</param>
+ /// <param name="value">When this method returns, contains the value associated with the specified key, if the key is found; otherwise, the default value for the type of <paramref name="value"/>. This parameter can be passed uninitialized.</param>
+ /// <returns><see langword="true"/> if the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> contains an element with the specified key; otherwise, <see langword="false"/>.</returns>
+ public bool TryGetValue(TKey key, out TValue value)
+ {
+ return Dictionary.TryGetValue(key, out value);
+ }
+
+ /// <summary>
+ /// Gets an <see cref="T:ICollection{TValue}">ICollection&lt;TValue&gt;</see> object containing the values in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>.
+ /// </summary>
+ /// <value>An <see cref="T:ICollection{TValue}">ICollection&lt;TValue&gt;</see> object containing the values in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>.</value>
+ /// <remarks>The returned <see cref="T:ICollection{TValue}">ICollection&lt;TKey&gt;</see> object is not a static copy; instead, the collection refers back to the values in the original <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>. Therefore, changes to the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> continue to be reflected in the value collection.</remarks>
+ public ICollection<TValue> Values
+ {
+ get
+ {
+ return Dictionary.Values;
+ }
+ }
+
+ /// <summary>
+ /// Adds the specified value to the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> with the specified key.
+ /// </summary>
+ /// <param name="item">The <see cref="T:KeyValuePair{TKey,TValue}">KeyValuePair&lt;TKey,TValue&gt;</see> structure representing the key and value to add to the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>.</param>
+ void ICollection<KeyValuePair<TKey,TValue>>.Add(KeyValuePair<TKey, TValue> item)
+ {
+ Add(item.Key, item.Value);
+ }
+
+ /// <summary>
+ /// Determines whether the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> contains a specific key and value.
+ /// </summary>
+ /// <param name="item">The <see cref="T:KeyValuePair{TKey,TValue}">KeyValuePair&lt;TKey,TValue&gt;</see> structure to locate in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>.</param>
+ /// <returns><see langword="true"/> if <paramref name="keyValuePair"/> is found in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>; otherwise, <see langword="false"/>.</returns>
+ bool ICollection<KeyValuePair<TKey,TValue>>.Contains(KeyValuePair<TKey, TValue> item)
+ {
+ return ((ICollection<KeyValuePair<TKey,TValue>>)Dictionary).Contains(item);
+ }
+
+ /// <summary>
+ /// Copies the elements of the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see> to an array of type <see cref="T:KeyValuePair`2>"/>, starting at the specified index.
+ /// </summary>
+ /// <param name="array">The one-dimensional array of type <see cref="T:KeyValuePair{TKey,TValue}">KeyValuePair&lt;TKey,TValue&gt;</see> that is the destination of the <see cref="T:KeyValuePair{TKey,TValue}">KeyValuePair&lt;TKey,TValue&gt;</see> elements copied from the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>. The array must have zero-based indexing.</param>
+ /// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
+ void ICollection<KeyValuePair<TKey,TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
+ {
+ ((ICollection<KeyValuePair<TKey,TValue>>)Dictionary).CopyTo(array, arrayIndex);
+ }
+
+ /// <summary>
+ /// Removes a key and value from the dictionary.
+ /// </summary>
+ /// <param name="item">The <see cref="T:KeyValuePair{TKey,TValue}">KeyValuePair&lt;TKey,TValue&gt;</see> structure representing the key and value to remove from the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>.</param>
+ /// <returns><see langword="true"/> if the key and value represented by <paramref name="keyValuePair"/> is successfully found and removed; otherwise, <see langword="false"/>. This method returns <see langword="false"/> if <paramref name="keyValuePair"/> is not found in the <see cref="OrderedDictionary{TKey,TValue}">OrderedDictionary&lt;TKey,TValue&gt;</see>.</returns>
+ bool ICollection<KeyValuePair<TKey,TValue>>.Remove(KeyValuePair<TKey, TValue> item)
+ {
+ return Remove(item.Key);
+ }
+ }
+}
diff --git a/NW4RTools/bin/Debug/NW4RTools.dll b/NW4RTools/bin/Debug/NW4RTools.dll
new file mode 100755
index 0000000..adab05f
--- /dev/null
+++ 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
new file mode 100644
index 0000000..51a7fb0
--- /dev/null
+++ b/NW4RTools/bin/Debug/NW4RTools.dll.mdb
Binary files differ
diff --git a/TestApp/AssemblyInfo.cs b/TestApp/AssemblyInfo.cs
new file mode 100644
index 0000000..293a655
--- /dev/null
+++ b/TestApp/AssemblyInfo.cs
@@ -0,0 +1,27 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+
+[assembly: AssemblyTitle("TestApp")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("")]
+[assembly: AssemblyCopyright("")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+
+[assembly: AssemblyVersion("1.0.*")]
+
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+
diff --git a/TestApp/Main.cs b/TestApp/Main.cs
new file mode 100644
index 0000000..9fb3771
--- /dev/null
+++ b/TestApp/Main.cs
@@ -0,0 +1,13 @@
+using System;
+using NW4RTools;
+
+namespace TestApp {
+ class MainClass {
+ public static void Main(string[] args) {
+ //byte[] file = System.IO.File.ReadAllBytes("/home/me/Games/Newer/miscStuff/CS_W1/g3d/model.brres");
+ byte[] file = System.IO.File.ReadAllBytes("/mnt/h/ISOs/NSMBWii/ais0.1.3/model.brres");
+ ResFile rf = BrresReader.LoadFile(file);
+ }
+ }
+}
+
diff --git a/TestApp/TestApp.csproj b/TestApp/TestApp.csproj
new file mode 100644
index 0000000..5c96e3b
--- /dev/null
+++ b/TestApp/TestApp.csproj
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="3.5" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.21022</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{3A064CD8-CFAD-412D-986F-ED7D2D54CDB1}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <RootNamespace>TestApp</RootNamespace>
+ <AssemblyName>TestApp</AssemblyName>
+ <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug</OutputPath>
+ <DefineConstants>DEBUG</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <Externalconsole>true</Externalconsole>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>none</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Release</OutputPath>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <Externalconsole>true</Externalconsole>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Main.cs" />
+ <Compile Include="AssemblyInfo.cs" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+ <ItemGroup>
+ <ProjectReference Include="..\NW4RTools\NW4RTools.csproj">
+ <Project>{A9C9FABD-0A5F-4DAB-979D-9F288F96866F}</Project>
+ <Name>NW4RTools</Name>
+ </ProjectReference>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/TestApp/TestApp.pidb b/TestApp/TestApp.pidb
new file mode 100644
index 0000000..07e4928
--- /dev/null
+++ b/TestApp/TestApp.pidb
Binary files differ
diff --git a/TestApp/bin/Debug/NW4RTools.dll b/TestApp/bin/Debug/NW4RTools.dll
new file mode 100755
index 0000000..adab05f
--- /dev/null
+++ b/TestApp/bin/Debug/NW4RTools.dll
Binary files differ
diff --git a/TestApp/bin/Debug/NW4RTools.dll.mdb b/TestApp/bin/Debug/NW4RTools.dll.mdb
new file mode 100644
index 0000000..51a7fb0
--- /dev/null
+++ b/TestApp/bin/Debug/NW4RTools.dll.mdb
Binary files differ
diff --git a/TestApp/bin/Debug/TestApp.exe b/TestApp/bin/Debug/TestApp.exe
new file mode 100755
index 0000000..7adcab2
--- /dev/null
+++ b/TestApp/bin/Debug/TestApp.exe
Binary files differ
diff --git a/TestApp/bin/Debug/TestApp.exe.mdb b/TestApp/bin/Debug/TestApp.exe.mdb
new file mode 100644
index 0000000..7e547e0
--- /dev/null
+++ b/TestApp/bin/Debug/TestApp.exe.mdb
Binary files differ