Browse Source

EQ2Modeler (aka EQ2 Map Editor) upload

Image 4 years ago
parent
commit
8fa28fa054
100 changed files with 8879 additions and 0 deletions
  1. 43 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer.sln
  2. 14 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/App.config
  3. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Background.bmp
  4. 190 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/BitmapClass.cs
  5. 12 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/CPUClass.cs
  6. 82 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/CameraClass.cs
  7. 122 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/EQ2ModelViewer.csproj
  8. 20 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Configuration.xml
  9. 97 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Eq2VpkTool.csproj
  10. 138 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/License.txt
  11. 29 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Properties/AssemblyInfo.cs
  12. 63 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Properties/Resources.Designer.cs
  13. 117 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Properties/Resources.resx
  14. 26 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Properties/Settings.Designer.cs
  15. 7 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Properties/Settings.settings
  16. 68 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Readme.txt
  17. 169 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Source/Configuration.cs
  18. 90 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Source/DirectoryContentsComparer.cs
  19. 262 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Source/ExtractionManager.cs
  20. 550 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Source/FileSystemViewController.cs
  21. 171 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Source/IconManager.cs
  22. 44 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Source/Program.cs
  23. 103 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Source/TextureDecryptor.cs
  24. 343 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Source/UI/MainWindow.Designer.cs
  25. 572 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Source/UI/MainWindow.cs
  26. 309 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Source/UI/MainWindow.resx
  27. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Debug/Eq2VpkTool.exe
  28. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Debug/Eq2VpkTool.pdb
  29. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Debug/ICSharpCode.SharpZipLib.dll
  30. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Debug/Spart.dll
  31. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Debug/Spart.pdb
  32. 668 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Debug/Spart.xml
  33. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Debug/libeq2.dll
  34. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Debug/libeq2.pdb
  35. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Release/Eq2VpkTool.exe
  36. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Release/ICSharpCode.SharpZipLib.dll
  37. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Release/Spart.dll
  38. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Release/libeq2.dll
  39. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/eq2.ico
  40. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache
  41. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Debug/Eq2VpkTool.MainWindow.resources
  42. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Debug/Eq2VpkTool.Properties.Resources.resources
  43. 0 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Debug/Eq2VpkTool.csproj.CopyComplete
  44. 45 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Debug/Eq2VpkTool.csproj.FileListAbsolute.txt
  45. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Debug/Eq2VpkTool.csproj.GenerateResource.cache
  46. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Debug/Eq2VpkTool.csprojAssemblyReference.cache
  47. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Debug/Eq2VpkTool.exe
  48. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Debug/Eq2VpkTool.pdb
  49. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Release/Eq2VpkTool.MainWindow.resources
  50. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Release/Eq2VpkTool.Properties.Resources.resources
  51. 0 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Release/Eq2VpkTool.csproj.CopyComplete
  52. 20 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Release/Eq2VpkTool.csproj.FileListAbsolute.txt
  53. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Release/Eq2VpkTool.csproj.GenerateResource.cache
  54. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Release/Eq2VpkTool.csprojAssemblyReference.cache
  55. BIN
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Release/Eq2VpkTool.exe
  56. 35 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/FPSClass.cs
  57. 158 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/FontClass.cs
  58. 176 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/FontShaderClass.cs
  59. 153 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/FrustumClass.cs
  60. 50 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/GameObject.cs
  61. 269 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/GraphicClass.cs
  62. 151 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/InputClass.cs
  63. 208 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/LightShaderClass.cs
  64. 127 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Main.Designer.cs
  65. 551 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Main.cs
  66. 123 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Main.resx
  67. 159 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/MeshClass.cs
  68. 290 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Model.cs
  69. 30 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/ModelManager.cs
  70. 247 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/PositionClass.cs
  71. 22 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Program.cs
  72. 36 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Properties/AssemblyInfo.cs
  73. 71 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Properties/Resources.Designer.cs
  74. 117 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Properties/Resources.resx
  75. 30 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Properties/Settings.Designer.cs
  76. 7 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Properties/Settings.settings
  77. 37 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/SkyBox.cs
  78. 103 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Actions/ActionEventArgs.cs
  79. 33 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Actions/ActionHandler.cs
  80. 70 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Actions/Actions.cs
  81. 58 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Actions/Actors/AppendActor.cs
  82. 55 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Actions/Actors/AssignActor.cs
  83. 44 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Actions/Actors/IActor.cs
  84. 46 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Actions/Actors/ThrowActor.cs
  85. 58 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/AssemblyInfo.cs
  86. 78 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/BinaryTerminalParser.cs
  87. 68 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/Composite/AlternativeParser.cs
  88. 69 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/Composite/DifferenceParser.cs
  89. 68 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/Composite/IntersectionParser.cs
  90. 79 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/Composite/ListParser.cs
  91. 126 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/Composite/RepetitionParser.cs
  92. 71 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/Composite/SequenceParser.cs
  93. 33 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/Directives/LexemeDirective.cs
  94. 16 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/Dirs.cs
  95. 57 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/NegatableParser.cs
  96. 34 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/NonTerminal/IParserContext.cs
  97. 123 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/NonTerminal/NonTerminalParser.cs
  98. 74 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/NonTerminal/PostParseEventArgs.cs
  99. 30 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/NonTerminal/PostParseEventHandler.cs
  100. 65 0
      EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/NonTerminal/PreParseEventArgs.cs

+ 43 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer.sln

@@ -0,0 +1,43 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EQ2ModelViewer", "EQ2ModelViewer\EQ2ModelViewer.csproj", "{D82B7FE5-F1A5-4BEC-8D98-14DB7561C8B2}"
+	ProjectSection(ProjectDependencies) = postProject
+		{2F133C14-27F5-4593-BBCC-F12670A59E03} = {2F133C14-27F5-4593-BBCC-F12670A59E03}
+	EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Eq2VpkTool", "EQ2ModelViewer\Eq2VpkTool\Eq2VpkTool.csproj", "{2F133C14-27F5-4593-BBCC-F12670A59E03}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "libeq2", "EQ2ModelViewer\libeq2\libeq2.csproj", "{C81BFE28-59EA-421A-85BA-2E7F0A027595}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spart", "EQ2ModelViewer\Spart\Spart.csproj", "{52C43CC3-4D59-4328-9713-7225D3EEE893}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{D82B7FE5-F1A5-4BEC-8D98-14DB7561C8B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{D82B7FE5-F1A5-4BEC-8D98-14DB7561C8B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{D82B7FE5-F1A5-4BEC-8D98-14DB7561C8B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{D82B7FE5-F1A5-4BEC-8D98-14DB7561C8B2}.Release|Any CPU.Build.0 = Release|Any CPU
+		{2F133C14-27F5-4593-BBCC-F12670A59E03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{2F133C14-27F5-4593-BBCC-F12670A59E03}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{2F133C14-27F5-4593-BBCC-F12670A59E03}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{2F133C14-27F5-4593-BBCC-F12670A59E03}.Release|Any CPU.Build.0 = Release|Any CPU
+		{C81BFE28-59EA-421A-85BA-2E7F0A027595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{C81BFE28-59EA-421A-85BA-2E7F0A027595}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{C81BFE28-59EA-421A-85BA-2E7F0A027595}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C81BFE28-59EA-421A-85BA-2E7F0A027595}.Release|Any CPU.Build.0 = Release|Any CPU
+		{52C43CC3-4D59-4328-9713-7225D3EEE893}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{52C43CC3-4D59-4328-9713-7225D3EEE893}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{52C43CC3-4D59-4328-9713-7225D3EEE893}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{52C43CC3-4D59-4328-9713-7225D3EEE893}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal

+ 14 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/App.config

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
+    </startup>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="ICSharpCode.SharpZipLib" publicKeyToken="1b03e6acf1164f73" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-0.86.0.518" newVersion="0.86.0.518" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
+</configuration>

BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Background.bmp


+ 190 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/BitmapClass.cs

@@ -0,0 +1,190 @@
+using System;
+using System.Collections.Generic;
+
+using SlimDX;
+using SlimDX.D3DCompiler;
+using SlimDX.Direct3D11;
+using SlimDX.DXGI;
+using SlimDX.Windows;
+using Device = SlimDX.Direct3D11.Device;
+using Resource = SlimDX.Direct3D11.Resource;
+using Buffer = SlimDX.Direct3D11.Buffer;
+
+
+namespace EQ2ModelViewer {
+    public class BitmapClass {
+
+        public struct VertexType {
+            public Vector3 position;
+            public Vector2 texture;
+        };
+
+        Buffer m_vertexBuffer;
+        Buffer m_indexBuffer;
+        TextureClass m_texture;
+        int m_vertexCount;
+        int m_indexCount;
+        int m_screenWidth;
+        int m_screenHeight;
+        int m_bmpWidth;
+        int m_bmpHeight;
+        int m_prevPosX;
+        int m_prevPosY;
+        TextureShaderClass m_textureShader;
+        private Matrix m_BaseViewMatrix;
+
+        public bool Initialize(Device device, int screenWidth, int screenHeight, string filename, int bmpWidth, int bmpHeight, Matrix baseViewMatrix) {
+            // Store screen size
+            m_screenWidth = screenWidth;
+            m_screenHeight = screenHeight;
+
+            // Store the size in pixels that this bitmap should be rendered at
+            m_bmpWidth = bmpWidth;
+            m_bmpHeight = bmpHeight;
+
+            // Init previous rendering position to -1
+            m_prevPosX = -1;
+            m_prevPosY = -1;
+
+            m_BaseViewMatrix = baseViewMatrix;
+
+            // Init the vertex and index buffers
+            if (!InitializeBuffers(device))
+                return false;
+
+            if (!LoadTexture(device, filename))
+                return false;
+
+            m_textureShader = new TextureShaderClass();
+            if (!m_textureShader.Initialize(device))
+                return false;
+
+            return true;
+        }
+
+        public void ShutDown() {
+            if (m_indexBuffer != null)
+                m_indexBuffer.Dispose();
+            if (m_vertexBuffer != null)
+                m_vertexBuffer.Dispose();
+            if (m_texture != null)
+                m_texture.ShutDown();
+            if (m_textureShader != null)
+                m_textureShader.ShutDown();
+        }
+
+        public bool Render(GraphicClass Graphics, int posX, int posY) {
+            if (!UpdateBuffers(Graphics.Context, posX, posY))
+                return false;
+
+            RenderBuffers(Graphics);
+
+            return true;
+        }
+
+        public int GetIndexCount() {
+            return m_indexCount;
+        }
+
+        public ShaderResourceView GetTexture() {
+            return m_texture.GetTexture();
+        }
+
+        private bool InitializeBuffers(Device device) {
+            BufferDescription vertexBufferDesc;
+            BufferDescription indexBufferDesc;
+            DataStream vertexData;
+            DataStream indexData;
+
+            m_vertexCount = 6;
+            m_indexCount = m_vertexCount;
+
+            vertexData = new DataStream(m_vertexCount * System.Runtime.InteropServices.Marshal.SizeOf(typeof(VertexType)), true, true);
+            indexData = new DataStream(m_indexCount * sizeof(uint), true, true);
+
+            for (uint i = 0; i < m_indexCount; i++)
+                indexData.Write(i);
+
+            indexData.Position = 0;
+
+            vertexBufferDesc = new BufferDescription(System.Runtime.InteropServices.Marshal.SizeOf(typeof(VertexType)) * m_vertexCount, ResourceUsage.Dynamic, BindFlags.VertexBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0);
+            m_vertexBuffer = new Buffer(device, vertexData, vertexBufferDesc);
+
+            indexBufferDesc = new BufferDescription(System.Runtime.InteropServices.Marshal.SizeOf(typeof(UInt32)) * m_indexCount, ResourceUsage.Default, BindFlags.IndexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
+            m_indexBuffer = new Buffer(device, indexData, indexBufferDesc);
+
+            return true;
+        }
+
+        private bool UpdateBuffers(DeviceContext context, int posX, int posY) {
+            float left;
+            float right;
+            float top;
+            float bottom;
+            VertexType[] vertices;
+            DataBox mappedResource;
+
+            if (posX == m_prevPosX && posY == m_prevPosY)
+                return true;
+
+            m_prevPosX = posX;
+            m_prevPosY = posY;
+
+            // Calculate the screen coordinate of the left side of the bitmap
+            left = (float)((m_screenWidth / 2) * -1) + (float)posX;
+            right = left + (float)m_bmpWidth;
+
+            // Calculate the screen coordinates of the top of the bitmap
+            top = (float)(m_screenHeight / 2) - (float)posY;
+            bottom = top - (float)m_bmpHeight;
+
+            vertices = new VertexType[m_vertexCount];
+            vertices[0].position = new Vector3(left, top, 0.0f);
+            vertices[0].texture = new Vector2(0.0f, 0.0f);
+
+            vertices[1].position = new Vector3(right, bottom, 0.0f);
+            vertices[1].texture = new Vector2(1.0f, 1.0f);
+
+            vertices[2].position = new Vector3(left, bottom, 0.0f);
+            vertices[2].texture = new Vector2(0.0f, 1.0f);
+
+            vertices[3].position = new Vector3(left, top, 0.0f);
+            vertices[3].texture = new Vector2(0.0f, 0.0f);
+
+            vertices[4].position = new Vector3(right, top, 0.0f);
+            vertices[4].texture = new Vector2(1.0f, 0.0f);
+
+            vertices[5].position = new Vector3(right, bottom, 0.0f);
+            vertices[5].texture = new Vector2(1.0f, 1.0f);
+
+            mappedResource = context.MapSubresource(m_vertexBuffer, MapMode.WriteDiscard, SlimDX.Direct3D11.MapFlags.None);
+            VertexType[] data = vertices;
+            mappedResource.Data.WriteRange<VertexType>(data);
+            context.UnmapSubresource(m_vertexBuffer, 0);
+
+            return true;
+        }
+
+        private void RenderBuffers(GraphicClass Graphics) {
+            int stride;
+            int offset;
+
+            stride = System.Runtime.InteropServices.Marshal.SizeOf(typeof(VertexType));
+            offset = 0;
+
+            Graphics.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(m_vertexBuffer, stride, offset));
+            Graphics.Context.InputAssembler.SetIndexBuffer(m_indexBuffer, Format.R32_UInt, offset);
+            Graphics.Context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
+
+            m_textureShader.Render(Graphics.Context, m_indexCount, Graphics.GetWorldMatrix(), m_BaseViewMatrix, Graphics.GetOrthoMatrix(), GetTexture());
+        }
+
+        private bool LoadTexture(Device device, string filename) {
+            m_texture = new TextureClass();
+            if (!m_texture.Initialize(device, filename))
+                return false;
+
+            return true;
+        }
+    }
+}

+ 12 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/CPUClass.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EQ2ModelViewer
+{
+    public class CPUClass
+    {
+    }
+}

+ 82 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/CameraClass.cs

@@ -0,0 +1,82 @@
+using System;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+using SlimDX;
+using SlimDX.D3DCompiler;
+using SlimDX.Direct3D11;
+using SlimDX.DXGI;
+using SlimDX.Windows;
+using Device = SlimDX.Direct3D11.Device;
+using Resource = SlimDX.Direct3D11.Resource;
+using Buffer = SlimDX.Direct3D11.Buffer;
+
+namespace EQ2ModelViewer
+{
+    public class CameraClass
+    {
+        private float m_positionX;
+        private float m_positionY;
+        private float m_positionZ;
+        private float m_rotationX;
+        private float m_rotationY;
+        private float m_rotationZ;
+        private Matrix m_ViewMatrix;
+
+        public void SetPosition(float x, float y, float z)
+        {
+            m_positionX = x;
+            m_positionY = y;
+            m_positionZ = z;
+        }
+
+        public void SetPosition(Vector3 pos)
+        {
+            SetPosition(pos.X, pos.Y, pos.Z);
+        }
+
+        public void SetRotation(float x, float y, float z)
+        {
+            m_rotationX = x;
+            m_rotationY = y;
+            m_rotationZ = z;
+        }
+
+        public void SetRotation(Vector3 rot)
+        {
+            SetRotation(rot.X, rot.Y, rot.Z);
+        }
+
+        public Vector3 GetPosition()
+        {
+            return new Vector3(m_positionX, m_positionY, m_positionZ);
+        }
+
+        public Vector3 GetRotation()
+        {
+            return new Vector3(m_rotationX, m_rotationY, m_rotationZ);
+        }
+
+        public void Render()
+        {
+            Vector3 up = new Vector3(0.0f, 1.0f, 0.0f);
+            Vector3 pos = new Vector3(m_positionX, m_positionY, m_positionZ);
+            Vector3 lookAt = new Vector3(0.0f, 0.0f, 1.0f);
+            float pitch = m_rotationX * 0.0174532925f;
+            float yaw = m_rotationY * 0.0174532925f;
+            float roll = m_rotationZ * 0.0174532925f;
+            Matrix rotationMatrix = Matrix.RotationYawPitchRoll(yaw, pitch, roll);
+
+            lookAt = Vector3.TransformCoordinate(lookAt, rotationMatrix);
+            up = Vector3.TransformCoordinate(up, rotationMatrix);
+
+            lookAt = pos + lookAt;
+            m_ViewMatrix = Matrix.LookAtLH(pos, lookAt, up);
+        }
+
+        public Matrix GetViewMatrix()
+        {
+            return m_ViewMatrix;
+        }
+    }
+}

+ 122 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/EQ2ModelViewer.csproj

@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{D82B7FE5-F1A5-4BEC-8D98-14DB7561C8B2}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>EQ2ModelViewer</RootNamespace>
+    <AssemblyName>EQ2ModelViewer</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>x64</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="ICSharpCode.SharpZipLib">
+      <HintPath>libeq2\bin\Debug\ICSharpCode.SharpZipLib.dll</HintPath>
+    </Reference>
+    <Reference Include="libeq2">
+      <HintPath>libeq2\bin\Debug\libeq2.dll</HintPath>
+    </Reference>
+    <Reference Include="SlimDX, Version=4.0.13.43, Culture=neutral, PublicKeyToken=b1b0c32fd1ffe4f9, processorArchitecture=x86">
+      <HintPath>..\packages\SlimDX.4.0.13.44\lib\NET40\SlimDX.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Spart">
+      <HintPath>libeq2\bin\Debug\Spart.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Deployment" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="BitmapClass.cs" />
+    <Compile Include="CameraClass.cs" />
+    <Compile Include="CPUClass.cs" />
+    <Compile Include="FontClass.cs" />
+    <Compile Include="FontShaderClass.cs" />
+    <Compile Include="FPSClass.cs" />
+    <Compile Include="FrustumClass.cs" />
+    <Compile Include="GameObject.cs" />
+    <Compile Include="GraphicClass.cs" />
+    <Compile Include="InputClass.cs" />
+    <Compile Include="LightShaderClass.cs" />
+    <Compile Include="Main.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="Main.Designer.cs">
+      <DependentUpon>Main.cs</DependentUpon>
+    </Compile>
+    <Compile Include="MeshClass.cs" />
+    <Compile Include="Model.cs" />
+    <Compile Include="ModelManager.cs" />
+    <Compile Include="PositionClass.cs" />
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="SkyBox.cs" />
+    <Compile Include="TextClass.cs" />
+    <Compile Include="TextureClass.cs" />
+    <Compile Include="TextureShaderClass.cs" />
+    <Compile Include="TimerClass.cs" />
+    <EmbeddedResource Include="Main.resx">
+      <DependentUpon>Main.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+    <None Include="packages.config" />
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>

+ 20 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Configuration.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<configuration>
+	<!-- 
+	The encrypted-maps section defines the maps that the tool knows how to decrypt.
+	All the maps in the 'nrvobm' folder are encrypted using the same algorithm, 
+	but the key needed to decrypt them is sent from the server when the character
+	enters the zone, and varies from map to map.
+	This section lists the maps for which decryption keys are known.
+	-->
+    <encrypted-maps>
+        <map>
+            <name>nrvobm/nrvobm_antonica_west.dds</name>
+            <decryption-key>53 c9 0b 20 d8 fc 82 08</decryption-key>
+        </map>
+        <map>
+            <name>nrvobm/nrvobm_antonica_east.dds</name>
+            <decryption-key>5d 18 d3 5e 80 f4 c1 65</decryption-key>
+        </map>
+    </encrypted-maps>
+</configuration>

+ 97 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Eq2VpkTool.csproj

@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="14.0">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>8.0.50727</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{2F133C14-27F5-4593-BBCC-F12670A59E03}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <RootNamespace>Eq2VpkTool</RootNamespace>
+    <AssemblyName>Eq2VpkTool</AssemblyName>
+    <WarningLevel>4</WarningLevel>
+    <ApplicationIcon>eq2.ico</ApplicationIcon>
+    <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
+    <FileUpgradeFlags>
+    </FileUpgradeFlags>
+    <UpgradeBackupLocation>
+    </UpgradeBackupLocation>
+    <OldToolsVersion>2.0</OldToolsVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>.\bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <PlatformTarget>x64</PlatformTarget>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugSymbols>false</DebugSymbols>
+    <Optimize>true</Optimize>
+    <OutputPath>.\bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DesignTime>True</DesignTime>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+      <DependentUpon>Settings.settings</DependentUpon>
+    </Compile>
+    <Compile Include="Source\Configuration.cs" />
+    <Compile Include="Source\DirectoryContentsComparer.cs" />
+    <Compile Include="Source\ExtractionManager.cs" />
+    <Compile Include="Source\FileSystemViewController.cs" />
+    <Compile Include="Source\IconManager.cs" />
+    <Compile Include="Source\TextureDecryptor.cs" />
+    <Compile Include="Source\UI\MainWindow.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="Source\UI\MainWindow.Designer.cs">
+      <DependentUpon>MainWindow.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Source\Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <EmbeddedResource Include="Source\UI\MainWindow.resx">
+      <DependentUpon>MainWindow.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <SubType>Designer</SubType>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+    </EmbeddedResource>
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+    <AppDesigner Include="Properties\" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\libeq2\libeq2.csproj">
+      <Project>{C81BFE28-59EA-421A-85BA-2E7F0A027595}</Project>
+      <Package>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</Package>
+      <Name>libeq2</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="Configuration.xml">
+      <CopyToOutputDirectory>True</CopyToOutputDirectory>
+    </None>
+    <Content Include="eq2.ico" />
+    <Content Include="License.txt" />
+    <Content Include="Readme.txt" />
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSHARP.Targets" />
+</Project>

+ 138 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/License.txt

@@ -0,0 +1,138 @@
+GNU GENERAL PUBLIC LICENSE
+
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc.  
+59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too.
+
+When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and modification follow.
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. 
+
+    b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. 
+
+    c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) 
+
+These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.
+
+3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, 
+
+    b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, 
+
+    c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) 
+
+The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.
+
+If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
+
+5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.
+
+This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.
+
+NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
+How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
+
+To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
+
+one line to give the program's name and an idea of what it does.
+Copyright (C) yyyy  name of author
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this when it starts in an interactive mode:
+
+Gnomovision version 69, Copyright (C) year name of author
+Gnomovision comes with ABSOLUTELY NO WARRANTY; for details
+type `show w'.  This is free software, and you are welcome
+to redistribute it under certain conditions; type `show c' 
+for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names:
+
+Yoyodyne, Inc., hereby disclaims all copyright
+interest in the program `Gnomovision'
+(which makes passes at compilers) written 
+by James Hacker.
+
+signature of Ty Coon, 1 April 1989
+Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. 

+ 29 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Properties/AssemblyInfo.cs

@@ -0,0 +1,29 @@
+#region Using directives
+
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+#endregion
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Eq2VpkTool")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Eq2VpkTool")]
+[assembly: AssemblyCopyright("Copyright @ Blaz 2005")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Revision and Build Numbers 
+// by using the '*' as shown below:
+[assembly: AssemblyVersion("1.0.*")]

+ 63 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Properties/Resources.Designer.cs

@@ -0,0 +1,63 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Eq2VpkTool.Properties {
+    using System;
+    
+    
+    /// <summary>
+    ///   A strongly-typed resource class, for looking up localized strings, etc.
+    /// </summary>
+    // This class was auto-generated by the StronglyTypedResourceBuilder
+    // class via a tool like ResGen or Visual Studio.
+    // To add or remove a member, edit your .ResX file then rerun ResGen
+    // with the /str option, or rebuild your VS project.
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources {
+        
+        private static global::System.Resources.ResourceManager resourceMan;
+        
+        private static global::System.Globalization.CultureInfo resourceCulture;
+        
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources() {
+        }
+        
+        /// <summary>
+        ///   Returns the cached ResourceManager instance used by this class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager {
+            get {
+                if (object.ReferenceEquals(resourceMan, null)) {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Eq2VpkTool.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+        
+        /// <summary>
+        ///   Overrides the current thread's CurrentUICulture property for all
+        ///   resource lookups using this strongly typed resource class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture {
+            get {
+                return resourceCulture;
+            }
+            set {
+                resourceCulture = value;
+            }
+        }
+    }
+}

+ 117 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Properties/Resources.resx

@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 26 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Properties/Settings.Designer.cs

@@ -0,0 +1,26 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Eq2VpkTool.Properties {
+    
+    
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+        
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+        
+        public static Settings Default {
+            get {
+                return defaultInstance;
+            }
+        }
+    }
+}

+ 7 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Properties/Settings.settings

@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='iso-8859-1'?>
+<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>

+ 68 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Readme.txt

@@ -0,0 +1,68 @@
+Eq2VpkTool v1.2.3 Readme
+------------------------
+
+Eq2VpkTool is a simple tool that lets you view and extract the contents of Everquest II VPK files.
+This first version provides only basic functionality, but future versions may provide more advanced features.
+
+- Installation
+
+Just unzip all files to the desired folder.
+
+This tool is written in C# 2.0, so you need the .NET Framework 2.0. You can download the framework from:
+
+http://www.microsoft.com/downloads/details.aspx?familyid=0856EACB-4362-4B0D-8EDD-AAB15C5E04F5&displaylang=en (for x86)
+http://www.microsoft.com/downloads/details.aspx?familyid=B44A0000-ACF8-4FA1-AFFB-40E78D788B00&displaylang=en (for x64)
+
+There are no other prerequisites except for a working Everquest II installation.
+
+- Usage
+
+You can't open VPK files directly in this version. Instead you must open a VPL file (usually AssetsLib.vpl).
+
+Note: If you don't see an AssetsLib.vpl file on your main EQ2 directory please run EQ2 once so the file is created.
+
+VPL files don't contain any file data, they only reference a set of VPK files. Once you point the tool to a 
+valid VPL file it will start processing the referenced VPK files and listing their contents. You can start 
+exploring the contents immediately. The list of contents will be updated in real-time.
+
+Processing the whole Everquest II asset library takes anywhere between five seconds and a few minutes, depending 
+on the speed of the machine. Extracting all of it takes much longer.
+
+At any time, you can view any of the files by double-clicking on them. They will be extracted to your temporary
+folder and deleted when you close the application.
+
+You can extract a directory by right-clicking on it in the directory tree and selecting "Extract" or "Extract with
+path information" from the context menu. You can also use the main menu for this. Alternatively, you can select multiple
+files and folders to extract from the file list view.
+
+If you choose the "Extract with path information" option, the directory structure will be preserved when the files
+are extracted. If you just want to extract the selected files/directories to the destination folder with no extra
+folders created, use the "Extract" option.
+
+- Decrypting maps
+
+Starting from version 1.2 it is possible to decrypt some of them maps in the 'nrvobm' directory. When you right-click
+on any file in this directory you'll see a new option in the context menu: "Decrypt and extract". This option is only
+enabled for the maps for which the tool knows the decryption key.
+
+All the maps in the 'nrvobm' folder are encrypted using the same algorithm, but the key needed to decrypt them is sent 
+from the server when the character enters the zone, and varies from map to map. The known decryption keys are stored
+in the configuration file.
+
+If you don't find an 'nrvobm' folder in your VPK files it probably means it was removed on a patch.
+
+- Notes
+
+* Don't try opening the "MediaSupLib.vpl" file, as it is not supported. This file is not a valid VPL file.
+
+- License
+
+This program is free software and is distributed under the GNU General Public License. 
+
+Read License.txt for details.
+
+- Contact
+
+Please send any feedback to blaz@blazlabs.com.
+
+Website: http://eq2.blazlabs.com

+ 169 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Source/Configuration.cs

@@ -0,0 +1,169 @@
+#region Using directives
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Xml;
+using System.Xml.XPath;
+using System.IO;
+
+#endregion
+
+namespace Eq2VpkTool
+{
+    public class Configuration
+    {
+        #region Constructors
+        static Configuration()
+        {
+            instance = new Configuration();
+        }
+
+
+        protected Configuration()
+        {
+        }
+        #endregion
+
+
+        public static Configuration Instance
+        {
+            get { return instance; }
+        }
+
+
+        public void Load(Stream stream)
+        {
+            reader    = new XPathDocument(stream);
+            navigator = reader.CreateNavigator();
+            loaded    = true;
+        }
+
+
+        public string GetValue(string key)
+        {
+            #region Preconditions
+            Debug.Assert(reader != null && navigator != null, "No XML file loaded, call Load() first.");
+            #endregion
+
+            XPathNavigator nodeNavigator;
+            
+            // Select the first node that matches the query.
+            // If the query is invalid, XPathNavigator.SelectSingleNode() throws an exception and we return null.
+            try               { nodeNavigator = navigator.SelectSingleNode(key); }
+            catch (Exception) { return null; }
+
+            // If the query is valid but it yields no results, return null.
+            if (nodeNavigator == null) return null;
+
+            // Otherwise, return the first node found.
+            return nodeNavigator.Value;
+        }
+
+
+        /*public T? GetValue<T>(string key)
+        {
+            #region Preconditions
+            Debug.Assert(typeof(T).IsValueType, "Type " + typeof(T).Name + " is not a value type. Use of GetValue<T>() requires a value type. Use GetValue() instead.");
+            Debug.Assert(reader != null && navigator != null, "No XML file loaded, call Load() first.");
+            #endregion
+
+            XPathNavigator nodeNavigator;
+
+            // Select the first node that matches the query.
+            // If the query is invalid, XPathNavigator.SelectSingleNode() throws an exception and we return null.
+            try               { nodeNavigator = navigator.SelectSingleNode(key); }
+            catch (Exception) { return null; }
+
+            // If the query is valid but yields no results, return null.
+            if (nodeNavigator == null) return null;
+
+            T? value;
+
+            // Read the first result as a value of the required type.
+            // If the format or the cast is invalid we return null.
+            try               { value = (T)nodeNavigator.ValueAs(typeof(T)); }
+            catch (Exception) { return null; }
+
+            // Otherwise, we return the converted value.
+            return value;
+        }*/
+
+
+        public IEnumerator<string> GetValues(string key)
+        {
+            #region Preconditions
+            Debug.Assert(reader != null && navigator != null, "No XML file loaded");
+            #endregion
+
+            XPathNodeIterator nodes;
+
+            // Select all the nodes that match the query.
+            // If the query is invalid XPathNavigator.Select() throws an exception and we return an empty enumerator.
+            try               { nodes = navigator.Select(key); }
+            catch (Exception) { yield break; }
+
+            // Yield values until we consume all of them.
+            foreach (XPathNavigator node in nodes) yield return node.Value;
+        }
+
+
+        /*public IEnumerator<T> GetValues<T>(string key)
+        {
+            // First check whether the type parameter T is actually class String.
+            // If it is, redirect this method call to GetValues().
+            // Note: This workaround makes it possible to use GetValues() and GetValues<string>() interchangeably.
+
+            if (typeof(T) == typeof(string))
+            {
+                // This cast is safe because we've made sure T is String.
+                IEnumerator<T> iterator = GetValues(key) as IEnumerator<T>;
+
+                // We can't just return the iterator, we have to yield all the elements.
+                while (iterator.MoveNext()) yield return iterator.Current;
+            }
+            else
+            {
+                #region Preconditions
+                Debug.Assert(typeof(T).IsValueType, "Type " + typeof(T).Name + " is not a value type. Use of GetValue<T>() requires a value type. Use GetValue() instead.");
+                Debug.Assert(reader != null && navigator != null, "No XML file loaded, call Load() first.");
+                #endregion
+
+                XPathNodeIterator nodes;
+
+                // Select all the nodes that match the query.
+                // If the query is invalid XPathNavigator.Select throws an exception and we return null.
+                try               { nodes = navigator.Select(key); }
+                catch (Exception) { yield break; }
+
+                // Yield values until we consume all of them.
+                foreach (XPathNavigator node in nodes)
+                {
+                    T? value = null;
+
+                    // If the format or the cast to type T is invalid we don't yield this value.
+                    // Note: If none of the values have valid conversions to T, an empty enumerator is returned.
+                    try               { value = (T)node.ValueAs(typeof(T)); }
+                    catch (Exception) {}
+
+                    if (value.HasValue) yield return value.Value;
+                }
+            }
+        }*/
+
+
+        public bool IsLoaded
+        {
+            get { return loaded; }
+        }
+
+
+        private bool           loaded = false;
+        private XPathDocument  reader;
+        private XPathNavigator navigator;
+
+        private static Configuration instance;
+    }
+}
+
+/* EOF */

+ 90 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Source/DirectoryContentsComparer.cs

@@ -0,0 +1,90 @@
+#region License information
+// ----------------------------------------------------------------------------
+//
+//          Eq2VpkTool - A tool to extract Everquest II VPK files
+//                         Blaz (blaz@blazlabs.com)
+//
+//       This program is free software; you can redistribute it and/or
+//        modify it under the terms of the GNU General Public License
+//      as published by the Free Software Foundation; either version 2
+//          of the License, or (at your option) any later version.
+//
+//      This program is distributed in the hope that it will be useful,
+//      but WITHOUT ANY WARRANTY; without even the implied warranty of
+//       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//                GNU General Public License for more details.
+//
+//      You should have received a copy of the GNU General Public License
+//         along with this program; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+//
+//   ( The full text of the license can be found in the License.txt file )
+//
+// ----------------------------------------------------------------------------
+#endregion
+
+#region Using directives
+
+using System;
+using System.Collections;
+using System.Windows.Forms;
+
+using Eq2FileSystemInfo = Everquest2.IO.FileSystemInfo;
+using Eq2FileInfo       = Everquest2.IO.FileInfo;
+using Eq2DirectoryInfo  = Everquest2.IO.DirectoryInfo;
+
+#endregion
+
+namespace Eq2VpkTool
+{
+    /// <summary>
+    /// Compares two file system items for sorting the list view.
+    /// Directories always compare lower than files.
+    /// Directories with directories and files with files compare lexicographically.
+    /// </summary>
+    public class DirectoryContentsComparer : IComparer
+    {
+        public int Compare(object x, object y)
+        {
+            ListViewItem item1 = x as ListViewItem;
+            ListViewItem item2 = y as ListViewItem;
+
+            // The item tags could be null because some of the ListViewItem's passed to this function
+            // are probably newly created items.
+            if (item1.Tag == null) return  1;
+            if (item2.Tag == null) return -1;
+
+            Eq2FileSystemInfo child1 = item1.Tag as Eq2FileSystemInfo;
+            Eq2FileSystemInfo child2 = item2.Tag as Eq2FileSystemInfo;
+
+            if (child1 is Eq2DirectoryInfo)
+            {
+                if (child2 is Eq2DirectoryInfo)
+                {
+                    Eq2DirectoryInfo directory1 = child1 as Eq2DirectoryInfo;
+                    Eq2DirectoryInfo directory2 = child2 as Eq2DirectoryInfo;
+
+                    return directory1.Name.CompareTo(directory2.Name);
+                }
+                else
+                {
+                    return -1;
+                }
+            }
+            else
+            {
+                if (child2 is Eq2DirectoryInfo)
+                {
+                    return 1;
+                }
+                else
+                {
+                    Eq2FileInfo file1 = child1 as Eq2FileInfo;
+                    Eq2FileInfo file2 = child2 as Eq2FileInfo;
+
+                    return file1.Name.CompareTo(file2.Name);
+                }
+            }
+        }
+    }
+}

+ 262 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Source/ExtractionManager.cs

@@ -0,0 +1,262 @@
+#region License information
+// ----------------------------------------------------------------------------
+//
+//          Eq2VpkTool - A tool to extract Everquest II VPK files
+//                         Blaz (blaz@blazlabs.com)
+//
+//       This program is free software; you can redistribute it and/or
+//        modify it under the terms of the GNU General Public License
+//      as published by the Free Software Foundation; either version 2
+//          of the License, or (at your option) any later version.
+//
+//      This program is distributed in the hope that it will be useful,
+//      but WITHOUT ANY WARRANTY; without even the implied warranty of
+//       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//                GNU General Public License for more details.
+//
+//      You should have received a copy of the GNU General Public License
+//         along with this program; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+//
+//   ( The full text of the license can be found in the License.txt file )
+//
+// ----------------------------------------------------------------------------
+#endregion
+
+#region Using directives
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Collections.Generic;
+
+using Eq2FileSystem     = Everquest2.IO.FileSystem;
+using Eq2FileSystemInfo = Everquest2.IO.FileSystemInfo;
+using Eq2FileInfo       = Everquest2.IO.FileInfo;
+using Eq2DirectoryInfo  = Everquest2.IO.DirectoryInfo;
+using Eq2FileStream     = Everquest2.IO.FileStream;
+
+#endregion
+
+namespace Eq2VpkTool
+{
+    /// <summary>
+    /// Manages a set of extractor threads.
+    /// </summary>
+    public class ExtractionManager
+    {
+        #region Methods
+        /// <summary>
+        /// Begins an asynchronous extraction.
+        /// </summary>
+        /// <param name="items">Files and directories to extract.</param>
+        /// <param name="outputPath">Path to extract the items to. Must end with a directory separator character.</param>
+        /// <param name="userCallback">Callback method to invoke every time a file is extracted.</param>
+        /// <param name="stateObject">User defined object.</param>
+        /// <returns>IAsyncResult object that can be used to query the state of the extraction.</returns>
+        public IAsyncResult BeginExtract(Eq2FileSystemInfo[] items, string outputPath, AsyncCallback userCallback, object stateObject)
+        {
+            Thread      thread = new Thread(new ParameterizedThreadStart(Extract));
+            AsyncResult result = new AsyncResult(thread, items, outputPath, userCallback, stateObject);
+
+            lock (currentExtractions) currentExtractions.Add(result);
+            thread.Start(result);
+
+            return result;
+        }
+
+
+        public void Extract(object obj)
+        {
+            AsyncResult info = obj as AsyncResult;
+
+            try
+            {
+                try 
+                { 
+                    if (!Directory.Exists(info.outputPath))
+                    {
+                        Directory.CreateDirectory(info.outputPath);
+                    }
+
+                    foreach (Eq2FileSystemInfo item in info.items)
+                    {
+                        if (item is Eq2DirectoryInfo)
+                        {
+                            ExtractDirectory(item as Eq2DirectoryInfo, info.outputPath, info);
+                        }
+                        else if (item is Eq2FileInfo)
+                        {
+                            FileStream stream = ExtractFile(item as Eq2FileInfo, info.outputPath, info);
+                            // Close the stream so the file gets written to disk immediately.
+                            stream.Close();
+                        }
+                    }
+                }
+                catch (UnauthorizedAccessException) {}
+                catch (IOException) {}
+            }
+            finally
+            {
+                lock (currentExtractions) currentExtractions.Remove(info);
+            }
+        }
+
+
+        public FileStream ExtractFile(Eq2FileInfo file, string outputPath)
+        {
+            AsyncResult dummy = new AsyncResult();
+
+            return ExtractFile(file, outputPath, dummy);
+        }
+
+
+        private FileStream ExtractFile(Eq2FileInfo file, string outputPath, AsyncResult info)
+        {
+            string     filename = outputPath + file.Name;
+            FileStream stream   = new FileStream(filename, FileMode.Create, FileAccess.Write);
+
+            using (Eq2FileStream eq2Stream = file.OpenRead())
+            {
+                long   size = eq2Stream.Length;
+                byte[] data = new byte[size];
+
+                eq2Stream.Read(data, 0, (int)size);
+                stream.Write(data, 0, (int)size);
+            }
+
+            if (info.userCallback != null) info.userCallback(info);
+
+            return stream;
+        }
+
+
+        public void ExtractDirectory(Eq2DirectoryInfo directory, string outputPath)
+        {
+            AsyncResult dummy = new AsyncResult();
+
+            ExtractDirectory(directory, outputPath, dummy);
+        }
+
+
+        private void ExtractDirectory(Eq2DirectoryInfo directory, string outputPath, AsyncResult info)
+        {
+            string directoryPath = outputPath + directory.Name + Path.DirectorySeparatorChar;
+            Directory.CreateDirectory(directoryPath); 
+
+            Eq2FileSystemInfo[] children = directory.GetFileSystemInfos();
+            foreach (Eq2FileSystemInfo child in children)
+            {
+                if (child is Eq2DirectoryInfo)
+                {
+                    ExtractDirectory(child as Eq2DirectoryInfo, directoryPath, info);
+                }
+                else if (child is Eq2FileInfo)
+                {
+                    FileStream stream = ExtractFile(child as Eq2FileInfo, directoryPath, info);
+                    // Close the stream so the file gets written to disk immediately.
+                    stream.Close();
+                }
+            }
+        }
+
+
+        /// <summary>
+        /// Blocks the calling thread until the specified extraction completes.
+        /// </summary>
+        /// <param name="asyncResult">Object representing the state, as returned from BeginExtract.</param>
+        /// <returns>Always returns zero.</returns>
+        public int EndExtract(IAsyncResult asyncResult)
+        {
+            AsyncResult result = asyncResult as AsyncResult;
+
+            result.thread.Join();
+            result.CompletedSynchronously = true;
+            lock (currentExtractions) currentExtractions.Remove(result);
+
+            return 0;
+        }
+
+
+        /// <summary>
+        /// Aborts all ongoing extractions.
+        /// </summary>
+        public void Close()
+        {
+            AsyncResult[] extractionInfos;
+            lock (currentExtractions)
+            {
+                extractionInfos = new AsyncResult[currentExtractions.Count];
+                currentExtractions.CopyTo(extractionInfos, 0);
+            }
+
+            foreach (AsyncResult extractionInfo in extractionInfos)
+            {
+                extractionInfo.thread.Abort();
+                extractionInfo.thread.Join();
+            }
+        }
+        #endregion
+
+
+        #region Types
+        private class AsyncResult : IAsyncResult
+        {
+            public AsyncResult()
+            {
+            }
+
+
+            public AsyncResult(Thread thread, Eq2FileSystemInfo[] items, string outputPath, AsyncCallback userCallback, object stateObject)
+            {
+                this.thread       = thread;
+                this.items        = items;
+                this.outputPath   = outputPath;
+                this.userCallback = userCallback;
+                this.stateObject  = stateObject;
+
+                completedSynchronously = false;
+            }
+
+
+            public object AsyncState
+            {
+                get { return stateObject; }
+            }
+
+
+            public bool IsCompleted
+            {
+                get { return !thread.IsAlive; }
+            }
+
+
+            public bool CompletedSynchronously
+            {
+                get          { return completedSynchronously; }
+                internal set { completedSynchronously = value; }
+            }
+
+
+            public WaitHandle AsyncWaitHandle
+            {
+                // Not implemented. Always return null.
+                get { return null; }
+            }
+
+
+            public  Thread              thread;
+            public  Eq2FileSystemInfo[] items;
+            public  string              outputPath;
+            public  AsyncCallback       userCallback;
+            private object              stateObject;
+            private bool                completedSynchronously;
+        }
+        #endregion
+
+
+        #region Fields
+        private IList<AsyncResult> currentExtractions = new List<AsyncResult>();
+        #endregion
+    }
+}

+ 550 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Source/FileSystemViewController.cs

@@ -0,0 +1,550 @@
+#region License information
+// ----------------------------------------------------------------------------
+//
+//          Eq2VpkTool - A tool to extract Everquest II VPK files
+//                         Blaz (blaz@blazlabs.com)
+//
+//       This program is free software; you can redistribute it and/or
+//        modify it under the terms of the GNU General Public License
+//      as published by the Free Software Foundation; either version 2
+//          of the License, or (at your option) any later version.
+//
+//      This program is distributed in the hope that it will be useful,
+//      but WITHOUT ANY WARRANTY; without even the implied warranty of
+//       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//                GNU General Public License for more details.
+//
+//      You should have received a copy of the GNU General Public License
+//         along with this program; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+//
+//   ( The full text of the license can be found in the License.txt file )
+//
+// ----------------------------------------------------------------------------
+#endregion
+
+#region Using directives
+
+using System;
+using System.Threading;
+using System.Windows.Forms;
+using System.Collections.Generic;
+using System.IO;
+
+using Eq2FileSystem     = Everquest2.IO.FileSystem;
+using Eq2FileSystemInfo = Everquest2.IO.FileSystemInfo;
+using Eq2FileInfo       = Everquest2.IO.FileInfo;
+using Eq2DirectoryInfo  = Everquest2.IO.DirectoryInfo;
+
+#endregion
+
+namespace Eq2VpkTool
+{
+    public class FileSystemViewController
+    {
+        #region Methods
+
+        public void Open(string filename, TreeView treeview, ListView listview)
+        {
+            this.treeview = treeview;
+            this.listview = listview;
+
+            nodeDictionary.Clear();
+            fileCount = 0;
+
+            filesystem        = new Eq2FileSystem();
+            extractionManager = new ExtractionManager();
+
+            // Initialize treeview and listview
+            treeview.Nodes.Clear();
+            listview.Items.Clear();
+
+            listview.Sorting            = SortOrder.Ascending;
+            listview.ListViewItemSorter = new DirectoryContentsComparer();
+
+            iconManager             = new IconManager(new ImageList());
+            treeview.ImageList      = iconManager.ImageList;
+            treeview.ImageIndex     = iconManager.GetDirectoryImageIndex();
+            listview.SmallImageList = iconManager.ImageList;
+
+            // We must first de-register the event handlers, in case they were registered in a previous call to Open().
+            treeview.BeforeExpand   -= OnExpandNode;
+            treeview.BeforeExpand   += OnExpandNode;
+            treeview.BeforeCollapse -= OnCollapseNode;
+            treeview.BeforeCollapse += OnCollapseNode;
+            treeview.BeforeSelect   -= OnSelectNode;
+            treeview.BeforeSelect   += OnSelectNode;
+            listview.DoubleClick    -= OnListViewDoubleClick;
+            listview.DoubleClick    += OnListViewDoubleClick;
+
+            thread = new Thread(new ParameterizedThreadStart(OpenFileSystem));
+            thread.Start(filename);
+        }
+
+
+        /// <summary>
+        /// Start function for the processing thread.
+        /// </summary>
+        /// <param name="obj">String that represents the path to the VPL file.</param>
+        private void OpenFileSystem(object obj)
+        {
+            filename = obj as string;
+
+            try
+            {
+                filesystem.DirectoryAdded += OnRootDirectoryAdded;
+                filesystem.FileAdded      += OnFileAdded;
+                filesystem.Open(filename);
+            }
+            catch (ThreadAbortException)
+            {
+            }
+            catch (Exception e)
+            {
+                MessageBox.Show("Error processing file.\n\n" + e, "Error");
+            }
+        }
+        
+
+        /// <summary>
+        /// Stops the processing thread and deletes all generated temporary files.
+        /// </summary>
+        public void Close()
+        {
+            if (thread != null)
+            {
+                thread.Abort();
+                thread.Join();
+                thread = null;
+
+                // Delete temporary files
+                foreach (string path in temporaryFiles)
+                {
+                    try                                 { File.Delete(path); }
+                    catch (UnauthorizedAccessException) {}
+                    catch (IOException)                 {}
+                }
+            }
+        }
+
+
+        /// <summary>
+        /// Callback invoked every time a file is added to the file system.
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        private void OnFileAdded(object sender, Eq2FileSystem.FileAddedEventArgs e)
+        {
+            lock (this) ++fileCount;
+        }
+
+
+        /// <summary>
+        /// Callback invoked when the first directory (the root directory) is added to the file system.
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        private void OnRootDirectoryAdded(object sender, Eq2FileSystem.DirectoryAddedEventArgs e)
+        {
+            filesystem.DirectoryAdded  -= OnRootDirectoryAdded;
+            e.directory.DirectoryAdded += OnDirectoryAdded;
+
+            treeview.Invoke(new Action<Eq2DirectoryInfo>(AddRootDirectory), e.directory);
+        }
+
+
+        /// <summary>
+        /// Adds the root directory node to the TreeView.
+        /// </summary>
+        /// <remarks>This method is called from the UI thread.</remarks>
+        /// <param name="rootDirectory">Root directory.</param>
+        private void AddRootDirectory(Eq2DirectoryInfo rootDirectory)
+        {
+            string rootNodeText = System.IO.Path.GetFileName(filename);
+
+            TreeNode node = new TreeNode(rootNodeText);
+            node.Tag = rootDirectory;
+
+            treeview.Nodes.Add(node);
+            // No need to acquire the lock on nodeDictionary because we're sure 
+            // no other thread will be accessing it at this point.
+            nodeDictionary.Add(rootDirectory, new NodeInfo(node));
+        }
+
+
+        /// <summary>
+        /// Callback invoked every time a new directory is added to an existing directory.
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        private void OnDirectoryAdded(object sender, Eq2FileSystem.DirectoryAddedEventArgs e)
+        {
+            Eq2DirectoryInfo directory       = e.directory;
+            Eq2DirectoryInfo parentDirectory = directory.Parent;
+
+            NodeInfo parentDirectoryNodeInfo;
+            lock (nodeDictionary) parentDirectoryNodeInfo = nodeDictionary[parentDirectory];
+                
+            if (parentDirectoryNodeInfo.Expanded)
+            {
+                // The parent directory is expanded, so we must reflect the addition of the new directory.
+                // This new directory doesn't have any subdirectories yet, so we will install a listener on it.
+                TreeNode directoryNode = treeview.Invoke(new AddDirectoryDelegate(AddDirectoryToExpandedDirectory), 
+                                                         parentDirectoryNodeInfo.Node, 
+                                                         directory) as TreeNode;
+
+                lock (nodeDictionary) nodeDictionary.Add(directory, new NodeInfo(directoryNode));
+                directory.DirectoryAdded += OnDirectoryAdded;
+            }
+            else
+            {
+                // The parent directory didn't have any subdirectories, but it now has one.
+                // We'll add a dummy child node so the plus icon appears and the node can be expanded.
+                // Note: More than one thread can invoke this method before we deregister from the DirectoryAdded event.
+                //       The means more than one dummy subnode can be added to this directory.
+                parentDirectory.DirectoryAdded -= OnDirectoryAdded;
+
+                treeview.Invoke(new Action<TreeNode>(AddDirectoryToCollapsedDirectory), parentDirectoryNodeInfo.Node);
+            }
+        }
+
+
+        /// <summary>
+        /// Adds a new tree node as a child of an expanded directory node.
+        /// </summary>
+        /// <remarks>This method is called from the UI thread.</remarks>
+        /// <param name="parentNode"></param>
+        /// <param name="directory"></param>
+        /// <returns></returns>
+        private TreeNode AddDirectoryToExpandedDirectory(TreeNode parentNode, Eq2DirectoryInfo directory)
+        {
+            string directoryName = directory.Name;
+
+            TreeNode node = parentNode.Nodes.Add(directoryName, directoryName);
+            node.Tag = directory;
+
+            return node;
+        }
+
+
+        /// <summary>
+        /// Adds a dummy tree node as a child of a collapsed directory node.
+        /// This dummy node makes the treeview render a plus sign used to expand the directory node.
+        /// </summary>
+        /// <remarks>This method is called from the UI thread.</remarks>
+        /// <param name="directoryNode"></param>
+        private void AddDirectoryToCollapsedDirectory(TreeNode directoryNode)
+        {
+            directoryNode.Nodes.Add(new TreeNode());
+        }
+
+
+        /// <summary>
+        /// Callback invoked when a tree node is expanded.
+        /// This method must create the nodes for the subdirectories of the expanded directory.
+        /// </summary>
+        /// <remarks>This method is called from the UI thread.</remarks>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        private void OnExpandNode(object sender, TreeViewCancelEventArgs e)
+        {
+            TreeNode         node      = e.Node;
+            Eq2DirectoryInfo directory = node.Tag as Eq2DirectoryInfo;
+
+            // Remove dummy nodes if present
+            if (node.Nodes[0].Text.Length == 0) node.Nodes.Clear();
+
+            lock (nodeDictionary)
+            {
+                treeview.BeginUpdate();
+
+                // Add new subdirectories as nodes to the treeview and to the node dictionary.
+                Eq2DirectoryInfo[] subdirectories = directory.GetDirectories();
+                foreach (Eq2DirectoryInfo subdirectory in subdirectories)
+                {
+                    if (!node.Nodes.ContainsKey(subdirectory.Name))
+                    {
+                        TreeNode subdirectoryNode = AddDirectoryToExpandedDirectory(node, subdirectory);
+
+                        nodeDictionary.Add(subdirectory, new NodeInfo(subdirectoryNode));
+
+                        // Check whether this subdirectory has subdirectories of its own or not.
+                        // If it does, we will add a dummy tree node to it so the plus icon appears.
+                        // If it does not, we will track any changes to it.
+                        if (subdirectory.DirectoryCount > 0)
+                        {
+                            subdirectoryNode.Nodes.Add(new TreeNode());
+                        }
+                        else
+                        {
+                            subdirectory.DirectoryAdded += OnDirectoryAdded;
+                        }
+                    }
+                }
+
+                treeview.EndUpdate();
+
+                // Mark this directory as expanded
+                nodeDictionary[directory].Expanded = true;
+
+                directory.DirectoryAdded += OnDirectoryAdded;
+            }
+        }
+
+
+        /// <summary>
+        /// Callback invoked when a tree node is collapsed.
+        /// We won't delete the tree nodes already added to the collapsed directory, 
+        /// but we won't add any more until the node is expanded again.
+        /// </summary>
+        /// <remarks>This method is called from the UI thread.</remarks>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        private void OnCollapseNode(object sender, TreeViewCancelEventArgs e)
+        {
+            TreeNode         node      = e.Node;
+            Eq2DirectoryInfo directory = node.Tag as Eq2DirectoryInfo;
+
+            directory.DirectoryAdded -= OnDirectoryAdded;
+
+            // Mark this directory as collapsed
+            lock (nodeDictionary) nodeDictionary[directory].Expanded = false;
+        }
+
+
+        /// <summary>
+        /// Callback invoked when a tree node is selected.
+        /// Updates the list view with the directory contents.
+        /// </summary>
+        /// <remarks>This method is called from the UI thread.</remarks>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        private void OnSelectNode(object sender, TreeViewCancelEventArgs e)
+        {
+            TreeView         treeview     = sender as TreeView;
+            TreeNode         node         = e.Node;
+            Eq2DirectoryInfo newDirectory = node.Tag as Eq2DirectoryInfo;
+
+            if (treeview.SelectedNode != null)
+            {
+                // Deregister from the old directory
+                Eq2DirectoryInfo oldDirectory = treeview.SelectedNode.Tag as Eq2DirectoryInfo;
+                oldDirectory.ChildAdded -= OnDirectoryChildAdded;
+            }
+
+            // FIXME: There is a race condition here. It is possible that after having deregistered from
+            //        the old directory there is still a child of that directory in the process of being
+            //        added. In that case, it will be added to the listview erroneously as if it were a
+            //        child of the newly selected directory.
+
+            // Clear the old items from the listview
+            listview.Items.Clear();
+            listview.BeginUpdate();
+            // Add the current children of the new directory to the listview
+            Eq2FileSystemInfo[] children = newDirectory.GetFileSystemInfos();
+            foreach (Eq2FileSystemInfo child in children)
+            {
+                ListViewItem item = listview.Items.Add(child.Name);
+
+                item.Tag = child;
+                item.ImageIndex = iconManager.GetImageIndex(child);
+            }
+            listview.EndUpdate();
+
+            // Register to receive child added events in the future to the new directory
+            newDirectory.ChildAdded += OnDirectoryChildAdded;
+        }
+
+
+        /// <summary>
+        /// Callback invoked when a file or directory is added to an existing directory.
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        private void OnDirectoryChildAdded(object sender, Eq2DirectoryInfo.ChildAddedEventArgs e)
+        {
+            listview.Invoke(new AddChildToDirectoryDelegate(AddChildToDirectory), sender as Eq2DirectoryInfo, e.child);
+        }
+
+
+        /// <summary>
+        /// Adds a file or directory to the list view.
+        /// </summary>
+        /// <remarks>This method is called from the UI thread.</remarks>
+        /// <param name="directory"></param>
+        /// <param name="child"></param>
+        private void AddChildToDirectory(Eq2DirectoryInfo directory, Eq2FileSystemInfo child)
+        {
+            // Note: The second condition is probably not needed.
+            if (treeview.SelectedNode != null && treeview.SelectedNode.Tag == directory)
+            {
+                ListViewItem item = listview.Items.Add(child.Name);
+
+                item.Tag = child;
+                item.ImageIndex = iconManager.GetImageIndex(child);
+            }
+        }
+
+
+        /// <summary>
+        /// Callback invoked when the user double-clicks on the list view.
+        /// This operation opens the selected directory or extracts and opens the selected file.
+        /// </summary>
+        /// <remarks>This method is called from the UI thread.</remarks>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        private void OnListViewDoubleClick(object sender, EventArgs e)
+        {
+            ListView listview = sender as ListView;
+            
+            if (listview.SelectedItems.Count != 1) return;
+
+            Eq2FileSystemInfo child = listview.SelectedItems[0].Tag as Eq2FileSystemInfo;
+
+            if (child is Eq2DirectoryInfo)
+            {
+                OnListViewDoubleClickDirectory(child as Eq2DirectoryInfo);
+            }
+            else if (child is Eq2FileInfo)
+            {
+                OnListViewDoubleClickFile(child as Eq2FileInfo);
+            }
+        }
+
+
+        private void OnListViewDoubleClickDirectory(Eq2DirectoryInfo directory)
+        {
+            Stack<Eq2DirectoryInfo> directoryHierarchy = new Stack<Eq2DirectoryInfo>();
+            directoryHierarchy.Push(directory);
+
+            Eq2DirectoryInfo parent = directory.Parent;
+            while (parent != null)
+            {
+                directoryHierarchy.Push(parent);
+                parent = parent.Parent;
+            }
+            // Pop root directory
+            directoryHierarchy.Pop();
+
+            TreeNode rootNode = treeview.Nodes[0];
+            rootNode.Expand();
+
+            TreeNodeCollection directoryNodes = rootNode.Nodes;
+            TreeNode           nodeToExpand   = null;
+            while (directoryHierarchy.Count > 0)
+            {
+                Eq2DirectoryInfo directoryToExpand = directoryHierarchy.Pop();
+                
+                nodeToExpand = directoryNodes[directoryToExpand.Name];
+                if (!nodeToExpand.IsExpanded) nodeToExpand.Expand();
+
+                directoryNodes = nodeToExpand.Nodes;
+            }
+
+            treeview.SelectedNode = nodeToExpand;
+        }
+
+
+        private void OnListViewDoubleClickFile(Eq2FileInfo file)
+        {
+            OpenFile(file);
+        }
+
+
+        private void OpenFile(Eq2FileInfo file)
+        {
+            string path     = Path.GetTempPath() + file.DirectoryName.Replace('/', Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
+            string filename = path + file.Name;
+
+            try
+            {
+                Directory.CreateDirectory(path);
+                FileStream stream = extractionManager.ExtractFile(file, path);
+                stream.Close();
+            }
+            catch (UnauthorizedAccessException) { return; }
+            catch (IOException)                 { return; }
+
+            try
+            {
+                temporaryFiles.Add(filename);
+                System.Diagnostics.Process.Start(filename);
+            }
+            catch (System.ComponentModel.Win32Exception)
+            {
+                // There is no associated program for this file type.
+                // Ignore the error.
+            }
+        }
+
+        #endregion
+
+
+        #region Types
+        /// <summary>
+        /// Contains information about a tree view node. This information is used by the processing thread,
+        /// not the UI thread. It's a way of reducing the number of cross-thread invocations.
+        /// </summary>
+        private class NodeInfo
+        {
+            public NodeInfo(TreeNode node)
+            {
+                this.node     = node;
+                this.expanded = false;
+            }
+
+
+            public TreeNode Node
+            { 
+                get { return node; }
+            }
+
+
+            public bool Expanded 
+            { 
+                get { return expanded; } 
+                set { expanded = value; } 
+            }
+
+
+            private TreeNode node;
+            private bool     expanded;
+        }
+        #endregion
+
+
+        #region Delegates
+        private delegate TreeNode AddDirectoryDelegate        (TreeNode parentNode, Eq2DirectoryInfo directory);
+        private delegate void     AddChildToDirectoryDelegate (Eq2DirectoryInfo directory, Eq2FileSystemInfo child);
+        #endregion
+
+
+        #region Properties
+        public int FileCount
+        {
+            get { lock (this) return fileCount; }
+        }
+
+
+        public Eq2FileSystem FileSystem
+        {
+            get { return filesystem; }
+        }
+        #endregion
+
+
+        #region Fields
+        private IDictionary<Eq2DirectoryInfo, NodeInfo> nodeDictionary = new Dictionary<Eq2DirectoryInfo, NodeInfo>();
+        private IList<string> temporaryFiles = new List<string>();
+
+        private int               fileCount;
+        private string            filename;
+        private Thread            thread;
+        private Eq2FileSystem     filesystem;
+        private TreeView          treeview;
+        private ListView          listview;
+        private IconManager       iconManager;
+        private ExtractionManager extractionManager;
+        #endregion
+    }
+}

+ 171 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Source/IconManager.cs

@@ -0,0 +1,171 @@
+#region License information
+// ----------------------------------------------------------------------------
+//
+//          Eq2VpkTool - A tool to extract Everquest II VPK files
+//                         Blaz (blaz@blazlabs.com)
+//
+//       This program is free software; you can redistribute it and/or
+//        modify it under the terms of the GNU General Public License
+//      as published by the Free Software Foundation; either version 2
+//          of the License, or (at your option) any later version.
+//
+//      This program is distributed in the hope that it will be useful,
+//      but WITHOUT ANY WARRANTY; without even the implied warranty of
+//       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//                GNU General Public License for more details.
+//
+//      You should have received a copy of the GNU General Public License
+//         along with this program; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+//
+//   ( The full text of the license can be found in the License.txt file )
+//
+// ----------------------------------------------------------------------------
+#endregion
+
+#region Using directives
+
+using System;
+using System.Drawing;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+
+using Eq2FileSystemInfo = Everquest2.IO.FileSystemInfo;
+using Eq2FileInfo       = Everquest2.IO.FileInfo;
+using Eq2DirectoryInfo  = Everquest2.IO.DirectoryInfo;
+
+#endregion
+
+namespace Eq2VpkTool
+{
+    /// <summary>
+    /// Manages the image list used to represent folder and file icons on the TreeView and ListView controls.
+    /// </summary>
+    public class IconManager
+    {
+        #region Methods
+
+        #region Constructors
+        public IconManager(ImageList imageList)
+        {
+            this.imageList = imageList;
+
+            // Create the directory icon in advance.
+            imageList.Images.Add(GetIcon("directory", true, IconSize.Small));
+        }
+        #endregion
+
+
+        /// <summary>
+        /// Returns the index of the directory icon.
+        /// </summary>
+        /// <returns>Index in the image list of the directory icon.</returns>
+        public int GetDirectoryImageIndex()
+        {
+            return imageList.Images.IndexOfKey("directory");
+        }
+
+
+        /// <summary>
+        /// Returns the index of the icon that best represents the provided item.
+        /// </summary>
+        /// <param name="item">File or directory to get the icon from.</param>
+        /// <returns>Index in the image list of the appropriate icon.</returns>
+        public int GetImageIndex(Eq2FileSystemInfo item)
+        {
+            bool isDirectory = item is Eq2DirectoryInfo;
+
+            string name;
+
+            if (isDirectory)
+            {
+                // All directories share the same key on the image list.
+                name = "directory";
+            }
+            else
+            {
+                Eq2FileInfo file = item as Eq2FileInfo;
+
+                // Get the image list key from the file name. The extension will be used for this purpose.
+                name = System.IO.Path.GetExtension(file.Name);
+            }
+
+            int imageIndex;
+
+            lock (imageList)
+            {
+                if (!imageList.Images.ContainsKey(name))
+                {
+                    // If the image list doesn't contain an icon for this item, get it from the OS and cache it.
+                    imageList.Images.Add(name, GetIcon(name, isDirectory, IconSize.Small));
+                }
+
+                imageIndex = imageList.Images.IndexOfKey(name);
+            }
+
+            return imageIndex;
+        }
+
+        #endregion
+
+
+        // The following code is borrowed from http://www.sliver.com/dotnet/FileSystemIcons/
+        // Slightly modified to not require physical files on disk.
+        #region Icon querying code
+
+        [StructLayout(LayoutKind.Sequential)]
+        private struct SHFILEINFO
+        {
+            public IntPtr hIcon;
+            public int    iIcon;
+            public int    dwAttributes;
+            public string szDisplayName;
+            public string szTypeName;
+        }
+
+        private enum IconSize
+        {
+            Large,
+            Small
+        }
+
+        private const uint SHGFI_LARGEICON          = 0x0;
+        private const uint SHGFI_SMALLICON          = 0x1;
+        private const uint SHGFI_USEFILEATTRIBUTES  = 0x10;
+        private const uint SHGFI_ICON               = 0x100;
+        private const uint SHGFI_SYSICONINDEX       = 0x4000;
+        private const uint FILE_ATTRIBUTE_DIRECTORY = 0x10;
+        private const uint FILE_ATTRIBUTE_NORMAL    = 0x80;
+
+        [DllImport("Shell32.dll")]
+        private static extern int SHGetFileInfo(string path, uint fileAttributes, out SHFILEINFO psfi, uint fileInfo, uint flags);
+
+        private static Icon GetIcon(string name, bool isDirectory, IconSize iconSize)
+        {
+            SHFILEINFO info = new SHFILEINFO();
+
+            uint sizeFlags  = iconSize == IconSize.Large ? SHGFI_LARGEICON : SHGFI_SMALLICON;
+            uint attributes = isDirectory ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL;
+            uint flags      = SHGFI_USEFILEATTRIBUTES | SHGFI_SYSICONINDEX | SHGFI_ICON | sizeFlags;
+
+            int hTcdf = SHGetFileInfo(name, attributes, out info, (uint)Marshal.SizeOf(typeof(SHFILEINFO)), flags);
+
+            return Icon.FromHandle(info.hIcon);
+        }
+
+        #endregion
+
+
+        #region Properties
+        public ImageList ImageList
+        {
+            get { lock (imageList) return imageList; }
+        }
+        #endregion
+
+
+        #region Fields
+        private ImageList imageList;
+        #endregion
+    }
+}

+ 44 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Source/Program.cs

@@ -0,0 +1,44 @@
+#region License information
+// ----------------------------------------------------------------------------
+//
+//          Eq2VpkTool - A tool to extract Everquest II VPK files
+//                         Blaz (blaz@blazlabs.com)
+//
+//       This program is free software; you can redistribute it and/or
+//        modify it under the terms of the GNU General Public License
+//      as published by the Free Software Foundation; either version 2
+//          of the License, or (at your option) any later version.
+//
+//      This program is distributed in the hope that it will be useful,
+//      but WITHOUT ANY WARRANTY; without even the implied warranty of
+//       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//                GNU General Public License for more details.
+//
+//      You should have received a copy of the GNU General Public License
+//         along with this program; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+//
+//   ( The full text of the license can be found in the License.txt file )
+//
+// ----------------------------------------------------------------------------
+#endregion
+
+#region Using directives
+
+using System;
+using System.Windows.Forms;
+
+#endregion
+
+namespace Eq2VpkTool
+{
+    static class Program
+    {
+        [STAThread]
+        static void Main()
+        {
+            Application.EnableVisualStyles();
+            Application.Run(new MainWindow());
+        }
+    }
+}

+ 103 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Source/TextureDecryptor.cs

@@ -0,0 +1,103 @@
+#region Using directives
+
+using System;
+using System.IO;
+using System.Xml;
+using System.Collections.Generic;
+
+using Eq2FileInfo   = Everquest2.IO.FileInfo;
+using Eq2FileStream = Everquest2.IO.FileStream;
+
+#endregion
+
+namespace Eq2VpkTool
+{
+    public class TextureDecryptor
+    {
+        public static byte[] Decrypt(Eq2FileInfo textureFile)
+        {
+            if (!Configuration.Instance.IsLoaded) return null;
+            if (!CanDecrypt(textureFile))         return null;
+
+            using (Eq2FileStream stream = textureFile.OpenRead())
+            {
+                // Read encrypted data
+                byte[] data = new byte[stream.Length];
+                stream.Read(data, 0, (int)stream.Length);
+
+                string key = GetDecryptionKey(textureFile);
+
+                Decrypt(data, key);
+                return data;
+            }
+        }
+
+
+        public static bool CanDecrypt(Eq2FileInfo textureFile)
+        {
+            return GetDecryptionKey(textureFile) != null;
+        }
+
+
+        private static string GetDecryptionKey(Eq2FileInfo textureFile)
+        {
+            return Configuration.Instance.GetValue("/configuration/encrypted-maps/map[name=\"" + textureFile.FullName + "\"]/decryption-key");
+        }
+
+
+        private static void Decrypt(byte[] data, string key)
+        {
+            string[] bytes = key.Split(' ');
+            if (bytes.Length != 8) return;
+
+            byte[] baseKey = new byte[8];
+            for (int i = 0; i < 8; ++i)
+            {
+                baseKey[i] = byte.Parse(bytes[i], System.Globalization.NumberStyles.HexNumber);
+            }
+
+            byte[] decryptionKey = CreateDecryptionKey(baseKey);
+            Decrypt(data, decryptionKey);
+        }
+
+
+        private static byte[] CreateDecryptionKey(byte[] baseKey)
+        {
+            byte[] key = new byte[256];
+            for (int i = 0; i < 256; ++i) key[i] = (byte)i;
+            
+            byte b = 0;
+            for (int i = 0; i < 256; ++i)
+            {
+                byte k = key[(byte)i];
+
+                b += (byte)(baseKey[i & 7] + k);
+
+                key[i] = key[b];
+                key[b] = k;
+            }
+
+            return key;
+        }
+
+
+        private static void Decrypt(byte[] data, byte[] key)
+        {
+            byte[] localKey = key.Clone() as byte[];
+
+            byte b = 0;            
+            for (int i = 0; i < data.Length; ++i)
+            {
+                byte j = (byte)(i + 1);
+                byte k = localKey[j];
+
+                b += k;
+
+                localKey[j] = localKey[b];
+                localKey[b] = k;
+                
+                data[i] ^= localKey[(byte)(localKey[j] + k)];
+            }
+        }
+    }
+}

+ 343 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Source/UI/MainWindow.Designer.cs

@@ -0,0 +1,343 @@
+namespace Eq2VpkTool
+{
+    public partial class MainWindow
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.components = new System.ComponentModel.Container();
+            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainWindow));
+            this.contextMenu = new System.Windows.Forms.ContextMenuStrip(this.components);
+            this.extractToolStripContextMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.extractWithPathInformationToolStripContextMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.decryptToolStripContextMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.openFileDialog = new System.Windows.Forms.OpenFileDialog();
+            this.splitContainer = new System.Windows.Forms.SplitContainer();
+            this.directoryTreeView = new System.Windows.Forms.TreeView();
+            this.fileListView = new System.Windows.Forms.ListView();
+            this.nameColumnHeader = new System.Windows.Forms.ColumnHeader();
+            this.extractFolderDialog = new System.Windows.Forms.FolderBrowserDialog();
+            this.mainMenu = new System.Windows.Forms.MenuStrip();
+            this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.mainFileOpenMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripSeparator();
+            this.extractToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.extractWithPathInformationToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.toolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator();
+            this.mainFileExitMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.helpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.mainHelpWebsiteMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.statusMainPanelStrip = new System.Windows.Forms.StatusStrip();
+            this.statusMainPanel = new System.Windows.Forms.ToolStripStatusLabel();
+            this.splitContainer1 = new System.Windows.Forms.SplitContainer();
+            this.contextMenu.SuspendLayout();
+            this.splitContainer.Panel1.SuspendLayout();
+            this.splitContainer.Panel2.SuspendLayout();
+            this.splitContainer.SuspendLayout();
+            this.mainMenu.SuspendLayout();
+            this.statusMainPanelStrip.SuspendLayout();
+            this.splitContainer1.Panel1.SuspendLayout();
+            this.splitContainer1.Panel2.SuspendLayout();
+            this.splitContainer1.SuspendLayout();
+            this.SuspendLayout();
+            // 
+            // contextMenu
+            // 
+            this.contextMenu.AllowDrop = true;
+            this.contextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            this.extractToolStripContextMenuItem,
+            this.extractWithPathInformationToolStripContextMenuItem,
+            this.decryptToolStripContextMenuItem});
+            this.contextMenu.Name = "contextMenuStrip1";
+            this.contextMenu.Size = new System.Drawing.Size(227, 70);
+            // 
+            // extractToolStripContextMenuItem
+            // 
+            this.extractToolStripContextMenuItem.Enabled = false;
+            this.extractToolStripContextMenuItem.Name = "extractToolStripContextMenuItem";
+            this.extractToolStripContextMenuItem.Size = new System.Drawing.Size(226, 22);
+            this.extractToolStripContextMenuItem.Text = "&Extract...";
+            // 
+            // extractWithPathInformationToolStripContextMenuItem
+            // 
+            this.extractWithPathInformationToolStripContextMenuItem.Enabled = false;
+            this.extractWithPathInformationToolStripContextMenuItem.Name = "extractWithPathInformationToolStripContextMenuItem";
+            this.extractWithPathInformationToolStripContextMenuItem.Size = new System.Drawing.Size(226, 22);
+            this.extractWithPathInformationToolStripContextMenuItem.Text = "Extract with &path information...";
+            // 
+            // decryptToolStripContextMenuItem
+            // 
+            this.decryptToolStripContextMenuItem.Enabled = false;
+            this.decryptToolStripContextMenuItem.Name = "decryptToolStripContextMenuItem";
+            this.decryptToolStripContextMenuItem.Size = new System.Drawing.Size(226, 22);
+            this.decryptToolStripContextMenuItem.Text = "Decrypt and extract...";
+            this.decryptToolStripContextMenuItem.Visible = false;
+            // 
+            // openFileDialog
+            // 
+            this.openFileDialog.DefaultExt = "vpl";
+            this.openFileDialog.Filter = "VPL files|*.vpl|All files|*.*";
+            this.openFileDialog.Title = "Open VPL File...";
+            // 
+            // splitContainer
+            // 
+            this.splitContainer.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.splitContainer.Location = new System.Drawing.Point(0, 0);
+            this.splitContainer.Name = "splitContainer";
+            // 
+            // splitContainer.Panel1
+            // 
+            this.splitContainer.Panel1.Controls.Add(this.directoryTreeView);
+            // 
+            // splitContainer.Panel2
+            // 
+            this.splitContainer.Panel2.Controls.Add(this.fileListView);
+            this.splitContainer.Size = new System.Drawing.Size(592, 422);
+            this.splitContainer.SplitterDistance = 200;
+            this.splitContainer.TabIndex = 14;
+            // 
+            // directoryTreeView
+            // 
+            this.directoryTreeView.ContextMenuStrip = this.contextMenu;
+            this.directoryTreeView.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.directoryTreeView.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+            this.directoryTreeView.FullRowSelect = true;
+            this.directoryTreeView.HideSelection = false;
+            this.directoryTreeView.Location = new System.Drawing.Point(0, 0);
+            this.directoryTreeView.MinimumSize = new System.Drawing.Size(100, 0);
+            this.directoryTreeView.Name = "directoryTreeView";
+            this.directoryTreeView.PathSeparator = "/";
+            this.directoryTreeView.Size = new System.Drawing.Size(200, 422);
+            this.directoryTreeView.Sorted = true;
+            this.directoryTreeView.TabIndex = 4;
+            this.directoryTreeView.Enter += new System.EventHandler(this.directoryTreeView_Enter);
+            this.directoryTreeView.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.directoryTreeView_AfterSelect);
+            this.directoryTreeView.MouseUp += new System.Windows.Forms.MouseEventHandler(this.directoryTreeView_MouseUp);
+            // 
+            // fileListView
+            // 
+            this.fileListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
+            this.nameColumnHeader});
+            this.fileListView.ContextMenuStrip = this.contextMenu;
+            this.fileListView.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.fileListView.FullRowSelect = true;
+            this.fileListView.HideSelection = false;
+            this.fileListView.Location = new System.Drawing.Point(0, 0);
+            this.fileListView.Name = "fileListView";
+            this.fileListView.Size = new System.Drawing.Size(388, 422);
+            this.fileListView.TabIndex = 9;
+            this.fileListView.UseCompatibleStateImageBehavior = false;
+            this.fileListView.View = System.Windows.Forms.View.Details;
+            this.fileListView.Enter += new System.EventHandler(this.fileListView_Enter);
+            this.fileListView.MouseUp += new System.Windows.Forms.MouseEventHandler(this.fileListView_MouseUp);
+            this.fileListView.ItemSelectionChanged += new System.Windows.Forms.ListViewItemSelectionChangedEventHandler(this.fileListView_ItemSelectionChanged);
+            // 
+            // nameColumnHeader
+            // 
+            this.nameColumnHeader.Text = "Name";
+            this.nameColumnHeader.Width = 350;
+            // 
+            // extractFolderDialog
+            // 
+            this.extractFolderDialog.Description = "Select a folder to extract the selected items to.";
+            // 
+            // mainMenu
+            // 
+            this.mainMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            this.fileToolStripMenuItem,
+            this.helpToolStripMenuItem});
+            this.mainMenu.Location = new System.Drawing.Point(0, 0);
+            this.mainMenu.Name = "mainMenu";
+            this.mainMenu.Size = new System.Drawing.Size(592, 24);
+            this.mainMenu.TabIndex = 5;
+            // 
+            // fileToolStripMenuItem
+            // 
+            this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            this.mainFileOpenMenuItem,
+            this.toolStripMenuItem1,
+            this.extractToolStripMenuItem,
+            this.extractWithPathInformationToolStripMenuItem,
+            this.toolStripMenuItem2,
+            this.mainFileExitMenuItem});
+            this.fileToolStripMenuItem.Name = "fileToolStripMenuItem";
+            this.fileToolStripMenuItem.Size = new System.Drawing.Size(35, 20);
+            this.fileToolStripMenuItem.Text = "&File";
+            // 
+            // mainFileOpenMenuItem
+            // 
+            this.mainFileOpenMenuItem.Name = "mainFileOpenMenuItem";
+            this.mainFileOpenMenuItem.Size = new System.Drawing.Size(226, 22);
+            this.mainFileOpenMenuItem.Text = "&Open VPL file...";
+            this.mainFileOpenMenuItem.Click += new System.EventHandler(this.mainFileOpenMenuItem_Click);
+            // 
+            // toolStripMenuItem1
+            // 
+            this.toolStripMenuItem1.Name = "toolStripMenuItem1";
+            this.toolStripMenuItem1.Size = new System.Drawing.Size(223, 6);
+            // 
+            // extractToolStripMenuItem
+            // 
+            this.extractToolStripMenuItem.Enabled = false;
+            this.extractToolStripMenuItem.Name = "extractToolStripMenuItem";
+            this.extractToolStripMenuItem.Size = new System.Drawing.Size(226, 22);
+            this.extractToolStripMenuItem.Text = "&Extract...";
+            this.extractToolStripMenuItem.Click += new System.EventHandler(this.extractToolStripMenuItem_Click);
+            // 
+            // extractWithPathInformationToolStripMenuItem
+            // 
+            this.extractWithPathInformationToolStripMenuItem.Enabled = false;
+            this.extractWithPathInformationToolStripMenuItem.Name = "extractWithPathInformationToolStripMenuItem";
+            this.extractWithPathInformationToolStripMenuItem.Size = new System.Drawing.Size(226, 22);
+            this.extractWithPathInformationToolStripMenuItem.Text = "Extract with &path information...";
+            this.extractWithPathInformationToolStripMenuItem.Click += new System.EventHandler(this.extractWithPathInformationToolStripMenuItem_Click);
+            // 
+            // toolStripMenuItem2
+            // 
+            this.toolStripMenuItem2.Name = "toolStripMenuItem2";
+            this.toolStripMenuItem2.Size = new System.Drawing.Size(223, 6);
+            // 
+            // mainFileExitMenuItem
+            // 
+            this.mainFileExitMenuItem.Name = "mainFileExitMenuItem";
+            this.mainFileExitMenuItem.Size = new System.Drawing.Size(226, 22);
+            this.mainFileExitMenuItem.Text = "E&xit";
+            this.mainFileExitMenuItem.Click += new System.EventHandler(this.mainFileExitMenuItem_Click);
+            // 
+            // helpToolStripMenuItem
+            // 
+            this.helpToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            this.mainHelpWebsiteMenuItem,
+            this.aboutToolStripMenuItem});
+            this.helpToolStripMenuItem.Name = "helpToolStripMenuItem";
+            this.helpToolStripMenuItem.Size = new System.Drawing.Size(40, 20);
+            this.helpToolStripMenuItem.Text = "&Help";
+            // 
+            // mainHelpWebsiteMenuItem
+            // 
+            this.mainHelpWebsiteMenuItem.Name = "mainHelpWebsiteMenuItem";
+            this.mainHelpWebsiteMenuItem.Size = new System.Drawing.Size(125, 22);
+            this.mainHelpWebsiteMenuItem.Text = "&Website...";
+            this.mainHelpWebsiteMenuItem.Click += new System.EventHandler(this.mainHelpWebsiteMenuItem_Click);
+            // 
+            // aboutToolStripMenuItem
+            // 
+            this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem";
+            this.aboutToolStripMenuItem.Size = new System.Drawing.Size(125, 22);
+            this.aboutToolStripMenuItem.Text = "&About...";
+            this.aboutToolStripMenuItem.Click += new System.EventHandler(this.aboutToolStripMenuItem_Click);
+            // 
+            // statusMainPanelStrip
+            // 
+            this.statusMainPanelStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            this.statusMainPanel});
+            this.statusMainPanelStrip.Location = new System.Drawing.Point(0, 451);
+            this.statusMainPanelStrip.Name = "statusMainPanelStrip";
+            this.statusMainPanelStrip.Size = new System.Drawing.Size(592, 22);
+            this.statusMainPanelStrip.TabIndex = 15;
+            // 
+            // statusMainPanel
+            // 
+            this.statusMainPanel.Name = "statusMainPanel";
+            this.statusMainPanel.Size = new System.Drawing.Size(0, 17);
+            // 
+            // splitContainer1
+            // 
+            this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel1;
+            this.splitContainer1.IsSplitterFixed = true;
+            this.splitContainer1.Location = new System.Drawing.Point(0, 0);
+            this.splitContainer1.Name = "splitContainer1";
+            this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal;
+            // 
+            // splitContainer1.Panel1
+            // 
+            this.splitContainer1.Panel1.Controls.Add(this.mainMenu);
+            // 
+            // splitContainer1.Panel2
+            // 
+            this.splitContainer1.Panel2.Controls.Add(this.splitContainer);
+            this.splitContainer1.Size = new System.Drawing.Size(592, 451);
+            this.splitContainer1.SplitterDistance = 25;
+            this.splitContainer1.TabIndex = 10;
+            // 
+            // MainWindow
+            // 
+            this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
+            this.ClientSize = new System.Drawing.Size(592, 473);
+            this.Controls.Add(this.splitContainer1);
+            this.Controls.Add(this.statusMainPanelStrip);
+            this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
+            this.MinimumSize = new System.Drawing.Size(300, 200);
+            this.Name = "MainWindow";
+            this.Text = "Eq2VpkTool";
+            this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainWindow_FormClosing);
+            this.contextMenu.ResumeLayout(false);
+            this.splitContainer.Panel1.ResumeLayout(false);
+            this.splitContainer.Panel2.ResumeLayout(false);
+            this.splitContainer.ResumeLayout(false);
+            this.mainMenu.ResumeLayout(false);
+            this.mainMenu.PerformLayout();
+            this.statusMainPanelStrip.ResumeLayout(false);
+            this.statusMainPanelStrip.PerformLayout();
+            this.splitContainer1.Panel1.ResumeLayout(false);
+            this.splitContainer1.Panel1.PerformLayout();
+            this.splitContainer1.Panel2.ResumeLayout(false);
+            this.splitContainer1.ResumeLayout(false);
+            this.ResumeLayout(false);
+            this.PerformLayout();
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.OpenFileDialog openFileDialog;
+        private System.Windows.Forms.SplitContainer splitContainer;
+        private System.Windows.Forms.FolderBrowserDialog extractFolderDialog;
+        private System.Windows.Forms.ContextMenuStrip contextMenu;
+        private System.Windows.Forms.ToolStripMenuItem extractToolStripContextMenuItem;
+        private System.Windows.Forms.ToolStripMenuItem extractWithPathInformationToolStripContextMenuItem;
+        private System.Windows.Forms.ToolStripMenuItem decryptToolStripContextMenuItem;
+        private System.Windows.Forms.MenuStrip mainMenu;
+        private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem;
+        private System.Windows.Forms.ToolStripMenuItem mainFileOpenMenuItem;
+        private System.Windows.Forms.ToolStripSeparator toolStripMenuItem1;
+        private System.Windows.Forms.ToolStripMenuItem extractToolStripMenuItem;
+        private System.Windows.Forms.ToolStripMenuItem extractWithPathInformationToolStripMenuItem;
+        private System.Windows.Forms.ToolStripSeparator toolStripMenuItem2;
+        private System.Windows.Forms.ToolStripMenuItem mainFileExitMenuItem;
+        private System.Windows.Forms.ToolStripMenuItem helpToolStripMenuItem;
+        private System.Windows.Forms.ToolStripMenuItem mainHelpWebsiteMenuItem;
+        private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem;
+        private System.Windows.Forms.StatusStrip statusMainPanelStrip;
+        private System.Windows.Forms.ToolStripStatusLabel statusMainPanel;
+        private System.Windows.Forms.TreeView directoryTreeView;
+        private System.Windows.Forms.ListView fileListView;
+        private System.Windows.Forms.ColumnHeader nameColumnHeader;
+        private System.Windows.Forms.SplitContainer splitContainer1;
+    }
+}
+

+ 572 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Source/UI/MainWindow.cs

@@ -0,0 +1,572 @@
+#region License information
+// ----------------------------------------------------------------------------
+//
+//          Eq2VpkTool - A tool to extract Everquest II VPK files
+//                         Blaz (blaz@blazlabs.com)
+//
+//       This program is free software; you can redistribute it and/or
+//        modify it under the terms of the GNU General Public License
+//      as published by the Free Software Foundation; either version 2
+//          of the License, or (at your option) any later version.
+//
+//      This program is distributed in the hope that it will be useful,
+//      but WITHOUT ANY WARRANTY; without even the implied warranty of
+//       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//                GNU General Public License for more details.
+//
+//      You should have received a copy of the GNU General Public License
+//         along with this program; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+//
+//   ( The full text of the license can be found in the License.txt file )
+//
+// ----------------------------------------------------------------------------
+#endregion
+
+#region Using directives
+
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Windows.Forms;
+using System.Threading;
+using System.Runtime.InteropServices;
+
+using Eq2FileSystem     = Everquest2.IO.FileSystem;
+using Eq2FileSystemInfo = Everquest2.IO.FileSystemInfo;
+using Eq2FileInfo       = Everquest2.IO.FileInfo;
+using Eq2DirectoryInfo  = Everquest2.IO.DirectoryInfo;
+
+#endregion
+
+namespace Eq2VpkTool
+{
+    public partial class MainWindow : Form
+    {
+        #region Methods
+
+        #region Constructors
+        public MainWindow()
+        {
+            InitializeComponent();
+
+            Text = ApplicationName;
+
+            if (File.Exists(ConfigurationFileName))
+            {
+                try                 { Configuration.Instance.Load(new FileStream(ConfigurationFileName, FileMode.Open, FileAccess.Read)); } 
+                catch (Exception e) { MessageBox.Show("Error loading configuration file '" + ConfigurationFileName + "'\n\n" + e); }
+            }
+
+            loadingUpdateTimer           = new System.Windows.Forms.Timer();
+            loadingUpdateTimer.Interval  = 200;
+            loadingUpdateTimer.Tick     += OnLoadingUpdateTimerTick;
+
+            fileSystemViewController = new FileSystemViewController();
+            extractionManager        = new ExtractionManager();
+
+            extractToolStripContextMenuItem.Click += extractToolStripContextMenuItem_Click;
+            extractWithPathInformationToolStripContextMenuItem.Click += extractWithPathInformationToolStripContextMenuItem_Click;
+            decryptToolStripContextMenuItem.Click += decryptToolStripContextMenuItem_Click;
+        }
+        #endregion
+
+
+        private void mainHelpWebsiteMenuItem_Click(object sender, EventArgs e)
+        {
+            System.Diagnostics.Process.Start(ApplicationWebsite);
+        }
+
+
+        private void mainFileExitMenuItem_Click(object sender, EventArgs e)
+        {
+            Close();
+        }
+
+        
+        private void mainFileOpenMenuItem_Click(object sender, EventArgs e)
+        {
+            if (openFileDialog.ShowDialog() == DialogResult.OK)
+            {
+                loadingUpdateTimer.Enabled = false;
+                extractionManager.Close();
+                fileSystemViewController.Close();
+                extractionProgress = null;
+
+                fileSystemViewController.Open(openFileDialog.FileName, directoryTreeView, fileListView);
+                loadingUpdateTimer.Enabled = true;
+            }
+        }
+
+
+        private void extractToolStripMenuItem_Click(object sender, EventArgs e)
+        {
+            if (extractFolderDialog.ShowDialog() == DialogResult.OK)
+            {
+                ExtractFromMainMenu(extractFolderDialog.SelectedPath, false);
+            }
+        }
+
+        
+        private void extractWithPathInformationToolStripMenuItem_Click(object sender, EventArgs e)
+        {
+            if (extractFolderDialog.ShowDialog() == DialogResult.OK)
+            {
+                ExtractFromMainMenu(extractFolderDialog.SelectedPath, true);
+            }
+        }
+
+
+        private void extractToolStripContextMenuItem_Click(object sender, EventArgs e)
+        {
+            if (extractFolderDialog.ShowDialog() == DialogResult.OK)
+            {
+                ExtractFromContextMenu(extractFolderDialog.SelectedPath, false);
+            }
+        }
+
+        
+        private void extractWithPathInformationToolStripContextMenuItem_Click(object sender, EventArgs e)
+        {
+            if (extractFolderDialog.ShowDialog() == DialogResult.OK)
+            {
+                ExtractFromContextMenu(extractFolderDialog.SelectedPath, true);
+            }
+        }
+
+        
+        private void decryptToolStripContextMenuItem_Click(object sender, EventArgs e)
+        {
+            if (extractFolderDialog.ShowDialog() == DialogResult.OK)
+            {
+                DecryptFromContextMenu(extractFolderDialog.SelectedPath, false);
+            }
+        }
+
+        
+        private void decryptWithPathInformationToolStripContextMenuItem_Click(object sender, EventArgs e)
+        {
+            if (extractFolderDialog.ShowDialog() == DialogResult.OK)
+            {
+                DecryptFromContextMenu(extractFolderDialog.SelectedPath, true);
+            }
+        }
+
+
+        private void DecryptFromContextMenu(string path, bool withPathInfo)
+        {
+            if (fileListView.SelectedItems.Count != 1) return;
+
+            Decrypt(path, withPathInfo, fileListView.SelectedItems[0].Tag as Eq2FileInfo);
+        }
+
+
+        private void Decrypt(string outputPath, bool withPathInfo, Eq2FileInfo file)
+        {
+            if (!TextureDecryptor.CanDecrypt(file)) return;
+
+            // Make sure the path ends with a directory separator char.
+            if (outputPath[outputPath.Length - 1] != Path.DirectorySeparatorChar)
+            {
+                outputPath += Path.DirectorySeparatorChar;
+            }
+
+            try
+            {
+                using (FileStream stream = new FileStream(outputPath + file.Name, FileMode.Create, FileAccess.Write))
+                {
+                    byte[] decryptedData = TextureDecryptor.Decrypt(file);
+
+                    stream.Write(decryptedData, 0, decryptedData.Length);
+                }
+
+                statusMainPanel.Text = file.Name + " decrypted successfully.";
+            }
+            catch (Exception e)
+            {
+                MessageBox.Show("Error saving decrypted file:\n\n" + e, "Error");
+            }
+        }
+
+
+        private void ExtractFromMainMenu(string path, bool withPathInfo)
+        {
+            if (directoryTreeView.Focused)
+            {
+                ExtractTreeViewSelection(path, withPathInfo);
+            }
+            else if (fileListView.Focused)
+            {
+                ExtractListViewSelection(path, withPathInfo);
+            }
+        }
+
+        
+        private void ExtractFromContextMenu(string path, bool withPathInfo)
+        {
+            if (contextMenu.Tag == directoryTreeView)
+            {
+                ExtractTreeViewSelection(path, withPathInfo);
+            }
+            else if (contextMenu.Tag == fileListView)
+            {
+                ExtractListViewSelection(path, withPathInfo);
+            }
+        }
+
+        
+        private void OnLoadingUpdateTimerTick(object sender, EventArgs args)
+        {
+            if (extractionProgress != null)
+            {
+                ExtractionProgress progress = extractionProgress.AsyncState as ExtractionProgress;
+
+                if (extractionProgress.IsCompleted)
+                {
+                    statusMainPanel.Text = progress.extractedFileCount + " files extracted.";
+
+                    System.Windows.Forms.Timer timer = sender as System.Windows.Forms.Timer;
+                    timer.Enabled = false;
+                    extractionProgress = null;
+                }
+                else
+                {
+                    statusMainPanel.Text = progress.extractedFileCount + " of " + progress.totalFileCount + " files extracted.";
+                }
+            }
+            else
+            {
+                int fileCount      = fileSystemViewController.FileCount;
+                int totalFileCount = fileSystemViewController.FileSystem.FileCount;
+
+                if (totalFileCount > 0 && fileCount >= totalFileCount) 
+                {
+                    statusMainPanel.Text = totalFileCount + " files processed.";
+
+                    System.Windows.Forms.Timer timer = sender as System.Windows.Forms.Timer;
+                    timer.Enabled = false;
+                }
+                else
+                {
+                    statusMainPanel.Text = fileCount + " of " + totalFileCount + " files processed.";
+                }
+            }
+        }
+
+
+        private void MainWindow_FormClosing(object sender, FormClosingEventArgs e)
+        {
+            loadingUpdateTimer.Enabled = false;
+            fileSystemViewController.Close();
+            extractionManager.Close();
+        }
+
+           
+        private void directoryTreeView_Enter(object sender, EventArgs e)
+        {
+            UpdateExtractionMenus(directoryTreeView);
+        }
+
+
+        private void directoryTreeView_AfterSelect(object sender, TreeViewEventArgs e)
+        {
+            UpdateExtractionMenus(directoryTreeView);
+        }
+
+
+        private void fileListView_Enter(object sender, EventArgs e)
+        {
+            UpdateExtractionMenus(fileListView);
+        }
+
+
+        private void fileListView_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
+        {
+            UpdateExtractionMenus(fileListView);
+        }
+
+
+        private void UpdateExtractionMenus(object sender)
+        {
+            bool enabled;
+            string text = string.Empty;
+
+            if (sender == directoryTreeView)
+            {
+                enabled = directoryTreeView.SelectedNode != null && extractionProgress == null;
+                if (enabled)
+                {
+                    text = directoryTreeView.SelectedNode.Text;
+                }
+            }
+            else
+            {
+                int selectedItemCount = fileListView.SelectedItems.Count;
+
+                enabled = selectedItemCount > 0 && extractionProgress == null;
+                if (enabled)
+                {
+                    if (selectedItemCount == 1)
+                    {
+                        text = fileListView.SelectedItems[0].Text;
+                    }
+                    else
+                    {
+                        text = selectedItemCount + " items";
+                    }
+                }
+            }
+
+            if (enabled)
+            {
+                extractToolStripMenuItem.Text = "Extract " + text + "...";
+                extractToolStripContextMenuItem.Text = "Extract " + text + "...";
+                extractWithPathInformationToolStripMenuItem.Text = "Extract " + text + " with path information...";
+                extractWithPathInformationToolStripContextMenuItem.Text = "Extract " + text + " with path information...";
+            }
+            else
+            {
+                extractToolStripMenuItem.Text = "Extract...";
+                extractToolStripContextMenuItem.Text = "Extract...";
+                extractWithPathInformationToolStripMenuItem.Text = "Extract with path information...";
+                extractWithPathInformationToolStripContextMenuItem.Text = "Extract with path information...";
+            }
+
+            extractToolStripMenuItem.Enabled = enabled;
+            extractToolStripContextMenuItem.Enabled = enabled;
+            extractWithPathInformationToolStripMenuItem.Enabled = enabled;
+            extractWithPathInformationToolStripContextMenuItem.Enabled = enabled;
+
+
+            bool visible;
+
+            if (sender == directoryTreeView)
+            {
+                enabled = false;
+                visible = false;
+            }
+            else
+            {
+                enabled = false;
+                visible = false;
+
+                if (fileListView.SelectedItems.Count == 1)
+                {
+                    Eq2FileSystemInfo item = fileListView.SelectedItems[0].Tag as Eq2FileSystemInfo;
+
+                    if (item is Eq2FileInfo)
+                    {
+                        Eq2FileInfo file = item as Eq2FileInfo;
+
+                        if (TextureDecryptor.CanDecrypt(file))
+                        {
+                            enabled = true;
+                            text    = file.Name;
+                        }
+
+                        visible = StringComparer.InvariantCultureIgnoreCase.Equals(file.DirectoryName, "nrvobm");
+                    }
+                }
+            }
+
+            if (enabled)
+            {
+                decryptToolStripContextMenuItem.Text = "Decrypt and extract " + text + "...";
+            }
+            else
+            {
+                decryptToolStripContextMenuItem.Text = "Decrypt and extract...";
+            }
+
+            decryptToolStripContextMenuItem.Enabled = enabled;
+            decryptToolStripContextMenuItem.Visible = visible;
+        }
+
+        
+        private void ExtractTreeViewSelection(string outputPath, bool withPathInfo)
+        {
+            if (directoryTreeView.SelectedNode == null) return;
+
+            Eq2FileSystemInfo[] children = null;
+
+            children    = new Eq2FileSystemInfo[1];
+            children[0] = directoryTreeView.SelectedNode.Tag as Eq2DirectoryInfo;
+
+            // Make sure the path ends with a directory separator char.
+            if (outputPath[outputPath.Length - 1] != Path.DirectorySeparatorChar)
+            {
+                outputPath += Path.DirectorySeparatorChar;
+            }
+
+            string newOutputPath = outputPath;
+            if (withPathInfo)
+            {
+                Eq2DirectoryInfo directory           = directoryTreeView.SelectedNode.Tag as Eq2DirectoryInfo;
+                Eq2DirectoryInfo parentDirectory     = directory.Parent;
+                string           parentDirectoryName = parentDirectory != null ? parentDirectory.FullName : string.Empty;
+
+                newOutputPath = outputPath + parentDirectoryName.Replace('/', Path.DirectorySeparatorChar);
+            }
+
+            if (newOutputPath[newOutputPath.Length-1] != Path.DirectorySeparatorChar)
+            {
+                newOutputPath += Path.DirectorySeparatorChar;
+            }
+
+            loadingUpdateTimer.Enabled = true;
+            extractionProgress = extractionManager.BeginExtract(children, newOutputPath, OnFileExtracted, new ExtractionProgress(children));
+
+            UpdateExtractionMenus(directoryTreeView);
+        }
+
+
+        private void ExtractListViewSelection(string outputPath, bool withPathInfo)
+        {
+            if (fileListView.SelectedItems.Count == 0) return;
+
+            Eq2FileSystemInfo[] children = null;
+
+            children = new Eq2FileSystemInfo[fileListView.SelectedItems.Count];
+            for (int i = 0; i < children.Length; ++i)
+            {
+                children[i] = fileListView.SelectedItems[i].Tag as Eq2FileSystemInfo;
+            }
+
+            // Make sure the path ends with a directory separator char.
+            if (outputPath[outputPath.Length - 1] != Path.DirectorySeparatorChar)
+            {
+                outputPath += Path.DirectorySeparatorChar;
+            }
+
+            string newOutputPath = outputPath;
+            if (withPathInfo)
+            {
+                Eq2DirectoryInfo parentDirectory = null;
+    
+                if (children[0] is Eq2FileInfo)
+                {
+                    Eq2FileInfo file = children[0] as Eq2FileInfo;
+                    parentDirectory = file.Directory;
+                }
+                else if (children[0] is Eq2DirectoryInfo)
+                {
+                    Eq2DirectoryInfo directory = children[0] as Eq2DirectoryInfo;
+                    parentDirectory = directory.Parent;
+                }
+
+                newOutputPath = outputPath + parentDirectory.FullName.Replace('/', Path.DirectorySeparatorChar);
+            }
+
+            if (newOutputPath[newOutputPath.Length-1] != Path.DirectorySeparatorChar)
+            {
+                newOutputPath += Path.DirectorySeparatorChar;
+            }
+
+            loadingUpdateTimer.Enabled = true;
+            extractionProgress = extractionManager.BeginExtract(children, newOutputPath, OnFileExtracted, new ExtractionProgress(children));
+
+            UpdateExtractionMenus(fileListView);
+        }
+
+
+        private void OnFileExtracted(IAsyncResult result)
+        {
+            ExtractionProgress progress = result.AsyncState as ExtractionProgress;
+
+            ++progress.extractedFileCount;
+        }
+
+
+        private void fileListView_MouseUp(object sender, MouseEventArgs e)
+        {
+            if (e.Button == MouseButtons.Right)
+            {
+                contextMenu.Tag = fileListView;
+                UpdateExtractionMenus(fileListView);
+            }
+        }
+
+
+        private void directoryTreeView_MouseUp(object sender, MouseEventArgs e)
+        {
+            if (e.Button == MouseButtons.Right)
+            {
+                TreeNode node = directoryTreeView.GetNodeAt(e.Location);
+                if (node != null) directoryTreeView.SelectedNode = node;
+
+                contextMenu.Tag = directoryTreeView;
+                UpdateExtractionMenus(directoryTreeView);
+            }
+        }
+
+
+        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
+        {
+            MessageBox.Show(ApplicationName + "\n\n" + "Send any feedback to blaz@blazlabs.com", "About...");
+        }
+        #endregion
+
+
+        #region Types
+        private class ExtractionProgress
+        {
+            public ExtractionProgress(Eq2FileSystemInfo[] children)
+            {
+                totalFileCount = 0;
+
+                foreach (Eq2FileSystemInfo child in children)
+                {
+                    totalFileCount += GetChildrenCount(child);
+                }
+            }
+
+
+            private int GetChildrenCount(Eq2FileSystemInfo parent)
+            {
+                int count = 0;
+
+                if (parent is Eq2FileInfo)
+                {
+                    ++count;    
+                }
+                else if (parent is Eq2DirectoryInfo)
+                {
+                    Eq2DirectoryInfo directory = parent as Eq2DirectoryInfo;
+
+                    count += directory.FileCount;
+
+                    Eq2DirectoryInfo[] subdirectories = directory.GetDirectories();
+                    foreach (Eq2DirectoryInfo subdirectory in subdirectories)
+                    {
+                        count += GetChildrenCount(subdirectory);
+                    }
+                }
+
+                return count;
+            }
+
+
+            public int extractedFileCount;
+            public int totalFileCount;
+        }
+        #endregion
+
+
+        #region Fields
+        private System.Windows.Forms.Timer  loadingUpdateTimer;
+        private FileSystemViewController    fileSystemViewController;
+        private ExtractionManager           extractionManager;
+        private IAsyncResult                extractionProgress;
+
+        #region Constants
+        private static string ApplicationName       = "Eq2VpkTool v1.2.3";
+        private static string ApplicationWebsite    = "http://eq2.blazlabs.com";
+        private static string ConfigurationFileName = "Configuration.xml";
+        #endregion
+
+        #endregion
+    }
+}

+ 309 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/Source/UI/MainWindow.resx

@@ -0,0 +1,309 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <metadata name="contextMenu.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>149, 17</value>
+  </metadata>
+  <metadata name="openFileDialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>517, 17</value>
+  </metadata>
+  <metadata name="extractFolderDialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>369, 17</value>
+  </metadata>
+  <metadata name="mainMenu.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>641, 17</value>
+  </metadata>
+  <metadata name="statusMainPanelStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>17, 54</value>
+  </metadata>
+  <assembly alias="System.Drawing" name="System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
+  <data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        AAABAAYAICAQAAEABADoAgAAZgAAABAQEAABAAQAKAEAAE4DAAAgIAAAAQAIAKgIAAB2BAAAEBAAAAEA
+        CABoBQAAHg0AACAgAAABACAAqBAAAIYSAAAQEAAAAQAgAGgEAAAuIwAAKAAAACAAAABAAAAAAQAEAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8A/wCAAIAA/wAAAIAAAAD//wAAgIAAAAD/AAAAgAAAAP//AACA
+        gAAAAP8AAACAAP///wDAwMAAgICAAAAAAAD//d3d3d3d3d3d3d3d3c///9IiIiIiIiIiIiIiIiLd//0i
+        IiIiIiIiIiIiIiIiLd/SMiIiIiIiIv//8iIiIi3c3zIiIiIiIiuYibvyIiIiLd8yIiIiIiKZiIiJ/yIi
+        Ii3fMiIiIiL/mIIi2JsiIiIt3zIiIiIpiIiSIiIiIiIiLd8yIiIimIiIvyIiIiIiIi3fMiIiL4ibiLv/
+        //L/8iIt3zIiH/+IuYifu7u/u7vyLd8yKZmfuIiIn4iImIiJsi3fMuiIibuIKIsoib+ImyIt3zKIiIiL
+        /yiLKIPyiD8iLd8yiBIpiJsYiyiD8og/Ii3fMoiyKYiTKIv4g/KIPyIt3zKIsimI8hiLKIPyiD8iLd8y
+        iL8piP+YiSiD8og/Ii3fMoi/KYj7iIEog/KIPyIt3zKIv/KImIifKIPyiD8iLd8yiL+/6IiIEiiD8og/
+        Ii3fMoiInyiIiRIog/KIPyIt3zKIiIkiiIHyKIMziDMiLd8yiJ8iIuifIiiDv4ibIi3fMoi//x8iIiKI
+        iLiIiSIt3zKIv/mfIiIiIiIiIiIiLd/ziJu4jyIiIiIiIiIiIi3f84iIiJ8iIiIiIiIiIiItwvM76Igi
+        IiIiIiIiIiIiLP1f8zP/MzMzMzMzMzMzIt//0v//////////////8i3///zd3d3d3d3d3d3d3dzP/+AA
+        AAfAAAADgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAcAA
+        AAPgAAAHKAAAABAAAAAgAAAAAQAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8A/wCAAIAA/wAAAIAA
+        AAD//wAAgIAAAAD/AAAAgAAAAP//AACAgAAAAP8AAACAAP///wDAwMAAgICAAAAAAAD+7u7u7u7u3+Ii
+        IiIzMiLtIyIiLoifIi4jIiOY4uMiLiMiOIizMjMuIym5mJmZmT4jmImeno+JLiOD6P6Zg4MuI4PoOZ6D
+        gy4jjziYnoODLiOJvog+g4MuI4jiiS6Piy4jj/8iKZmZLi+IiSIiIiIu0/mTMzMzMy39IiIiIiIi34AB
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAB
+        AAAoAAAAIAAAAEAAAAABAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AMz//wCZ//8AZv//ADP/
+        /wAA//8A/8z/AMzM/wCZzP8AZsz/ADPM/wAAzP8A/5n/AMyZ/wCZmf8AZpn/ADOZ/wAAmf8A/2b/AMxm
+        /wCZZv8AZmb/ADNm/wAAZv8A/zP/AMwz/wCZM/8AZjP/ADMz/wAAM/8A/wD/AMwA/wCZAP8AZgD/ADMA
+        /wAAAP8A///MAMz/zACZ/8wAZv/MADP/zAAA/8wA/8zMAMzMzACZzMwAZszMADPMzAAAzMwA/5nMAMyZ
+        zACZmcwAZpnMADOZzAAAmcwA/2bMAMxmzACZZswAZmbMADNmzAAAZswA/zPMAMwzzACZM8wAZjPMADMz
+        zAAAM8wA/wDMAMwAzACZAMwAZgDMADMAzAAAAMwA//+ZAMz/mQCZ/5kAZv+ZADP/mQAA/5kA/8yZAMzM
+        mQCZzJkAZsyZADPMmQAAzJkA/5mZAMyZmQCZmZkAZpmZADOZmQAAmZkA/2aZAMxmmQCZZpkAZmaZADNm
+        mQAAZpkA/zOZAMwzmQCZM5kAZjOZADMzmQAAM5kA/wCZAMwAmQCZAJkAZgCZADMAmQAAAJkA//9mAMz/
+        ZgCZ/2YAZv9mADP/ZgAA/2YA/8xmAMzMZgCZzGYAZsxmADPMZgAAzGYA/5lmAMyZZgCZmWYAZplmADOZ
+        ZgAAmWYA/2ZmAMxmZgCZZmYAZmZmADNmZgAAZmYA/zNmAMwzZgCZM2YAZjNmADMzZgAAM2YA/wBmAMwA
+        ZgCZAGYAZgBmADMAZgAAAGYA//8zAMz/MwCZ/zMAZv8zADP/MwAA/zMA/8wzAMzMMwCZzDMAZswzADPM
+        MwAAzDMA/5kzAMyZMwCZmTMAZpkzADOZMwAAmTMA/2YzAMxmMwCZZjMAZmYzADNmMwAAZjMA/zMzAMwz
+        MwCZMzMAZjMzADMzMwAAMzMA/wAzAMwAMwCZADMAZgAzADMAMwAAADMA//8AAMz/AACZ/wAAZv8AADP/
+        AAAA/wAA/8wAAMzMAACZzAAAZswAADPMAAAAzAAA/5kAAMyZAACZmQAAZpkAADOZAAAAmQAA/2YAAMxm
+        AACZZgAAZmYAADNmAAAAZgAA/zMAAMwzAACZMwAAZjMAADMzAAAAMwAA/wAAAMwAAACZAAAAZgAAADMA
+        AAAAAO4AAADdAAAAuwAAAKoAAACIAAAAdwAAAFUAAABEAAAAIgAAABEAAO4AAADdAAAAuwAAAKoAAACI
+        AAAAdwAAAFUAAABEAAAAIgAAABEAAO4AAADdAAAAuwAAAKoAAACIAAAAdwAAAFUAAABEAAAAIgAAABEA
+        AADu7u4A3d3dALu7uwCqqqoAiIiIAHd3dwBVVVUAREREACIiIgAREREAAAAAAP///05OTk5OTk5OTk5O
+        Tk5OTk5OTk5OTk5OTk4q//////9OeoCAgICAgICAgICAgICAgICAgICAgICAgCoq////TnqqgIB4eHhy
+        cnJycnJ4gICqqqqAgHp4eHh4ek4q/06AqoCAeHh4eHh4cnJyeICtra2tq6qqgHh4eHh4Tk4qTqurgIB4
+        eHh4eHh4eHiAiV0zV13d3auqgHh4eHhOeCpOq6uAgHh4eHh4eHiAgIeBLS0tLS1drauAgH5+eE5yKk6r
+        q4B+eHh4eHh4gK2tgTMtgIB4+C1d3YCAgIB4Tk4qTqurgH54eHh4eICBMzMtLVeqgHh4eHh+foCAgH5O
+        TipOq6uAfnh4foCAgTMtMy0tiauqgICAgICAgICAgHh4Kk6rq4B+foCqqqszLYOJLS2J3aqqqqqqqqur
+        q6qqgHhOTqurgH6A/K2trTMz3V0tLV2tid3d3d2tra2tra2GeCpOq6uAgF1dXYGtiTMtLS1dXa1dXV1d
+        XV1dXV1d3YB4Kk6rq4D5My0tLV3diTMtgDNd3aozM13dqzNXXd2GgHIqTqurgDMtLfotLTOJrauqMzPd
+        qjMz3fyqMzPd/IZ4cipOq6uALS38qoD4LC1diaozM4mqLTPdq6otM92rgHhyKk6rq4AtLYmqgPgtLV1d
+        qi0ziastLYmrqi0tiauAeHIqTqurgC0tiaqA+C1dq6r8LS2Jqi0tiauqLS2Jq4B4cipOq6uALS2Jq4B6
+        LYOtq4EtLYeqLS2Jq6otLYmrhnhyKk6rq4AILYmrqoAIM62JLS0t/KotLYmrqi0tiauGgHIqTqurgAgt
+        iaurqggJXTMJCV2rqggtiauqLS2Jq6qAcipOq6uACAmJrYmr+QgICQgJ/KqqCAmJq6oICYmrqoByKk6r
+        q6oICAgJg62qCAgICF38qoYICd2rqggJ3auqgHIqTqurqggICAgtgaqACAgt/KuqhggJ3bHvCAndsaqA
+        cipOq6uqCAmBq6qqqqr7LYGrqqqGCAndiasICIPdqoByKk6rq6oICYmrq6v8q6qqqqqqqggICAiJCQgI
+        CYOGgHgqTqurqwgJia2rXVerqqqqqqqqqqqqqqqqqqqqqoaAeCpOq7H8CAhXid0tM6uqqqqqqqqqqqqq
+        qqqqqqqqhoB4Kk6r//wICAgICS2Bq6qqqqqqqqqqqqqqqqqqqqqGgHgqKoD/sauA+SwILaqqqqqqqqqq
+        qqqqqqqqqqqqqqqGcir/TqT//7GxsaursbGxsbGxsbGxsbGxsbGxsbGxqnhO////ToCrq6urq6urq6ux
+        q6urq6urq6urq6urq4ByTv//////JE5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OKiT////gAAAHwAAAA4AA
+        AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAHAAAAD4AAABygA
+        AAAQAAAAIAAAAAEACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8AzP//AJn//wBm//8AM///AAD/
+        /wD/zP8AzMz/AJnM/wBmzP8AM8z/AADM/wD/mf8AzJn/AJmZ/wBmmf8AM5n/AACZ/wD/Zv8AzGb/AJlm
+        /wBmZv8AM2b/AABm/wD/M/8AzDP/AJkz/wBmM/8AMzP/AAAz/wD/AP8AzAD/AJkA/wBmAP8AMwD/AAAA
+        /wD//8wAzP/MAJn/zABm/8wAM//MAAD/zAD/zMwAzMzMAJnMzABmzMwAM8zMAADMzAD/mcwAzJnMAJmZ
+        zABmmcwAM5nMAACZzAD/ZswAzGbMAJlmzABmZswAM2bMAABmzAD/M8wAzDPMAJkzzABmM8wAMzPMAAAz
+        zAD/AMwAzADMAJkAzABmAMwAMwDMAAAAzAD//5kAzP+ZAJn/mQBm/5kAM/+ZAAD/mQD/zJkAzMyZAJnM
+        mQBmzJkAM8yZAADMmQD/mZkAzJmZAJmZmQBmmZkAM5mZAACZmQD/ZpkAzGaZAJlmmQBmZpkAM2aZAABm
+        mQD/M5kAzDOZAJkzmQBmM5kAMzOZAAAzmQD/AJkAzACZAJkAmQBmAJkAMwCZAAAAmQD//2YAzP9mAJn/
+        ZgBm/2YAM/9mAAD/ZgD/zGYAzMxmAJnMZgBmzGYAM8xmAADMZgD/mWYAzJlmAJmZZgBmmWYAM5lmAACZ
+        ZgD/ZmYAzGZmAJlmZgBmZmYAM2ZmAABmZgD/M2YAzDNmAJkzZgBmM2YAMzNmAAAzZgD/AGYAzABmAJkA
+        ZgBmAGYAMwBmAAAAZgD//zMAzP8zAJn/MwBm/zMAM/8zAAD/MwD/zDMAzMwzAJnMMwBmzDMAM8wzAADM
+        MwD/mTMAzJkzAJmZMwBmmTMAM5kzAACZMwD/ZjMAzGYzAJlmMwBmZjMAM2YzAABmMwD/MzMAzDMzAJkz
+        MwBmMzMAMzMzAAAzMwD/ADMAzAAzAJkAMwBmADMAMwAzAAAAMwD//wAAzP8AAJn/AABm/wAAM/8AAAD/
+        AAD/zAAAzMwAAJnMAABmzAAAM8wAAADMAAD/mQAAzJkAAJmZAABmmQAAM5kAAACZAAD/ZgAAzGYAAJlm
+        AABmZgAAM2YAAABmAAD/MwAAzDMAAJkzAABmMwAAMzMAAAAzAAD/AAAAzAAAAJkAAABmAAAAMwAAAAAA
+        7gAAAN0AAAC7AAAAqgAAAIgAAAB3AAAAVQAAAEQAAAAiAAAAEQAA7gAAAN0AAAC7AAAAqgAAAIgAAAB3
+        AAAAVQAAAEQAAAAiAAAAEQAA7gAAAN0AAAC7AAAAqgAAAIgAAAB3AAAAVQAAAEQAAAAiAAAAEQAAAO7u
+        7gDd3d0Au7u7AKqqqgCIiIgAd3d3AFVVVQBEREQAIiIiABEREQAAAAAA/52dnZ2dnZ2dnZ2dnZ2d/3J/
+        eVROTk54+6uqf1RUTrpypXhOTk6AgTQtXqt5VE66cqRUTk6AXi0teCtWeXlOunKkVH6kMwkJiKqjqaqq
+        f7pypIBerTM0CoItLS0tLfu6cqQtCS1eXS5egC37LWR/unKlCoF/CYEuXqotqi38eLpypAn8fy6sBFeq
+        LasJiH66cqoJrKQEXgT6qgmrCfx/unKqAzSI9wEEqqkJqgiso7pyqgMKLKksXbCpCPwCiH+6cqsCrauH
+        qampCQkJCQl/unKsAQoJM6mpqa+vqa+pf7py0KtcXbGwsLCwsLCwsH+6/yQkJCQkJCQkJCQkJCQk/4AB
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAB
+        AAAoAAAAIAAAAEAAAAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAinJfAN+4nACYfGkS3raZ8d+4
+        nP/fuJz/37ic/9+4nP/fuJz/37ic/9+4m//guJv/4Lib/+C4m//guJv/4Lib/+C4m//guJv/4Lib/+C4
+        m//guJv/37ib/9+4m//fuJz/37ic/9+4nP/fuJz/3rqh9uzayfrs18Uk7NfFAOzXxQCKcl8A37icOd+3
+        m/nGkXT/pF1M/8dzXP/Ne2D/0H5k/9CAZ//RhGj/0YRp/9KGav/Thmr/04Rq/9OEav/QhWn/yn9k/8Jz
+        W/+6bFb/umlU/7ptVv/Cdlz/y35j/82BZf/Me2X/yX5l/89+Y//Oimz/6tTB/uvXxvvs18Uk7NfFAIpy
+        Xwrdtpn1zZt9/4FGO/+yalX/yXdd/+aed//uq4H/7auA//KxhP/ysof/+LaL//m3if/4t4v/87SK/+am
+        f//IgmT/pVhE/5RENf+TQTT/l0c4/6hdSP/Cf2P/05Z1/92jgP/gpYP/3KWC/9Sfff/hqYb/5MKr++zX
+        xfvs18Uk3reb+pFcTP+CRzz/wm1Z/8dzW//fmHT/6aN5/++rff/uq37/8at+//Ovg//2sYX/9LCE//Kw
+        hf/qpnz/uXhh/x8rPv8IHTP/Dyc+/xMnPf8xJjT/ii8l/51IN/+7b1X/3Jdy/+urgv/srYT/5aSB/+ql
+        hP/ntJX/5s+4+O7e0Prft5v7WzIm/3s/Mf/GcVr/0IFl/+Cac//qpnr/76l9//Gtgf/yrYH/8q6C//Ou
+        gv/spHz/3JBt/61uWv8nSWr/RI2y/2CoyP9Qlbb/QnmX/xMtRP8SKkH/VzU+/5U9Lf+7alD/3JJw/+Wg
+        ef/dmHf/6qCC/+a3ov/fqoL/7NfF/N+4m/xaMiT/djsu/8ZxWv/Tgmf/4pdz/+egev/vp33/8KuA//Ku
+        gv/wrIH/6KB4/755Xf+bUUL/Lk5v/zdrkP9sudj/d8Ld/2W32P9wu9f/crzX/1CCm/8QKD7/ZTc0/65c
+        R//OfWD/245u/9WKbf/imn7/5bag/++5j//s18T837ib/FoxI/93Oy7/y3Nc/9qJbP/il3b/5p95//Co
+        fv/xqoH/8q6C/+Ogef/DeV3/DyM4/wgdM/8uWnr/W6zQ/2S01v+KYV7/0odn/+ylfP+LpKz/YLXW/zqO
+        tf8TKkD/xXVc/9B/Yv/ThGX/0YFm/96SeP/ls6D/9b2V/+zXxP3fuJv8WzIk/3c7Lv/IcVv/3Ypr/+OW
+        c//moXr/7ql9/+6ogP/vqn//rX5s/yxXev86m8X/PKvV/0iw1/9WrtP/YZu5/5Y9Lv/Edlj/5p53/+qh
+        ev/lmnf/4pRw/92Lb//Wg2b/znlg/8t1XP/Od17/3o12/+e0oP/2vZX/7NfE/d+4m/xbMST/eDsv/8lx
+        W//bimv/4JNx/92VcP/WjGj/0YNk/86CYf84XHr/OqLP/zy03v87rdf/QrPb/0Wy2v8cO1T/fCki/55J
+        Nv+/b1P/yXlc/8RyVv/AbFP/vGVP/7ZeTP+oWEn/rlRD/7ZbSf/Cb13/2JmE/+qsh//r1cL937ib/Fwy
+        JP94PC//yXFa/92Ja//Vh2n/vHFV/5lKOf+ZQDH/cTs3/zqgzP88tN7/IFx6/xk4Uf89td//UbHV/xYw
+        Sf8TKkD/giYd/4YyJv+JNCn/hjIn/4ItJv+DKyP/ezAt/34nH/9+Jx//hSoj/401K/+qXFD/1pFy/+jR
+        vv3fuJv8WzEk/3c7Lv/Hb1n/24Rp/65yYf8uTmr/DSY9/w0kO/8PJz7/QqXQ/0ut1P8QKUD/K36i/z61
+        3/9gsdP/NIGr/w4mPf8aNlH/CS9R/wIoSf8CKEn/AytK/wAbOP8AGzj/ABs4/wAbOP8DID3/ABs4/4BO
+        T//Wk3P/6dK//eC4m/1bMSP/eDsv/8lyWv/EfGf/Qnqd/0yPtf9Ng6T/LWSM/w0lPf8eR2T/Vq/U/1ix
+        1v9Gtd3/TLPa/ziIs/8zg67/DSM6/zl7o/8/jbj/QY24/0KNt/9BiLL/P4aw/0eNtf9AibP/Q464/z+E
+        r/8RLkb/qnFp/+mqhf/r1sP94Lib/VwxJP94PS//yHBZ/5F5e/9GqdH/PrXf/0G03f9Ysdb/P4qz/xEr
+        RP8gT27/TJ3F/2Cx0/+PZF//PZjE/zeMuv8RLkb/jDgt/0WZxP9FmMP/RICl/xEuRv99MzP/Zq/R/0aT
+        vv9DfaT/ES5G/41FQf/Rj3//8bWO/+zXxP3guJv9WzEk/3c8Lv+6dWP/SKvU/zy13/89sdv/YnaI/3u1
+        zP98x+P/UqTL/xs7Vv8UKD3/Ziwt/5Q+Lv9HncL/Op3L/xEuRv+ILyf/XK7T/0uiz/8RLkb/SDtO/5Ev
+        K/9hrtL/TJ3I/xEuRv9HO0//pU5D/9mYif/xto3/7NfE/eC4m/1cMiP/dzsv/8RuWP9Itd3/PbXf/yxA
+        Vv+LPzL/y31d/3+ouf+H0On/b7/d/z56m/8XMEf/dUdI/z+s1v87qdf/HD9c/4EqJP9TuN3/TK/Z/xEu
+        Rv9bKTH/lCwr/2C01/9LptH/ES5G/1wqM/+sVEr/25qM//G1j//s18T94Lib/V0wJP95PC//uG1e/0K0
+        3v8/sdv/IDVM/4U7Mf/JeVv/oZqX/0u64/93wNz/iNDo/y5um/+ZVUz/PLXf/zuu2v8cP1z/fCwp/z+1
+        3/8/tN7/HkJh/2kaG/+MLzD/SrXd/z+z3P8eQ2H/ah4g/6xTSf/cmo//8bSN/+zXxP3guJv9WzEi/3g6
+        L/+xbGD/Trnf/0ax2v8eNEz/hDov/8NyV/+hmJP/PL/f/y9wmP9gJSX/hjcw/3NCQv88t9//PLLf/xw/
+        XP+AJiL/P7Xf/z603v8eQ2H/Zhka/40wNP9Btt//P7Te/x5DYf9nHh7/rVFH/9qXjP/xtY3/7NfE/eC4
+        m/1aLyH/djgt/8BiUP9xxuX/Ua3U/x0zS/9/NCz/umZO/6uWjv9y0Oj/JGKF/xonPf9wGhX/P1pw/0TG
+        4f88td//Nk1s/4YpJf9JuN//Q7Le/x5DYf9jFxr/jy0u/z+23/89td7/HkNi/2QeIP+nTkb/1pOJ//C0
+        jP/s18T94Lib/VosIP90Mir/vF9M/43b7/9Vtt3/HDJJ/3QqJP+gSDj/p4J5/47d8f9Hr9r/Dyc//x4z
+        UP9PtNv/Vsnl/zyy3/9LN0r/jTU3/2XD4/9EteD/HkRi/2EWGP+OLC3/Rrjh/0i44v8eRmT/ZRoa/6BI
+        Qf/RjYX/8LOM/+zXxP3guJv9Vywf/28tJv+4WEj/jdvv/1e95f8ZM0z/USQq/3wkHf+MNy7/mN/y/3/Z
+        7/8ydJ7/N5HA/3HW6/9x0uz/MnOb/20lKP+VOz3/k9vx/0O44/8eRGL/Vhkf/4wnKf9PvOL/R7vl/xw/
+        XP9PHyf/nUQ+/8uHgP/vsov/7NfE/eC4m/1VKh7/bCch/7RTRP+P3PD/bMvq/xk6Vv8MJTz/IDBH/2Eg
+        H/+GdHn/n+T2/4fd8f992+7/luH0/3XQ7f9EO1D/gx8f/502N/+d5ff/SsHq/xo4U/9ZFxz/jCMj/47a
+        8v9NxfD/HD9c/0wgKP+fRj//y4WA/+6xiv/s18T94Lib/VMoHf9pJh7/sEg//4/c8P+Y4vT/itnx/3HL
+        7P8nao3/GiU3/4wwKv+J2u//nOP1/5ri9f+S4PP/RY2y/1AwQv+QJCT/oEJE/5nk9/9l1/j/Ei5J/1gU
+        GP+GICD/neX3/0zQ+P8LK0f/Txwj/5g/O//Gfnr/7rGK/+zXxP3guJv9USYc/2cfG/+qQTv/kd3w/4rd
+        8f+O2Ov/oOP1/23H4v8/WnD/oDs0/4dhZf+S3/P/kN/y/2K11/83R2X/fScr/5wqKv+fQEL/muT3/23c
+        9/8CLUv/VA0R/3EVFf+Z5Pf/c933/wAnRf9ZDxD/hC4r/7tybv/usYr/7NfE/eC4m/1QJBr/ZBsZ/6I4
+        Mv+T3vH/bNDr/0tccf92HRn/lDUv/6I9NP+TLin/hiQi/35aYf9uy+f/P2qK/3gwO/+cKSn/oiwt/5VM
+        Uv+Y5Pf/dt73/wItTP8lOFP/dzQ4/5vk9/+C4Pf/IFV0/wcsSP+IMS//t21s/+6wi//s18T94Lib/U0h
+        GP9fFRT/mC0s/5Tf8/9w1uz/IDBH/1IYHf90HRr/aS41/zVBW/9jIyv/iyEi/54qK/+iLC3/oSsr/6Er
+        LP+iLC7/jeL3/6Pm9/+j5vf/gNXw/xE9Xf9+3Pf/keP3/5Lj9/912/f/H1Bz/6ZHRP/AdXb/7K6J/+zX
+        xP3guJv9RR0U/1kREf91MTX/luH0/3XY7f8XMEn/GB8w/0wXHf9BfqH/XZWz/18eJP+GHyD/nCkq/6Er
+        Lf+gKyz/oiwu/6MsLf+jKir/oyor/6IsLf+iLC7/oiwu/6AyNP+iLS7/oy0u/6MtLf+lMDH/s05O/8J1
+        d//qqYb/7NfE/eC4m/0/HBP/RwoM/3Q6Qv+W4vT/iuDx/0ycvf8dPV3/DilB/1+y1P9aosL/VSUv/4sh
+        I/+bKCn/nyst/54qLP+gKyz/oCss/6IsLf+iKyv/oiss/6ErLP+iLC//oSss/6IrLP+iLC7/oSst/6Et
+        L/+vS03/umhq/+ajgP/s18T85r6f+Vs1JP8jAAD/a0BK/5jj9f+f5Pb/oeX3/5Pf8v961+3/Y7TU/z9n
+        gv9qIin/liYo/5soKv+bKSz/nykr/6AqLv+gKiz/oCos/6AqLP+hKCr/oSot/6AqLf+fKCr/nykt/58p
+        Lf+cKCz/nyot/6c+Qf+zU1X/5aB2/+zXxPX/6c7/uIJd/xAAAP88BAb/eCwy/41SWP+Ug4//lNPk/6Lm
+        9/90y+X/hSAk/5AgJP+VJCr/kiQq/5IjKf+UIyv/lSMp/5UjKf+VIyn/lSMp/5UkK/+VIif/lCIo/5Qj
+        Kv+TIij/kiIo/48hJ/+MICb/jBwk/7RLQv/xsoP/9One///69f//1bD/ilk//xIAAP8mAAD/UQoO/18N
+        E/9dCw//YBcd/1koMv9cCAv/WgoQ/1cMEv9WDxT/WAsR/1oLD/9YCxH/WQwR/1oLEf9YDBH/WgsR/1cL
+        Ef9XCxH/WAsS/1gMEf9YCxH/WAkQ/1kID/+CJiL/5pdr///Vs////v/n//r1AP/27uf/17X/xItm/2g+
+        LP8/GxH/OBAL/z0SDf9BEQ3/QREN/0ERDf9CEQ3/RBAL/0QPC/9CEQ3/QREO/0ERDf9BEQ3/QREN/0ER
+        Df9BEQ7/QREN/0ERDf9BEQ7/RBMP/0wZE/9nLiL/pGZK//Gxg///2rf///n05//+/wD/+vUA//buAP/9
+        +ef/8d3//969//rFmvv6xZr/+sWa//rFmv/6xZr/+sWa//rFmv/6xZr/+sWa//rFmv/6xZr/+sWa//rF
+        mv/6xZr/+sWa//rFmv/6xZr/+sWa//rFmv/6xZr/+sWa//zHnPn/4sL///Df///9/P//+fQA//7/AMAA
+        AAOAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAA
+        AAHAAAADKAAAABAAAAAgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wDCkC3/wpAt/8KQ
+        Lf/CkC3/wpAt/8KQLf/CkC3/wpAt/8KQLf/CkC3/wpAt/8KQLf/CkC3/wpAt/////wD/217/uWtR/9eC
+        YP//rIX//7OF///Ihf//2Iz/8aRy/19OSP9bLDH/kDMs/8d2Vf//q4f//66J///NqP//tAD//9te/35W
+        Nv//q33//7mU//+3k///uIv/omxX/2peev9GrNP/U7LP/ypomP9RNzj/4Ido//+njP//y53//7QA///b
+        Xv+HXD3//6mR//+8k///uZD/j29m/zhylf9Qs+H/fLbG//ahdv/ez7//ooGM/9KFXf/RjWP//8Sg//+0
+        AP//217/jF1A//+hgf/ufFH/gFhH/1in0/9Y4fr/UNfv/yw/V/+uPTP/w2BB/8RMRP+KRjv/lDs4/91t
+        W///tAD//9te/45eOP+UcGH/SGuM/xc3TP9Rq9f/R6nS/zPl9v8sV3v/WLHk/1ix5P9YseT/WLHk/1ix
+        5P9rRU7//7QA///bXv+AXT3/cbvb/1Dc9P9iudX/IGWZ/1Jkgf9Gx9T/O2OY/4BSZ/9YseT/X0le/2ew
+        4P84TYX/znNt//+0AP//217/fFZI/07d9v95VlH/wWVe/3bg9v9XVHr/RbvM/zR7pf+aOUb/Z7Dg/4Mw
+        M/9evuL/MDFP//yLaP//tAD//9te/4BUR/942fr/TkpK/8ReVv8k1eb/TiMx/0Ts//9Tja//nzlH/16+
+        4v9+MDT/b9P2/zE7Uf/xfWj//7QA///bXv+MTk7/WOH6/x0sOv+sU0z/ROz//yp6oP8s9///b22E/40t
+        Q/9v0/b/fic0/3LS+P82Ok7/yWZc//+0AP//217/hzk+/1r4//9No83/ID5T/7exvP+8////PvH//4Up
+        Of++ICr/ctL4/4weL/+C3f//MyxJ/8dlTv//tAD//9te/4ovMf9Z/P//SNzq/5G51f+4Ojj/m8vg/2p1
+        iv+nFB7/viAq/4Ld//9KNEj/nf///x1Edf++VVT//7QA///bXv95Kzj/h/H//w8tQP9fNkf/ak5i/7Aa
+        Jf++ICr/xicy/2/H6P9q1vH/b8fo/2rW8f9vx+j/vGJo//+0AP//217/Ohw4/8D1//864+3/Xc3z/3Cc
+        uv/GGSD/yyIs/8kiLf/JFh7/zRce/9IbJP/KExv/0Rkk/8ZQVv//tAD//9te/0YiFf9QHC3/jWqE/258
+        lv92CSf/gwIi/4MFI/+DBSP/gwUj/4MFI/+DBSP/gQUj/4kHIP/fWVD//7QA/////wD/6L7//+i+///o
+        vv//6L7//+i+///ovv//6L7//+i+///ovv//6L7//+i+///ovv//6L7//+i+/////wCAAQAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAA
+</value>
+  </data>
+</root>

BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Debug/Eq2VpkTool.exe


BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Debug/Eq2VpkTool.pdb


BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Debug/ICSharpCode.SharpZipLib.dll


BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Debug/Spart.dll


BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Debug/Spart.pdb


+ 668 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Debug/Spart.xml

@@ -0,0 +1,668 @@
+<?xml version="1.0"?>
+<doc>
+    <assembly>
+        <name>Spart</name>
+    </assembly>
+    <members>
+        <member name="T:Spart.Actions.ActionEventArgs">
+            <summary>
+            Action event argument class
+            </summary>
+        </member>
+        <member name="M:Spart.Actions.ActionEventArgs.#ctor(Spart.Parsers.ParserMatch)">
+            <summary>
+            Construct a new event argument instance
+            </summary>
+            <param name="match"></param>
+        </member>
+        <member name="M:Spart.Actions.ActionEventArgs.#ctor(Spart.Parsers.ParserMatch,System.Object)">
+            <summary>
+            Construct a new event argument instance
+            </summary>
+            <param name="match"></param>
+            <param name="typedValue"></param>
+        </member>
+        <member name="P:Spart.Actions.ActionEventArgs.Match">
+            <summary>
+            The parser match
+            </summary>
+        </member>
+        <member name="P:Spart.Actions.ActionEventArgs.Value">
+            <summary>
+            The parser match value
+            </summary>
+        </member>
+        <member name="P:Spart.Actions.ActionEventArgs.TypeValue">
+            <summary>
+            The typed parse result
+            </summary>
+        </member>
+        <member name="T:Spart.Actions.ActionHandler">
+            <summary>
+            Action handler delegate
+            </summary>
+        </member>
+        <member name="T:Spart.Actions.Actor">
+            <summary>
+            Static helper class that creates actors
+            </summary>
+        </member>
+        <member name="M:Spart.Actions.Actor.Append(System.Collections.IList)">
+            <summary>
+            Create an actor that append the parse result to a <see cref="T:System.Collections.IList"/>.
+            </summary>
+            <param name="list"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Spart.Actions.Actor.Assign(System.Object)">
+            <summary>
+            Create an actor that assign the parse result to an object
+            </summary>
+            <param name="o"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Spart.Actions.Actor.Throw(System.Exception)">
+            <summary>
+            Creates an actor that throws an exception
+            </summary>
+            <param name="ex"></param>
+            <returns></returns>
+        </member>
+        <member name="T:Spart.Actions.Actors.AppendActor">
+            <summary>
+            Actor that appends parse result to <see cref="T:System.Collections.IList"/>.
+            </summary>
+        </member>
+        <member name="T:Spart.Actions.Actors.IActor">
+            <summary>
+            Actor interface
+            </summary>
+        </member>
+        <member name="M:Spart.Actions.Actors.IActor.DoAction(System.Object,Spart.Actions.ActionEventArgs)">
+            <summary>
+            Handler method.
+            <seealso cref="T:Spart.Actions.ActionHandler"/>
+            <seealso cref="T:Spart.Actions.ActionEventArgs"/>
+            </summary>
+            <param name="sender"></param>
+            <param name="args"></param>
+        </member>
+        <member name="T:Spart.Parsers.Composite.DifferenceParser">
+            <summary>
+            Summary description for DifferenceParser.
+            </summary>
+        </member>
+        <member name="T:Spart.Parsers.Dirs">
+            <summary>
+            Static helper class to create directives
+            </summary>
+        </member>
+        <member name="T:Spart.Parsers.NonTerminal.NonTerminalParser">
+            <summary>
+            NonTerminal parser abstract class
+            </summary>
+        </member>
+        <member name="M:Spart.Parsers.NonTerminal.NonTerminalParser.#ctor">
+            <summary>
+            Default constructor
+            </summary>
+        </member>
+        <member name="P:Spart.Parsers.NonTerminal.NonTerminalParser.ID">
+            <summary>
+            Rule ID, used for debugging
+            </summary>
+        </member>
+        <member name="E:Spart.Parsers.NonTerminal.NonTerminalParser.PreParse">
+            <summary>
+            Pre parse event
+            </summary>
+        </member>
+        <member name="E:Spart.Parsers.NonTerminal.NonTerminalParser.PostParse">
+            <summary>
+            Post parse event 
+            </summary>
+        </member>
+        <member name="M:Spart.Parsers.NonTerminal.NonTerminalParser.OnPreParse(Spart.Scanners.IScanner)">
+            <summary>
+            Preparse event caller
+            </summary>
+            <param name="scan"></param>
+        </member>
+        <member name="M:Spart.Parsers.NonTerminal.NonTerminalParser.OnPostParse(Spart.Parsers.ParserMatch,Spart.Scanners.IScanner)">
+            <summary>
+            Post parse event caller
+            </summary>
+            <param name="match"></param>
+            <param name="scan"></param>
+        </member>
+        <member name="M:Spart.Parsers.NonTerminal.NonTerminalParser.AddContext(Spart.Parsers.NonTerminal.IParserContext)">
+            <summary>
+            Adds event handlers
+            </summary>
+            <param name="context"></param>
+        </member>
+        <member name="M:Spart.Parsers.NonTerminal.NonTerminalParser.RemoveContext(Spart.Parsers.NonTerminal.IParserContext)">
+            <summary>
+            Removes event handlers
+            </summary>
+            <param name="context"></param>
+        </member>
+        <member name="T:Spart.Parsers.NonTerminal.PreParseEventArgs">
+            <summary>
+            Summary description for PostParseEventArgs.
+            </summary>
+        </member>
+        <member name="T:Spart.Parsers.NonTerminal.Rule">
+            <summary>
+            A rule is a parser holder.
+            </summary>
+        </member>
+        <member name="M:Spart.Parsers.NonTerminal.Rule.#ctor">
+            <summary>
+            Empty rule creator
+            </summary>
+        </member>
+        <member name="M:Spart.Parsers.NonTerminal.Rule.#ctor(Spart.Parsers.Parser)">
+            <summary>
+            Creates a rule and assign parser
+            </summary>
+            <param name="p"></param>
+        </member>
+        <member name="P:Spart.Parsers.NonTerminal.Rule.Parser">
+            <summary>
+            Rule parser
+            </summary>
+        </member>
+        <member name="M:Spart.Parsers.NonTerminal.Rule.AssignParser(Spart.Parsers.NonTerminal.Rule,Spart.Parsers.Parser)">
+            <summary>
+            Assign a parser to a rule, if r is null, a new rule is created
+            </summary>
+            <param name="r"></param>
+            <param name="p"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Spart.Parsers.NonTerminal.Rule.Parse(Spart.Scanners.IScanner)">
+            <summary>
+            Parse the input
+            </summary>
+            <param name="scanner"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Spart.Parsers.NonTerminal.Rule.ParseMain(Spart.Scanners.IScanner)">
+            <summary>
+            Inner parse method
+            </summary>
+            <param name="scanner"></param>
+            <returns></returns>
+        </member>
+        <member name="T:Spart.Parsers.Ops">
+            <summary>
+            Static helper class to create new parser operators
+            </summary>
+        </member>
+        <member name="M:Spart.Parsers.Ops.Seq(Spart.Parsers.Parser,Spart.Parsers.Parser)">
+            <summary>
+            &gt;&gt; operator
+            </summary>
+            <param name="first"></param>
+            <param name="second"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Spart.Parsers.Ops.Klenee(Spart.Parsers.Parser)">
+            <summary>
+            * operators
+            </summary>
+            <param name="parser"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Spart.Parsers.Ops.Positive(Spart.Parsers.Parser)">
+            <summary>
+            + operator
+            </summary>
+            <param name="parser"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Spart.Parsers.Ops.Optional(Spart.Parsers.Parser)">
+            <summary>
+            ! operator
+            </summary>
+            <param name="parser"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Spart.Parsers.Ops.Alternative(Spart.Parsers.Parser,Spart.Parsers.Parser)">
+            <summary>
+            | operator
+            </summary>
+            <param name="first"></param>
+            <param name="second"></param>
+            <returns></returns>
+        </member>
+        <!-- Badly formed XML comment ignored for member "M:Spart.Parsers.Ops.Intersection(Spart.Parsers.Parser,Spart.Parsers.Parser)" -->
+        <member name="M:Spart.Parsers.Ops.Difference(Spart.Parsers.Parser,Spart.Parsers.Parser)">
+            <summary>
+            - operator
+            </summary>
+            <param name="first"></param>
+            <param name="second"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Spart.Parsers.Ops.List(Spart.Parsers.Parser,Spart.Parsers.Parser)">
+            <summary>
+            % operator
+            </summary>
+            <param name="first"></param>
+            <param name="second"></param>
+            <returns></returns>
+        </member>
+        <member name="T:Spart.Parsers.Parser">
+            <summary>
+            Abstract parser class
+            </summary>
+        </member>
+        <member name="M:Spart.Parsers.Parser.#ctor">
+            <summary>
+            Default constructor
+            </summary>
+        </member>
+        <member name="M:Spart.Parsers.Parser.ParseMain(Spart.Scanners.IScanner)">
+            <summary>
+            Inner parse method
+            </summary>
+            <param name="scan">scanner</param>
+            <returns>the match</returns>
+        </member>
+        <member name="M:Spart.Parsers.Parser.Parse(Spart.Scanners.IScanner)">
+            <summary>
+            Outer parse method
+            </summary>
+            <param name="scan"></param>
+            <returns></returns>
+        </member>
+        <member name="E:Spart.Parsers.Parser.Act">
+            <summary>
+            Action event
+            </summary>
+        </member>
+        <member name="M:Spart.Parsers.Parser.OnAction(Spart.Parsers.ParserMatch)">
+            <summary>
+            Action caller method
+            </summary>
+            <param name="m"></param>
+        </member>
+        <member name="M:Spart.Parsers.Parser.op_UnaryPlus(Spart.Parsers.Parser)">
+            <summary>
+            Positive operator
+            </summary>
+            <param name="p"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Spart.Parsers.Parser.op_LogicalNot(Spart.Parsers.Parser)">
+            <summary>
+            Optional operator
+            </summary>
+            <param name="p"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Spart.Parsers.Parser.op_BitwiseOr(Spart.Parsers.Parser,Spart.Parsers.Parser)">
+            <summary>
+            Alternative operator
+            </summary>
+            <param name="left"></param>
+            <param name="right"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Spart.Parsers.Parser.op_BitwiseAnd(Spart.Parsers.Parser,Spart.Parsers.Parser)">
+            <summary>
+            Intersection operator
+            </summary>
+            <param name="left"></param>
+            <param name="right"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Spart.Parsers.Parser.op_Subtraction(Spart.Parsers.Parser,Spart.Parsers.Parser)">
+            <summary>
+            Difference operator
+            </summary>
+            <param name="left"></param>
+            <param name="right"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Spart.Parsers.Parser.op_Modulus(Spart.Parsers.Parser,Spart.Parsers.Parser)">
+            <summary>
+            List operator
+            </summary>
+            <param name="left"></param>
+            <param name="right"></param>
+            <returns></returns>
+        </member>
+        <member name="T:Spart.Parsers.ParserMatch">
+            <summary>
+            A parser match
+            </summary>
+        </member>
+        <member name="M:Spart.Parsers.ParserMatch.#ctor(Spart.Scanners.IScanner,System.Int64,System.Int32)">
+            <summary>
+            Builds a new match
+            </summary>
+            <param name="scanner"></param>
+            <param name="offset"></param>
+            <param name="length"></param>
+        </member>
+        <member name="P:Spart.Parsers.ParserMatch.Scanner">
+            <summary>
+            Scanner
+            </summary>
+        </member>
+        <member name="P:Spart.Parsers.ParserMatch.Offset">
+            <summary>
+            Offset
+            </summary>
+        </member>
+        <member name="P:Spart.Parsers.ParserMatch.Length">
+            <summary>
+            Length
+            </summary>
+        </member>
+        <member name="P:Spart.Parsers.ParserMatch.Value">
+            <summary>
+            Extracts the match value
+            </summary>
+        </member>
+        <member name="P:Spart.Parsers.ParserMatch.Success">
+            <summary>
+            True if match successfull
+            </summary>
+        </member>
+        <member name="P:Spart.Parsers.ParserMatch.Empty">
+            <summary>
+            True if match empty
+            </summary>
+        </member>
+        <member name="M:Spart.Parsers.ParserMatch.Concat(Spart.Parsers.ParserMatch)">
+            <summary>
+            Concatenates match with m
+            </summary>
+            <param name="m"></param>
+        </member>
+        <member name="T:Spart.Parsers.Prims">
+            <summary>
+            Static helper class to create primitive parsers
+            </summary>
+        </member>
+        <member name="M:Spart.Parsers.Prims.Ch(System.Char)">
+            <summary>
+            Creates a parser that matches a single character
+            </summary>
+            <param name="c">character to match</param>
+            <returns></returns>
+        </member>
+        <member name="M:Spart.Parsers.Prims.Str(System.String)">
+            <summary>
+            Creates a parser that matches a string
+            </summary>
+            <param name="s">string to match</param>
+            <returns></returns>
+        </member>
+        <member name="M:Spart.Parsers.Prims.Range(System.Char,System.Char)">
+            <summary>
+            Creates a parser that matches a range of character
+            </summary>
+            <param name="first"></param>
+            <param name="last"></param>
+            <returns></returns>
+        </member>
+        <member name="P:Spart.Parsers.Prims.AnyChar">
+            <summary>
+            Creates a parser that matches any character
+            </summary>
+        </member>
+        <member name="P:Spart.Parsers.Prims.Control">
+            <summary>
+            Creates a parser that matches control characters
+            </summary>
+        </member>
+        <member name="P:Spart.Parsers.Prims.Digit">
+            <summary>
+            Creates a parser that matches digit characters
+            </summary>
+        </member>
+        <member name="P:Spart.Parsers.Prims.Letter">
+            <summary>
+            Creates a parser that matches letter characters
+            </summary>
+        </member>
+        <member name="P:Spart.Parsers.Prims.LetterOrDigit">
+            <summary>
+            Creates a parser that matches letter or digit characters
+            </summary>
+        </member>
+        <member name="P:Spart.Parsers.Prims.Lower">
+            <summary>
+            Creates a parser that matches lower case characters
+            </summary>
+        </member>
+        <member name="P:Spart.Parsers.Prims.Punctuation">
+            <summary>
+            Creates a parser that matches punctuation characters
+            </summary>
+        </member>
+        <member name="P:Spart.Parsers.Prims.Separator">
+            <summary>
+            Creates a parser that matches separator characters
+            </summary>
+        </member>
+        <member name="P:Spart.Parsers.Prims.Symbol">
+            <summary>
+            Creates a parser that matches symbol characters
+            </summary>
+        </member>
+        <member name="P:Spart.Parsers.Prims.Upper">
+            <summary>
+            Creates a parser that matches upper case characters
+            </summary>
+        </member>
+        <member name="P:Spart.Parsers.Prims.WhiteSpace">
+            <summary>
+            Creates a parser that matches whitespace characters
+            </summary>
+        </member>
+        <member name="P:Spart.Parsers.Prims.Eol">
+            <summary>
+            Creates a parser that matches and end of line
+            </summary>
+        </member>
+        <member name="P:Spart.Parsers.Prims.End">
+            <summary>
+            Creates a parser that matches the end of the input
+            </summary>
+        </member>
+        <member name="T:Spart.Scanners.IFilter">
+            <summary>
+            Input filter interface
+            </summary>
+        </member>
+        <member name="M:Spart.Scanners.IFilter.Filter(System.String)">
+            <summary>
+            Converts s to lower string
+            </summary>
+            <param name="c"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Spart.Scanners.IFilter.Filter(System.Char)">
+            <summary>
+            Converts c to lower char
+            </summary>
+            <param name="c"></param>
+            <returns></returns>
+        </member>
+        <member name="T:Spart.Scanners.IScanner">
+            <summary>
+            Input scanner interface
+            </summary>
+        </member>
+        <member name="P:Spart.Scanners.IScanner.AtEnd">
+            <summary>
+            Return true if all input is consummed
+            </summary>
+        </member>
+        <member name="M:Spart.Scanners.IScanner.Read">
+            <summary>
+            Reads one character of the input
+            </summary>
+            <returns>true if not at end</returns>
+        </member>
+        <member name="M:Spart.Scanners.IScanner.Peek">
+            <summary>
+            Current character
+            </summary>
+            <returns></returns>
+        </member>
+        <member name="P:Spart.Scanners.IScanner.Offset">
+            <summary>
+            Scanner cursor position
+            </summary>
+        </member>
+        <member name="M:Spart.Scanners.IScanner.Seek(System.Int64)">
+            <summary>
+            Move cursor position to the offset
+            </summary>
+            <param name="offset"></param>
+        </member>
+        <member name="M:Spart.Scanners.IScanner.Substring(System.Int64,System.Int32)">
+            <summary>
+            Extracts a substring of the input
+            </summary>
+            <param name="offset"></param>
+            <param name="length"></param>
+            <returns></returns>
+        </member>
+        <member name="P:Spart.Scanners.IScanner.Filter">
+            <summary>
+            Sets the input filter
+            </summary>
+        </member>
+        <member name="P:Spart.Scanners.IScanner.NoMatch">
+            <summary>
+            Create a failure match
+            </summary>
+        </member>
+        <member name="P:Spart.Scanners.IScanner.EmptyMatch">
+            <summary>
+            Create an empty match
+            </summary>
+        </member>
+        <member name="M:Spart.Scanners.IScanner.CreateMatch(System.Int64,System.Int32)">
+            <summary>
+            Create a match out of the intput
+            </summary>
+            <param name="offset"></param>
+            <param name="length"></param>
+            <returns></returns>
+        </member>
+        <member name="T:Spart.Scanners.StringScanner">
+            <summary>
+            Scanner acting on a string.
+            <seealso cref="T:Spart.Scanners.IScanner"/>
+            </summary>
+        </member>
+        <member name="M:Spart.Scanners.StringScanner.#ctor(System.String)">
+            <summary>
+            Creates a scanner on the string.
+            </summary>
+            <param name="inputString">Input string</param>
+            <exception cref="T:System.ArgumentNullException">input string is null</exception>
+        </member>
+        <member name="M:Spart.Scanners.StringScanner.#ctor(System.String,System.Int64)">
+            <summary>
+            Creates a scanner on the string at a specified offset
+            </summary>
+            <param name="inputString">Input string</param>
+            <exception cref="T:System.ArgumentNullException">input string is null</exception>
+            <exception cref="T:System.ArgumentException">offset if out of range</exception>
+        </member>
+        <member name="P:Spart.Scanners.StringScanner.InputString">
+            <summary>
+            the input string
+            </summary>
+        </member>
+        <member name="P:Spart.Scanners.StringScanner.Offset">
+            <summary>
+            Current offset
+            </summary>
+        </member>
+        <member name="P:Spart.Scanners.StringScanner.AtEnd">
+            <summary>
+            true if at the end of the string
+            </summary>
+        </member>
+        <member name="M:Spart.Scanners.StringScanner.Read">
+            <summary>
+            Advance the cursor once
+            </summary>
+            <returns>true if not at end</returns>
+            <exception cref="T:System.Exception">If called while AtEnd is true</exception>
+        </member>
+        <member name="M:Spart.Scanners.StringScanner.Peek">
+            <summary>
+            Current character
+            </summary>
+            <returns>character at cursor position</returns>
+        </member>
+        <member name="M:Spart.Scanners.StringScanner.Substring(System.Int64,System.Int32)">
+            <summary>
+            Extracts a substring 
+            </summary>
+            <param name="offset"></param>
+            <param name="length"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Spart.Scanners.StringScanner.Seek(System.Int64)">
+            <summary>
+            Moves the cursor to the offset position
+            </summary>
+            <param name="offset"></param>
+        </member>
+        <member name="P:Spart.Scanners.StringScanner.Filter">
+            <summary>
+            Current filter
+            </summary>
+        </member>
+        <member name="P:Spart.Scanners.StringScanner.NoMatch">
+            <summary>
+            Failure match
+            </summary>
+        </member>
+        <member name="P:Spart.Scanners.StringScanner.EmptyMatch">
+            <summary>
+            Empty match
+            </summary>
+        </member>
+        <member name="M:Spart.Scanners.StringScanner.CreateMatch(System.Int64,System.Int32)">
+            <summary>
+            Creates a successful match
+            </summary>
+            <param name="offset"></param>
+            <param name="length"></param>
+            <returns></returns>
+        </member>
+        <member name="T:Spart.Scanners.ToLowerFilter">
+            <summary>
+            A to lower input filter
+            </summary>
+        </member>
+        <member name="M:Spart.Scanners.ToLowerFilter.Filter(System.String)">
+            <summary>
+            Converts s to lower string
+            </summary>
+            <param name="s"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Spart.Scanners.ToLowerFilter.Filter(System.Char)">
+            <summary>
+            Converts i to lower i
+            </summary>
+            <param name="i"></param>
+            <returns></returns>
+        </member>
+    </members>
+</doc>

BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Debug/libeq2.dll


BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Debug/libeq2.pdb


BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Release/Eq2VpkTool.exe


BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Release/ICSharpCode.SharpZipLib.dll


BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Release/Spart.dll


BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/bin/Release/libeq2.dll


BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/eq2.ico


BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache


BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Debug/Eq2VpkTool.MainWindow.resources


BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Debug/Eq2VpkTool.Properties.Resources.resources


+ 0 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Debug/Eq2VpkTool.csproj.CopyComplete


+ 45 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Debug/Eq2VpkTool.csproj.FileListAbsolute.txt

@@ -0,0 +1,45 @@
+D:\github\Maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\Eq2VpkTool.exe
+D:\github\Maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\Eq2VpkTool.pdb
+D:\github\Maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\libeq2.dll
+D:\github\Maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\Spart.dll
+D:\github\Maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\ICSharpCode.SharpZipLib.dll
+D:\github\Maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\libeq2.pdb
+D:\github\Maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\Spart.pdb
+D:\github\Maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\Spart.xml
+D:\github\Maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Debug\Eq2VpkTool.csprojAssemblyReference.cache
+D:\github\Maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Debug\Eq2VpkTool.MainWindow.resources
+D:\github\Maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Debug\Eq2VpkTool.Properties.Resources.resources
+D:\github\Maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Debug\Eq2VpkTool.csproj.GenerateResource.cache
+D:\github\Maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Debug\Eq2VpkTool.csproj.CopyComplete
+D:\github\Maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Debug\Eq2VpkTool.exe
+D:\github\Maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Debug\Eq2VpkTool.pdb
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\Eq2VpkTool.exe
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\Eq2VpkTool.pdb
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\libeq2.dll
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\Spart.dll
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\ICSharpCode.SharpZipLib.dll
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\libeq2.pdb
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\Spart.pdb
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\Spart.xml
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Debug\Eq2VpkTool.csprojAssemblyReference.cache
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Debug\Eq2VpkTool.MainWindow.resources
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Debug\Eq2VpkTool.Properties.Resources.resources
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Debug\Eq2VpkTool.csproj.GenerateResource.cache
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Debug\Eq2VpkTool.csproj.CopyComplete
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Debug\Eq2VpkTool.exe
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Debug\Eq2VpkTool.pdb
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\Eq2VpkTool.exe
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\Eq2VpkTool.pdb
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\libeq2.dll
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\Spart.dll
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\ICSharpCode.SharpZipLib.dll
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\libeq2.pdb
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\Spart.pdb
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Debug\Spart.xml
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Debug\Eq2VpkTool.csprojAssemblyReference.cache
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Debug\Eq2VpkTool.MainWindow.resources
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Debug\Eq2VpkTool.Properties.Resources.resources
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Debug\Eq2VpkTool.csproj.GenerateResource.cache
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Debug\Eq2VpkTool.csproj.CopyComplete
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Debug\Eq2VpkTool.exe
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Debug\Eq2VpkTool.pdb

BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Debug/Eq2VpkTool.csproj.GenerateResource.cache


BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Debug/Eq2VpkTool.csprojAssemblyReference.cache


BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Debug/Eq2VpkTool.exe


BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Debug/Eq2VpkTool.pdb


BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Release/Eq2VpkTool.MainWindow.resources


BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Release/Eq2VpkTool.Properties.Resources.resources


+ 0 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Release/Eq2VpkTool.csproj.CopyComplete


+ 20 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Release/Eq2VpkTool.csproj.FileListAbsolute.txt

@@ -0,0 +1,20 @@
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Release\Eq2VpkTool.exe
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Release\libeq2.dll
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Release\Spart.dll
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Release\ICSharpCode.SharpZipLib.dll
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Release\Eq2VpkTool.csprojAssemblyReference.cache
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Release\Eq2VpkTool.MainWindow.resources
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Release\Eq2VpkTool.Properties.Resources.resources
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Release\Eq2VpkTool.csproj.GenerateResource.cache
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Release\Eq2VpkTool.csproj.CopyComplete
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewerFixed\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Release\Eq2VpkTool.exe
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Release\Eq2VpkTool.exe
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Release\libeq2.dll
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Release\Spart.dll
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\bin\Release\ICSharpCode.SharpZipLib.dll
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Release\Eq2VpkTool.csprojAssemblyReference.cache
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Release\Eq2VpkTool.MainWindow.resources
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Release\Eq2VpkTool.Properties.Resources.resources
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Release\Eq2VpkTool.csproj.GenerateResource.cache
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Release\Eq2VpkTool.csproj.CopyComplete
+D:\github\maps\ModelViewer_SAVE\EQ2ModelViewer\EQ2ModelViewer\EQ2ModelViewer\Eq2VpkTool\obj\Release\Eq2VpkTool.exe

BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Release/Eq2VpkTool.csproj.GenerateResource.cache


BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Release/Eq2VpkTool.csprojAssemblyReference.cache


BIN
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Eq2VpkTool/obj/Release/Eq2VpkTool.exe


+ 35 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/FPSClass.cs

@@ -0,0 +1,35 @@
+using System;
+
+namespace EQ2ModelViewer
+{
+    public class FPSClass
+    {
+        private int m_FPS;
+        private int m_Count;
+        private long m_StartTime;
+
+        public void Initialize()
+        {
+            m_FPS = 0;
+            m_Count = 0;
+            m_StartTime = Environment.TickCount;
+        }
+
+        public void Frame()
+        {
+            m_Count++;
+
+            if (Environment.TickCount >= m_StartTime + 1000)
+            {
+                m_FPS = m_Count;
+                m_Count = 0;
+                m_StartTime = Environment.TickCount;
+            }
+        }
+
+        public int GetFPS()
+        {
+            return m_FPS;
+        }
+    }
+}

+ 158 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/FontClass.cs

@@ -0,0 +1,158 @@
+using System;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using System.IO;
+
+using SlimDX;
+using SlimDX.D3DCompiler;
+using SlimDX.Direct3D11;
+using SlimDX.DXGI;
+using SlimDX.Windows;
+using Device = SlimDX.Direct3D11.Device;
+using Resource = SlimDX.Direct3D11.Resource;
+using Buffer = SlimDX.Direct3D11.Buffer;
+
+namespace EQ2ModelViewer
+{
+    public class FontClass
+    {
+        private struct FontType
+        {
+            public float left;
+            public float right;
+            public int size;
+        }
+
+        private FontType[] m_font;
+        private TextureClass m_texture;
+
+        public bool Initialize(Device device, string fontFile, string textureFile)
+        {
+            if (!LoadFontData(fontFile))
+                return false;
+
+            if (!LoadTexture(device, textureFile))
+                return false;
+
+            return true;
+        }
+
+        public void ShutDown()
+        {
+            ReleaseTexture();
+        }
+
+        public ShaderResourceView GetTexture()
+        {
+            return m_texture.GetTexture();
+        }
+
+        public void BuildVertexArray(string sentence, float drawX, float drawY, ref TextClass.VertexType[] vertices)
+        {
+            int numLetters;
+            int index;
+            int i;
+            int letter;
+
+            numLetters = sentence.Length;
+            index = 0;
+            for (i = 0; i < numLetters; i++)
+            {
+                letter = ((int)sentence[i]) - 32;
+                if (letter == 0)
+                {
+                    drawX += 3.0f;
+                }
+                else
+                {
+                    // First Triangle
+                    // Top Left
+                    vertices[index].position = new Vector3(drawX, drawY, 0.0f);
+                    vertices[index].texture = new Vector2(m_font[letter].left, 0.0f);
+                    index++;
+
+                    // Bottom Right
+                    vertices[index].position = new Vector3(drawX + m_font[letter].size, (drawY - 16), 0.0f);
+                    vertices[index].texture = new Vector2(m_font[letter].right, 1.0f);
+                    index++;
+
+                    // Bottom Left
+                    vertices[index].position = new Vector3(drawX, (drawY - 16), 0.0f);
+                    vertices[index].texture = new Vector2(m_font[letter].left, 1.0f);
+                    index++;
+
+                    // Second Triangle
+                    // Top Left
+                    vertices[index].position = new Vector3(drawX, drawY, 0.0f);
+                    vertices[index].texture = new Vector2(m_font[letter].left, 0.0f);
+                    index++;
+
+                    // Top Right
+                    vertices[index].position = new Vector3(drawX + m_font[letter].size, drawY, 0.0f);
+                    vertices[index].texture = new Vector2(m_font[letter].right, 0.0f);
+                    index++;
+
+                    // Bottom right
+                    vertices[index].position = new Vector3((drawX + m_font[letter].size), (drawY - 16), 0.0f);
+                    vertices[index].texture = new Vector2(m_font[letter].right, 1.0f);
+                    index++;
+
+                    // Update the x location for drawing by the size of the letter and one pixel
+                    drawX += m_font[letter].size + 1.0f;
+                }
+            }
+        }
+
+        private bool LoadFontData(string fontFile)
+        {
+            string line;
+            byte i = 0;
+            m_font = new FontType[95];
+            StreamReader reader = new StreamReader(File.Open(fontFile, FileMode.Open));
+            while (i < 95)
+            {
+                line = reader.ReadLine();
+                string[] data = line.Split(' ');
+                /*string line2 = "";
+                for (int z = 0; z < data.Length; z++)
+                    line2 += data[z] + "\n";
+
+                MessageBox.Show(line2);*/
+                int y = 2;
+                if (data[y] == "")
+                    y++;
+
+                m_font[i].left = float.Parse(data[y]);
+                y++; 
+                while (data[y] == "")
+                    y++;
+
+                m_font[i].right = float.Parse(data[y]);
+
+                y++;
+                while (data[y] == "")
+                    y++;
+
+                m_font[i].size = int.Parse(data[y]);
+
+                i++;
+            }
+            reader.Close();
+
+            return true;
+        }
+
+        private bool LoadTexture(Device device, string textureFile)
+        {
+            m_texture = new TextureClass();
+            m_texture.Initialize(device, textureFile);
+            return true;
+        }
+
+        private void ReleaseTexture()
+        {
+            if (m_texture != null)
+                m_texture.ShutDown();
+        }
+    }
+}

+ 176 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/FontShaderClass.cs

@@ -0,0 +1,176 @@
+using System;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+using SlimDX;
+using SlimDX.D3DCompiler;
+using SlimDX.Direct3D11;
+using SlimDX.DXGI;
+using SlimDX.Windows;
+using Device = SlimDX.Direct3D11.Device;
+using Resource = SlimDX.Direct3D11.Resource;
+using Buffer = SlimDX.Direct3D11.Buffer;
+
+namespace EQ2ModelViewer
+{
+    public class FontShaderClass
+    {
+        private struct MatrixBufferType
+        {
+            public Matrix world;
+            public Matrix view;
+            public Matrix projection;
+        }
+
+        private struct PixelBufferType
+        {
+            public Vector4 pixelColor;
+        }
+
+        private VertexShader m_VertexShader;
+        private PixelShader m_PixelShader;
+        private InputLayout m_Layout;
+        private Buffer m_MatrixBuffer;
+        private SamplerState m_SamplerState;
+        private Buffer m_PixelBuffer;
+
+        public bool Initialize(Device device)
+        {
+            return InitializeShader(device, "Font.vs", "Font.ps");
+        }
+
+        private bool InitializeShader(Device device, string vertexShader, string pixelShader)
+        {
+            ShaderSignature inputSignature;
+            string error;
+            // load and compile the vertex shader
+            using (var bytecode = ShaderBytecode.CompileFromFile(vertexShader, "FontVertexShader", "vs_5_0", ShaderFlags.EnableStrictness, EffectFlags.None, null, null, out error))
+            {
+                inputSignature = ShaderSignature.GetInputSignature(bytecode);
+                m_VertexShader = new VertexShader(device, bytecode);
+            }
+
+            if (m_VertexShader == null)
+            {
+                Console.WriteLine("InitializeShader: Error creating vertex shader: " + error);
+                return false;
+            }
+
+            // load and compile the pixel shader
+            using (var bytecode = ShaderBytecode.CompileFromFile(pixelShader, "FontPixelShader", "ps_5_0", ShaderFlags.EnableStrictness, EffectFlags.None, null, null, out error))
+                m_PixelShader = new PixelShader(device, bytecode);
+
+            if (m_PixelShader == null)
+            {
+                Console.WriteLine("InitializeShader: Error creating pixel shader: " + error);
+                return false;
+            }
+
+            var elements = new[] {
+                new InputElement("POSITION", 0, Format.R32G32B32_Float, 0, 0, InputClassification.PerVertexData, 0),
+                new InputElement("TEXCOORD", 0, Format.R32G32_Float, -1, 0, InputClassification.PerVertexData, 0) };
+            m_Layout = new InputLayout(device, inputSignature, elements);
+
+            m_MatrixBuffer = new Buffer(device, System.Runtime.InteropServices.Marshal.SizeOf(typeof(MatrixBufferType)), ResourceUsage.Dynamic, BindFlags.ConstantBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0);
+            if (m_MatrixBuffer == null)
+            {
+                Console.WriteLine("InitializeShader: Unable to create the matrix buffer.");
+                return false;
+            }
+
+            SamplerDescription samplerDesc = new SamplerDescription();
+            samplerDesc.Filter = Filter.MinMagMipLinear;
+            samplerDesc.AddressU = TextureAddressMode.Wrap;
+            samplerDesc.AddressV = TextureAddressMode.Wrap;
+            samplerDesc.AddressW = TextureAddressMode.Wrap;
+            samplerDesc.MipLodBias = 0.0f;
+            samplerDesc.MaximumAnisotropy = 1;
+            samplerDesc.ComparisonFunction = Comparison.Always;
+            samplerDesc.BorderColor = new Color4(0.0f, 0.0f, 0.0f, 0.0f);
+            samplerDesc.MinimumLod = 0;
+            samplerDesc.MaximumLod = 3.402823466e+38f; // from d3d11.h, #define D3D11_FLOAT32_MAX	( 3.402823466e+38f )
+
+            m_SamplerState = SamplerState.FromDescription(device, samplerDesc);
+            if (m_SamplerState == null)
+            {
+                Console.WriteLine("InitializeShader: Unable to create the sampler state.");
+                return false;
+            }
+
+            m_PixelBuffer = new Buffer(device, System.Runtime.InteropServices.Marshal.SizeOf(typeof(PixelBufferType)), ResourceUsage.Dynamic, BindFlags.ConstantBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0);
+            if (m_PixelBuffer == null)
+            {
+                Console.WriteLine("InitializeShader: Unable to create the pixel buffer.");
+                return false;
+            }
+
+            return true;
+        }
+
+        public void ShutDown()
+        {
+            m_VertexShader.Dispose();
+            m_PixelShader.Dispose();
+            m_Layout.Dispose();
+            m_MatrixBuffer.Dispose();
+            m_SamplerState.Dispose();
+            m_PixelBuffer.Dispose();
+        }
+
+        private bool SetShaderParameters(DeviceContext context, Matrix worldMatrix, Matrix viewMatrix, Matrix projectionMatrix, ShaderResourceView texture, Vector4 PixelColor)
+        {
+            Matrix.Transpose(ref worldMatrix, out worldMatrix);
+            Matrix.Transpose(ref viewMatrix, out viewMatrix);
+            Matrix.Transpose(ref projectionMatrix, out projectionMatrix);
+
+
+            var mappedResource = context.MapSubresource(m_MatrixBuffer, 0, MapMode.WriteDiscard, SlimDX.Direct3D11.MapFlags.None);
+            MatrixBufferType data = new MatrixBufferType();
+            data.world = worldMatrix;
+            data.view = viewMatrix;
+            data.projection = projectionMatrix;
+            mappedResource.Data.Write<MatrixBufferType>(data);
+            context.UnmapSubresource(m_MatrixBuffer, 0);
+
+            context.VertexShader.SetConstantBuffer(m_MatrixBuffer, 0);
+            context.PixelShader.SetShaderResource(texture, 0);
+
+            mappedResource = context.MapSubresource(m_PixelBuffer, 0, MapMode.WriteDiscard, SlimDX.Direct3D11.MapFlags.None);
+            PixelBufferType data2 = new PixelBufferType();
+            data2.pixelColor = PixelColor;
+            mappedResource.Data.Write<PixelBufferType>(data2);
+            context.UnmapSubresource(m_PixelBuffer, 0);
+
+            context.PixelShader.SetConstantBuffer(m_PixelBuffer, 0);
+
+            return true;
+        }
+
+        public bool Render(DeviceContext context, int indexCount, Matrix worldMatrix, Matrix viewMatrix, Matrix projectionMatrix, ShaderResourceView texture, Vector4 PixelColor)
+        {
+            if (!SetShaderParameters(context, worldMatrix, viewMatrix, projectionMatrix, texture, PixelColor))
+                return false;
+
+            RenderShader(context, indexCount);
+
+            return true;
+        }
+
+        private void RenderShader(DeviceContext context, int indexCount)
+        {
+            // Set the vertex input layout
+            context.InputAssembler.InputLayout = m_Layout;
+
+            // Set the vertex and pixel shaders that will be used to render
+            context.VertexShader.Set(m_VertexShader);
+            context.PixelShader.Set(m_PixelShader);
+
+            // Set the sampler state in the pixel shader
+            context.PixelShader.SetSampler(m_SamplerState, 0);
+
+            // Render
+            context.DrawIndexed(indexCount, 0, 0);
+        }
+    }
+}
+

+ 153 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/FrustumClass.cs

@@ -0,0 +1,153 @@
+using System;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+using SlimDX;
+using SlimDX.D3DCompiler;
+using SlimDX.Direct3D11;
+using SlimDX.DXGI;
+using SlimDX.Windows;
+using Device = SlimDX.Direct3D11.Device;
+using Resource = SlimDX.Direct3D11.Resource;
+using Buffer = SlimDX.Direct3D11.Buffer;
+
+
+namespace EQ2ModelViewer
+{
+    public class FrustumClass
+    {
+        private Plane[] m_Planes = new Plane[6];
+
+        public void ConstructFrustum(float screenDepth, Matrix projectionMatrix, Matrix viewMatrix)
+        {
+            float zMin;
+            float r;
+            Matrix matrix;
+
+            zMin = -projectionMatrix.M43 / projectionMatrix.M33;
+            r = screenDepth / (screenDepth - zMin);
+            projectionMatrix.M33 = r;
+            projectionMatrix.M43 = -r * zMin;
+
+            matrix = Matrix.Multiply(viewMatrix, projectionMatrix);
+
+            float a = matrix.M14 + matrix.M13;
+            float b = matrix.M24 + matrix.M23;
+            float c = matrix.M34 + matrix.M33;
+            float d = matrix.M44 + matrix.M43;
+            m_Planes[0] = new Plane(a, b, c, d);
+            m_Planes[0].Normalize();
+
+            a = matrix.M14 - matrix.M13;
+            b = matrix.M24 - matrix.M23;
+            c = matrix.M34 - matrix.M33;
+            d = matrix.M44 - matrix.M43;
+            m_Planes[1] = new Plane(a, b, c, d);
+            m_Planes[1].Normalize();
+
+            a = matrix.M14 + matrix.M11;
+            b = matrix.M24 + matrix.M21;
+            c = matrix.M34 + matrix.M31;
+            d = matrix.M44 + matrix.M41;
+            m_Planes[2] = new Plane(a, b, c, d);
+            m_Planes[2].Normalize();
+
+            a = matrix.M14 - matrix.M11;
+            b = matrix.M24 - matrix.M21;
+            c = matrix.M34 - matrix.M31;
+            d = matrix.M44 - matrix.M41;
+            m_Planes[3] = new Plane(a, b, c, d);
+            m_Planes[3].Normalize();
+
+            a = matrix.M14 - matrix.M12;
+            b = matrix.M24 - matrix.M22;
+            c = matrix.M34 - matrix.M32;
+            d = matrix.M44 - matrix.M42;
+            m_Planes[4] = new Plane(a, b, c, d);
+            m_Planes[4].Normalize();
+
+            a = matrix.M14 + matrix.M12;
+            b = matrix.M24 + matrix.M22;
+            c = matrix.M34 + matrix.M32;
+            d = matrix.M44 + matrix.M42;
+            m_Planes[5] = new Plane(a, b, c, d);
+            m_Planes[5].Normalize();
+        }
+
+        public bool CheckPoint(float x, float y, float z)
+        {
+            for (int i = 0; i < 6; i++)
+            {
+                if (Plane.DotCoordinate(m_Planes[i], new Vector3(x, y, z)) < 0.0f)
+                    return false;
+            }
+
+            return true;
+        }
+
+        public bool CheckCube(float xCenter, float yCenter, float zCenter, float radius)
+        {
+            for (int i = 0; i < 6; i++)
+            {
+                if (Plane.DotCoordinate(m_Planes[i], new Vector3((xCenter - radius), (yCenter - radius), (zCenter - radius))) >= 0.0f)
+                    continue;
+                if (Plane.DotCoordinate(m_Planes[i], new Vector3((xCenter + radius), (yCenter - radius), (zCenter - radius))) >= 0.0f)
+                    continue;
+                if (Plane.DotCoordinate(m_Planes[i], new Vector3((xCenter - radius), (yCenter + radius), (zCenter - radius))) >= 0.0f)
+                    continue;
+                if (Plane.DotCoordinate(m_Planes[i], new Vector3((xCenter + radius), (yCenter + radius), (zCenter - radius))) >= 0.0f)
+                    continue;
+                if (Plane.DotCoordinate(m_Planes[i], new Vector3((xCenter - radius), (yCenter - radius), (zCenter + radius))) >= 0.0f)
+                    continue;
+                if (Plane.DotCoordinate(m_Planes[i], new Vector3((xCenter + radius), (yCenter - radius), (zCenter + radius))) >= 0.0f)
+                    continue;
+                if (Plane.DotCoordinate(m_Planes[i], new Vector3((xCenter - radius), (yCenter + radius), (zCenter + radius))) >= 0.0f)
+                    continue;
+                if (Plane.DotCoordinate(m_Planes[i], new Vector3((xCenter + radius), (yCenter + radius), (zCenter + radius))) >= 0.0f)
+                    continue;
+
+                return false;
+            }
+
+            return true;
+        }
+
+        public bool CheckSphere(float xCenter, float yCenter, float zCenter, float radius)
+        {
+            for (int i = 0; i < 6; i++)
+            {
+                if (Plane.DotCoordinate(m_Planes[i], new Vector3(xCenter, yCenter, zCenter)) < -radius)
+                    return false;
+            }
+
+            return true;
+        }
+
+        public bool CheckRectangle(float xCenter, float yCenter, float zCenter, float xSize, float ySize, float zSize)
+        {
+            for (int i = 0; i < 6; i++)
+            {
+                if (Plane.DotCoordinate(m_Planes[i], new Vector3((xCenter - xSize), (yCenter - ySize), (zCenter - zSize))) >= 0.0f)
+                    continue;
+                if (Plane.DotCoordinate(m_Planes[i], new Vector3((xCenter + xSize), (yCenter - ySize), (zCenter - zSize))) >= 0.0f)
+                    continue;
+                if (Plane.DotCoordinate(m_Planes[i], new Vector3((xCenter - xSize), (yCenter + ySize), (zCenter - zSize))) >= 0.0f)
+                    continue;
+                if (Plane.DotCoordinate(m_Planes[i], new Vector3((xCenter - xSize), (yCenter - ySize), (zCenter + zSize))) >= 0.0f)
+                    continue;
+                if (Plane.DotCoordinate(m_Planes[i], new Vector3((xCenter + xSize), (yCenter + ySize), (zCenter - zSize))) >= 0.0f)
+                    continue;
+                if (Plane.DotCoordinate(m_Planes[i], new Vector3((xCenter + xSize), (yCenter - ySize), (zCenter + zSize))) >= 0.0f)
+                    continue;
+                if (Plane.DotCoordinate(m_Planes[i], new Vector3((xCenter - xSize), (yCenter + ySize), (zCenter + zSize))) >= 0.0f)
+                    continue;
+                if (Plane.DotCoordinate(m_Planes[i], new Vector3((xCenter + xSize), (yCenter + ySize), (zCenter + zSize))) >= 0.0f)
+                    continue;
+
+                return false;
+            }
+
+            return true;
+        }
+    }
+}

+ 50 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/GameObject.cs

@@ -0,0 +1,50 @@
+using System;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+using SlimDX;
+using SlimDX.D3DCompiler;
+using SlimDX.Direct3D11;
+using SlimDX.DXGI;
+using SlimDX.Windows;
+using Device = SlimDX.Direct3D11.Device;
+using Resource = SlimDX.Direct3D11.Resource;
+using Buffer = SlimDX.Direct3D11.Buffer;
+
+namespace EQ2ModelViewer
+{
+    public class GameObject
+    {
+        private struct YawPitchRoll
+        {
+            public float Yaw;
+            public float Pitch;
+            public float Roll;
+        };
+
+        public Vector3 Position = new Vector3(0.0f, 0.0f, 0.0f);
+        private YawPitchRoll Rotation = new YawPitchRoll();
+
+        GameObject()
+        {
+            Rotation.Yaw = 0.0f;
+            Rotation.Pitch = 0.0f;
+            Rotation.Roll = 0.0f;
+        }
+
+        private float m_scale;
+
+        public float Scale
+        {
+            get { return m_scale; }
+            set { m_scale = value; }
+        }
+
+        private UInt32 m_model;
+        public UInt32 Model
+        {
+            get { return m_model; }
+            set { m_model = value; }
+        }
+    }
+}

+ 269 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/GraphicClass.cs

@@ -0,0 +1,269 @@
+using System;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+using SlimDX;
+using SlimDX.D3DCompiler;
+using SlimDX.Direct3D11;
+using SlimDX.DXGI;
+using SlimDX.Windows;
+using Device = SlimDX.Direct3D11.Device;
+using Resource = SlimDX.Direct3D11.Resource;
+using Buffer = SlimDX.Direct3D11.Buffer;
+
+namespace EQ2ModelViewer
+{
+    public class GraphicClass
+    {
+        private Device m_device;
+        private DeviceContext m_context;
+        private SwapChain m_swapChain;
+        private RenderTargetView m_renderTarget;
+        private Texture2D m_depthBuffer;
+        private DepthStencilState m_depthStencilState;
+        private DepthStencilState m_depthDisabledStencilState;
+        private DepthStencilView m_depthStencilView;
+        private RasterizerState m_rasterState;
+
+        private BlendState m_alphaEnabledBlendState;
+        private BlendState m_alphaDisableBlendState;
+
+        private Matrix m_worldMatrix;
+        private Matrix m_orthoMatrix;
+        private Matrix m_projectionMatrix;
+
+        public Device Device
+        {
+            get { return m_device; }
+        }
+
+        public DeviceContext Context
+        {
+            get { return m_context; }
+        }
+
+        public SwapChain SwapChain
+        {
+            get { return m_swapChain; }
+        }
+
+        public Matrix GetWorldMatrix()
+        {
+            return m_worldMatrix;
+        }
+
+        public Matrix GetOrthoMatrix()
+        {
+            return m_orthoMatrix;
+        }
+
+        public Matrix GetProjectionMatrix()
+        {
+            return m_projectionMatrix;
+        }
+
+        public bool Initialize(Panel pGraphics)
+        {
+            SwapChainDescription description = new SwapChainDescription();
+
+            ModeDescription modedesc = new ModeDescription(pGraphics.ClientSize.Width, pGraphics.ClientSize.Height, new Rational(0, 1), Format.R8G8B8A8_UNorm);
+            modedesc.ScanlineOrdering = DisplayModeScanlineOrdering.Unspecified;
+            modedesc.Scaling = DisplayModeScaling.Unspecified;
+            description.BufferCount = 1;
+            description.IsWindowed = true;
+            description.ModeDescription = modedesc;
+            description.Usage = Usage.RenderTargetOutput;
+            description.OutputHandle = pGraphics.Handle;
+            description.SampleDescription = new SampleDescription(1, 0);
+            description.Flags = 0;
+
+            Device.CreateWithSwapChain(DriverType.Hardware, DeviceCreationFlags.None, description, out m_device, out m_swapChain);
+            m_context = m_device.ImmediateContext;
+
+            using (var resource = Resource.FromSwapChain<Texture2D>(m_swapChain, 0))
+                m_renderTarget = new RenderTargetView(m_device, resource);
+
+            Texture2DDescription depthBufferDesc = new Texture2DDescription();
+            depthBufferDesc.Width = pGraphics.ClientSize.Width;
+            depthBufferDesc.Height = pGraphics.ClientSize.Height;
+            depthBufferDesc.MipLevels = 1;
+            depthBufferDesc.ArraySize = 1;
+            depthBufferDesc.Format = Format.D24_UNorm_S8_UInt;
+            depthBufferDesc.SampleDescription = new SampleDescription(1, 0);
+            depthBufferDesc.Usage = ResourceUsage.Default;
+            depthBufferDesc.BindFlags = BindFlags.DepthStencil;
+            depthBufferDesc.CpuAccessFlags = CpuAccessFlags.None;
+            depthBufferDesc.OptionFlags = ResourceOptionFlags.None;
+            m_depthBuffer = new Texture2D(m_device, depthBufferDesc);
+
+            DepthStencilStateDescription depthStencilStateDesc = new DepthStencilStateDescription();
+            depthStencilStateDesc.IsDepthEnabled = true;
+            depthStencilStateDesc.DepthWriteMask = DepthWriteMask.All;
+            depthStencilStateDesc.DepthComparison = Comparison.Less;
+
+            depthStencilStateDesc.IsStencilEnabled = true;
+            depthStencilStateDesc.StencilReadMask = 0xFF;
+            depthStencilStateDesc.StencilWriteMask = 0xFF;
+
+            DepthStencilOperationDescription frontface = new DepthStencilOperationDescription();
+            frontface.FailOperation = StencilOperation.Keep;
+            frontface.DepthFailOperation = StencilOperation.Increment;
+            frontface.PassOperation = StencilOperation.Keep;
+            frontface.Comparison = Comparison.Always;
+            depthStencilStateDesc.FrontFace = frontface;
+
+            DepthStencilOperationDescription backface = new DepthStencilOperationDescription();
+            backface.FailOperation = StencilOperation.Keep;
+            backface.DepthFailOperation = StencilOperation.Decrement;
+            backface.PassOperation = StencilOperation.Keep;
+            backface.Comparison = Comparison.Always;
+            depthStencilStateDesc.BackFace = backface;
+
+            m_depthStencilState = DepthStencilState.FromDescription(m_device, depthStencilStateDesc);
+
+            m_context.OutputMerger.DepthStencilState = m_depthStencilState;
+            m_context.OutputMerger.DepthStencilReference = 1;
+
+            DepthStencilViewDescription depthStencilViewDesc = new DepthStencilViewDescription();
+            depthStencilViewDesc.Format = Format.D24_UNorm_S8_UInt;
+            depthStencilViewDesc.Dimension = DepthStencilViewDimension.Texture2D;
+            depthStencilViewDesc.MipSlice = 0;
+
+            m_depthStencilView = new DepthStencilView(m_device, m_depthBuffer, depthStencilViewDesc);
+            m_context.OutputMerger.SetTargets(m_depthStencilView, m_renderTarget);
+
+            RasterizerStateDescription rasterDesc = new RasterizerStateDescription();
+            rasterDesc.IsAntialiasedLineEnabled = false;
+            rasterDesc.CullMode = CullMode.Back;
+            rasterDesc.DepthBias = 0;
+            rasterDesc.DepthBiasClamp = 0.0f;
+            rasterDesc.IsDepthClipEnabled = true;
+            rasterDesc.FillMode = FillMode.Solid;
+            rasterDesc.IsFrontCounterclockwise = false;
+            rasterDesc.IsMultisampleEnabled = false;
+            rasterDesc.IsScissorEnabled = false;
+            rasterDesc.SlopeScaledDepthBias = 0.0f;
+
+            m_rasterState = RasterizerState.FromDescription(m_device, rasterDesc);
+            m_context.Rasterizer.State = m_rasterState;
+
+            var viewport = new Viewport(0.0f, 0.0f, pGraphics.ClientSize.Width, pGraphics.ClientSize.Height, 0.0f, 1.0f);
+            m_context.Rasterizer.SetViewports(viewport);
+
+            using (var factory = m_swapChain.GetParent<Factory>())
+                factory.SetWindowAssociation(pGraphics.Handle, WindowAssociationFlags.IgnoreAltEnter);
+
+            // World matrix
+            m_worldMatrix = Matrix.Identity;
+
+            // Projection matrix            
+            float fieldOfView = (float)Math.PI / 4.0f;
+            float screenAspect = (float)viewport.Width / viewport.Height;
+            m_projectionMatrix = Matrix.PerspectiveFovLH(fieldOfView, screenAspect, 0.1f, 1000.0f);
+            
+            // Ortho matrix
+            m_orthoMatrix = Matrix.OrthoLH(viewport.Width, viewport.Height, 0.1f, 1000.0f);
+
+            DepthStencilStateDescription depthDisabledDesc = new DepthStencilStateDescription();
+            depthDisabledDesc.IsDepthEnabled = false;
+            depthDisabledDesc.DepthWriteMask = DepthWriteMask.All;
+            depthDisabledDesc.DepthComparison = Comparison.Less;
+            depthDisabledDesc.IsStencilEnabled = true;
+            depthDisabledDesc.StencilReadMask = 0xFF;
+            depthDisabledDesc.StencilWriteMask = 0xFF;
+            depthDisabledDesc.FrontFace = frontface;
+            depthDisabledDesc.BackFace = backface;
+
+            m_depthDisabledStencilState = DepthStencilState.FromDescription(m_device, depthDisabledDesc);
+
+            BlendStateDescription blendDesc = new BlendStateDescription();
+            blendDesc.RenderTargets[0].BlendEnable = true;
+            blendDesc.RenderTargets[0].SourceBlend = BlendOption.One;
+            blendDesc.RenderTargets[0].DestinationBlend = BlendOption.InverseSourceAlpha;
+            blendDesc.RenderTargets[0].BlendOperation = BlendOperation.Add;
+            blendDesc.RenderTargets[0].SourceBlendAlpha = BlendOption.One;
+            blendDesc.RenderTargets[0].DestinationBlendAlpha = BlendOption.Zero;
+            blendDesc.RenderTargets[0].BlendOperationAlpha = BlendOperation.Add;
+            blendDesc.RenderTargets[0].RenderTargetWriteMask = ColorWriteMaskFlags.All;
+
+            m_alphaEnabledBlendState = BlendState.FromDescription(m_device, blendDesc);
+
+            blendDesc.RenderTargets[0].BlendEnable = false;
+            m_alphaDisableBlendState = BlendState.FromDescription(m_device, blendDesc);
+
+            /*
+            BlendStateDescription blendDesc2 = new BlendStateDescription();
+            blendDesc2.RenderTargets[0].BlendEnable = false;
+            blendDesc2.RenderTargets[0].SourceBlend = BlendOption.One;
+            blendDesc2.RenderTargets[0].DestinationBlend = BlendOption.InverseSourceAlpha;
+            blendDesc2.RenderTargets[0].BlendOperation = BlendOperation.Add;
+            blendDesc2.RenderTargets[0].SourceBlendAlpha = BlendOption.One;
+            blendDesc2.RenderTargets[0].DestinationBlendAlpha = BlendOption.Zero;
+            blendDesc2.RenderTargets[0].BlendOperationAlpha = BlendOperation.Add;
+            blendDesc2.RenderTargets[0].RenderTargetWriteMask = ColorWriteMaskFlags.All;
+            m_alphaDisableBlendState = BlendState.FromDescription(m_device, blendDesc2);
+            */
+
+            return true;
+        }
+
+        public void ShutDown()
+        {
+            if (m_alphaEnabledBlendState != null)
+                m_alphaEnabledBlendState.Dispose();
+            if (m_alphaDisableBlendState != null)
+                m_alphaDisableBlendState.Dispose();
+            if (m_rasterState != null)
+                m_rasterState.Dispose();
+            if (m_depthStencilView != null)
+                m_depthStencilView.Dispose();
+            if (m_depthDisabledStencilState != null)
+                m_depthDisabledStencilState.Dispose();
+            if (m_depthStencilState != null)
+                m_depthStencilState.Dispose();
+            if (m_depthBuffer != null)
+                m_depthBuffer.Dispose();
+            if (m_renderTarget != null)
+                m_renderTarget.Dispose();
+            if (m_swapChain != null)
+                m_swapChain.Dispose();
+            if (m_device != null)
+                m_device.Dispose();
+        }
+
+        public void BeginScene()
+        {
+            m_context.ClearRenderTargetView(m_renderTarget, new Color4(0.0f, 0.0f, 0.0f));
+            m_context.ClearDepthStencilView(m_depthStencilView, DepthStencilClearFlags.Depth, 1.0f, 0);
+        }
+
+        public void EndScene()
+        {
+            m_swapChain.Present(0, PresentFlags.None);
+        }
+
+        public void TurnZBufferOn()
+        {
+            m_context.OutputMerger.DepthStencilState = m_depthStencilState;
+            m_context.OutputMerger.DepthStencilReference = 1;
+        }
+
+        public void TurnZBufferOff()
+        {
+            m_context.OutputMerger.DepthStencilState = m_depthDisabledStencilState;
+            m_context.OutputMerger.DepthStencilReference = 1;
+        }
+
+        public void TurnOnAlphaBlending()
+        {
+            m_context.OutputMerger.BlendState = m_alphaEnabledBlendState;
+            m_context.OutputMerger.BlendFactor = new Color4(0.0f, 0.0f, 0.0f, 0.0f);
+        }
+
+        public void TurnOffAlphaBlending()
+        {
+            m_context.OutputMerger.BlendState = m_alphaDisableBlendState;
+            m_context.OutputMerger.BlendFactor = new Color4(0.0f, 0.0f, 0.0f, 0.0f);
+        }
+    }
+}

+ 151 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/InputClass.cs

@@ -0,0 +1,151 @@
+using System;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+using SlimDX;
+using SlimDX.D3DCompiler;
+using SlimDX.Direct3D11;
+using SlimDX.DirectInput;
+using SlimDX.DXGI;
+using SlimDX.Windows;
+using Device = SlimDX.Direct3D11.Device;
+using Resource = SlimDX.Direct3D11.Resource;
+using Buffer = SlimDX.Direct3D11.Buffer;
+
+namespace EQ2ModelViewer
+{
+    public class InputClass
+    {
+        private DirectInput m_DirectInput;
+        private Keyboard m_Keyboard;
+        private KeyboardState m_KeyboardState;
+        private Mouse m_mouse;
+        private MouseState m_mouseState;
+
+        public bool Initialize(Control control)
+        {
+            m_DirectInput = new DirectInput();
+            m_Keyboard = new Keyboard(m_DirectInput);
+            m_mouse = new Mouse(m_DirectInput);
+            try
+            {
+                Result result = m_Keyboard.SetCooperativeLevel(control, CooperativeLevel.Nonexclusive | CooperativeLevel.Background);
+                Result result2 = m_mouse.SetCooperativeLevel(control, CooperativeLevel.Nonexclusive | CooperativeLevel.Background);
+            }
+            catch (DirectInputException e)
+            {
+                MessageBox.Show(e.Message);
+            }
+            m_Keyboard.Acquire();
+            m_mouse.Acquire();
+
+            return true;
+        }
+
+        public void ShutDown()
+        {
+            m_mouse.Dispose();
+            m_Keyboard.Dispose();
+            m_DirectInput.Dispose();
+        }
+
+        public bool Frame()
+        {
+            if (!ReadKeyboard())
+            {
+                Console.WriteLine("InputClass: Failed to read the keyboard.");
+                return false;
+            }
+            if (!ReadMouse()) {
+                Console.WriteLine("InputClass: Failed to read the mouse.");
+                return false;
+            }
+
+            ProcessInput();
+            return true;
+        }
+
+        private bool ReadKeyboard()
+        {
+            m_KeyboardState = m_Keyboard.GetCurrentState();
+            return true;
+        }
+
+        private bool ReadMouse() {
+            m_mouseState = m_mouse.GetCurrentState();
+            return true;
+        }
+
+        private bool ProcessInput()
+        {
+            return true;
+        }
+
+        public bool IsAPressed()
+        {
+            return m_KeyboardState.IsPressed(Key.A);
+        }
+
+        public bool IsZPressed()
+        {
+            return m_KeyboardState.IsPressed(Key.Z);
+        }
+
+        public bool IsPgUpPressed()
+        {
+            return m_KeyboardState.IsPressed(Key.PageUp);
+        }
+
+        public bool IsPgDownPressed()
+        {
+            return m_KeyboardState.IsPressed(Key.PageDown);
+        }
+
+        public bool IsDownPressed()
+        {
+            return m_KeyboardState.IsPressed(Key.DownArrow);
+        }
+
+        public bool IsUpPressed()
+        {
+            return m_KeyboardState.IsPressed(Key.UpArrow);
+        }
+
+        public bool IsLeftPressed()
+        {
+            return m_KeyboardState.IsPressed(Key.LeftArrow);
+        }
+
+        public bool IsRightPressed()
+        {
+            return m_KeyboardState.IsPressed(Key.RightArrow);
+        }
+
+        public bool IsLeftMousePressed() {
+            return m_mouseState.IsPressed((int)MouseObject.Button1);
+        }
+
+        public bool IsEscapePressed()
+        {
+            return m_KeyboardState.IsPressed(Key.Escape);
+        }
+
+        public bool IsKeyPressed(Key key)
+        {
+            return m_KeyboardState.IsPressed(key);
+        }
+        public bool IsDeletePressed()
+        {
+            return m_KeyboardState.IsPressed(Key.Delete);
+        }
+
+        public int GetMouseX() {
+
+            return RenderForm.MousePosition.X;
+        }
+
+        public int GetMouseY() {
+            return RenderForm.MousePosition.Y;
+        }
+    }
+}

+ 208 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/LightShaderClass.cs

@@ -0,0 +1,208 @@
+using System;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+using SlimDX;
+using SlimDX.D3DCompiler;
+using SlimDX.Direct3D11;
+using SlimDX.DXGI;
+using SlimDX.Windows;
+using Device = SlimDX.Direct3D11.Device;
+using Resource = SlimDX.Direct3D11.Resource;
+using Buffer = SlimDX.Direct3D11.Buffer;
+
+namespace EQ2ModelViewer
+{
+    public class LightShaderClass
+    {
+        private struct MatrixBufferType
+        {
+            public Matrix world;
+            public Matrix view;
+            public Matrix projection;
+        }
+
+        private struct CameraBufferType
+        {
+            public Vector3 cameraPosition;
+            public float padding;
+        }
+
+        private struct LightBufferType
+        {
+            public Vector4 ambientColor;
+            public Vector4 diffuseColor;
+            public Vector3 lightDirection;
+            public float specularPower;
+            public Vector4 specularColor;
+        }
+
+        private VertexShader m_VertexShader;
+        private PixelShader m_PixelShader;
+        private InputLayout m_Layout;
+        private Buffer m_MatrixBuffer;
+        private SamplerState m_SamplerState;
+        private Buffer m_CameraBuffer;
+        private Buffer m_LightBuffer;
+
+        public bool Initialize(Device device)
+        {
+            return InitializeShader(device, "Light.vs", "Light.ps");
+        }
+
+        private bool InitializeShader(Device device, string vertexShader, string pixelShader)
+        {
+            ShaderSignature inputSignature;
+            string error;
+            // load and compile the vertex shader
+            using (var bytecode = ShaderBytecode.CompileFromFile(vertexShader, "LightVertexShader", "vs_5_0", ShaderFlags.EnableStrictness, EffectFlags.None, null, null, out error))
+            {
+                inputSignature = ShaderSignature.GetInputSignature(bytecode);
+                m_VertexShader = new VertexShader(device, bytecode);
+            }
+
+            if (m_VertexShader == null)
+            {
+                Console.WriteLine("InitializeShader: Error creating vertex shader: " + error);
+                return false;
+            }
+
+            // load and compile the pixel shader
+            using (var bytecode = ShaderBytecode.CompileFromFile(pixelShader, "LightPixelShader", "ps_5_0", ShaderFlags.EnableStrictness, EffectFlags.None, null, null, out error))
+                m_PixelShader = new PixelShader(device, bytecode);
+
+            if (m_PixelShader == null)
+            {
+                Console.WriteLine("InitializeShader: Error creating pixel shader: " + error);
+                return false;
+            }
+
+            var elements = new[] {
+                new InputElement("POSITION", 0, Format.R32G32B32_Float, 0, 0, InputClassification.PerVertexData, 0),
+                new InputElement("TEXCOORD", 0, Format.R32G32_Float, -1, 0, InputClassification.PerVertexData, 0),
+                new InputElement("NORMAL", 0, Format.R32G32B32_Float, -1, 0, InputClassification.PerVertexData, 0) };
+            m_Layout = new InputLayout(device, inputSignature, elements);
+
+            m_MatrixBuffer = new Buffer(device, System.Runtime.InteropServices.Marshal.SizeOf(typeof(MatrixBufferType)), ResourceUsage.Dynamic, BindFlags.ConstantBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0);
+            if (m_MatrixBuffer == null)
+            {
+                Console.WriteLine("InitializeShader: Unable to create the matrix buffer.");
+                return false;
+            }
+
+            SamplerDescription samplerDesc = new SamplerDescription();
+            samplerDesc.Filter = Filter.MinMagMipLinear;
+            samplerDesc.AddressU = TextureAddressMode.Wrap;
+            samplerDesc.AddressV = TextureAddressMode.Wrap;
+            samplerDesc.AddressW = TextureAddressMode.Wrap;
+            samplerDesc.MipLodBias = 0.0f;
+            samplerDesc.MaximumAnisotropy = 1;
+            samplerDesc.ComparisonFunction = Comparison.Always;
+            samplerDesc.BorderColor = new Color4(0.0f, 0.0f, 0.0f, 0.0f);
+            samplerDesc.MinimumLod = 0;
+            samplerDesc.MaximumLod = 3.402823466e+38f; // from d3d11.h, #define D3D11_FLOAT32_MAX	( 3.402823466e+38f )
+
+            m_SamplerState = SamplerState.FromDescription(device, samplerDesc);
+            if (m_SamplerState == null)
+            {
+                Console.WriteLine("InitializeShader: Unable to create the sampler state.");
+                return false;
+            }
+
+            m_CameraBuffer = new Buffer(device, System.Runtime.InteropServices.Marshal.SizeOf(typeof(CameraBufferType)), ResourceUsage.Dynamic, BindFlags.ConstantBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0);
+            if (m_CameraBuffer == null)
+            {
+                Console.WriteLine("InitializeShader: Unable to create the camera buffer.");
+                return false;
+            }
+
+            m_LightBuffer = new Buffer(device, System.Runtime.InteropServices.Marshal.SizeOf(typeof(LightBufferType)), ResourceUsage.Dynamic, BindFlags.ConstantBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0);
+            if (m_LightBuffer == null)
+            {
+                Console.WriteLine("InitializeShader: Unable to create the light buffer.");
+                return false;
+            }
+
+            return true;
+        }
+
+        public void ShutDown()
+        {
+            m_VertexShader.Dispose();
+            m_PixelShader.Dispose();
+            m_Layout.Dispose();
+            m_MatrixBuffer.Dispose();
+            m_SamplerState.Dispose();
+            m_CameraBuffer.Dispose();
+            m_LightBuffer.Dispose();
+        }
+
+        private bool SetShaderParameters(DeviceContext context, Matrix worldMatrix, Matrix viewMatrix, Matrix projectionMatrix, ShaderResourceView texture, Vector3 lightDirection, Vector4 ambientColor, Vector4 diffuseColor, Vector3 cameraPosition, Vector4 specularColor, float specularPower)
+        {
+            Matrix.Transpose(ref worldMatrix, out worldMatrix);
+            Matrix.Transpose(ref viewMatrix, out viewMatrix);
+            Matrix.Transpose(ref projectionMatrix, out projectionMatrix);
+
+
+            var mappedResource = context.MapSubresource(m_MatrixBuffer, 0, MapMode.WriteDiscard, SlimDX.Direct3D11.MapFlags.None);
+            MatrixBufferType data = new MatrixBufferType();
+            data.world = worldMatrix;
+            data.view = viewMatrix;
+            data.projection = projectionMatrix;
+            mappedResource.Data.Write<MatrixBufferType>(data);
+            context.UnmapSubresource(m_MatrixBuffer, 0);
+
+            context.VertexShader.SetConstantBuffer(m_MatrixBuffer, 0);
+            context.PixelShader.SetShaderResource(texture, 0);
+
+            mappedResource = context.MapSubresource(m_CameraBuffer, 0, MapMode.WriteDiscard, SlimDX.Direct3D11.MapFlags.None);
+            CameraBufferType data2 = new CameraBufferType();
+            data2.cameraPosition = cameraPosition;
+            data2.padding = 0.0f;
+            mappedResource.Data.Write<CameraBufferType>(data2);
+            context.UnmapSubresource(m_CameraBuffer, 0);
+
+            context.VertexShader.SetConstantBuffer(m_CameraBuffer, 1);
+
+            mappedResource = context.MapSubresource(m_LightBuffer, 0, MapMode.WriteDiscard, SlimDX.Direct3D11.MapFlags.None);
+            LightBufferType data3 = new LightBufferType();
+            data3.ambientColor = ambientColor;
+            data3.diffuseColor = diffuseColor;
+            data3.lightDirection = lightDirection;
+            data3.specularColor = specularColor;
+            data3.specularPower = specularPower;
+            mappedResource.Data.Write<LightBufferType>(data3);
+            context.UnmapSubresource(m_LightBuffer, 0);
+
+            context.PixelShader.SetConstantBuffer(m_LightBuffer, 0);
+
+            return true;
+        }
+
+        public bool Render(DeviceContext context, int indexCount, Matrix worldMatrix, Matrix viewMatrix, Matrix projectionMatrix, ShaderResourceView texture, Vector3 lightDirection, Vector4 ambientColor, Vector4 diffuseColor, Vector3 cameraPosition, Vector4 specularColor, float specularPower)
+        {
+            if (!SetShaderParameters(context, worldMatrix, viewMatrix, projectionMatrix, texture, lightDirection, ambientColor, diffuseColor, cameraPosition, specularColor, specularPower))
+                return false;
+
+            RenderShader(context, indexCount);
+
+            return true;
+        }
+
+        private void RenderShader(DeviceContext context, int indexCount)
+        {
+            // Set the vertex input layout
+            context.InputAssembler.InputLayout = m_Layout;
+
+            // Set the vertex and pixel shaders that will be used to render
+            context.VertexShader.Set(m_VertexShader);
+            context.PixelShader.Set(m_PixelShader);
+
+            // Set the sampler state in the pixel shader
+            context.PixelShader.SetSampler(m_SamplerState, 0);
+
+            // Render
+            context.DrawIndexed(indexCount, 0, 0);
+        }
+    }
+}

+ 127 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Main.Designer.cs

@@ -0,0 +1,127 @@
+namespace EQ2ModelViewer
+{
+    partial class frmMain
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.menuStrip1 = new System.Windows.Forms.MenuStrip();
+            this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.loadVPLToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.pGraphics = new System.Windows.Forms.Panel();
+            this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
+            this.exportToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.menuStrip1.SuspendLayout();
+            this.SuspendLayout();
+            // 
+            // menuStrip1
+            // 
+            this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            this.fileToolStripMenuItem});
+            this.menuStrip1.Location = new System.Drawing.Point(0, 0);
+            this.menuStrip1.Name = "menuStrip1";
+            this.menuStrip1.Size = new System.Drawing.Size(851, 24);
+            this.menuStrip1.TabIndex = 0;
+            this.menuStrip1.Text = "menuStrip1";
+            // 
+            // fileToolStripMenuItem
+            // 
+            this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            this.loadVPLToolStripMenuItem,
+            this.exportToolStripMenuItem,
+            this.toolStripSeparator2,
+            this.exitToolStripMenuItem});
+            this.fileToolStripMenuItem.Name = "fileToolStripMenuItem";
+            this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20);
+            this.fileToolStripMenuItem.Text = "File";
+            // 
+            // loadVPLToolStripMenuItem
+            // 
+            this.loadVPLToolStripMenuItem.Name = "loadVPLToolStripMenuItem";
+            this.loadVPLToolStripMenuItem.Size = new System.Drawing.Size(152, 22);
+            this.loadVPLToolStripMenuItem.Text = "Load Zone";
+            this.loadVPLToolStripMenuItem.Click += new System.EventHandler(this.loadZoneToolStripMenuItem_Click);
+            // 
+            // exitToolStripMenuItem
+            // 
+            this.exitToolStripMenuItem.Name = "exitToolStripMenuItem";
+            this.exitToolStripMenuItem.Size = new System.Drawing.Size(152, 22);
+            this.exitToolStripMenuItem.Text = "Exit";
+            this.exitToolStripMenuItem.Click += new System.EventHandler(this.exitToolStripMenuItem_Click);
+            // 
+            // pGraphics
+            // 
+            this.pGraphics.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
+            this.pGraphics.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.pGraphics.Location = new System.Drawing.Point(0, 24);
+            this.pGraphics.Name = "pGraphics";
+            this.pGraphics.Size = new System.Drawing.Size(851, 448);
+            this.pGraphics.TabIndex = 1;
+            // 
+            // toolStripSeparator2
+            // 
+            this.toolStripSeparator2.Name = "toolStripSeparator2";
+            this.toolStripSeparator2.Size = new System.Drawing.Size(149, 6);
+            // 
+            // exportToolStripMenuItem
+            // 
+            this.exportToolStripMenuItem.Name = "exportToolStripMenuItem";
+            this.exportToolStripMenuItem.Size = new System.Drawing.Size(152, 22);
+            this.exportToolStripMenuItem.Text = "Export";
+            this.exportToolStripMenuItem.Click += new System.EventHandler(this.exportToolStripMenuItem_Click);
+            // 
+            // frmMain
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(851, 472);
+            this.Controls.Add(this.pGraphics);
+            this.Controls.Add(this.menuStrip1);
+            this.MainMenuStrip = this.menuStrip1;
+            this.Name = "frmMain";
+            this.Text = "EQ2 Model Viewer";
+            this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.frmMain_FormClosing);
+            this.Load += new System.EventHandler(this.frmMain_Load);
+            this.menuStrip1.ResumeLayout(false);
+            this.menuStrip1.PerformLayout();
+            this.ResumeLayout(false);
+            this.PerformLayout();
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.MenuStrip menuStrip1;
+        private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem;
+        private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem;
+        private System.Windows.Forms.Panel pGraphics;
+        private System.Windows.Forms.ToolStripMenuItem loadVPLToolStripMenuItem;
+        private System.Windows.Forms.ToolStripMenuItem exportToolStripMenuItem;
+        private System.Windows.Forms.ToolStripSeparator toolStripSeparator2;
+    }
+}
+

+ 551 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Main.cs

@@ -0,0 +1,551 @@
+using System;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using System.Collections.Generic;
+
+using SlimDX;
+using SlimDX.D3DCompiler;
+using SlimDX.Direct3D11;
+using SlimDX.DXGI;
+using SlimDX.Windows;
+using Device = SlimDX.Direct3D11.Device;
+using Resource = SlimDX.Direct3D11.Resource;
+using Buffer = SlimDX.Direct3D11.Buffer;
+
+using Everquest2.Util;
+using Everquest2.Visualization;
+using System.IO;
+
+namespace EQ2ModelViewer
+{
+    public partial class frmMain : Form
+    {
+        private System.Collections.Generic.List<Model> m_Models = new System.Collections.Generic.List<Model>();
+        private GraphicClass Graphics = new GraphicClass();
+        public Model SelectedModel = null;
+        private string ZoneFile;
+        private bool Render3DAspect = true;
+        private bool AutoExportOnLoad = false;
+        private String AutoLoadFileName = "";
+        public frmMain()
+        {
+            InitializeComponent();
+        }
+
+        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
+        {
+            Close();
+        }
+
+        private void CleanUp()
+        {
+            foreach (Model model in m_Models)
+                model.ShutDown();
+
+            if (Graphics != null)
+                Graphics.ShutDown();
+        }
+
+        private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
+        {
+            CleanUp();
+        }
+
+        private CameraClass camera;
+        private void frmMain_Load(object sender, EventArgs e)
+        {
+            this.WindowState = FormWindowState.Maximized;
+            Graphics.Initialize(pGraphics);
+
+            /*LightShaderClass lightShader = new LightShaderClass();
+            lightShader.Initialize(Graphics.Device);*/
+
+            camera = new CameraClass();
+            // Initialize a base view matrix for 2D rendering
+            camera.SetPosition(0.0f, 0.0f, -1.0f);
+            camera.Render();
+            Matrix baseViewMatrix = camera.GetViewMatrix();
+
+            // now set the cameras starting position
+            camera.SetPosition(0.0f, 1.50f, -3.0f);
+
+            PositionClass position = new PositionClass();
+            position.SetPosition(0.0f, 1.50f, -3.0f);
+
+            InputClass input = new InputClass();
+            input.Initialize(this);
+
+            TimerClass timer = new TimerClass();
+            timer.Initialize();
+
+            FPSClass fps = new FPSClass();
+            fps.Initialize();
+
+            TextClass text = new TextClass();
+            text.Initialize(Graphics.Device, Graphics.Context, pGraphics.ClientSize.Width, pGraphics.ClientSize.Height, baseViewMatrix);
+
+            BitmapClass bmp = new BitmapClass();
+            bmp.Initialize(Graphics.Device, pGraphics.ClientSize.Width, pGraphics.ClientSize.Height, "Background.bmp", 145, 220, baseViewMatrix);
+            
+            string[] args = Environment.GetCommandLineArgs();
+            if (args.Length > 1)
+            {
+                for (int i = 1; i < args.Length; i++)
+                {
+                    string cmd = args[i].ToLower();
+                    if (cmd.Equals("norender"))
+                    {
+                        Render3DAspect = false;
+                    }
+                    else if (cmd.Equals("export"))
+                    {
+                        AutoExportOnLoad = true;
+                    }
+                    else
+                    {
+                        AutoLoadFileName = args[i];
+                        break;
+                    }
+                }
+            }
+
+            if (AutoLoadFileName.Length > 0)
+                LoadZoneFile(AutoLoadFileName);
+            if (AutoExportOnLoad)
+                exportToolStripMenuItem_Click(null, EventArgs.Empty);
+
+            if (!Render3DAspect)
+            {
+                Application.Exit();
+                return;
+            }
+            //  FrustumClass frustum = new FrustumClass();
+            try
+            {
+                MessagePump.Run(this, () =>
+                {
+                    if (!Graphics.SwapChain.Disposed) {
+                        timer.Frame();
+                        fps.Frame();
+
+                        // Input code
+                        input.Frame();
+
+                        position.SetFrameTime(timer.GetTime());
+
+                        if (this.Focused)
+                        {
+                            position.TurnLeft(input.IsLeftPressed());
+                            position.TurnRight(input.IsRightPressed());
+                            position.MoveForward(input.IsUpPressed());
+                            position.MoveBackward(input.IsDownPressed());
+                            position.MoveUpward(input.IsAPressed());
+                            position.MoveDownward(input.IsZPressed());
+                            position.LookUpward(input.IsPgUpPressed());
+                            position.LookDownward(input.IsPgDownPressed());
+
+                            if (input.IsLeftMousePressed())
+                            {
+                                TestIntersection(input.GetMouseX(), input.GetMouseY());
+                            }
+
+                            if (SelectedModel != null)
+                            {
+                                if (input.IsKeyPressed(SlimDX.DirectInput.Key.Delete))
+                                {
+                                    m_Models.Remove(SelectedModel);
+                                    SelectedModel = null;
+                                }
+                                else if (input.IsKeyPressed(SlimDX.DirectInput.Key.Escape))
+                                {
+                                    SelectedModel = null;
+                                }
+                            }
+                        }
+
+                        camera.SetPosition(position.GetPosition());
+                        camera.SetRotation(position.GetRotation());
+
+                        // Render Code
+                        Graphics.BeginScene();
+
+                        // 3D
+                        // View matrix
+                        camera.Render();
+
+                        //frustum.ConstructFrustum(1000.0f, Graphics.GetProjectionMatrix(), camera.GetViewMatrix());
+
+                        foreach (Model model in m_Models) {
+                            //if (frustum.CheckSphere(model.Position.X, model.Position.Y, model.Position.Z, 10.0f))
+                            //{
+                            /*Matrix temp = Matrix.Multiply(Graphics.GetWorldMatrix(), Matrix.Scaling(model.Scale, model.Scale, model.Scale));
+                            temp = Matrix.Multiply(temp, Matrix.RotationYawPitchRoll(model.Rotation.X, model.Rotation.Y, model.Rotation.Z));
+                            temp = Matrix.Multiply(temp, Matrix.Translation(model.Position.X, model.Position.Y, model.Position.Z));*/
+                            model.Render(Graphics, camera, (model == SelectedModel)/*Graphics.Context*/);
+                            //lightShader.Render(Graphics.Context, model.GetIndexCount(), temp, camera.GetViewMatrix(), Graphics.GetProjectionMatrix(), model.GetTexture(), new Vector3(0.0f, 0.0f, 0.0f), new Vector4(1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 0.0f, 0.0f), camera.GetPosition(), new Vector4(0.0f, 0.0f, 0.0f, 0.0f), 0.0f);
+                            //}
+                        }
+
+                        // 2D
+                        Graphics.TurnZBufferOff();
+                        Graphics.TurnOnAlphaBlending();
+
+                        bmp.Render(Graphics, 10, 10);
+
+                        text.SetFPS(fps.GetFPS(), Graphics.Context);
+                        text.SetPosition(position.GetPosition(), Graphics.Context);
+                        text.SetSelectedModel(SelectedModel, Graphics.Context);
+                        text.Render(Graphics.Context, Graphics.GetWorldMatrix(), Graphics.GetOrthoMatrix());
+
+                        Graphics.TurnOffAlphaBlending();
+                        Graphics.TurnZBufferOn();
+
+                        Graphics.EndScene();
+                    }
+                });
+            }
+            catch (Exception exception) { }
+        }
+
+        public bool TestIntersection(int mouseX, int mouseY) {
+            float pointX, pointY;
+            Matrix projectionMatrix, viewMatrix, inverseViewMatrix;
+
+            projectionMatrix = Graphics.GetProjectionMatrix();
+            pointX = (2.0F * (float)mouseX / (float)pGraphics.ClientSize.Width - 1.0f) / projectionMatrix.M11;
+            pointY = (-2.0f * (float)mouseY / (float)pGraphics.ClientSize.Height + 1.0f) / projectionMatrix.M22;
+            Ray ray = new Ray(new Vector3(), new Vector3(pointX, pointY, 1.0f));
+
+            viewMatrix = camera.GetViewMatrix();
+            inverseViewMatrix = Matrix.Invert(viewMatrix);
+            ray = new Ray(Vector3.TransformCoordinate(ray.Position, inverseViewMatrix), Vector3.TransformNormal(ray.Direction, inverseViewMatrix));
+            ray.Direction.Normalize();
+
+            float selectionDistance = 0.0f;
+            foreach (Model model in m_Models) {
+                float distance = model.TestIntersection(ray, Graphics);
+                if (distance > 0.0f && (selectionDistance == 0.0f || distance < selectionDistance)) {
+                    selectionDistance = distance;
+                    SelectedModel = model;
+                }
+            }
+
+            return false;
+        }
+
+        public static void AppendLoadFile(String txt)
+        {
+            StreamWriter sw = File.AppendText("loaded.txt");
+            sw.WriteLine(txt);
+            sw.Close();
+        }
+
+        private void loadZoneToolStripMenuItem_Click(object sender, EventArgs e)
+        {
+            LoadZoneFile();
+        }
+
+        private void LoadZoneFile(String filename="")
+        {
+            string fullName = "";
+            String dirName = "";
+            if (filename.Length < 1)
+            {
+                OpenFileDialog fd = new OpenFileDialog();
+                fd.Filter = "lut files (*.lut)|*.lut";
+                if (fd.ShowDialog() == DialogResult.OK)
+                {
+                    AppendLoadFile("===================================================");
+                    AppendLoadFile("Loading " + fd.FileName);
+
+                    string temp = fd.FileName.Substring(0, fd.FileName.IndexOf("zones"));
+                    ZoneFile = fd.SafeFileName.Substring(0, fd.SafeFileName.IndexOf(".lut"));
+                    fullName = ZoneFile;
+                    dirName = temp;
+                    filename = fd.FileName;
+                }
+            }
+            else
+            {
+                string temp = filename.Substring(0, filename.IndexOf("zones"));
+                ZoneFile = filename.Substring(0, filename.IndexOf(".lut"));
+                fullName = filename;
+                dirName = temp;
+            }
+
+            if (fullName.Length < 1)
+            {
+                MessageBox.Show("No filename provided for loading a zonefile!", "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
+                return;
+            }
+                System.IO.BinaryReader reader2 = new System.IO.BinaryReader(new System.IO.FileStream(filename, System.IO.FileMode.Open, System.IO.FileAccess.Read));
+                // Image(2020): Was ReadUint32, qey_harbor.lut however has 00 1F 00 7A, so that as an int32 is a very large number!
+                reader2.ReadUInt32();
+                do
+                {
+                    if (reader2.BaseStream.Position + 2 >= reader2.BaseStream.Length)
+                    {
+                        break;
+                    }
+                    UInt16 size = reader2.ReadUInt16();
+                    string file = new string(reader2.ReadChars(size));
+
+                    // was duplicating drive name
+                    file = file.Replace("/", "\\");
+
+                    file = dirName + file;
+                    AppendLoadFile("VOC Loading: " + file);
+
+                    Eq2Reader reader = new Eq2Reader(new System.IO.FileStream(file, System.IO.FileMode.Open, System.IO.FileAccess.Read));
+                    VeNode venode = reader.ReadNodeObject();
+
+                    CheckNode(dirName, venode);
+
+                    //MessageBox.Show("Done!");
+
+                    // 16 bytes between file names, grid id's maybe?
+                    reader2.ReadBytes(16);
+                } while (true);
+            }
+
+        float x, y, z = 0;
+        float yaw, pitch, roll = 0;
+        float scale = 0;
+        UInt32 widgetID;
+        UInt32 GridID;
+        private void CheckNode(string temp, object item)
+        {
+            if (item is VeMeshGeometryNode)
+            {
+                widgetID = ((VeNode)item).WidgetID;
+
+                if ( widgetID == 3365739999)
+                {
+                    int test = 0;
+                }
+                Model model = new Model();
+                model.Initialize(Graphics.Device, (VeMeshGeometryNode)item, temp);
+                model.Position.X = x;
+                model.Position.Y = y;
+                model.Position.Z = z;
+                model.Rotation.X = yaw;
+                model.Rotation.Y = pitch;
+                model.Rotation.Z = roll;
+                model.Scale = scale;
+                model.WidgetID = widgetID;
+                model.GridID = GridID;
+                m_Models.Add(model);
+            }
+            else
+            {
+                float x1 = 0.0f;
+                float y1 = 0.0f;
+                float z1 = 0.0f;
+                if (item is VeRoomItemNode)
+                {
+                    yaw = ((VeRoomItemNode)item).orientation[0];
+                    pitch = ((VeRoomItemNode)item).orientation[1];
+                    roll = ((VeRoomItemNode)item).orientation[2];
+                    GridID = ((VeRoomItemNode)item).unk0;
+                }
+                else if (item is VeXformNode)
+                {
+                    x1 = ((VeXformNode)item).position[0];
+                    y1 = ((VeXformNode)item).position[1];
+                    z1 = ((VeXformNode)item).position[2];
+                    yaw = ((VeXformNode)item).orientation[0] * (3.141592654f / 180.0f);
+                    pitch = ((VeXformNode)item).orientation[1] * (3.141592654f / 180.0f);
+                    roll = ((VeXformNode)item).orientation[2] * (3.141592654f / 180.0f);
+                    scale = ((VeXformNode)item).scale;
+
+                    x += x1;
+                    y += y1;
+                    z += z1;
+                }
+                if (item != null)
+                {
+                    System.Collections.IEnumerator enumerator = ((VeNode)item).EnumerateChildren();
+                    while (enumerator.MoveNext())
+                    {
+                        CheckNode(temp, enumerator.Current);
+                    }
+                    x -= x1;
+                    y -= y1;
+                    z -= z1;
+                }
+            }
+        }
+
+        public static string[] GetTextureFile(string[] spPath, string basePath)
+        {
+            string ret = "goblin_ice.dds";
+            System.Collections.Generic.List<string> strings = new System.Collections.Generic.List<string>();
+
+            int i = 0;
+            while (i < spPath.Length /*&& ret == "goblin_ice.dds"*/)
+            {
+                Eq2Reader reader = new Eq2Reader(new System.IO.FileStream(basePath + spPath[i], System.IO.FileMode.Open, System.IO.FileAccess.Read));
+                VeBase sp = reader.ReadObject();
+                reader.Close();
+
+                if (sp is VeShaderPalette)
+                {
+                    bool found = false;
+                    for (int s = 0; s < ((VeShaderPalette)sp).shaderNames.Length; s++)
+                    {
+                        String fileName = basePath + ((VeShaderPalette)sp).shaderNames[s];
+                        fileName = fileName.Replace("/", "\\");
+                        System.IO.StreamReader reader2 = new System.IO.StreamReader(fileName);
+                        while (!reader2.EndOfStream)
+                        {
+                            string lineOrig = reader2.ReadLine();
+                            if (lineOrig.Contains("name = \"@tex") && !lineOrig.Contains("Blend") && !lineOrig.Contains("UVSet"))
+                            {
+                                String line = reader2.ReadLine();
+                                while (line.Length < 1)
+                                    line = reader2.ReadLine();
+
+                                line = line.Substring(line.IndexOf('"') + 1);
+                                line = line.Substring(0, line.Length - 1);
+                                ret = basePath + line;
+                                strings.Add(ret);
+                                found = true;
+                                break;
+                                //break;
+                            }
+                            if (found)
+                                break;
+                        }
+                        reader2.Close();
+                    }
+                }
+                i++;
+            }
+
+            if (strings.Count == 0)
+                strings.Add(ret);
+
+
+            return strings.ToArray();
+        }
+
+        private void exportToolStripMenuItem_Click(object sender, EventArgs e)
+        {
+            //List<Vector3> MasterVertexList = new List<Vector3>();
+            Dictionary<UInt32, List<Vector3>> MasterVertexList = new Dictionary<UInt32, List<Vector3>>();
+            foreach (Model model in m_Models)
+            {
+                List<Vector3> VertexList = model.GetVertices();
+                UInt32 grid = model.GridID;
+
+                if (!MasterVertexList.ContainsKey(grid))
+                    MasterVertexList[grid] = new List<Vector3>();
+
+                MasterVertexList[grid].AddRange(VertexList);
+            }
+
+            float minX = float.NaN;
+            float minZ = float.NaN;
+            float maxX = float.NaN;
+            float maxZ = float.NaN;
+            foreach (KeyValuePair<UInt32, List<Vector3>> entry in MasterVertexList)
+            {
+                foreach (Vector3 v in entry.Value)
+                {
+                    if (float.IsNaN(minX))
+                    {
+                        minX = v.X;
+                        maxX = v.X;
+                        minZ = v.Z;
+                        maxZ = v.Z;
+                    }
+                    else
+                    {
+                        if (v.X < minX)
+                            minX = v.X;
+                        if (v.X > maxX)
+                            maxX = v.X;
+                        if (v.Z < minZ)
+                            minZ = v.Z;
+                        if (v.Z > maxZ)
+                            maxZ = v.Z;
+                    }
+                }
+            }
+
+
+            using (StreamWriter file = new StreamWriter(ZoneFile + ".obj"))
+            {
+                //   file.WriteLine(ZoneFile);
+                //  file.WriteLine("Min");
+                //   file.WriteLine(minX + " " + minZ);
+                //  file.WriteLine("Max");
+                //  file.WriteLine(maxX + " " + maxZ);
+
+                //  file.WriteLine("Grid count");
+                //  file.WriteLine(MasterVertexList.Count);
+                //  file.WriteLine();
+
+                List<string> indices = new List<string>();
+                int count = 0;
+                string buildStr = "";
+                int curcount = 0;
+                foreach (KeyValuePair<UInt32, List<Vector3>> entry in MasterVertexList)
+                {
+                    buildStr = "f ";
+                   // file.WriteLine("Grid");
+                   // file.WriteLine(entry.Key);
+
+                   // file.WriteLine("Face count");
+                   // file.WriteLine(entry.Value.Count);
+                    foreach (Vector3 v in entry.Value)
+                    {
+                        if (curcount > 2)
+                        {
+                            buildStr += count;
+                            indices.Add(buildStr);
+                            buildStr = "f ";
+                            curcount = 0;
+                        }
+                        else
+                            buildStr += count + " ";
+
+                        file.WriteLine("v " + v.X.ToString() + " " + v.Y.ToString()
+                            + " " + v.Z.ToString());
+                        count++;
+                        curcount++;
+                    }
+                }
+                foreach (string str in indices)
+                {
+                    file.WriteLine(str);
+                }
+                file.Close();
+            }
+
+            using (BinaryWriter file = new BinaryWriter(File.Open(ZoneFile + ".EQ2Map", FileMode.Create)))
+            {
+                file.Write(ZoneFile);
+                file.Write(minX);
+                file.Write(minZ);
+                file.Write(maxX);
+                file.Write(maxZ);
+                file.Write(MasterVertexList.Count);
+                foreach (KeyValuePair<UInt32, List<Vector3>> entry in MasterVertexList)
+                {
+                    file.Write(entry.Key);
+                    file.Write(entry.Value.Count);
+                    foreach (Vector3 v in entry.Value)
+                    {
+                        file.Write(v.X);
+                        file.Write(v.Y);
+                        file.Write(v.Z);
+                    }
+                }
+                file.Close();
+            }
+            if (sender != null)
+               MessageBox.Show("Export Complete!");
+        }
+    }
+}

+ 123 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Main.resx

@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <metadata name="menuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>17, 17</value>
+  </metadata>
+</root>

+ 159 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/MeshClass.cs

@@ -0,0 +1,159 @@
+using System;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using System.Collections.Generic;
+
+using SlimDX;
+using SlimDX.D3DCompiler;
+using SlimDX.Direct3D11;
+using SlimDX.DXGI;
+using SlimDX.Windows;
+using Device = SlimDX.Direct3D11.Device;
+using Resource = SlimDX.Direct3D11.Resource;
+using Buffer = SlimDX.Direct3D11.Buffer;
+
+namespace EQ2ModelViewer
+{
+    public class MeshClass
+    {
+        public struct EQ2Model
+        {
+            public float x, y, z;
+            public float tu, tv;
+            public float nx, ny, nz;
+        }
+
+        private Buffer m_VertexBuffer;
+        private Buffer m_IndexBuffer;
+        private int m_VertexCount;
+        private int m_IndexCount;
+        public EQ2Model[] m_model;
+        TextureClass m_Texture;
+
+        public void SetFaceCount(int count)
+        {
+            m_VertexCount = count;
+            m_IndexCount = count;
+            m_model = new EQ2Model[count];
+        }
+
+        public void AddData(int index, float x, float y, float z, float nx, float ny, float nz, float tu, float tv)
+        {
+            if (index > m_VertexCount)
+                throw new IndexOutOfRangeException("MeshClass AddData Failed: index (" + index + ") is greater then m_VertexCount (" + m_VertexCount + ")");
+
+            m_model[index].x = x;
+            m_model[index].y = y;
+            m_model[index].z = z;
+
+            m_model[index].nx = nx;
+            m_model[index].ny = ny;
+            m_model[index].nz = nz;
+
+            m_model[index].tu = tu;
+            m_model[index].tv = tv;
+        }
+
+        public List<Vector3> GetVertices()
+        {
+            List<Vector3> ret = new List<Vector3>();
+            foreach (EQ2Model m in m_model)
+            {
+                Vector3 newVec = new Vector3(m.x, m.y, m.z);
+                ret.Add(newVec);
+            }
+
+            return ret;
+        }
+
+        public bool InitializeBuffers(Device device)
+        {
+            BufferDescription vertexBufferDesc = new BufferDescription();
+            BufferDescription indexBufferDesc = new BufferDescription();
+            DataStream vertices = new DataStream(System.Runtime.InteropServices.Marshal.SizeOf(typeof(EQ2Model)) * m_VertexCount, true, true);
+            DataStream indices = new DataStream(sizeof(ulong) * m_IndexCount, true, true);
+
+            for (int i = 0; i < m_VertexCount; i++)
+            {
+                vertices.Write(new Vector3(m_model[i].x, m_model[i].y, m_model[i].z));
+                vertices.Write(new Vector2(m_model[i].tu, m_model[i].tv));
+                vertices.Write(new Vector3(m_model[i].nx, m_model[i].ny, m_model[i].nz));
+                indices.Write(i);
+            }
+            vertices.Position = 0;
+            indices.Position = 0;
+
+            vertexBufferDesc.Usage = ResourceUsage.Default;
+            vertexBufferDesc.SizeInBytes = (int)vertices.Length;
+            vertexBufferDesc.BindFlags = BindFlags.VertexBuffer;
+            vertexBufferDesc.CpuAccessFlags = CpuAccessFlags.None;
+            vertexBufferDesc.OptionFlags = ResourceOptionFlags.None;
+            vertexBufferDesc.StructureByteStride = 0;
+
+
+            m_VertexBuffer = new Buffer(device, vertices, vertexBufferDesc);
+            vertices.Dispose();
+
+            indexBufferDesc.Usage = ResourceUsage.Default;
+            indexBufferDesc.SizeInBytes = (int)indices.Length;
+            indexBufferDesc.BindFlags = BindFlags.IndexBuffer;
+            indexBufferDesc.CpuAccessFlags = CpuAccessFlags.None;
+            indexBufferDesc.OptionFlags = ResourceOptionFlags.None;
+            indexBufferDesc.StructureByteStride = 0;
+
+            m_IndexBuffer = new Buffer(device, indices, indexBufferDesc);
+            indices.Dispose();
+            return true;
+        }
+
+        public void RenderBuffers(DeviceContext context)
+        {
+            int stride = System.Runtime.InteropServices.Marshal.SizeOf(typeof(EQ2Model));
+            int offset = 0;
+
+            context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(m_VertexBuffer, stride, offset));
+            context.InputAssembler.SetIndexBuffer(m_IndexBuffer, Format.R32_UInt, 0);
+            context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
+        }
+
+        public void ShutDown()
+        {
+            ReleaseTexture();
+            m_VertexBuffer.Dispose();
+            m_IndexBuffer.Dispose();
+        }
+
+        public int GetIndexCount()
+        {
+            return m_IndexCount;
+        }
+
+        public ShaderResourceView GetTexture()
+        {
+            return m_Texture.GetTexture();
+        }
+
+        private void ReleaseTexture()
+        {
+            m_Texture.ShutDown();
+        }
+
+        public bool LoadTexture(Device device, string filename)
+        {
+            m_Texture = new TextureClass();
+            if (m_Texture == null)
+            {
+                Console.WriteLine("Model: Failed to create an instance of TextureClass()...WTF!");
+                return false;
+            }
+
+            if (!m_Texture.Initialize(device, filename))
+            {
+                Console.WriteLine("Model: Failed to load the texture.");
+                return false;
+            }
+
+            return true;
+        }
+    }
+}

+ 290 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Model.cs

@@ -0,0 +1,290 @@
+using System;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+using SlimDX;
+using SlimDX.D3DCompiler;
+using SlimDX.Direct3D11;
+using SlimDX.DXGI;
+using SlimDX.Windows;
+using Device = SlimDX.Direct3D11.Device;
+using Resource = SlimDX.Direct3D11.Resource;
+using Buffer = SlimDX.Direct3D11.Buffer;
+using System.IO;
+using System.Collections.Generic;
+using System.Collections;
+
+using Everquest2.Util;
+using Everquest2.Visualization;
+
+namespace EQ2ModelViewer
+{
+    public class Model
+    {
+        public Vector3 Position = new Vector3(0.0f, 0.0f, 0.0f);
+        public Vector3 Rotation = new Vector3(0.0f, 0.0f, 0.0f);
+        public float Scale = 1.0f;
+        public UInt32 WidgetID = 0;
+        public UInt32 GridID = 0;
+        public String modelName = "";
+        List<MeshClass> m_meshes = new List<MeshClass>();
+        LightShaderClass lightShader = new LightShaderClass();
+
+        public bool Initialize(Device device, string modelFileName, string[] textureFileName)
+        {
+            if (!LoadModel(modelFileName))
+            {
+                Console.WriteLine("Model: Failed to load the model");
+                return false;
+            }
+
+            int count = 0;
+            foreach (MeshClass mesh in m_meshes)
+            {
+                if (!mesh.InitializeBuffers(device))
+                {
+                    Console.WriteLine("Model: Failed to initialize buffers.");
+                    return false;
+                }
+
+                if (count >= textureFileName.Length)
+                    mesh.LoadTexture(device, textureFileName[0]);
+                else
+                    mesh.LoadTexture(device, textureFileName[count]);
+
+                count++;
+            }
+
+            lightShader.Initialize(device);
+            return true;
+        }
+
+        public bool Initialize(Device device, VeMeshGeometryNode item, String baseDir)
+        {
+            ArrayList textures = new ArrayList();
+
+            string[][] meshes = ((VeMeshGeometryNode)item).renderMeshNames;
+            for (int i = 0; i < meshes.Length; i++)
+            {
+                for (int j = 0; j < meshes[i].Length; j++)
+                {
+                    if (meshes[i][j] != null)
+                    {
+                        string path = meshes[i][j];
+
+                        path = baseDir + path.Replace("/", "\\");
+                        //                        if (path.Contains("\\flora\\"))
+                            if (path.Contains("\\accessories\\"))
+                        {
+                            Console.WriteLine("Model: skipping loading of model (Accessories suck!)" + path);
+                            return false;
+                        }
+                        string[] texture = frmMain.GetTextureFile(((VeMeshGeometryNode)item).shaderPaletteNames, baseDir);
+                        string pickedTexture = "";
+                        if (i < texture.Length && texture[i] != "goblin_ice.dds")
+                        {
+                            pickedTexture = texture[i];
+                        }
+                        else if (texture[0] != "goblin_ice.dds")
+                        {
+                            pickedTexture = texture[0];
+                        }
+
+                        if (pickedTexture.Length < 1)
+                        {
+                            Console.WriteLine("Model: missing texture " + path + " at i:" + i + " and j:" + j);
+                        }
+                        else
+                        {
+                            textures.Add(pickedTexture);
+
+                            if (!LoadModel(path))
+                            {
+                                Console.WriteLine("Model: Failed to load the model " + path);
+                                return false;
+                            }
+                        }
+                    }
+                }
+            }
+
+            int count = 0;
+            ArrayList removeList = new ArrayList();
+            foreach (MeshClass mesh in m_meshes)
+            {
+                if (mesh.GetVertices().Count < 1)
+                {
+                    removeList.Add(mesh);
+                    continue;
+                }
+
+                if (!mesh.InitializeBuffers(device))
+                {
+                    Console.WriteLine("Model: Failed to initialize buffers.");
+                    return false;
+                }
+
+                if (textures.Count > 0)
+                {
+                    if (count >= textures.Count)
+                        mesh.LoadTexture(device, (string)textures[0]);
+                    else
+                        mesh.LoadTexture(device, (string)textures[count]);
+                }
+
+                count++;
+            }
+
+            // these are for meshclass files we couldn't load correctly, usually cause no primitivecount, only vertex count
+            // moving on means trying to access a null texture and crashing in the render
+            foreach (MeshClass mesh in removeList)
+            {
+                m_meshes.Remove(mesh);
+            }
+
+                lightShader.Initialize(device);
+            return true;
+        }
+
+        public void Render(GraphicClass Graphics, CameraClass camera, bool highlight = false)
+        {
+            Matrix temp = Matrix.Multiply(Graphics.GetWorldMatrix(), Matrix.Scaling(Scale, Scale, Scale));
+            temp = Matrix.Multiply(temp, Matrix.RotationYawPitchRoll(Rotation.X, Rotation.Y, Rotation.Z));
+            temp = Matrix.Multiply(temp, Matrix.Translation(Position.X, Position.Y, Position.Z));
+
+            Vector4 ambientColor;
+            if (highlight)
+                ambientColor = new Vector4(0.0f, 1.0f, 0.0f, 1.0f);
+            else
+                ambientColor = new Vector4(1.0f, 1.0f, 1.0f, 1.0f);
+
+            foreach (MeshClass mesh in m_meshes)
+            {
+                mesh.RenderBuffers(Graphics.Context);
+                lightShader.Render(Graphics.Context, mesh.GetIndexCount(), temp, camera.GetViewMatrix(), Graphics.GetProjectionMatrix(), mesh.GetTexture(), new Vector3(0.0f, 0.0f, 0.0f), ambientColor/*new Vector4(1.0f, 1.0f, 1.0f, 1.0f)*/, new Vector4(0.0f, 0.0f, 0.0f, 0.0f), camera.GetPosition(), new Vector4(0.0f, 0.0f, 0.0f, 0.0f), 0.0f);
+            }
+        }
+
+        public float TestIntersection(Ray ray, GraphicClass Graphics)
+        {
+
+            Matrix temp = Matrix.Multiply(Graphics.GetWorldMatrix(), Matrix.Scaling(Scale, Scale, Scale));
+            temp = Matrix.Multiply(temp, Matrix.RotationYawPitchRoll(Rotation.X, Rotation.Y, Rotation.Z));
+            temp = Matrix.Multiply(temp, Matrix.Translation(Position.X, Position.Y, Position.Z));
+
+            float ret = 0.0f;
+            foreach (MeshClass mesh in m_meshes)
+            {
+                int i = 0;
+                while (i < mesh.m_model.Length)
+                {
+                    Vector3 vec1 = new Vector3(mesh.m_model[i].x, mesh.m_model[i].y, mesh.m_model[i].z);
+                    Vector3 vec2 = new Vector3(mesh.m_model[i + 1].x, mesh.m_model[i + 1].y, mesh.m_model[i + 1].z);
+                    Vector3 vec3 = new Vector3(mesh.m_model[i + 2].x, mesh.m_model[i + 2].y, mesh.m_model[i + 2].z);
+
+                    Vector4 transformVec = Vector3.Transform(vec1, temp);
+                    vec1 = new Vector3(transformVec.X, transformVec.Y, transformVec.Z);
+                    transformVec = Vector3.Transform(vec2, temp);
+                    vec2 = new Vector3(transformVec.X, transformVec.Y, transformVec.Z);
+                    transformVec = Vector3.Transform(vec3, temp);
+                    vec3 = new Vector3(transformVec.X, transformVec.Y, transformVec.Z);
+
+                    if (Ray.Intersects(ray, vec1, vec2, vec3, out ret))
+                    {
+                        break;
+                    }
+                    i += 3;
+
+                }
+                if (ret != 0.0f)
+                    break;
+            }
+
+            return ret;
+        }
+
+        private bool LoadModel(string modelFileName)
+        {
+            frmMain.AppendLoadFile("LoadModel: " + modelFileName);
+            Eq2Reader reader = new Eq2Reader(File.OpenRead(modelFileName));
+            VeRenderMesh model = (VeRenderMesh)reader.ReadObject();
+            reader.Dispose();
+
+            if (model.subMeshes.Length > 0)
+            {
+                for (int count = 0; count < model.subMeshes.Length; count++)
+                {
+                    MeshClass mesh = new MeshClass();
+                    mesh.SetFaceCount(model.subMeshes[count].PrimitiveCount * 3);
+                    int start = model.subMeshes[count].StartingIndex;
+                    int end = model.subMeshes[count].PrimitiveCount * 3;
+
+                    for (int i = 0; i < end; i++)
+                    {
+                        int index = i + start;
+                        int indicesIdx = model.indices[index];
+                        if (indicesIdx < 0 || indicesIdx >= model.vertices.Length)
+                            break;
+                        float x = model.vertices[model.indices[index]].X;
+                        float y = model.vertices[model.indices[index]].Y;
+                        float z = model.vertices[model.indices[index]].Z;
+
+                        float nx = model.normals[model.indices[index]].X;
+                        float ny = model.normals[model.indices[index]].Y;
+                        float nz = model.normals[model.indices[index]].Z;
+
+                        float tu = model.texCoords[0][model.indices[index]].U;
+                        float tv = model.texCoords[0][model.indices[index]].V;
+
+                        mesh.AddData(i, x, y, z, nx, ny, nz, tu, tv);
+                    }
+
+                    if (model.indices.Length < 1 || model.normals.Length < 1 || model.vertices.Length < 1)
+                    {
+                        int test = 0;
+                    }
+
+                    m_meshes.Add(mesh);
+                }
+            }
+            else
+            {
+                MeshClass mesh = new MeshClass();
+
+                mesh.SetFaceCount(model.indices.Length);
+
+                for (int i = 0; i < model.indices.Length /*m_VertexCount*/; i++)
+                {
+                    if (model.indices[i] < 0 || model.indices[i] > model.vertices.Length)
+                    {
+
+                    }
+                    else
+                        mesh.AddData(i, model.vertices[model.indices[i]].X, model.vertices[model.indices[i]].Y, model.vertices[model.indices[i]].Z, model.normals[model.indices[i]].X, model.normals[model.indices[i]].Y, model.normals[model.indices[i]].Z, model.texCoords[0][model.indices[i]].U, model.texCoords[0][model.indices[i]].V);
+                }
+
+                m_meshes.Add(mesh);
+            }
+
+            return true;
+        }
+
+        public void ShutDown()
+        {
+            foreach (MeshClass mesh in m_meshes)
+                mesh.ShutDown();
+        }
+
+        public List<Vector3> GetVertices()
+        {
+            List<Vector3> ret = new List<Vector3>();
+            foreach (MeshClass m in m_meshes)
+            {
+                List<Vector3> newList = m.GetVertices();
+                ret.AddRange(newList);
+            }
+
+            return ret;
+        }
+    }
+}

+ 30 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/ModelManager.cs

@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EQ2ModelViewer
+{
+    public class ModelManager
+    {
+        private UInt32 id;
+        private Dictionary<UInt32, Model> model_list = new Dictionary<UInt32, Model>();
+        private Dictionary<string, UInt32> id_lookup_list = new Dictionary<string, UInt32>();
+
+        ModelManager()
+        {
+            id = 0;
+        }
+
+        /*public UInt32 AddModel(string file)
+        {
+            if (id_lookup_list.ContainsKey(file))
+                return id_lookup_list[file];
+
+            id_lookup_list[file] = id;
+            Model model = new Model();
+            //model.Initialize
+        }*/
+    }
+}

+ 247 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/PositionClass.cs

@@ -0,0 +1,247 @@
+using System;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+using SlimDX;
+using SlimDX.D3DCompiler;
+using SlimDX.Direct3D11;
+using SlimDX.DXGI;
+using SlimDX.Windows;
+using Device = SlimDX.Direct3D11.Device;
+using Resource = SlimDX.Direct3D11.Resource;
+using Buffer = SlimDX.Direct3D11.Buffer;
+
+namespace EQ2ModelViewer
+{
+    public class PositionClass
+    {
+        private float m_PositionX;
+        private float m_positionY;
+        private float m_PositionZ;
+        private float m_RotationX;
+        private float m_RotationY;
+        private float m_RotationZ;
+        private float m_FrameTime;
+        private float m_ForwardSpeed;
+        private float m_BackwardSpeed;
+        private float m_UpwardSpeed;
+        private float m_DownwardSpeed;
+        private float m_LeftTurnSpeed;
+        private float m_RightTurnSpeed;
+        private float m_LookUpSpeed;
+        private float m_LookDownSpeed;
+
+        public void SetPosition(float x, float y, float z)
+        {
+            m_PositionX = x;
+            m_positionY = y;
+            m_PositionZ = z;
+        }
+
+        public void SetRotation(float x, float y, float z)
+        {
+            m_RotationX = x;
+            m_RotationY = y;
+            m_RotationZ = z;
+        }
+
+        public Vector3 GetPosition()
+        {
+            return new Vector3(m_PositionX, m_positionY, m_PositionZ);
+        }
+
+        public Vector3 GetRotation()
+        {
+            return new Vector3(m_RotationX, m_RotationY, m_RotationZ);
+        }
+
+        public void SetFrameTime(float time)
+        {
+            m_FrameTime = time;
+        }
+
+        public void MoveForward(bool keydown)
+        {
+            float radians;
+
+            if (keydown)
+            {
+                m_ForwardSpeed += m_FrameTime * 0.001f;
+                if (m_ForwardSpeed > (m_FrameTime * 0.03f))
+                    m_ForwardSpeed = m_FrameTime * 0.03f;
+            }
+            else
+            {
+                m_ForwardSpeed -= m_FrameTime * 0.0007f;
+                if (m_ForwardSpeed < 0.0f)
+                    m_ForwardSpeed = 0.0f;
+            }
+            radians = m_RotationY * 0.0174532925f;
+
+            m_PositionX += (float)Math.Sin(radians) * m_ForwardSpeed;
+            m_PositionZ += (float)Math.Cos(radians) * m_ForwardSpeed;
+        }
+
+        public void MoveBackward(bool keydown)
+        {
+            float radians;
+
+            if (keydown)
+            {
+                m_BackwardSpeed += m_FrameTime * 0.001f;
+                if (m_BackwardSpeed > (m_FrameTime * 0.03f))
+                    m_BackwardSpeed = m_FrameTime * 0.03f;
+            }
+            else
+            {
+                m_BackwardSpeed -= m_FrameTime * 0.0007f;
+                if (m_BackwardSpeed < 0.0f)
+                    m_BackwardSpeed = 0.0f;
+            }
+            radians = m_RotationY * 0.0174532925f;
+
+            m_PositionX -= (float)Math.Sin(radians) * m_BackwardSpeed;
+            m_PositionZ -= (float)Math.Cos(radians) * m_BackwardSpeed;
+        }
+
+        public void MoveUpward(bool keydown)
+        {
+            if (keydown)
+            {
+                m_UpwardSpeed += m_FrameTime * 0.003f;
+                if (m_UpwardSpeed > (m_FrameTime * 0.03f))
+                    m_UpwardSpeed = m_FrameTime * 0.03f;
+            }
+            else
+            {
+                m_UpwardSpeed -= m_FrameTime * 0.002f;
+                if (m_UpwardSpeed < 0.0f)
+                    m_UpwardSpeed = 0.0f;
+            }
+
+            m_positionY += m_UpwardSpeed;
+        }
+
+        public void MoveDownward(bool keydown)
+        {
+            if (keydown)
+            {
+                m_DownwardSpeed += m_FrameTime * 0.003f;
+                if (m_DownwardSpeed > (m_FrameTime * 0.03f))
+                    m_DownwardSpeed = m_FrameTime * 0.03f;
+            }
+            else
+            {
+                m_DownwardSpeed -= m_FrameTime * 0.002f;
+                if (m_DownwardSpeed < 0.0f)
+                    m_DownwardSpeed = 0.0f;
+            }
+
+            m_positionY -= m_DownwardSpeed;
+        }
+
+        public void TurnLeft(bool keydown)
+        {
+            // Update the left turn speed movement based on the frame time and whether the user is holding the key down or not.
+            if (keydown)
+            {
+                m_LeftTurnSpeed += m_FrameTime * 0.01f;
+
+                if (m_LeftTurnSpeed > (m_FrameTime * 0.15f))
+                    m_LeftTurnSpeed = m_FrameTime * 0.15f;
+            }
+            else
+            {
+                m_LeftTurnSpeed -= m_FrameTime * 0.005f;
+
+                if (m_LeftTurnSpeed < 0.0f)
+                    m_LeftTurnSpeed = 0.0f;
+            }
+
+            // Update the rotation.
+            m_RotationY -= m_LeftTurnSpeed;
+
+            // Keep the rotation in the 0 to 360 range.
+            if (m_RotationY < 0.0f)
+                m_RotationY += 360.0f;
+        }
+
+        public void TurnRight(bool keydown)
+        {
+            // Update the right turn speed movement based on the frame time and whether the user is holding the key down or not.
+            if (keydown)
+            {
+                m_RightTurnSpeed += m_FrameTime * 0.01f;
+
+                if (m_RightTurnSpeed > (m_FrameTime * 0.15f))
+                    m_RightTurnSpeed = m_FrameTime * 0.15f;
+            }
+            else
+            {
+                m_RightTurnSpeed -= m_FrameTime * 0.005f;
+
+                if (m_RightTurnSpeed < 0.0f)
+                    m_RightTurnSpeed = 0.0f;
+            }
+
+            // Update the rotation.
+            m_RotationY += m_RightTurnSpeed;
+
+            // Keep the rotation in the 0 to 360 range.
+            if (m_RotationY > 360.0f)
+                m_RotationY -= 360.0f;
+        }
+
+        public void LookUpward(bool keydown)
+        {
+            // Update the upward rotation speed movement based on the frame time and whether the user is holding the key down or not.
+            if (keydown)
+            {
+                m_LookUpSpeed += m_FrameTime * 0.01f;
+
+                if (m_LookUpSpeed > (m_FrameTime * 0.15f))
+                    m_LookUpSpeed = m_FrameTime * 0.15f;
+            }
+            else
+            {
+                m_LookUpSpeed -= m_FrameTime * 0.005f;
+
+                if (m_LookUpSpeed < 0.0f)
+                    m_LookUpSpeed = 0.0f;
+            }
+
+            // Update the rotation.
+            m_RotationX -= m_LookUpSpeed;
+
+            // Keep the rotation maximum 90 degrees.
+            if (m_RotationX > 90.0f)
+                m_RotationX = 90.0f;
+        }
+
+        public void LookDownward(bool keydown)
+        {
+            // Update the downward rotation speed movement based on the frame time and whether the user is holding the key down or not.
+            if (keydown)
+            {
+                m_LookDownSpeed += m_FrameTime * 0.01f;
+
+                if (m_LookDownSpeed > (m_FrameTime * 0.15f))
+                    m_LookDownSpeed = m_FrameTime * 0.15f;
+            }
+            else
+            {
+                m_LookDownSpeed -= m_FrameTime * 0.005f;
+
+                if (m_LookDownSpeed < 0.0f)
+                    m_LookDownSpeed = 0.0f;
+            }
+
+            // Update the rotation.
+            m_RotationX += m_LookDownSpeed;
+
+            // Keep the rotation maximum 90 degrees.
+            if (m_RotationX < -90.0f)
+                m_RotationX = -90.0f;
+        }
+    }
+}

+ 22 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Program.cs

@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace EQ2ModelViewer
+{
+    static class Program
+    {
+        /// <summary>
+        /// The main entry point for the application.
+        /// </summary>
+        [STAThread]
+        static void Main()
+        {
+            Application.EnableVisualStyles();
+            Application.SetCompatibleTextRenderingDefault(false);
+            Application.Run(new frmMain());
+        }
+    }
+}

+ 36 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Properties/AssemblyInfo.cs

@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("EQ2ModelViewer")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("EQ2ModelViewer")]
+[assembly: AssemblyCopyright("Copyright ©  2014")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("7bf0c4f2-e7b2-407a-bcfe-747c516c29c0")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers 
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 71 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Properties/Resources.Designer.cs

@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.18052
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace EQ2ModelViewer.Properties
+{
+
+
+    /// <summary>
+    ///   A strongly-typed resource class, for looking up localized strings, etc.
+    /// </summary>
+    // This class was auto-generated by the StronglyTypedResourceBuilder
+    // class via a tool like ResGen or Visual Studio.
+    // To add or remove a member, edit your .ResX file then rerun ResGen
+    // with the /str option, or rebuild your VS project.
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources
+    {
+
+        private static global::System.Resources.ResourceManager resourceMan;
+
+        private static global::System.Globalization.CultureInfo resourceCulture;
+
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources()
+        {
+        }
+
+        /// <summary>
+        ///   Returns the cached ResourceManager instance used by this class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager
+        {
+            get
+            {
+                if ((resourceMan == null))
+                {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EQ2ModelViewer.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+
+        /// <summary>
+        ///   Overrides the current thread's CurrentUICulture property for all
+        ///   resource lookups using this strongly typed resource class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture
+        {
+            get
+            {
+                return resourceCulture;
+            }
+            set
+            {
+                resourceCulture = value;
+            }
+        }
+    }
+}

+ 117 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Properties/Resources.resx

@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 30 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Properties/Settings.Designer.cs

@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.18052
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace EQ2ModelViewer.Properties
+{
+
+
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+    {
+
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+        public static Settings Default
+        {
+            get
+            {
+                return defaultInstance;
+            }
+        }
+    }
+}

+ 7 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Properties/Settings.settings

@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>

+ 37 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/SkyBox.cs

@@ -0,0 +1,37 @@
+using System;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+using SlimDX;
+using SlimDX.D3DCompiler;
+using SlimDX.Direct3D11;
+using SlimDX.DXGI;
+using SlimDX.Windows;
+using Device = SlimDX.Direct3D11.Device;
+using Resource = SlimDX.Direct3D11.Resource;
+using Buffer = SlimDX.Direct3D11.Buffer;
+
+namespace EQ2ModelViewer
+{
+    public class SkyBox
+    {
+        int NumSphereVertices = 0;
+        int NumSphereFaces = 0;
+
+        private void CreateSphere(int LatLines, int LongLines)
+        {
+            NumSphereVertices = ((LatLines - 2) * LongLines) + 2;
+            NumSphereFaces = ((LatLines - 3) * LongLines * 2) + (LongLines * 2);
+
+            float sphereYaw = 0.0f;
+            float spherePitch = 0.0f;
+
+            /*
+            DataStream vertices = new DataStream(System.Runtime.InteropServices.Marshal.SizeOf(typeof(EQ2Model)) * m_VertexCount, true, true);
+            Vector4 currVertPos = new Vector4(0.0f, 0.0f, 1.0f, 0.0f);
+
+            SlimDX.Direct3D11.Devic
+            */
+        }
+    }
+}

+ 103 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Actions/ActionEventArgs.cs

@@ -0,0 +1,103 @@
+/// Spart License (zlib/png)
+/// 
+/// 
+/// Copyright (c) 2003 Jonathan de Halleux
+/// 
+/// This software is provided 'as-is', without any express or implied warranty. 
+/// In no event will the authors be held liable for any damages arising from 
+/// the use of this software.
+/// 
+/// Permission is granted to anyone to use this software for any purpose, 
+/// including commercial applications, and to alter it and redistribute it 
+/// freely, subject to the following restrictions:
+/// 
+/// 1. The origin of this software must not be misrepresented; you must not 
+/// claim that you wrote the original software. If you use this software in a 
+/// product, an acknowledgment in the product documentation would be 
+/// appreciated but is not required.
+/// 
+/// 2. Altered source versions must be plainly marked as such, and must not be 
+/// misrepresented as being the original software.
+/// 
+/// 3. This notice may not be removed or altered from any source distribution.
+/// 
+/// Author: Jonathan de Halleuxnamespace Spart.Actions
+
+namespace Spart.Actions
+{
+	using System;
+	using Spart.Parsers;
+
+	/// <summary>
+	/// Action event argument class
+	/// </summary>
+	public class ActionEventArgs : EventArgs
+	{
+		private ParserMatch m_Match;
+		private Object m_TypeValue;
+
+		/// <summary>
+		/// Construct a new event argument instance
+		/// </summary>
+		/// <param name="match"></param>
+		public ActionEventArgs(ParserMatch match)
+		{
+			if (match == null)
+				throw new ArgumentNullException("match is null");
+			if (!match.Success)
+				throw new ArgumentException("Match is not successfull");
+			m_Match = match;
+			m_TypeValue = null;
+		}
+
+		/// <summary>
+		/// Construct a new event argument instance
+		/// </summary>
+		/// <param name="match"></param>
+		/// <param name="typedValue"></param>
+		public ActionEventArgs(ParserMatch match, Object typedValue)
+		{
+			if (match == null)
+				throw new ArgumentNullException("match is null");
+			if (!match.Success)
+				throw new ArgumentException("Match is not successfull");
+			if (typedValue==null)
+				throw new ArgumentNullException("typed value");
+			m_Match = match;
+			m_TypeValue = typedValue;
+		}
+ 
+		/// <summary>
+		/// The parser match
+		/// </summary>
+		public ParserMatch Match
+		{
+			get
+			{
+				return m_Match;   
+			}
+		}    
+
+        /// <summary>
+        /// The parser match value
+        /// </summary>
+		public String Value
+		{
+			get
+			{
+				return Match.Value;
+			}
+		}
+
+		/// <summary>
+		/// The typed parse result
+		/// </summary>
+		public Object TypeValue
+		{
+			get
+			{
+				return m_TypeValue;
+			}
+		}
+	}
+}

+ 33 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Actions/ActionHandler.cs

@@ -0,0 +1,33 @@
+/// Spart License (zlib/png)
+/// 
+/// 
+/// Copyright (c) 2003 Jonathan de Halleux
+/// 
+/// This software is provided 'as-is', without any express or implied warranty. 
+/// In no event will the authors be held liable for any damages arising from 
+/// the use of this software.
+/// 
+/// Permission is granted to anyone to use this software for any purpose, 
+/// including commercial applications, and to alter it and redistribute it 
+/// freely, subject to the following restrictions:
+/// 
+/// 1. The origin of this software must not be misrepresented; you must not 
+/// claim that you wrote the original software. If you use this software in a 
+/// product, an acknowledgment in the product documentation would be 
+/// appreciated but is not required.
+/// 
+/// 2. Altered source versions must be plainly marked as such, and must not be 
+/// misrepresented as being the original software.
+/// 
+/// 3. This notice may not be removed or altered from any source distribution.
+/// 
+/// Author: Jonathan de Halleuxnamespace Spart.Actions
+namespace Spart.Actions
+{
+    using System;
+
+	/// <summary>
+	/// Action handler delegate
+	/// </summary>
+    public delegate void ActionHandler(Object sender, ActionEventArgs args);
+}

+ 70 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Actions/Actions.cs

@@ -0,0 +1,70 @@
+/// Spart License (zlib/png)
+/// 
+/// 
+/// Copyright (c) 2003 Jonathan de Halleux
+/// 
+/// This software is provided 'as-is', without any express or implied warranty. 
+/// In no event will the authors be held liable for any damages arising from 
+/// the use of this software.
+/// 
+/// Permission is granted to anyone to use this software for any purpose, 
+/// including commercial applications, and to alter it and redistribute it 
+/// freely, subject to the following restrictions:
+/// 
+/// 1. The origin of this software must not be misrepresented; you must not 
+/// claim that you wrote the original software. If you use this software in a 
+/// product, an acknowledgment in the product documentation would be 
+/// appreciated but is not required.
+/// 
+/// 2. Altered source versions must be plainly marked as such, and must not be 
+/// misrepresented as being the original software.
+/// 
+/// 3. This notice may not be removed or altered from any source distribution.
+/// 
+/// Author: Jonathan de Halleuxusing System;
+
+using System;
+using System.Collections;
+using Spart.Actions.Actors;
+
+namespace Spart.Actions
+{
+	/// <summary>
+	/// Static helper class that creates actors
+	/// </summary>
+    public class Actor
+    {
+		/// <summary>
+		/// Create an actor that append the parse result to a <see cref="IList"/>.
+		/// </summary>
+		/// <param name="list"></param>
+		/// <returns></returns>
+        public static ActionHandler Append(IList list)
+        { 
+            AppendActor a =  new AppendActor(list);
+            return new ActionHandler( a.DoAction );
+        }
+
+		/// <summary>
+		/// Create an actor that assign the parse result to an object
+		/// </summary>
+		/// <param name="o"></param>
+		/// <returns></returns>
+		public static ActionHandler Assign(Object o)
+		{ 
+			AssignActor a =  new AssignActor(o);
+			return new ActionHandler( a.DoAction );
+		}
+
+		/// <summary>
+		/// Creates an actor that throws an exception
+		/// </summary>
+		/// <param name="ex"></param>
+		/// <returns></returns>
+		public static ActionHandler Throw(Exception ex)
+		{
+			ThrowActor a = new ThrowActor(ex);
+			return new ActionHandler( a.DoAction );
+		}
+    }
+}

+ 58 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Actions/Actors/AppendActor.cs

@@ -0,0 +1,58 @@
+/// Spart License (zlib/png)
+/// 
+/// 
+/// Copyright (c) 2003 Jonathan de Halleux
+/// 
+/// This software is provided 'as-is', without any express or implied warranty. 
+/// In no event will the authors be held liable for any damages arising from 
+/// the use of this software.
+/// 
+/// Permission is granted to anyone to use this software for any purpose, 
+/// including commercial applications, and to alter it and redistribute it 
+/// freely, subject to the following restrictions:
+/// 
+/// 1. The origin of this software must not be misrepresented; you must not 
+/// claim that you wrote the original software. If you use this software in a 
+/// product, an acknowledgment in the product documentation would be 
+/// appreciated but is not required.
+/// 
+/// 2. Altered source versions must be plainly marked as such, and must not be 
+/// misrepresented as being the original software.
+/// 
+/// 3. This notice may not be removed or altered from any source distribution.
+/// 
+/// Author: Jonathan de Halleux
+
+using System;
+using System.Collections;
+
+namespace Spart.Actions.Actors
+{
+	/// <summary>
+	/// Actor that appends parse result to <see cref="IList"/>.
+	/// </summary>
+    public class AppendActor
+    {
+        private IList m_List;
+
+        public AppendActor(IList list)
+        {
+            if (list == null)
+               throw new ArgumentNullException("list");
+            m_List = list;
+        }
+
+        public IList List
+        {
+            get
+            {
+                return m_List;
+            }
+        } 
+
+        public void DoAction(Object sender, ActionEventArgs args)
+        {
+            List.Add(args.Value);
+        }
+    }
+}

+ 55 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Actions/Actors/AssignActor.cs

@@ -0,0 +1,55 @@
+/// Spart License (zlib/png)
+/// 
+/// 
+/// Copyright (c) 2003 Jonathan de Halleux
+/// 
+/// This software is provided 'as-is', without any express or implied warranty. 
+/// In no event will the authors be held liable for any damages arising from 
+/// the use of this software.
+/// 
+/// Permission is granted to anyone to use this software for any purpose, 
+/// including commercial applications, and to alter it and redistribute it 
+/// freely, subject to the following restrictions:
+/// 
+/// 1. The origin of this software must not be misrepresented; you must not 
+/// claim that you wrote the original software. If you use this software in a 
+/// product, an acknowledgment in the product documentation would be 
+/// appreciated but is not required.
+/// 
+/// 2. Altered source versions must be plainly marked as such, and must not be 
+/// misrepresented as being the original software.
+/// 
+/// 3. This notice may not be removed or altered from any source distribution.
+/// 
+/// Author: Jonathan de Halleuxusing System;
+
+namespace Spart.Actions.Actors
+{
+	using System;
+	public class AssignActor : IActor
+    {
+        private Object m_Value;
+
+        public AssignActor(Object o)
+        {
+            m_Value = o;
+        }
+
+        public Object Value
+        {
+            get
+            {
+                return m_Value;
+            }
+            set
+            {
+                m_Value = value;
+            }
+        } 
+
+        public void DoAction(Object sender, ActionEventArgs args)
+        {
+           m_Value = args.Value;
+        }
+    }
+}

+ 44 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Actions/Actors/IActor.cs

@@ -0,0 +1,44 @@
+/// Spart License (zlib/png)
+/// 
+/// 
+/// Copyright (c) 2003 Jonathan de Halleux
+/// 
+/// This software is provided 'as-is', without any express or implied warranty. 
+/// In no event will the authors be held liable for any damages arising from 
+/// the use of this software.
+/// 
+/// Permission is granted to anyone to use this software for any purpose, 
+/// including commercial applications, and to alter it and redistribute it 
+/// freely, subject to the following restrictions:
+/// 
+/// 1. The origin of this software must not be misrepresented; you must not 
+/// claim that you wrote the original software. If you use this software in a 
+/// product, an acknowledgment in the product documentation would be 
+/// appreciated but is not required.
+/// 
+/// 2. Altered source versions must be plainly marked as such, and must not be 
+/// misrepresented as being the original software.
+/// 
+/// 3. This notice may not be removed or altered from any source distribution.
+/// 
+/// Author: Jonathan de Halleuxusing System;
+
+namespace Spart.Actions.Actors
+{
+	using System;
+
+	/// <summary>
+	/// Actor interface
+	/// </summary>
+	public interface IActor
+    {
+		/// <summary>
+		/// Handler method.
+		/// <seealso cref="ActionHandler"/>
+		/// <seealso cref="ActionEventArgs"/>
+		/// </summary>
+		/// <param name="sender"></param>
+		/// <param name="args"></param>
+        void DoAction(Object sender, ActionEventArgs args);
+    }
+}

+ 46 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Actions/Actors/ThrowActor.cs

@@ -0,0 +1,46 @@
+/// Spart License (zlib/png)
+/// 
+/// 
+/// Copyright (c) 2003 Jonathan de Halleux
+/// 
+/// This software is provided 'as-is', without any express or implied warranty. 
+/// In no event will the authors be held liable for any damages arising from 
+/// the use of this software.
+/// 
+/// Permission is granted to anyone to use this software for any purpose, 
+/// including commercial applications, and to alter it and redistribute it 
+/// freely, subject to the following restrictions:
+/// 
+/// 1. The origin of this software must not be misrepresented; you must not 
+/// claim that you wrote the original software. If you use this software in a 
+/// product, an acknowledgment in the product documentation would be 
+/// appreciated but is not required.
+/// 
+/// 2. Altered source versions must be plainly marked as such, and must not be 
+/// misrepresented as being the original software.
+/// 
+/// 3. This notice may not be removed or altered from any source distribution.
+/// 
+/// Author: Jonathan de Halleuxusing System;
+
+namespace Spart.Actions.Actors
+{
+	using System;
+	public class ThrowActor : IActor
+	{
+		private Exception m_Ex;
+
+		public ThrowActor(Exception ex)
+		{
+			if (ex == null)
+				throw new ArgumentNullException("ex");
+
+			m_Ex = ex;
+		}
+
+		public void DoAction(Object sender, ActionEventArgs args)
+		{
+			throw m_Ex;
+		}
+	}
+}

+ 58 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/AssemblyInfo.cs

@@ -0,0 +1,58 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+//
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+//
+[assembly: AssemblyTitle("Spart Library")]
+[assembly: AssemblyDescription("A parser generation framework for C#")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Jonathan de Halleux")]
+[assembly: AssemblyProduct("Spart")]
+[assembly: AssemblyCopyright("")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]		
+
+//
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Revision and Build Numbers 
+// by using the '*' as shown below:
+
+[assembly: AssemblyVersion("1.0.*")]
+
+//
+// In order to sign your assembly you must specify a key to use. Refer to the 
+// Microsoft .NET Framework documentation for more information on assembly signing.
+//
+// Use the attributes below to control which key is used for signing. 
+//
+// Notes: 
+//   (*) If no key is specified, the assembly is not signed.
+//   (*) KeyName refers to a key that has been installed in the Crypto Service
+//       Provider (CSP) on your machine. KeyFile refers to a file which contains
+//       a key.
+//   (*) If the KeyFile and the KeyName values are both specified, the 
+//       following processing occurs:
+//       (1) If the KeyName can be found in the CSP, that key is used.
+//       (2) If the KeyName does not exist and the KeyFile does exist, the key 
+//           in the KeyFile is installed into the CSP and used.
+//   (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility.
+//       When specifying the KeyFile, the location of the KeyFile should be
+//       relative to the project output directory which is
+//       %Project Directory%\obj\<configuration>. For example, if your KeyFile is
+//       located in the project directory, you would specify the AssemblyKeyFile 
+//       attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")]
+//   (*) Delay Signing is an advanced option - see the Microsoft .NET Framework
+//       documentation for more information on this.
+//
+[assembly: AssemblyDelaySign(false)]
+[assembly: AssemblyKeyFile("")]
+[assembly: AssemblyKeyName("")]

+ 78 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/BinaryTerminalParser.cs

@@ -0,0 +1,78 @@
+/// Spart License (zlib/png)
+/// 
+/// 
+/// Copyright (c) 2003 Jonathan de Halleux
+/// 
+/// This software is provided 'as-is', without any express or implied warranty. 
+/// In no event will the authors be held liable for any damages arising from 
+/// the use of this software.
+/// 
+/// Permission is granted to anyone to use this software for any purpose, 
+/// including commercial applications, and to alter it and redistribute it 
+/// freely, subject to the following restrictions:
+/// 
+/// 1. The origin of this software must not be misrepresented; you must not 
+/// claim that you wrote the original software. If you use this software in a 
+/// product, an acknowledgment in the product documentation would be 
+/// appreciated but is not required.
+/// 
+/// 2. Altered source versions must be plainly marked as such, and must not be 
+/// misrepresented as being the original software.
+/// 
+/// 3. This notice may not be removed or altered from any source distribution.
+/// 
+/// Author: Jonathan de Halleuxusing System;
+
+namespace Spart.Parsers
+{
+	using System;
+	public abstract class BinaryTerminalParser : TerminalParser
+	{
+		private Parser m_FirstParser;
+		private Parser m_SecondParser;
+
+		public BinaryTerminalParser(Parser first, Parser second)
+		{
+			FirstParser = first;
+			SecondParser = second;
+		}
+
+		public Parser FirstParser
+		{
+			get
+			{
+				return m_FirstParser;
+			}
+			set
+			{
+				if (value == null)
+					throw new ArgumentNullException("first parser is null");
+				m_FirstParser = value;
+			}
+		}
+
+		public Parser SecondParser
+		{
+			get
+			{
+				return m_SecondParser;
+			}
+			set
+			{
+				if (value == null)
+					throw new ArgumentNullException("second parser is null");
+				m_SecondParser = value;
+			}
+		}	
+
+    
+        public override Parser Clone()
+        {
+            BinaryTerminalParser clone = base.Clone() as BinaryTerminalParser;
+            clone.m_FirstParser = m_FirstParser.Clone();
+            clone.m_SecondParser = m_SecondParser.Clone();
+
+            return clone;
+        }
+    }
+}

+ 68 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/Composite/AlternativeParser.cs

@@ -0,0 +1,68 @@
+/// Spart License (zlib/png)
+/// 
+/// 
+/// Copyright (c) 2003 Jonathan de Halleux
+/// 
+/// This software is provided 'as-is', without any express or implied warranty. 
+/// In no event will the authors be held liable for any damages arising from 
+/// the use of this software.
+/// 
+/// Permission is granted to anyone to use this software for any purpose, 
+/// including commercial applications, and to alter it and redistribute it 
+/// freely, subject to the following restrictions:
+/// 
+/// 1. The origin of this software must not be misrepresented; you must not 
+/// claim that you wrote the original software. If you use this software in a 
+/// product, an acknowledgment in the product documentation would be 
+/// appreciated but is not required.
+/// 
+/// 2. Altered source versions must be plainly marked as such, and must not be 
+/// misrepresented as being the original software.
+/// 
+/// 3. This notice may not be removed or altered from any source distribution.
+/// 
+/// Author: Jonathan de Halleuxnamespace Spart.Parsers.Composite
+
+namespace Spart.Parsers.Composite
+{
+	using System;
+	using Spart.Scanners;
+	using Spart.Actions;
+	using Spart.Parsers.NonTerminal;
+
+	public class AlternativeParser : BinaryTerminalParser
+	{
+		public AlternativeParser(Parser first, Parser second)
+			:base(first,second)
+		{}
+
+		public override ParserMatch ParseMain(IScanner scan)
+		{
+			// save scanner state
+			long offset = scan.Offset;
+
+			// apply the first parser
+			ParserMatch m = FirstParser.Parse( scan );
+			// if m1 successful, do m2
+			if (m.Success)
+				return m;
+
+			// not found
+			scan.Seek(offset);
+
+			// apply the second parser
+			m = SecondParser.Parse( scan );
+			if (m.Success)
+				return m;
+
+			scan.Seek(offset);
+			return scan.NoMatch;
+		}
+
+        
+        public override Parser Clone()
+        {
+            return base.Clone();
+        }
+	}
+}

+ 69 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/Composite/DifferenceParser.cs

@@ -0,0 +1,69 @@
+/// Spart License (zlib/png)
+/// 
+/// 
+/// Copyright (c) 2003 Jonathan de Halleux
+/// 
+/// This software is provided 'as-is', without any express or implied warranty. 
+/// In no event will the authors be held liable for any damages arising from 
+/// the use of this software.
+/// 
+/// Permission is granted to anyone to use this software for any purpose, 
+/// including commercial applications, and to alter it and redistribute it 
+/// freely, subject to the following restrictions:
+/// 
+/// 1. The origin of this software must not be misrepresented; you must not 
+/// claim that you wrote the original software. If you use this software in a 
+/// product, an acknowledgment in the product documentation would be 
+/// appreciated but is not required.
+/// 
+/// 2. Altered source versions must be plainly marked as such, and must not be 
+/// misrepresented as being the original software.
+/// 
+/// 3. This notice may not be removed or altered from any source distribution.
+/// 
+/// Author: Jonathan de Halleuxusing System;
+
+namespace Spart.Parsers.Composite
+{
+	/// <summary>
+	/// Summary description for DifferenceParser.
+	/// </summary>
+	public class DifferenceParser : BinaryTerminalParser
+	{
+		public DifferenceParser(Parser left, Parser right)
+			:base(left,right)
+		{}
+
+		public override ParserMatch ParseMain(Spart.Scanners.IScanner scan)
+		{
+			long offset = scan.Offset;
+
+			ParserMatch m = FirstParser.Parse(scan);
+			long goodOffset= scan.Offset;
+
+			if (!m.Success)
+			{
+				scan.Seek(offset);
+				return scan.NoMatch;
+			}
+
+			// doing difference
+			scan.Seek(offset);
+			ParserMatch d = SecondParser.Parse(scan);
+			if (d.Success)
+			{
+				scan.Seek(offset);
+				return scan.NoMatch;
+			}
+
+			// ok
+			scan.Seek(goodOffset);
+			return m;
+		}
+
+        public override Parser Clone()
+        {
+            return base.Clone();
+        }
+	}
+}

+ 68 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/Composite/IntersectionParser.cs

@@ -0,0 +1,68 @@
+/// Spart License (zlib/png)
+/// 
+/// 
+/// Copyright (c) 2003 Jonathan de Halleux
+/// 
+/// This software is provided 'as-is', without any express or implied warranty. 
+/// In no event will the authors be held liable for any damages arising from 
+/// the use of this software.
+/// 
+/// Permission is granted to anyone to use this software for any purpose, 
+/// including commercial applications, and to alter it and redistribute it 
+/// freely, subject to the following restrictions:
+/// 
+/// 1. The origin of this software must not be misrepresented; you must not 
+/// claim that you wrote the original software. If you use this software in a 
+/// product, an acknowledgment in the product documentation would be 
+/// appreciated but is not required.
+/// 
+/// 2. Altered source versions must be plainly marked as such, and must not be 
+/// misrepresented as being the original software.
+/// 
+/// 3. This notice may not be removed or altered from any source distribution.
+/// 
+/// Author: Jonathan de Halleuxusing System;
+
+namespace Spart.Parsers.Composite
+{
+	using Spart.Scanners;
+
+	public class IntersectionParser : BinaryTerminalParser
+	{
+		public IntersectionParser(Parser first, Parser second)
+			:base(first,second)
+		{}
+
+		public override ParserMatch ParseMain(IScanner scan)
+		{
+			long offset = scan.Offset;
+			ParserMatch m = FirstParser.Parse(scan);
+			if (m.Success)
+			{
+				scan.Seek(offset);
+				ParserMatch m2 = SecondParser.Parse(scan);
+				if (m2.Success)
+				{
+					if (m.Length >= m2.Length)
+                    {
+                        scan.Seek(m.Offset + m.Length);
+                        return m;
+                    }
+                    else
+                    {
+                        return m2;
+                    }
+				}
+			}
+
+			scan.Seek(offset);
+			return scan.NoMatch;
+		}
+
+    
+        public override Parser Clone()
+        {
+            return base.Clone();
+        }
+    }
+}

+ 79 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/Composite/ListParser.cs

@@ -0,0 +1,79 @@
+/// Spart License (zlib/png)
+/// 
+/// 
+/// Copyright (c) 2003 Jonathan de Halleux
+/// 
+/// This software is provided 'as-is', without any express or implied warranty. 
+/// In no event will the authors be held liable for any damages arising from 
+/// the use of this software.
+/// 
+/// Permission is granted to anyone to use this software for any purpose, 
+/// including commercial applications, and to alter it and redistribute it 
+/// freely, subject to the following restrictions:
+/// 
+/// 1. The origin of this software must not be misrepresented; you must not 
+/// claim that you wrote the original software. If you use this software in a 
+/// product, an acknowledgment in the product documentation would be 
+/// appreciated but is not required.
+/// 
+/// 2. Altered source versions must be plainly marked as such, and must not be 
+/// misrepresented as being the original software.
+/// 
+/// 3. This notice may not be removed or altered from any source distribution.
+/// 
+/// Author: Jonathan de Halleuxusing System;
+
+namespace Spart.Parsers.Composite
+{
+	using Spart.Scanners;
+
+	public class ListParser : BinaryTerminalParser
+	{
+		public ListParser(Parser first, Parser second)
+			:base(first, second)
+		{}
+
+		public override ParserMatch ParseMain(Spart.Scanners.IScanner scan)
+		{
+			long offset = scan.Offset;
+			ParserMatch a = null;
+			ParserMatch b = null;
+
+			ParserMatch m = FirstParser.Parse(scan);
+			if (!m.Success)
+			{
+				scan.Seek(offset);
+				return scan.NoMatch;
+			}
+
+			while (!scan.AtEnd)
+			{
+				offset = scan.Offset;
+
+				b = SecondParser.Parse(scan);
+				if (!b.Success)
+				{
+					scan.Seek(offset);
+					return m;
+				}
+				a = FirstParser.Parse(scan);
+				if (!a.Success)
+				{
+					scan.Seek(offset);
+					return m;
+				}
+
+				m.Concat(b);
+				m.Concat(a);
+			}
+
+			return m;
+		}
+
+    
+        public override Parser Clone()
+        {
+            return base.Clone();
+        }
+    }
+}

+ 126 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/Composite/RepetitionParser.cs

@@ -0,0 +1,126 @@
+/// Spart License (zlib/png)
+/// 
+/// 
+/// Copyright (c) 2003 Jonathan de Halleux
+/// 
+/// This software is provided 'as-is', without any express or implied warranty. 
+/// In no event will the authors be held liable for any damages arising from 
+/// the use of this software.
+/// 
+/// Permission is granted to anyone to use this software for any purpose, 
+/// including commercial applications, and to alter it and redistribute it 
+/// freely, subject to the following restrictions:
+/// 
+/// 1. The origin of this software must not be misrepresented; you must not 
+/// claim that you wrote the original software. If you use this software in a 
+/// product, an acknowledgment in the product documentation would be 
+/// appreciated but is not required.
+/// 
+/// 2. Altered source versions must be plainly marked as such, and must not be 
+/// misrepresented as being the original software.
+/// 
+/// 3. This notice may not be removed or altered from any source distribution.
+/// 
+/// Author: Jonathan de Halleuxnamespace Spart.Parsers.Composite
+
+namespace Spart.Parsers.Composite
+{
+	using System;
+	using Spart.Scanners;
+	using Spart.Actions;
+	using Spart.Parsers.NonTerminal;
+
+	public class RepetitionParser : UnaryTerminalParser
+	{
+		private uint m_LowerBound;
+		private uint m_UpperBound;
+
+		public RepetitionParser(Parser parser, uint lowerBound, uint upperBound)
+			:base(parser)
+		{                
+			SetBounds(lowerBound,upperBound);
+		}
+
+		public uint LowerBound
+		{
+			get
+			{
+				return m_LowerBound;
+			}
+		}
+
+		public uint UpperBound
+		{
+			get
+			{
+				return m_UpperBound;
+			}
+		}
+
+		public void SetBounds(uint lb, uint ub)
+		{
+			if (ub < lb)
+				throw new ArgumentException("lower bound must be smaller than upper bound");
+			m_LowerBound = lb;
+			m_UpperBound = ub;
+		}
+
+		public override ParserMatch ParseMain(IScanner scanner)
+		{
+			// save scanner state
+			long offset = scanner.Offset;
+
+			ParserMatch m=scanner.EmptyMatch;
+			ParserMatch m_temp=null;
+
+			// execution bound                                
+			int count=0;
+
+			// lower bound, minimum number of executions
+			while(count < LowerBound && !scanner.AtEnd)
+			{
+				m_temp = Parser.Parse(scanner);
+				// stop if not successful
+				if (!m_temp.Success)
+					break;
+				// increment count and update full match
+				++count;
+
+                m.Concat(m_temp);
+			}
+                
+			if (count == LowerBound)
+			{
+				while(count < UpperBound && !scanner.AtEnd)
+				{
+					m_temp = Parser.Parse(scanner);
+
+					// stop if not successful
+					if (!m_temp.Success)
+						break;
+
+					// increment count
+					++count;
+
+                    m.Concat(m_temp);
+                }
+            }
+			else
+            {
+				m=scanner.NoMatch;
+			    // restoring parser failed, rewind scanner
+				scanner.Seek(offset);
+            }
+
+			return m;
+		}
+
+    
+        public override Parser Clone()
+        {
+            RepetitionParser clone = base.Clone() as RepetitionParser;
+
+            return clone;
+        }
+    }
+}

+ 71 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/Composite/SequenceParser.cs

@@ -0,0 +1,71 @@
+/// Spart License (zlib/png)
+/// 
+/// 
+/// Copyright (c) 2003 Jonathan de Halleux
+/// 
+/// This software is provided 'as-is', without any express or implied warranty. 
+/// In no event will the authors be held liable for any damages arising from 
+/// the use of this software.
+/// 
+/// Permission is granted to anyone to use this software for any purpose, 
+/// including commercial applications, and to alter it and redistribute it 
+/// freely, subject to the following restrictions:
+/// 
+/// 1. The origin of this software must not be misrepresented; you must not 
+/// claim that you wrote the original software. If you use this software in a 
+/// product, an acknowledgment in the product documentation would be 
+/// appreciated but is not required.
+/// 
+/// 2. Altered source versions must be plainly marked as such, and must not be 
+/// misrepresented as being the original software.
+/// 
+/// 3. This notice may not be removed or altered from any source distribution.
+/// 
+/// Author: Jonathan de Halleuxnamespace Spart.Parsers.Composite
+
+namespace Spart.Parsers.Composite
+{
+	using System;
+	using Spart.Scanners;
+	using Spart.Actions;
+	using Spart.Parsers.NonTerminal;
+
+	public class SequenceParser : BinaryTerminalParser
+	{
+		public SequenceParser(Parser first, Parser second)
+			:base(first,second)
+		{}
+
+		public override ParserMatch ParseMain(IScanner scanner)
+		{
+			// save scanner state
+			long offset = scanner.Offset;
+
+			// apply the first parser
+			ParserMatch m = FirstParser.Parse( scanner );
+			// if m1 successful, do m2
+			if (m.Success)
+			{
+				ParserMatch m2 = SecondParser.Parse( scanner );
+				if (m2.Success)
+					m.Concat(m2);
+				else
+					m = scanner.NoMatch;
+			}
+
+			// restoring parser failed, rewind scanner
+			if (!m.Success)
+				scanner.Seek(offset);
+
+			return m;
+		}
+
+    
+        public override Parser Clone()
+        {
+            SequenceParser clone = base.Clone() as SequenceParser;
+
+            return clone;
+        }
+    }
+}

+ 33 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/Directives/LexemeDirective.cs

@@ -0,0 +1,33 @@
+namespace Spart.Parsers.Directives
+{
+	using System;
+	using Spart.Scanners;
+	using Spart.Actions;
+	using Spart.Parsers.NonTerminal;
+
+	public class LexemeDirective : UnaryTerminalParser
+	{
+		public LexemeDirective(Parser parser) : base(parser)
+		{
+        }
+
+		public override ParserMatch ParseMain(IScanner scanner)
+		{
+            // Remove leading whitespace
+            Prims.Epsilon.Parse(scanner);
+
+            bool isSkipping = scanner.IsSkipping;
+            scanner.IsSkipping = false;
+            ParserMatch m = this.Parser.Parse(scanner);
+            scanner.IsSkipping = isSkipping;
+
+            return m;
+		}
+
+    
+        public override Parser Clone()
+        {
+            return base.Clone();
+        }
+    }
+}

+ 16 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/Dirs.cs

@@ -0,0 +1,16 @@
+namespace Spart.Parsers
+{
+	using System;
+	using Spart.Parsers.Directives;
+
+	/// <summary>
+	/// Static helper class to create directives
+	/// </summary>
+	public class Dirs
+	{
+		public static LexemeDirective Lexeme(Parser parser)
+		{
+		    return new LexemeDirective(parser);
+		}
+    }
+}

+ 57 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/NegatableParser.cs

@@ -0,0 +1,57 @@
+/// Spart License (zlib/png)
+/// 
+/// 
+/// Copyright (c) 2003 Jonathan de Halleux
+/// 
+/// This software is provided 'as-is', without any express or implied warranty. 
+/// In no event will the authors be held liable for any damages arising from 
+/// the use of this software.
+/// 
+/// Permission is granted to anyone to use this software for any purpose, 
+/// including commercial applications, and to alter it and redistribute it 
+/// freely, subject to the following restrictions:
+/// 
+/// 1. The origin of this software must not be misrepresented; you must not 
+/// claim that you wrote the original software. If you use this software in a 
+/// product, an acknowledgment in the product documentation would be 
+/// appreciated but is not required.
+/// 
+/// 2. Altered source versions must be plainly marked as such, and must not be 
+/// misrepresented as being the original software.
+/// 
+/// 3. This notice may not be removed or altered from any source distribution.
+/// 
+/// Author: Jonathan de Halleuxusing System;
+
+namespace Spart.Parsers
+{
+	public abstract class NegatableParser : TerminalParser
+	{
+		private bool m_Negate=false;
+
+		public bool Negate 
+		{
+			get
+			{
+				return m_Negate;
+			}
+			set
+			{
+				if( m_Negate != value )
+					m_Negate = value;
+			}
+		}
+
+		public static NegatableParser operator ~ (NegatableParser p)
+		{
+			p.Negate=!p.Negate;
+			return p;
+		}
+
+    
+        public override Parser Clone()
+        {
+            return base.Clone();
+        }
+    }
+}

+ 34 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/NonTerminal/IParserContext.cs

@@ -0,0 +1,34 @@
+/// Spart License (zlib/png)
+/// 
+/// 
+/// Copyright (c) 2003 Jonathan de Halleux
+/// 
+/// This software is provided 'as-is', without any express or implied warranty. 
+/// In no event will the authors be held liable for any damages arising from 
+/// the use of this software.
+/// 
+/// Permission is granted to anyone to use this software for any purpose, 
+/// including commercial applications, and to alter it and redistribute it 
+/// freely, subject to the following restrictions:
+/// 
+/// 1. The origin of this software must not be misrepresented; you must not 
+/// claim that you wrote the original software. If you use this software in a 
+/// product, an acknowledgment in the product documentation would be 
+/// appreciated but is not required.
+/// 
+/// 2. Altered source versions must be plainly marked as such, and must not be 
+/// misrepresented as being the original software.
+/// 
+/// 3. This notice may not be removed or altered from any source distribution.
+/// 
+/// Author: Jonathan de Halleuxusing System;
+
+namespace Spart.Parsers.NonTerminal
+{
+	using System;
+	public interface IParserContext
+	{
+		void PreParse(Object sender, PreParseEventArgs args);
+		void PostParse(Object sender, PostParseEventArgs args);
+	}
+}

+ 123 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/NonTerminal/NonTerminalParser.cs

@@ -0,0 +1,123 @@
+/// Spart License (zlib/png)
+/// 
+/// 
+/// Copyright (c) 2003 Jonathan de Halleux
+/// 
+/// This software is provided 'as-is', without any express or implied warranty. 
+/// In no event will the authors be held liable for any damages arising from 
+/// the use of this software.
+/// 
+/// Permission is granted to anyone to use this software for any purpose, 
+/// including commercial applications, and to alter it and redistribute it 
+/// freely, subject to the following restrictions:
+/// 
+/// 1. The origin of this software must not be misrepresented; you must not 
+/// claim that you wrote the original software. If you use this software in a 
+/// product, an acknowledgment in the product documentation would be 
+/// appreciated but is not required.
+/// 
+/// 2. Altered source versions must be plainly marked as such, and must not be 
+/// misrepresented as being the original software.
+/// 
+/// 3. This notice may not be removed or altered from any source distribution.
+/// 
+/// Author: Jonathan de Halleuxnamespace Spart.Parsers.NonTerminal
+
+
+namespace Spart.Parsers.NonTerminal
+{
+	using System;
+	using Spart.Scanners;
+	
+	/// <summary>
+	/// NonTerminal parser abstract class
+	/// </summary>
+	public abstract class NonTerminalParser : Parser
+	{
+		private String m_ID;
+
+		/// <summary>
+		/// Default constructor
+		/// </summary>
+		public NonTerminalParser()
+			:base()
+		{
+			ID = GetHashCode().ToString();
+		}
+
+		/// <summary>
+		/// Rule ID, used for debugging
+		/// </summary>
+		public string ID
+		{
+			get
+			{
+				return m_ID;
+			}
+			set
+			{
+				if( m_ID != value )
+					m_ID = value;
+			}
+		}
+
+		/// <summary>
+		/// Pre parse event
+		/// </summary>
+		public event PreParseEventHandler PreParse;
+
+		/// <summary>
+		/// Post parse event 
+		/// </summary>
+		public event PostParseEventHandler PostParse;
+
+		/// <summary>
+		/// Preparse event caller
+		/// </summary>
+		/// <param name="scan"></param>
+		public virtual void OnPreParse(IScanner scan)
+		{
+			if (PreParse != null)
+				PreParse(this, new PreParseEventArgs(this,scan) );
+		}
+
+		/// <summary>
+		/// Post parse event caller
+		/// </summary>
+		/// <param name="match"></param>
+		/// <param name="scan"></param>
+		public virtual void OnPostParse(ParserMatch match, IScanner scan)
+		{
+			if (PostParse != null)
+				PostParse(this,new PostParseEventArgs(match,this,scan));
+		}
+
+		/// <summary>
+		/// Adds event handlers
+		/// </summary>
+		/// <param name="context"></param>
+		public void AddContext(IParserContext context)
+		{
+			PreParse+=new PreParseEventHandler(context.PreParse);
+			PostParse+=new PostParseEventHandler(context.PostParse);
+		}
+
+		/// <summary>
+		/// Removes event handlers
+		/// </summary>
+		/// <param name="context"></param>
+		public void RemoveContext(IParserContext context)
+		{
+			PreParse-=new PreParseEventHandler(context.PreParse);
+			PostParse-=new PostParseEventHandler(context.PostParse);
+		}
+
+    
+        public override Parser Clone()
+        {
+            NonTerminalParser clone = base.Clone() as NonTerminalParser;
+
+            return clone;
+        }
+    }
+}

+ 74 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/NonTerminal/PostParseEventArgs.cs

@@ -0,0 +1,74 @@
+/// Spart License (zlib/png)
+/// 
+/// 
+/// Copyright (c) 2003 Jonathan de Halleux
+/// 
+/// This software is provided 'as-is', without any express or implied warranty. 
+/// In no event will the authors be held liable for any damages arising from 
+/// the use of this software.
+/// 
+/// Permission is granted to anyone to use this software for any purpose, 
+/// including commercial applications, and to alter it and redistribute it 
+/// freely, subject to the following restrictions:
+/// 
+/// 1. The origin of this software must not be misrepresented; you must not 
+/// claim that you wrote the original software. If you use this software in a 
+/// product, an acknowledgment in the product documentation would be 
+/// appreciated but is not required.
+/// 
+/// 2. Altered source versions must be plainly marked as such, and must not be 
+/// misrepresented as being the original software.
+/// 
+/// 3. This notice may not be removed or altered from any source distribution.
+/// 
+/// Author: Jonathan de Halleuxusing System;
+
+namespace Spart.Parsers.NonTerminal
+{
+	using System;
+	using Spart.Scanners;
+	public class PostParseEventArgs : EventArgs
+	{
+		private ParserMatch m_Match;
+		private NonTerminalParser m_Parser;
+		private IScanner m_Scanner;
+
+		public PostParseEventArgs(ParserMatch match, NonTerminalParser parser, IScanner scanner)
+		{
+			if (match == null)
+				throw new ArgumentNullException("match");
+			if (parser == null)
+				throw new ArgumentNullException("parser");
+			if (scanner == null)
+				throw new ArgumentNullException("scanner");
+
+			m_Match = match;
+			m_Parser = parser;
+			m_Scanner = scanner;
+		}
+
+		public ParserMatch Match
+		{
+			get
+			{
+				return m_Match;
+			}
+		}
+
+		public NonTerminalParser Parser
+		{
+			get
+			{
+				return m_Parser;
+			}
+		}
+
+		public IScanner Scanner
+		{
+			get
+			{
+				return m_Scanner;
+			}
+		}
+	}
+}

+ 30 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/NonTerminal/PostParseEventHandler.cs

@@ -0,0 +1,30 @@
+/// Spart License (zlib/png)
+/// 
+/// 
+/// Copyright (c) 2003 Jonathan de Halleux
+/// 
+/// This software is provided 'as-is', without any express or implied warranty. 
+/// In no event will the authors be held liable for any damages arising from 
+/// the use of this software.
+/// 
+/// Permission is granted to anyone to use this software for any purpose, 
+/// including commercial applications, and to alter it and redistribute it 
+/// freely, subject to the following restrictions:
+/// 
+/// 1. The origin of this software must not be misrepresented; you must not 
+/// claim that you wrote the original software. If you use this software in a 
+/// product, an acknowledgment in the product documentation would be 
+/// appreciated but is not required.
+/// 
+/// 2. Altered source versions must be plainly marked as such, and must not be 
+/// misrepresented as being the original software.
+/// 
+/// 3. This notice may not be removed or altered from any source distribution.
+/// 
+/// Author: Jonathan de Halleuxusing System;
+
+namespace Spart.Parsers.NonTerminal
+{
+	using System;
+	public delegate void PostParseEventHandler(Object sender, PostParseEventArgs args);
+}

+ 65 - 0
EQ2/devtools/EQ2ModelViewer/EQ2ModelViewer/Spart/Parsers/NonTerminal/PreParseEventArgs.cs

@@ -0,0 +1,65 @@
+/// Spart License (zlib/png)
+/// 
+/// 
+/// Copyright (c) 2003 Jonathan de Halleux
+/// 
+/// This software is provided 'as-is', without any express or implied warranty. 
+/// In no event will the authors be held liable for any damages arising from 
+/// the use of this software.
+/// 
+/// Permission is granted to anyone to use this software for any purpose, 
+/// including commercial applications, and to alter it and redistribute it 
+/// freely, subject to the following restrictions:
+/// 
+/// 1. The origin of this software must not be misrepresented; you must not 
+/// claim that you wrote the original software. If you use this software in a 
+/// product, an acknowledgment in the product documentation would be 
+/// appreciated but is not required.
+/// 
+/// 2. Altered source versions must be plainly marked as such, and must not be 
+/// misrepresented as being the original software.
+/// 
+/// 3. This notice may not be removed or altered from any source distribution.
+/// 
+/// Author: Jonathan de Halleuxusing System;
+
+namespace Spart.Parsers.NonTerminal
+{
+	using System;
+	using Spart.Scanners;
+	/// <summary>
+	/// Summary description for PostParseEventArgs.
+	/// </summary>
+	public class PreParseEventArgs : EventArgs
+	{
+		private NonTerminalParser m_Parser;
+		private IScanner m_Scanner;
+
+		public PreParseEventArgs(NonTerminalParser parser, IScanner scanner)
+		{
+			if (parser == null)
+				throw new ArgumentNullException("parser");
+			if (scanner == null)
+				throw new ArgumentNullException("scanner");
+
+			m_Parser = parser;
+			m_Scanner = scanner;
+		}
+
+		public NonTerminalParser Parser
+		{
+			get
+			{
+				return m_Parser;
+			}
+		}
+
+		public IScanner Scanner
+		{
+			get
+			{
+				return m_Scanner;
+			}
+		}
+	}
+}

Some files were not shown because too many files changed in this diff