From 2143931179ffe5b5ed04e673160378c7b480b78c Mon Sep 17 00:00:00 2001
From: Tazpn <tazpn@users.sourceforge.net>
Date: Sat, 24 Jun 2006 18:19:15 +0000
Subject: [PATCH] Initial Revision. 0.1

---
 MaxNifImport/AppSettings.cpp         |  102 +++
 MaxNifImport/AppSettings.h           |   82 ++
 MaxNifImport/DllEntry.cpp            |   78 ++
 MaxNifImport/MaxNifImport.cpp        | 1229 ++++++++++++++++++++++++++
 MaxNifImport/MaxNifImport.def        |    8 +
 MaxNifImport/MaxNifImport.h          |   32 +
 MaxNifImport/MaxNifImport.rc         |  152 ++++
 MaxNifImport/MaxNifImport.vcproj     |  322 +++++++
 MaxNifImport/MaxNifImport_Readme.txt |   75 ++
 MaxNifImport/MaxNifTools.ini         |   81 ++
 MaxNifImport/niutils.cpp             |  511 +++++++++++
 MaxNifImport/niutils.h               |  221 +++++
 MaxNifImport/objectParams.h          |  336 +++++++
 MaxNifImport/resource.h              |   28 +
 MaxNifImport/stdafx.cpp              |    1 +
 MaxNifImport/stdafx.h                |   28 +
 16 files changed, 3286 insertions(+)
 create mode 100644 MaxNifImport/AppSettings.cpp
 create mode 100644 MaxNifImport/AppSettings.h
 create mode 100644 MaxNifImport/DllEntry.cpp
 create mode 100644 MaxNifImport/MaxNifImport.cpp
 create mode 100644 MaxNifImport/MaxNifImport.def
 create mode 100644 MaxNifImport/MaxNifImport.h
 create mode 100644 MaxNifImport/MaxNifImport.rc
 create mode 100644 MaxNifImport/MaxNifImport.vcproj
 create mode 100644 MaxNifImport/MaxNifImport_Readme.txt
 create mode 100644 MaxNifImport/MaxNifTools.ini
 create mode 100644 MaxNifImport/niutils.cpp
 create mode 100644 MaxNifImport/niutils.h
 create mode 100644 MaxNifImport/objectParams.h
 create mode 100644 MaxNifImport/resource.h
 create mode 100644 MaxNifImport/stdafx.cpp
 create mode 100644 MaxNifImport/stdafx.h

diff --git a/MaxNifImport/AppSettings.cpp b/MaxNifImport/AppSettings.cpp
new file mode 100644
index 0000000..e21bb74
--- /dev/null
+++ b/MaxNifImport/AppSettings.cpp
@@ -0,0 +1,102 @@
+#include "stdafx.h"
+#include <io.h>
+#include <string.h>
+#include <tchar.h>
+#include "AppSettings.h"
+
+AppSettingsMap TheAppSettings;
+
+void AppSettings::Initialize(Interface *gi) 
+{
+   TCHAR iniName[MAX_PATH];
+   LPCTSTR pluginDir = gi->GetDir(APP_PLUGCFG_DIR);
+   PathCombine(iniName, pluginDir, "MaxNifTools.ini");
+   if (-1 != _taccess(iniName, 0)) {
+      bool reparse = GetIniValue<bool>("System", "Reparse", false, iniName);
+      if (reparse || TheAppSettings.empty()){
+         TheAppSettings.clear();
+      }
+
+      string Applications = GetIniValue<string>("System", "KnownApplications", "", iniName);
+      stringlist apps = TokenizeString(Applications.c_str(), ";");
+      for (stringlist::iterator appstr=apps.begin(); appstr != apps.end(); ++appstr){
+         AppSettingsMap::iterator itr = TheAppSettings.find(*appstr);
+         if (itr == TheAppSettings.end()){
+            itr = TheAppSettings.insert(TheAppSettings.end(), AppSettingsMap::value_type(*appstr, AppSettings(*appstr)) );
+            (*itr).second.ReadSettings(iniName);
+         }
+      }
+   }
+}
+
+void AppSettings::ReadSettings(string iniFile)
+{
+   NameValueCollection settings = ReadIniSection(Name.c_str(), iniFile.c_str());
+
+   // expand indirect values first
+   for (NameValueCollection::iterator itr = settings.begin(), end = settings.end(); itr != end; ++itr)
+      itr->second = GetIndirectValue(itr->second.c_str());
+
+   // next expand qualifiers
+   for (NameValueCollection::iterator itr = settings.begin(), end = settings.end(); itr != end; ++itr)
+      itr->second = ExpandQualifiers(itr->second.c_str(), settings);
+
+   // finally expand environment variables, last because it clobbers my custom qualifier expansion
+   for (NameValueCollection::iterator itr = settings.begin(), end = settings.end(); itr != end; ++itr)
+      itr->second = ExpandEnvironment(itr->second);
+
+   std::swap(Environment, settings);
+
+   rootPath = GetSetting<string>("RootPath");
+
+   string searchPathString = GetSetting<string>("TextureSearchPaths");
+   searchPaths = TokenizeString(searchPathString.c_str(), ";");
+
+   string extensionString = GetSetting<string>("TextureExtensions");
+   extensions = TokenizeString(extensionString.c_str(), ";");
+
+   string texRootPathString = GetSetting<string>("TextureRootPaths");
+   rootPaths = TokenizeString(texRootPathString.c_str(), ";");
+
+   Skeleton = GetSetting<string>("Skeleton");
+}
+
+string AppSettings::FindImage(const string& fname){
+   TCHAR buffer[MAX_PATH];
+
+   // Simply check for fully qualified path
+   if (PathIsRoot(fname.c_str())) {
+      if (-1 != _taccess(fname.c_str(), 0))
+         return fname;
+   }
+
+   // Test if its relative and in one of the specified root paths
+   for (stringlist::iterator itr = rootPaths.begin(), end = rootPaths.end(); itr != end; ++itr ){
+      PathCombine(buffer, itr->c_str(), fname.c_str());
+      if (-1 != _taccess(buffer, 0)){
+         return string(buffer);
+      }
+   }
+
+   // Hit the directories to find out whats out there
+   if (!parsedImages){
+      FindImages(imgTable, rootPath, searchPaths, extensions);
+      parsedImages = true;
+   }
+   
+   // Search my filename for our texture
+   _tcscpy(buffer, PathFindFileName(fname.c_str()));
+   PathRemoveExtension(buffer);
+   NameValueCollection::iterator itr = imgTable.find(buffer);
+   if (itr != imgTable.end()){
+      if (!rootPath.empty()) {
+         _tcscpy(buffer, rootPath.c_str());
+         PathCombine(buffer, rootPath.c_str(), ((*itr).second).c_str());
+         return string(buffer);
+      } else {
+         return (*itr).second;
+      }
+   }
+   return fname;
+}
+
diff --git a/MaxNifImport/AppSettings.h b/MaxNifImport/AppSettings.h
new file mode 100644
index 0000000..ebfb17d
--- /dev/null
+++ b/MaxNifImport/AppSettings.h
@@ -0,0 +1,82 @@
+/**********************************************************************
+*<
+FILE: AppSettings.h
+
+DESCRIPTION:	AppSetting helper class for managing game specific
+               file settings.
+
+CREATED BY: tazpn (Theo)
+
+HISTORY: 
+
+*>	Copyright (c) 2006, All Rights Reserved.
+**********************************************************************/
+#ifndef _APPSETTINGS_H_
+#define _APPSETTINGS_H_
+
+#include "niutils.h"
+
+class AppSettings
+{
+public:
+   AppSettings(const std::string& name) : Name(name), parsedImages(false) {}
+
+   std::string Name;
+   std::string rootPath;
+   bool parsedImages;
+   stringlist searchPaths;
+   stringlist rootPaths;
+   stringlist extensions;
+   std::string Skeleton;
+   NameValueCollection Environment;
+   NameValueCollection imgTable;
+
+   static void Initialize(Interface *gi);
+   void ReadSettings(std::string iniFile);
+   std::string FindImage(const std::string& fname);
+
+   template<typename T>
+   inline T GetSetting(std::string setting){
+      T v;
+      NameValueCollection::iterator itr = Environment.find(setting);
+      if (itr != Environment.end()){
+         stringstream sstr((*itr).second);
+         sstr >> v;
+      }
+      return v;
+   }
+   template<>
+   inline std::string GetSetting(std::string setting){
+      NameValueCollection::iterator itr = Environment.find(setting);
+      if (itr != Environment.end())
+         return (*itr).second;
+      return std::string();
+   }
+
+   template<typename T>
+   inline T GetSetting(std::string setting, T Default){
+      NameValueCollection::iterator itr = Environment.find(setting);
+      if (itr != Environment.end()){
+         T v;
+         stringstream sstr((*itr).second);
+         sstr >> v;
+         return v;
+      }
+      return Default;
+   }
+   template<>
+   inline std::string GetSetting(std::string setting, std::string Default){
+      NameValueCollection::iterator itr = Environment.find(setting);
+      if (itr != Environment.end())
+         return (*itr).second;
+      return Default;
+   }
+};
+
+typedef std::map<std::string, AppSettings, ltstr> AppSettingsMap;
+
+// The Global Map
+//  Global so that I dont have to parse the texture directories on every import
+extern AppSettingsMap TheAppSettings;
+
+#endif //_APPSETTINGS_H_
\ No newline at end of file
diff --git a/MaxNifImport/DllEntry.cpp b/MaxNifImport/DllEntry.cpp
new file mode 100644
index 0000000..97160e9
--- /dev/null
+++ b/MaxNifImport/DllEntry.cpp
@@ -0,0 +1,78 @@
+/**********************************************************************
+ *<
+	FILE: DllEntry.cpp
+
+	DESCRIPTION: Contains the Dll Entry stuff
+
+	CREATED BY: tazpn (Theo)
+
+	HISTORY: 
+
+ *>	Copyright (c) 2006, All Rights Reserved.
+ **********************************************************************/
+#include "MaxNifImport.h"
+
+extern ClassDesc2* GetMaxNifImportDesc();
+
+HINSTANCE hInstance;
+int controlsInit = FALSE;
+
+// This function is called by Windows when the DLL is loaded.  This 
+// function may also be called many times during time critical operations
+// like rendering.  Therefore developers need to be careful what they
+// do inside this function.  In the code below, note how after the DLL is
+// loaded the first time only a few statements are executed.
+
+BOOL WINAPI DllMain(HINSTANCE hinstDLL,ULONG fdwReason,LPVOID lpvReserved)
+{
+	hInstance = hinstDLL;				// Hang on to this DLL's instance handle.
+
+	if (!controlsInit) {
+		controlsInit = TRUE;
+		InitCustomControls(hInstance);	// Initialize MAX's custom controls
+		InitCommonControls();			// Initialize Win95 controls
+	}
+			
+	return (TRUE);
+}
+
+// This function returns a string that describes the DLL and where the user
+// could purchase the DLL if they don't have it.
+__declspec( dllexport ) const TCHAR* LibDescription()
+{
+	return GetString(IDS_LIBDESCRIPTION);
+}
+
+// This function returns the number of plug-in classes this DLL
+//TODO: Must change this number when adding a new class
+__declspec( dllexport ) int LibNumberClasses()
+{
+	return 1;
+}
+
+// This function returns the number of plug-in classes this DLL
+__declspec( dllexport ) ClassDesc* LibClassDesc(int i)
+{
+	switch(i) {
+		case 0: return GetMaxNifImportDesc();
+		default: return 0;
+	}
+}
+
+// This function returns a pre-defined constant indicating the version of 
+// the system under which it was compiled.  It is used to allow the system
+// to catch obsolete DLLs.
+__declspec( dllexport ) ULONG LibVersion()
+{
+	return VERSION_3DSMAX;
+}
+
+TCHAR *GetString(int id)
+{
+	static TCHAR buf[256];
+
+	if (hInstance)
+		return LoadString(hInstance, id, buf, sizeof(buf)) ? buf : NULL;
+	return NULL;
+}
+
diff --git a/MaxNifImport/MaxNifImport.cpp b/MaxNifImport/MaxNifImport.cpp
new file mode 100644
index 0000000..9c7022f
--- /dev/null
+++ b/MaxNifImport/MaxNifImport.cpp
@@ -0,0 +1,1229 @@
+/**********************************************************************
+ *<
+	FILE: MaxNifImport.cpp
+
+	DESCRIPTION:	Appwizard generated plugin
+
+	CREATED BY: tazpn (Theo)
+
+	HISTORY: 
+
+ *>	Copyright (c) 2006, All Rights Reserved.
+ **********************************************************************/
+#include "stdafx.h"
+#include <math.h>
+#include <io.h>
+#include <sstream>
+#include "MaxNifImport.h"
+#include <cs/Biped8Api.h>
+#include <scenetraversal.h> 
+#include <plugapi.h>
+#include <triobj.h> 
+#include <bitmap.h>
+#include <modstack.h>
+#include <iskin.h>
+#include <strclass.h>
+#include "objectParams.h"
+#undef ALPHA_NONE
+
+#include <math.h>
+#include <string.h>
+
+#include "niflib.h"
+#include "obj\NiObject.h"
+#include "obj\NiNode.h"
+#include "obj\NiTriShape.h"
+#include "obj\NiTriShapeData.h"
+#include "obj\NiTriStrips.h"
+#include "obj\NiTriStripsData.h"
+#include "obj\NiMaterialProperty.h"
+#include "obj\NiTexturingProperty.h"
+#include "obj\NiSourceTexture.h"
+#include "obj\NiExtraData.h"
+#include "obj\BSBound.h"
+#include "obj\NiSkinData.h"
+#include "obj\NiSkinInstance.h"
+#include "obj\NiSkinPartition.h"
+
+#include "niutils.h"
+#include "AppSettings.h"
+
+#ifndef ASSERT
+#ifdef _DEBUG
+#include <crtdbg.h>
+#define ASSERT _ASSERTE
+#else
+#define ASSERT(exp)
+#endif
+#endif
+
+using namespace Niflib;
+
+#define MaxNifImport_CLASS_ID	Class_ID(0x794ac1c1, 0x8b4c64c7)
+
+// Define the standard section names used in the ini file
+LPCTSTR NifImportSection = TEXT("MaxNifImport");
+LPCTSTR SystemSection = TEXT("System");
+LPCTSTR BipedImportSection = TEXT("BipedImport");
+
+struct NiNodeNameEquivalence : public NumericStringEquivalence
+{
+   bool operator()(const NiNodeRef& n1, const NiNodeRef& n2) const { 
+      return NumericStringEquivalence::operator()(n1->GetName(), n2->GetName());
+   }
+};
+
+// NIF Importer
+class NifImporter
+{
+public:
+   string name;
+   string path;
+   ImpInterface *i;
+   Interface *gi;
+   BOOL suppressPrompts;
+   bool iniFileValid;
+   string iniFileName;
+   AppSettings *appSettings;
+
+   // Ini settings
+   bool showTextures; // show textures in viewport
+   bool removeIllegalFaces;
+   bool removeDegenerateFaces;
+   bool enableAutoSmooth;
+   float autoSmoothAngle;
+   bool flipUVTextures;
+
+   // Biped/Bones related settings
+   string skeleton;
+   float bipedHeight;
+   string skeletonCheck;
+   float bipedAngle;
+   float bipedAnkleAttach;
+   bool bipedTrianglePelvis;
+   bool importSkeleton;
+   bool useBiped;
+   bool hasSkeleton;
+   bool isBiped;
+   bool removeUnusedImportedBones;
+
+   vector<NiObjectRef> blocks;
+   vector<NiNodeRef> nodes;
+
+   NifImporter(const TCHAR *Name,ImpInterface *I,Interface *GI, BOOL SuppressPrompts)
+      : name(Name), i(I), gi(GI), suppressPrompts(SuppressPrompts)
+   {
+      char buffer[MAX_PATH] = {0}, *p = NULL;
+      GetFullPathName(Name, _countof(buffer), buffer, &p);
+      if (p) *p = 0;
+      path = buffer;
+
+      blocks = ReadNifList( name );
+      nodes = DynamicCast<NiNode>(blocks);
+      iniFileValid = false;
+
+      if (isValid()) {
+         LoadIniSettings();
+      }
+   }
+   bool isValid() const { return (0 != blocks.size()); }
+
+   // Ini File related routines
+   void LoadIniSettings();
+   void SaveIniSettings();
+
+   // Generic IniFile reading routine
+   template<typename T>
+   T GetIniValue(LPCTSTR Section, LPCTSTR Setting, T Default){
+      if (!iniFileValid) 
+         return Default;
+      return ::GetIniValue<T>(Section, Setting, Default, iniFileName.c_str());
+   }
+   // Generic IniFile reading routine
+   template<typename T>
+   void SetIniValue(LPCTSTR Section, LPCTSTR Setting, T value){
+      if (!iniFileValid) 
+         return;
+      ::SetIniValue<T>(Section, Setting, value, iniFileName.c_str());
+   }
+
+   bool HasSkeleton();
+   bool IsBiped();
+   void ImportBones(vector<NiNodeRef>& bones);
+   void ImportBones(NiNodeRef blocks);
+   void ImportBipeds(vector<NiNodeRef>& blocks);
+   void AlignBiped(IBipMaster* master, NiNodeRef block);
+   void PositionBiped(IBipMaster* master, NiNodeRef block, bool Recurse = false);
+   void RotateBiped(IBipMaster* master, NiNodeRef block, bool Recurse = false);
+   void ScaleBiped(IBipMaster* master, NiNodeRef block, bool Recurse = false);
+   bool ImportMeshes(NiNodeRef block);
+   string FindImage(const string& name);
+
+   void SetTrangles(Mesh& mesh, vector<Triangle>& v, bool hasTexture);
+   bool ImportMesh(NiTriShapeRef triShape);
+   bool ImportMesh(NiTriStripsRef triStrips);
+   bool ImportMaterialAndTextures(ImpNode *node, NiAVObjectRef avObject);
+   bool ImportTransform(ImpNode *node, NiAVObjectRef avObject);
+   bool ImportMesh(ImpNode *node, TriObject *o, NiTriBasedGeomRef triGeom, NiTriBasedGeomDataRef triGeomData, bool hasTexture);
+
+   bool ImportSkin(ImpNode *node, NiTriBasedGeomRef triGeom);
+   Texmap* CreateTexture(TexDesc& desc);
+};
+
+
+
+
+class MaxNifImport : public SceneImport {
+	public:
+
+		static HWND hParams;
+		
+		int				ExtCount();					// Number of extensions supported
+		const TCHAR *	Ext(int n);					// Extension #n (i.e. "3DS")
+		const TCHAR *	LongDesc();					// Long ASCII description (i.e. "Autodesk 3D Studio File")
+		const TCHAR *	ShortDesc();				// Short ASCII description (i.e. "3D Studio")
+		const TCHAR *	AuthorName();				// ASCII Author name
+		const TCHAR *	CopyrightMessage();			// ASCII Copyright message
+		const TCHAR *	OtherMessage1();			// Other message #1
+		const TCHAR *	OtherMessage2();			// Other message #2
+		unsigned int	Version();					// Version number * 100 (i.e. v3.01 = 301)
+		void			   ShowAbout(HWND hWnd);		// Show DLL's "About..." box
+		int				DoImport(const TCHAR *name,ImpInterface *i,Interface *gi, BOOL suppressPrompts=FALSE);	// Import file
+		
+		//Constructor/Destructor
+		MaxNifImport();
+		~MaxNifImport();		
+
+      string iniFileName;
+      string shortDescription;
+};
+
+
+
+class MaxNifImportClassDesc : public ClassDesc2 {
+	public:
+	int 			   IsPublic() { return TRUE; }
+	void *			Create(BOOL loading = FALSE) { return new MaxNifImport(); }
+	const TCHAR *	ClassName() { return GetString(IDS_CLASS_NAME); }
+	SClass_ID		SuperClassID() { return SCENE_IMPORT_CLASS_ID; }
+	Class_ID		   ClassID() { return MaxNifImport_CLASS_ID; }
+	const TCHAR* 	Category() { return GetString(IDS_CATEGORY); }
+
+	const TCHAR*	InternalName() { return _T("MaxNifImport"); }	// returns fixed parsable name (scripter-visible name)
+	HINSTANCE		HInstance() { return hInstance; }					// returns owning module handle
+	
+
+};
+
+static MaxNifImportClassDesc MaxNifImportDesc;
+ClassDesc2* GetMaxNifImportDesc() { return &MaxNifImportDesc; }
+
+BOOL CALLBACK MaxNifImportOptionsDlgProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam) {
+	static NifImporter *imp = NULL;
+
+	switch(message) {
+		case WM_INITDIALOG:
+         {
+			   imp = (NifImporter *)lParam;
+			   CenterWindow(hWnd,GetParent(hWnd));
+
+            char buffer[33] = {0};
+            sprintf(buffer, "%f", imp->bipedHeight);
+            SetWindowText(GetDlgItem(hWnd, IDC_EDITHEIGHT), buffer);
+         }
+			return TRUE;
+
+		case WM_CLOSE:
+         {
+            char buffer[33] = {0}, *end = NULL;
+            GetWindowText(GetDlgItem(hWnd, IDC_EDITHEIGHT), buffer, 33);
+            imp->bipedHeight = strtod(buffer, &end);
+			   EndDialog(hWnd, 0);
+         }
+			return TRUE;
+
+      case WM_COMMAND : 
+         {
+            switch (wParam)
+            {
+            case IDCLOSE :
+               SendMessage(hWnd, WM_CLOSE, 0, 0);
+               return TRUE;
+            }
+         }
+         break;
+	}
+	return FALSE;
+}
+
+
+//--- MaxNifImport -------------------------------------------------------
+MaxNifImport::MaxNifImport()
+{
+   Interface *gi = GetCOREInterface();
+   TCHAR iniName[MAX_PATH];
+   if (gi) {
+      LPCTSTR pluginDir = gi->GetDir(APP_PLUGCFG_DIR);
+      PathCombine(iniName, pluginDir, "MaxNifTools.ini");
+   } else {
+      GetModuleFileName(NULL, iniName, _countof(iniName));
+      if (LPTSTR fname = PathFindFileName(iniName))
+         fname = NULL;
+      PathAddBackslash(iniName);
+      PathAppend(iniName, "plugcfg");
+      PathAppend(iniName, "MaxNifTools.ini");
+   }
+   iniFileName = iniName;
+   shortDescription = GetIniValue<string>(SystemSection, "ShortDescription", "Netimmerse/Gamebryo", iniFileName.c_str());
+}
+
+MaxNifImport::~MaxNifImport() 
+{
+
+}
+
+int MaxNifImport::ExtCount()
+{
+	//TODO: Returns the number of file name extensions supported by the plug-in.
+	return 1;
+}
+
+const TCHAR *MaxNifImport::Ext(int n)
+{		
+	//TODO: Return the 'i-th' file name extension (i.e. "3DS").
+	return _T("NIF");
+}
+
+const TCHAR *MaxNifImport::LongDesc()
+{
+	//TODO: Return long ASCII description (i.e. "Targa 2.0 Image File")
+	return _T("Netimmerse/Gamebryo");
+}
+	
+const TCHAR *MaxNifImport::ShortDesc() 
+{			
+	//TODO: Return short ASCII description (i.e. "Targa")
+	return shortDescription.c_str();
+}
+
+const TCHAR *MaxNifImport::AuthorName()
+{			
+	//TODO: Return ASCII Author name
+	return _T("Theo");
+}
+
+const TCHAR *MaxNifImport::CopyrightMessage() 
+{	
+	// Return ASCII Copyright message
+	return _T(
+      "Copyright (c) 2006, NIF File Format Library and Tools\n" 
+      "All rights reserved."
+      );
+}
+
+const TCHAR *MaxNifImport::OtherMessage1() 
+{		
+	return _T(
+      "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n"
+      "\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n"
+      "LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS\n"
+      "FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\n"
+      "COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n"
+      "INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n"
+      "BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n"
+      "LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n"
+      "CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n"
+      "LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\n"
+      "ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n"
+      "POSSIBILITY OF SUCH DAMAGE.\n"
+      );
+}
+
+const TCHAR *MaxNifImport::OtherMessage2() 
+{		
+	//TODO: Return other message #2 in any
+	return _T("");
+}
+
+unsigned int MaxNifImport::Version()
+{				
+	//TODO: Return Version number * 100 (i.e. v3.01 = 301)
+	return 1 * 100;
+}
+
+void MaxNifImport::ShowAbout(HWND hWnd)
+{			
+	// Optional
+}
+
+
+int MaxNifImport::DoImport(const TCHAR *filename,ImpInterface *i, Interface *gi, BOOL suppressPrompts)
+{
+   bool ok = true;
+   try 
+   {
+      HoldSuspend myHold(TRUE);
+
+      AppSettings::Initialize(gi);
+
+      std::string current_file = filename;
+
+      NifImporter importer(filename, i, gi, suppressPrompts);
+      if (!importer.isValid())
+         return FALSE;
+
+      //if(!suppressPrompts)
+      //	DialogBoxParam(hInstance, 
+      //			MAKEINTRESOURCE(IDD_PANEL), 
+      //			GetActiveWindow(), 
+      //			MaxNifImportOptionsDlgProc, (LPARAM)&importer);
+
+      // 
+      if (importer.isBiped)
+      {
+         if (importer.useBiped){
+            importer.ImportBipeds(importer.nodes);
+         } else {
+            importer.ImportBones(DynamicCast<NiNode>(importer.nodes[0]->GetChildren()));
+         }
+      }
+      else
+      {
+         vector<string> importedBones;
+         if (importer.importSkeleton && !importer.skeleton.empty())
+         {
+            NifImporter skelImport(importer.skeleton.c_str(), i, gi, suppressPrompts);
+            if (skelImport.isValid())
+            {
+               if (skelImport.useBiped){
+                  skelImport.ImportBipeds(skelImport.nodes);
+               } else {
+                  skelImport.ImportBones(DynamicCast<NiNode>(skelImport.nodes[0]->GetChildren()));
+                  if (importer.removeUnusedImportedBones){
+                     importedBones = GetNamesOfNodes(skelImport.nodes);
+                  }
+               }
+            }
+         }
+         if (importer.isValid()) {
+
+            importer.ImportBones(DynamicCast<NiNode>(importer.nodes[0]->GetChildren()));
+            ok = importer.ImportMeshes(importer.nodes[0]);
+            if (importer.removeUnusedImportedBones){
+               vector<string> importedNodes = GetNamesOfNodes(importer.nodes);
+               sort(importedBones.begin(), importedBones.end());
+               sort(importedNodes.begin(), importedNodes.end());
+               vector<string> results;
+               results.resize(importedBones.size());
+               vector<string>::iterator end = set_difference ( 
+                  importedBones.begin(), importedBones.end(),
+                  importedNodes.begin(), importedNodes.end(), results.begin());
+               for (vector<string>::iterator itr = results.begin(); itr != end; ++itr){
+                  if (INode *node = gi->GetINodeByName((*itr).c_str())){
+                     node->Delete(0, TRUE);
+                  }
+               }
+            }
+         }
+      }
+   }
+   catch( exception & e ) 
+   {
+      e=e;
+      ok = false;
+   }
+   catch( exception * e ) 
+   {
+      e=e;
+      ok = false;
+   }
+   catch( ... ) 
+   {
+      ok = false;
+   }
+   return ok ? TRUE : FALSE;
+}
+
+void NifImporter::LoadIniSettings()
+{
+   TCHAR iniName[MAX_PATH];
+   LPCTSTR pluginDir = gi->GetDir(APP_PLUGCFG_DIR);
+   PathCombine(iniName, pluginDir, "MaxNifTools.ini");
+   this->iniFileName = iniName;
+   iniFileValid = (-1 != _access(iniName, 0));
+
+   string curapp = GetIniValue<string>(NifImportSection, "CurrentApp", "");
+   AppSettingsMap::iterator itr = TheAppSettings.find(curapp);
+   appSettings = (itr != TheAppSettings.end()) ? &(*itr).second : NULL;
+
+   useBiped = GetIniValue<bool>(NifImportSection, "UseBiped", false);
+   skeletonCheck = GetIniValue<string>(NifImportSection, "SkeletonCheck", "Bip*");
+   showTextures = GetIniValue<bool>(NifImportSection, "ShowTextures", true);
+
+   removeIllegalFaces = GetIniValue<bool>(NifImportSection, "RemoveIllegalFaces", true);
+   removeDegenerateFaces = GetIniValue<bool>(NifImportSection, "RemoveDegenerateFaces", true);
+   enableAutoSmooth = GetIniValue<bool>(NifImportSection, "EnableAutoSmooth", true);
+   autoSmoothAngle = GetIniValue<float>(NifImportSection, "AutoSmoothAngle", 30.0f);
+   flipUVTextures = GetIniValue<bool>(NifImportSection, "FlipUVTextures", true);
+
+   skeleton = (appSettings != NULL) ? appSettings->Skeleton : "";
+
+   bipedHeight = GetIniValue<float>(BipedImportSection, "BipedHeight", 131.90f);
+   bipedAngle = GetIniValue<float>(BipedImportSection, "BipedAngle", 90.0f);
+   bipedAnkleAttach = GetIniValue<float>(BipedImportSection, "BipedAnkleAttach", 0.2f);
+   bipedTrianglePelvis = GetIniValue<bool>(BipedImportSection, "BipedTrianglePelvis", false);
+   removeUnusedImportedBones = GetIniValue<bool>(BipedImportSection, "RemoveUnusedImportedBones", false);
+
+   importSkeleton = hasSkeleton = HasSkeleton();
+   isBiped = IsBiped();
+}
+
+void NifImporter::SaveIniSettings()
+{
+   SetIniValue<bool>(NifImportSection, "UseBiped", useBiped);
+   SetIniValue<string>(NifImportSection, "Skeleton", skeleton);
+   SetIniValue<string>(NifImportSection, "SkeletonCheck", skeletonCheck);
+
+   SetIniValue<float>(BipedImportSection, "BipedHeight", bipedHeight);
+   SetIniValue<float>(BipedImportSection, "BipedAngle", bipedAngle);
+   SetIniValue<float>(BipedImportSection, "BipedAnkleAttach", bipedAnkleAttach);
+   SetIniValue<bool>(BipedImportSection, "BipedTrianglePelvis", bipedTrianglePelvis);
+}
+
+bool NifImporter::HasSkeleton()
+{
+   if (!skeletonCheck.empty()){
+      vector<NiNodeRef> bipedRoots = SelectNodesByName(nodes, skeletonCheck.c_str());
+      return !bipedRoots.empty();
+   }
+   return false;
+}
+
+bool NifImporter::IsBiped()
+{
+   if (hasSkeleton){
+      list<NiExtraDataRef> extraData = nodes[0]->GetExtraData();
+      if (!extraData.empty()) {
+         return ( DynamicCast<BSBound>(extraData).size() != 0 );
+      }
+   }
+   return false;
+}
+
+void NifImporter::ImportBipeds(vector<NiNodeRef>& nodes)
+{
+   IBipMaster* master = NULL;
+   try 
+   {
+      vector<NiNodeRef> bipedRoots = SelectNodesByName(nodes, "Bip??");
+      std::stable_sort(bipedRoots.begin(), bipedRoots.end(), NiNodeNameEquivalence());
+      for (vector<NiNodeRef>::iterator bipedItr = bipedRoots.begin(); bipedItr != bipedRoots.end(); ++bipedItr)
+      {
+         string bipname = (*bipedItr)->GetName();
+         string match = bipname + "*";
+         vector<NiNodeRef> bipedNodes = SelectNodesByName(nodes, match.c_str());
+
+         float height = this->bipedHeight;
+#if USE_CUSTOM_BSBOUND
+         list<NiExtraDataRef> extraData = nodes[0]->GetExtraData();
+         if (!extraData.empty()) {
+            BSBoundRef bound = SelectFirst<BSBound>(extraData);
+            if (bound) {
+               array<float,6> floats = bound->GetUnknownFloats();
+               height = floats[2] * 2.0f;
+            }
+         }
+#endif
+         float angle = TORAD(bipedAngle);
+         Point3 wpos(0.0f,0.0f,0.0f);
+         BOOL arms = (CountNodesByName(bipedNodes, FormatText("%s L UpperArm", bipname.c_str())) > 0) ? TRUE : FALSE;
+         BOOL triPelvis = bipedTrianglePelvis ? TRUE : FALSE;
+         int nnecklinks=CountNodesByName(bipedNodes, FormatText("%s Neck*", bipname.c_str()));
+         int nspinelinks=CountNodesByName(bipedNodes, FormatText("%s Spine*", bipname.c_str()));
+         int nleglinks = 3 + CountNodesByName(bipedNodes, FormatText("%s L HorseLink", bipname.c_str()));
+         int ntaillinks = CountNodesByName(bipedNodes, FormatText("%s Tail*", bipname.c_str()));
+         int npony1links = CountNodesByName(bipedNodes, FormatText("%s Ponytail1*", bipname.c_str()));
+         int npony2links = CountNodesByName(bipedNodes, FormatText("%s Ponytail2*", bipname.c_str()));
+         int numfingers = CountNodesByName(bipedNodes, FormatText("%s L Finger?", bipname.c_str()));
+         int nfinglinks = CountNodesByName(bipedNodes, FormatText("%s L Finger0*", bipname.c_str()));
+         int numtoes = CountNodesByName(bipedNodes, FormatText("%s L Toe?", bipname.c_str()));
+         int ntoelinks = CountNodesByName(bipedNodes, FormatText("%s L Toe0*", bipname.c_str()));
+         BOOL prop1exists = CountNodesByName(bipedNodes, FormatText("%s Prop1", bipname.c_str())) ? TRUE : FALSE;
+         BOOL prop2exists = CountNodesByName(bipedNodes, FormatText("%s Prop2", bipname.c_str())) ? TRUE : FALSE;
+         BOOL prop3exists = CountNodesByName(bipedNodes, FormatText("%s Prop3", bipname.c_str())) ? TRUE : FALSE;
+         int forearmTwistLinks = CountNodesByName(bipedNodes, FormatText("%s L Fore*Twist*", bipname.c_str()));
+         int upperarmTwistLinks = CountNodesByName(bipedNodes, FormatText("%s L Up*Twist*", bipname.c_str()));
+         int thighTwistLinks = CountNodesByName(bipedNodes, FormatText("%s L Thigh*Twist*", bipname.c_str()));
+         int calfTwistLinks = CountNodesByName(bipedNodes, FormatText("%s L Calf*Twist*", bipname.c_str()));
+         int horseTwistLinks = CountNodesByName(bipedNodes, FormatText("%s L Horse*Twist*", bipname.c_str()));
+
+         NiNodeRef root = nodes[0];
+         IBipMaster* master = CreateNewBiped(height, angle, wpos, arms, triPelvis, nnecklinks, nspinelinks, 
+            nleglinks, ntaillinks, npony1links, npony2links, numfingers, nfinglinks, numtoes, 
+            ntoelinks, bipedAnkleAttach, prop1exists, prop2exists, prop3exists,
+            forearmTwistLinks, upperarmTwistLinks, thighTwistLinks,
+            calfTwistLinks, horseTwistLinks);
+         master->SetRootName(const_cast<TCHAR*>(bipname.c_str()));
+         if (master)
+         {
+            master->BeginModes(BMODE_FIGURE, FALSE);
+            master->SetTrianglePelvis(FALSE);
+            master->SetDisplaySettings(BDISP_BONES);
+            LPCTSTR bipname = master->GetRootName();
+
+            // Rename twists, if necessary
+            RenameNode(gi, FormatText("%s L ForeTwist", bipname), FormatText("%s L ForearmTwist", bipname));
+            RenameNode(gi, FormatText("%s R ForeTwist", bipname), FormatText("%s R ForearmTwist", bipname));
+            RenameNode(gi, FormatText("%s R LUpArmTwist", bipname), FormatText("%s L UpperArmTwist", bipname));
+            RenameNode(gi, FormatText("%s R LUpArmTwist", bipname), FormatText("%s R UpperArmTwist", bipname));
+
+            NiNodeRef nifBip = FindNodeByName(nodes, bipname);
+            //AlignBiped(master, nifBip);
+            ScaleBiped(master, nifBip, true);
+            PositionBiped(master, nifBip, true);
+            RotateBiped(master, nifBip, true);
+         }
+      }
+   }
+   catch( exception & e ) 
+   {
+      e=e;
+   }
+   catch( ... ) 
+   {
+   }
+   if (master)
+      master->EndModes(BMODE_FIGURE, TRUE);
+}
+
+void NifImporter::AlignBiped(IBipMaster* master, NiNodeRef block)
+{
+   string name = block->GetName();
+   Matrix44 wt = block->GetWorldTransform();
+   Matrix44 lt = block->GetLocalTransform();
+
+   Vector3 pos; Matrix33 rot; float scale;
+   wt.Decompose(pos, rot, scale);
+
+   INode *node = gi->GetINodeByName(name.c_str());
+   if (node != NULL) 
+   {
+      Matrix3 m3 = node->GetNodeTM(TimeValue(), NULL); // local translation m
+      master->SetBipedPos(Point3(pos.x, pos.y, pos.z), 0, node, TRUE);
+
+      Matrix3 m(rot.rows[0].data, rot.rows[1].data, rot.rows[2].data, Point3());
+      Matrix3 im = Inverse(m);
+
+      Point3 p; Quat q; Point3 s;
+      DecomposeMatrix(im, p, q, s);
+      master->SetBipedRot(q, 0, node, TRUE);
+   }
+   else
+   {
+
+   }
+   vector<NiNodeRef> nodes = DynamicCast<NiNode>(block->GetChildren());
+   for (vector<NiNodeRef>::iterator itr = nodes.begin(), end = nodes.end(); itr != end; ++itr){
+      AlignBiped(master, *itr);
+   }
+}
+
+void NifImporter::PositionBiped(IBipMaster* master, NiNodeRef block, bool Recurse)
+{
+   string name = block->GetName();
+
+   Matrix44 wt = block->GetWorldTransform();
+   Matrix44 lt = block->GetLocalTransform();
+
+   Vector3 pos; Matrix33 rot; float scale;
+   wt.Decompose(pos, rot, scale);
+
+   INode *node = gi->GetINodeByName(name.c_str());
+   if (node != NULL) 
+   {
+      Matrix3 m3 = node->GetNodeTM(TimeValue(), NULL); // local translation m
+      master->SetBipedPos(Point3(pos.x, pos.y, pos.z), 0, node, TRUE);
+
+      Matrix3 m(rot.rows[0].data, rot.rows[1].data, rot.rows[2].data, Point3());
+      Matrix3 im = Inverse(m);
+      Point3 p; Quat q; Point3 s;
+      DecomposeMatrix(im, p, q, s);
+      master->SetBipedRot(q, 0, node, TRUE);
+   }
+   else
+   {
+
+   }
+   if (Recurse)
+   {
+      vector<NiNodeRef> nodes = DynamicCast<NiNode>(block->GetChildren());
+      for (vector<NiNodeRef>::iterator itr = nodes.begin(), end = nodes.end(); itr != end; ++itr){
+         PositionBiped(master, *itr, Recurse);
+      }
+   }
+}
+
+void NifImporter::RotateBiped(IBipMaster* master, NiNodeRef block, bool Recurse)
+{
+   string name = block->GetName();
+
+   Matrix44 wt = block->GetWorldTransform();
+   Matrix44 lt = block->GetLocalTransform();
+
+   Vector3 pos; Matrix33 rot; float scale;
+   wt.Decompose(pos, rot, scale);
+
+   INode *node = gi->GetINodeByName(name.c_str());
+   if (node != NULL) 
+   {
+      Matrix3 m3 = node->GetNodeTM(TimeValue(), NULL); // local translation m
+      master->SetBipedPos(Point3(pos.x, pos.y, pos.z), 0, node, TRUE);
+
+      Matrix3 m(rot.rows[0].data, rot.rows[1].data, rot.rows[2].data, Point3());
+      Matrix3 im = Inverse(m);
+
+      Point3 p; Quat q; Point3 s;
+      DecomposeMatrix(im, p, q, s);
+      master->SetBipedRot(q, 0, node, TRUE);
+   }
+   else
+   {
+
+   }
+   if (Recurse)
+   {
+      vector<NiNodeRef> nodes = DynamicCast<NiNode>(block->GetChildren());
+      for (vector<NiNodeRef>::iterator itr = nodes.begin(), end = nodes.end(); itr != end; ++itr){
+         RotateBiped(master, *itr, Recurse);
+      }
+   }
+}
+
+void NifImporter::ScaleBiped(IBipMaster* master, NiNodeRef block, bool Recurse)
+{
+   string name = block->GetName();
+
+   Matrix44 wt = block->GetWorldTransform();
+   Matrix44 lt = block->GetLocalTransform();
+
+   Vector3 pos; Matrix33 rot; float scale;
+   wt.Decompose(pos, rot, scale);
+
+   INode *node = gi->GetINodeByName(name.c_str());
+   if (node != NULL) 
+   {
+      Matrix3 m3 = node->GetNodeTM(TimeValue(), NULL); // local translation m
+      master->SetBipedPos(Point3(pos.x, pos.y, pos.z), 0, node, TRUE);
+
+      Matrix3 m(rot.rows[0].data, rot.rows[1].data, rot.rows[2].data, Point3());
+      Matrix3 im = Inverse(m);
+
+      Point3 p; Quat q; Point3 s;
+      DecomposeMatrix(im, p, q, s);
+      master->SetBipedRot(q, 0, node, TRUE);
+   }
+   else
+   {
+
+   }
+   if (Recurse)
+   {
+      vector<NiNodeRef> nodes = DynamicCast<NiNode>(block->GetChildren());
+      for (vector<NiNodeRef>::iterator itr = nodes.begin(), end = nodes.end(); itr != end; ++itr){
+         ScaleBiped(master, *itr, Recurse);
+      }
+   }
+}
+
+
+INode *CreateBone(const string& name, Point3 startPos, Point3 endPos, Point3 zAxis)
+{
+   if (FPInterface * fpBones = GetCOREInterface(Interface_ID(0x438aff72, 0xef9675ac)))
+   {
+      FunctionID createBoneID = fpBones->FindFn(TEXT("createBone"));
+      FPValue result;
+      FPParams params (3, TYPE_POINT3, &startPos, TYPE_POINT3, &endPos, TYPE_POINT3, &zAxis);     
+      FPStatus status = fpBones->Invoke(createBoneID, result, &params);
+      if (status == FPS_OK && result.type == TYPE_INODE)
+      {
+         if (INode *n = result.n)
+         {
+            n->SetName(const_cast<TCHAR*>(name.c_str()));
+
+            const float wmin = 0.5f;
+            const float wmax = 3.0f;
+            float len = fabs(Length(startPos)-Length(endPos));
+            float width = max(wmin, min(wmax, len / 4.0f));
+
+            if (Object* o = n->GetObjectRef())
+            {
+               setMAXScriptValue(o->GetReference(0), "width", 0, width);
+               setMAXScriptValue(o->GetReference(0), "height", 0, width);
+            }
+         }
+         return result.n;
+      }
+   }
+   return NULL;
+}
+
+void NifImporter::ImportBones(vector<NiNodeRef>& bones)
+{
+   for (vector<NiNodeRef>::iterator itr = bones.begin(), end = bones.end(); itr != end; ++itr){
+      ImportBones(*itr);
+   }
+}
+
+void NifImporter::ImportBones(NiNodeRef node)
+{
+   try 
+   {
+      string name = node->GetName();
+      vector<NiNodeRef> children = DynamicCast<NiNode>(node->GetChildren());
+      NiNodeRef parent = node->GetParent();
+
+      Matrix3 im = TOMATRIX3(node->GetWorldTransform(), true);
+      Point3 p = im.GetTrans();
+      Quat q(im);
+      q.Normalize();
+      INode *bone = gi->GetINodeByName(name.c_str());
+      if (bone)
+      {
+         // Is there a better way of "Affect Pivot Only" behaviors?
+         INode *pinode = bone->GetParentNode();
+         if (pinode)
+            bone->Detach(0,1);
+         PositionAndRotateNode(bone, p, q);
+         if (pinode)
+            pinode->AttachChild(bone, 1);
+      }
+      else
+      {
+         Vector3 ppos;
+         if (!children.empty()) {
+            for (vector<NiNodeRef>::iterator itr=children.begin(), end = children.end(); itr != end; ++itr) {
+               Matrix44 cwt = (*itr)->GetWorldTransform();
+               Vector3 cpos; Matrix33 crot; float cscale;
+               cwt.Decompose(cpos, crot, cscale);
+               ppos += cpos;
+            }
+            ppos /= children.size();
+         }
+         else if (parent)
+         {
+            Matrix44 pwt = parent->GetWorldTransform();
+            Matrix33 prot; float pscale;
+            pwt.Decompose(ppos, prot, pscale);
+         }
+         Point3 pp(ppos.x, ppos.y, ppos.z);
+         Point3 zAxis(0,1,0);
+         if (bone = CreateBone(name, p, pp, zAxis))
+         {
+            PositionAndRotateNode(bone, p, q);
+            if (parent)
+            {
+               if (INode *pn = gi->GetINodeByName(parent->GetName().c_str()))
+                  pn->AttachChild(bone, 1);
+            }
+         }
+      }
+      if (bone)
+      {
+         ImportBones(children);
+      }
+   }
+   catch( exception & e ) 
+   {
+      e=e;
+   }
+   catch( ... ) 
+   {
+   }
+}
+
+bool NifImporter::ImportMeshes(NiNodeRef node)
+{
+   bool ok = true;
+   try 
+   {
+      vector<NiTriShapeRef> trinodes = DynamicCast<NiTriShape>(node->GetChildren());
+      for (vector<NiTriShapeRef>::iterator itr = trinodes.begin(), end = trinodes.end(); ok && itr != end; ++itr){
+         ok |= ImportMesh(*itr);
+      }
+      vector<NiTriStripsRef> tristrips = DynamicCast<NiTriStrips>(node->GetChildren());
+      for (vector<NiTriStripsRef>::iterator itr = tristrips.begin(), end = tristrips.end(); ok && itr != end; ++itr){
+         ok |= ImportMesh(*itr);
+      }
+      vector<NiNodeRef> nodes = DynamicCast<NiNode>(node->GetChildren());
+      for (vector<NiNodeRef>::iterator itr = nodes.begin(), end = nodes.end(); itr != end; ++itr){
+         ok |= ImportMeshes(*itr);
+      }
+   }
+   catch( exception & e ) 
+   {
+      e=e;
+      ok = false;
+   }
+   catch( ... ) 
+   {
+      ok = false;
+   }
+   return ok;
+}
+
+Texmap* NifImporter::CreateTexture(TexDesc& desc)
+{
+   BitmapManager *bmpMgr = TheManager;
+   if (NiSourceTextureRef texSrc = desc.source){
+      string filename = texSrc->GetExternalFileName();
+      if (bmpMgr->CanImport(filename.c_str())){
+         BitmapTex *bmpTex = NewDefaultBitmapTex();
+         bmpTex->SetName(texSrc->GetName().c_str());
+         bmpTex->SetMapName(const_cast<TCHAR*>(FindImage(filename).c_str()));
+         bmpTex->SetAlphaAsMono(TRUE);
+         bmpTex->SetAlphaSource(ALPHA_NONE);
+         return bmpTex;
+      }
+   }
+   return NULL;
+}
+
+bool NifImporter::ImportMaterialAndTextures(ImpNode *node, NiAVObjectRef avObject)
+{
+   // Texture
+   NiTexturingPropertyRef texRef = avObject->GetPropertyByType( NiTexturingProperty::TypeConst() );
+   NiMaterialPropertyRef matRef = avObject->GetPropertyByType( NiMaterialProperty::TypeConst() );
+   
+   bool hasTexture = (texRef && matRef);
+   if (hasTexture){
+      StdMat2 *m = NewDefaultStdMat();
+      m->SetName(matRef->GetName().c_str());
+      if (showTextures)
+         m->SetMtlFlag(MTL_DISPLAY_ENABLE_FLAGS, TRUE);
+      m->SetAmbient(TOCOLOR(matRef->GetAmbientColor()),0);
+      m->SetDiffuse(TOCOLOR(matRef->GetDiffuseColor()),0);
+      m->SetSpecular(TOCOLOR(matRef->GetSpecularColor()),0);
+      Color c = TOCOLOR(matRef->GetEmissiveColor());
+      if (c.r != 0 || c.b != 0 || c.g != 0) {
+         m->SetSelfIllumColorOn(TRUE);
+         m->SetSelfIllumColor(c,0);
+      }
+      m->SetShinStr(0.0,0);
+      m->SetShininess(matRef->GetGlossiness()/100.0,0);
+      m->SetOpacity(matRef->GetTransparency(),0);
+
+      // Handle Base/Detail ???
+      if (texRef->HasTexture(DECAL_0_MAP)){
+         if (Texmap* tex = CreateTexture(texRef->GetTexture(DECAL_0_MAP)))
+            m->SetSubTexmap(ID_DI, tex);
+         if (texRef->HasTexture(BASE_MAP)){
+            m->LockAmbDiffTex(FALSE);
+            if (Texmap* tex = CreateTexture(texRef->GetTexture(BASE_MAP)))
+               m->SetSubTexmap(ID_AM, tex);
+         }
+      } else if (texRef->HasTexture(BASE_MAP)) {
+         if (Texmap* tex = CreateTexture(texRef->GetTexture(BASE_MAP)))
+            m->SetSubTexmap(ID_DI, tex);
+      } 
+      // Handle Bump map
+      if (texRef->HasTexture(BUMP_MAP)) {
+         if (Texmap* tex = CreateTexture(texRef->GetTexture(BUMP_MAP)))
+            m->SetSubTexmap(ID_BU, tex);
+      }
+      // Shiny map
+      if (texRef->HasTexture(GLOSS_MAP)) {
+         if (Texmap* tex = CreateTexture(texRef->GetTexture(GLOSS_MAP)))
+            m->SetSubTexmap(ID_SS, tex);
+      }
+
+      // Self illumination
+      if (texRef->HasTexture(GLOW_MAP)) {
+         if (Texmap* tex = CreateTexture(texRef->GetTexture(GLOW_MAP)))
+            m->SetSubTexmap(ID_SI, tex);
+      }
+
+      gi->GetMaterialLibrary().Add(m);
+      node->GetINode()->SetMtl(m);
+   }
+   return hasTexture;
+}
+
+bool NifImporter::ImportTransform(ImpNode *node, NiAVObjectRef avObject)
+{
+   Matrix44 wt = avObject->GetWorldTransform();
+
+   Vector3 pos; Matrix33 rot; float scale;
+   wt.Decompose(pos, rot, scale);
+   Point3 p(pos.x, pos.y, pos.z);
+
+   Matrix3 m(rot.rows[0].data, rot.rows[1].data, rot.rows[2].data, Point3());
+   m.SetTrans(p);
+   node->SetTransform(0,m);   
+   return true;
+}
+
+bool NifImporter::ImportMesh(ImpNode *node, TriObject *o, NiTriBasedGeomRef triGeom, NiTriBasedGeomDataRef triGeomData, bool hasTexture)
+{
+   Mesh& mesh = o->GetMesh();
+
+   // Vertex info
+   {
+      int nVertices = triGeomData->GetVertexCount();
+      vector<Vector3> vertices = triGeomData->GetVertices();
+      mesh.setNumVerts(nVertices);
+      for (int i=0; i < nVertices; ++i){
+         Vector3 &v = vertices[i];
+         mesh.verts[i].Set(v.x, v.y, v.z);
+      }
+   }
+   if (hasTexture) // uv texture info
+   {
+      int nUVSet = triGeomData->GetUVSetCount();
+      int n = 0;
+      for (int j=0; j<nUVSet; j++){
+         vector<TexCoord> texCoords = triGeomData->GetUVSet(j);
+         n = texCoords.size();
+         mesh.setNumTVerts(n, FALSE);
+         for (int i=0; i<n; ++i) {
+            TexCoord& texCoord = texCoords[i];
+            mesh.tVerts[i].Set(texCoord.u, (flipUVTextures) ? -texCoord.v : texCoord.v, 0);
+         }
+      }
+   }
+   if (removeDegenerateFaces)
+      mesh.RemoveDegenerateFaces();
+   if (removeIllegalFaces)
+      mesh.RemoveIllegalFaces();
+   if (enableAutoSmooth)
+      mesh.AutoSmooth(TORAD(autoSmoothAngle), FALSE, FALSE);
+
+   // Normals
+   {
+      mesh.checkNormals(TRUE);
+      vector<Vector3> n = triGeomData->GetNormals();
+      for (int i=0; i<n.size(); i++){
+         Vector3 v = n[i];
+         mesh.setNormal(i, Point3(v.x, v.y, v.z));
+      }
+   }
+   // vertex coloring
+   {
+      bool hasAlpha = false;
+      vector<Color4> cv = triGeomData->GetColors();
+      mesh.setNumVertCol(cv.size());
+      for (int i=0; i<cv.size(); i++){
+         Color4& c = cv[i];
+         mesh.vertCol[i].Set(c.r, c.g, c.b);
+         hasAlpha |= (c.a != 0.0);
+      }
+   }
+   return true;
+}
+
+void NifImporter::SetTrangles(Mesh& mesh, vector<Triangle>& v, bool hasTexture)
+{
+   int n = v.size();
+   mesh.setNumFaces(n);
+   if (hasTexture)
+      mesh.setNumTVFaces(n);
+   for (int i=0; i<n; ++i) {
+      Triangle& t = v[i];
+      Face& f = mesh.faces[i];
+      f.setVerts(t.v1, t.v2, t.v3);
+      f.Show();
+      f.setEdgeVisFlags(EDGE_VIS, EDGE_VIS, EDGE_VIS);
+      if (hasTexture) {
+         TVFace& tf = mesh.tvFace[i];
+         tf.setTVerts(t.v1, t.v2, t.v3);
+      }
+   }
+}
+
+bool NifImporter::ImportMesh(NiTriShapeRef triShape)
+{
+   bool ok = true;
+
+   ImpNode *node = i->CreateNode();
+   if(!node) return false;
+   TriObject *triObject = CreateNewTriObject();
+   node->Reference(triObject);
+   string name = triShape->GetName();
+   node->SetName(name.c_str());
+
+   // Texture
+   bool hasTexture = ImportMaterialAndTextures(node, triShape);
+   ImportTransform(node, triShape);
+
+   Mesh& mesh = triObject->GetMesh();
+   NiTriShapeDataRef triShapeData = DynamicCast<NiTriShapeData>(triShape->GetData());
+   if (triShapeData == NULL)
+      return false;
+
+   ok |= ImportMesh(node, triObject, triShape, triShapeData, hasTexture);
+
+   // Triangles and texture vertices
+   if (ok)
+   {
+      vector<Triangle> v = triShapeData->GetTriangles();
+      SetTrangles(mesh, v, hasTexture);
+   }
+   ImportSkin(node, triShape);
+   i->AddNodeToScene(node);   
+
+   if (enableAutoSmooth){
+      if (TriObject *tri = GetTriObject(node->GetINode()->GetObjectRef())){
+        tri->GetMesh().AutoSmooth(TORAD(autoSmoothAngle), FALSE, FALSE);
+      }
+   }
+
+   return ok;
+}
+
+bool NifImporter::ImportMesh(NiTriStripsRef triStrips)
+{
+   bool ok = true;
+
+   ImpNode *node = i->CreateNode();
+   if(!node) return false;
+   TriObject *triObject = CreateNewTriObject();
+   node->Reference(triObject);
+   string name = triStrips->GetName();
+   node->SetName(name.c_str());
+
+   // Texture
+   bool hasTexture = ImportMaterialAndTextures(node, triStrips);
+   ImportTransform(node, triStrips);
+
+   Mesh& mesh = triObject->GetMesh();
+   NiTriStripsDataRef triStripsData = DynamicCast<NiTriStripsData>(triStrips->GetData());
+   if (triStripsData == NULL)
+      return false;
+
+   ok |= ImportMesh(node, triObject, triStrips, triStripsData, hasTexture);
+
+   // Triangles and texture vertices
+   if (ok)
+   {
+      vector<Triangle> v = triStripsData->GetTriangles();
+      SetTrangles(mesh, v, hasTexture);
+   }
+   ImportSkin(node, triStrips);
+   i->AddNodeToScene(node);   
+
+   // apply autosmooth after object creation for it to take hold
+   if (enableAutoSmooth){
+      if (TriObject *tri = GetTriObject(node->GetINode()->GetObjectRef())){
+         tri->GetMesh().AutoSmooth(TORAD(autoSmoothAngle), FALSE, FALSE);
+      }
+   }
+
+   return ok;
+}
+
+struct VertexHolder
+{
+   VertexHolder() : vertIndex(0), count(0) {}
+
+   int vertIndex;
+   int count;
+   Tab<INode*> boneNodeList;
+   Tab<float> weights;
+};
+
+bool NifImporter::ImportSkin(ImpNode *node, NiTriBasedGeomRef triGeom)
+{
+   bool ok = true;
+   NiSkinInstanceRef nifSkin = triGeom->GetSkinInstance();
+   if (!nifSkin) 
+      return false;
+
+   INode *tnode = node->GetINode();
+
+   NiSkinDataRef data = nifSkin->GetSkinData();
+   NiSkinPartitionRef part = nifSkin->GetSkinPartition();
+
+   vector<NiNodeRef> nifBones = nifSkin->GetBones();
+
+   //create a skin modifier and add it
+   Modifier *skinMod = GetSkin(tnode);
+   TriObject *triObject = GetTriObject(tnode->GetObjectRef());
+   Mesh& m = triObject->GetMesh();
+
+   //get the skin interface
+   ISkin *skin = (ISkin *) skinMod->GetInterface(I_SKIN);
+   ISkinImportData* iskinImport = (ISkinImportData*) skinMod->GetInterface(I_SKINIMPORTDATA);
+
+   // Create Bone List
+   Tab<INode*> bones;
+   int i=0;
+   for (vector<NiNodeRef>::iterator itr = nifBones.begin(), end = nifBones.end(); itr != end; ++itr, ++i){
+      string name = (*itr)->GetName();
+      INode *boneRef = gi->GetINodeByName(name.c_str());
+      bones.Append(1, &boneRef);
+      iskinImport->AddBoneEx(boneRef, TRUE);
+
+      // Set Bone Transform
+      //Matrix3 tm = boneRef->GetObjectTM(0);
+      //Matrix3 m = TOMATRIX3(data->GetBoneTransform(i));
+      //iskinImport->SetBoneTm(boneRef, tm, m);
+   }
+   ObjectState os = tnode->EvalWorldState(0);
+
+   // Need to trigger ModifyObject in BonesDefMod prior to adding vertices or nothing is added
+   GetCOREInterface()->ForceCompleteRedraw();
+
+   // Need to get a list of bones and weights for each vertex.
+   vector<VertexHolder> vertexHolders;
+   vertexHolders.resize(m.numVerts);
+   for (int i=0, n=data->GetBoneCount();i<n; ++i){
+      if (INode *boneRef = bones[i]){
+         vector<SkinWeight> weights = data->GetBoneWeights(i);
+         for (vector<SkinWeight>::iterator itr=weights.begin(), end=weights.end(); itr != end; ++itr){
+            VertexHolder& h = vertexHolders[itr->index];
+            h.vertIndex = itr->index;
+            ++h.count;
+            h.weights.Append(1, &itr->weight);
+            h.boneNodeList.Append(1, &boneRef);
+         }
+      }
+   }
+
+   // Assign the weights 
+   for (vector<VertexHolder>::iterator itr=vertexHolders.begin(), end=vertexHolders.end(); itr != end; ++itr){
+      VertexHolder& h = (*itr);
+      if (h.count){
+         float sum = 0.0f;
+         for (int i=0; i<h.count; ++i)
+            sum += h.weights[i];
+         ASSERT(fabs(sum-1.0) < 0.001);
+         BOOL add = iskinImport->AddWeights(tnode, h.vertIndex, h.boneNodeList, h.weights);
+         add = add;
+      }
+   }
+   return ok;
+}
+
+string NifImporter::FindImage(const string& name)
+{
+   TCHAR buffer[MAX_PATH];
+
+   // Simply check for fully qualified path
+   if (PathIsRoot(name.c_str())) {
+      if (-1 != _taccess(name.c_str(), 0))
+         return string(buffer);
+   } 
+   if (!path.empty()) {
+      PathCombine(buffer, path.c_str(), name.c_str()); // try as-is
+      if (-1 != _taccess(buffer, 0))
+         return string(buffer);
+
+      // try only filename in nif directory
+      PathCombine(buffer, path.c_str(), PathFindFileName(name.c_str()));
+      if (-1 != _taccess(buffer, 0))
+         return string(buffer);
+   }
+   if (appSettings != NULL) {
+      return appSettings->FindImage(name);
+   }
+   return name;
+}
\ No newline at end of file
diff --git a/MaxNifImport/MaxNifImport.def b/MaxNifImport/MaxNifImport.def
new file mode 100644
index 0000000..d9a8109
--- /dev/null
+++ b/MaxNifImport/MaxNifImport.def
@@ -0,0 +1,8 @@
+LIBRARY MaxNifImport.dli
+EXPORTS
+	LibDescription			@1
+	LibNumberClasses		@2
+	LibClassDesc			@3
+	LibVersion				@4
+SECTIONS
+	.data READ WRITE
diff --git a/MaxNifImport/MaxNifImport.h b/MaxNifImport/MaxNifImport.h
new file mode 100644
index 0000000..574cd65
--- /dev/null
+++ b/MaxNifImport/MaxNifImport.h
@@ -0,0 +1,32 @@
+/**********************************************************************
+ *<
+	FILE: MaxNifImport.h
+
+	DESCRIPTION:	Includes for Plugins
+
+	CREATED BY: tazpn (Theo)
+
+	HISTORY:
+
+ *>	Copyright (c) 2006, All Rights Reserved.
+ **********************************************************************/
+
+#ifndef __MaxNifImport__H
+#define __MaxNifImport__H
+
+#include "Max.h"
+#include "resource.h"
+#include "istdplug.h"
+#include "iparamb2.h"
+#include "iparamm2.h"
+//SIMPLE TYPE
+
+#include <direct.h>
+#include <commdlg.h>
+
+
+extern TCHAR *GetString(int id);
+
+extern HINSTANCE hInstance;
+
+#endif
diff --git a/MaxNifImport/MaxNifImport.rc b/MaxNifImport/MaxNifImport.rc
new file mode 100644
index 0000000..9641d48
--- /dev/null
+++ b/MaxNifImport/MaxNifImport.rc
@@ -0,0 +1,152 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "afxres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_PANEL DIALOGEX 0, 0, 142, 59
+STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_TOOLWINDOW
+FONT 8, "MS Sans Serif", 0, 0, 0x1
+BEGIN
+    EDITTEXT        IDC_EDITHEIGHT,68,15,62,14,ES_AUTOHSCROLL
+    LTEXT           "&Height:",IDC_STATIC,16,18,49,8
+    PUSHBUTTON      "&Close",IDCLOSE,44,38,50,14
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO 
+BEGIN
+    IDD_PANEL, DIALOG
+    BEGIN
+        LEFTMARGIN, 7
+        RIGHTMARGIN, 135
+        TOPMARGIN, 7
+        BOTTOMMARGIN, 52
+    END
+END
+#endif    // APSTUDIO_INVOKED
+
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE 
+BEGIN
+    "resource.h\0"
+END
+
+2 TEXTINCLUDE 
+BEGIN
+    "#include ""afxres.h""\r\n"
+    "\0"
+END
+
+3 TEXTINCLUDE 
+BEGIN
+    "\r\n"
+    "\0"
+END
+
+#endif    // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,0,0,1
+ PRODUCTVERSION 1,0,0,1
+ FILEFLAGSMASK 0x37L
+#ifdef _DEBUG
+ FILEFLAGS 0x21L
+#else
+ FILEFLAGS 0x20L
+#endif
+ FILEOS 0x4L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904b0"
+        BEGIN
+            VALUE "FileDescription", "3ds Max Nif Importer"
+            VALUE "FileVersion", "0, 1, 0, 1"
+            VALUE "InternalName", "MaxNifImport.dli"
+            VALUE "LegalCopyright", "Copyright (c) 2006, NIF File Format Library and Tools\r\nAll rights reserved."
+            VALUE "OriginalFilename", "MaxNifImport.dli"
+            VALUE "ProductName", "3ds Max Nif Importer"
+            VALUE "ProductVersion", "0, 1, 0, 1"
+            VALUE "SpecialBuild", "Alpha"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1200
+    END
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE 
+BEGIN
+    IDS_LIBDESCRIPTION      "Importer for NIF files"
+    IDS_CATEGORY            "NifTools"
+    IDS_CLASS_NAME          "MaxNifImport"
+    IDS_PARAMS              "Parameters"
+    IDS_SPIN                "Spin"
+END
+
+#endif    // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
+
diff --git a/MaxNifImport/MaxNifImport.vcproj b/MaxNifImport/MaxNifImport.vcproj
new file mode 100644
index 0000000..02ae21c
--- /dev/null
+++ b/MaxNifImport/MaxNifImport.vcproj
@@ -0,0 +1,322 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+	ProjectType="Visual C++"
+	Version="8.00"
+	Name="MaxNifImport"
+	ProjectGUID="{283FC287-9AF1-4F34-9B74-2376FE3C2FCD}"
+	RootNamespace="MaxNifImport"
+	>
+	<Platforms>
+		<Platform
+			Name="Win32"
+		/>
+	</Platforms>
+	<ToolFiles>
+	</ToolFiles>
+	<Configurations>
+		<Configuration
+			Name="Release|Win32"
+			OutputDirectory=".\Release"
+			IntermediateDirectory=".\Release"
+			ConfigurationType="2"
+			InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
+			UseOfMFC="0"
+			ATLMinimizesCRunTimeLibraryUsage="false"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+				CommandLine=""
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+				PreprocessorDefinitions="NDEBUG"
+				MkTypLibCompatible="true"
+				SuppressStartupBanner="true"
+				TargetEnvironment="1"
+				TypeLibraryName=".\Release\MaxNifImport.tlb"
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				AdditionalOptions="/LD "
+				InlineFunctionExpansion="1"
+				AdditionalIncludeDirectories="C:\3dsmax8\maxsdk\include"
+				PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;USE_NIFLIB_TEMPLATE_HELPERS;_USE_MATH_DEFINES"
+				StringPooling="true"
+				RuntimeLibrary="0"
+				EnableFunctionLevelLinking="true"
+				ForceConformanceInForLoopScope="false"
+				UsePrecompiledHeader="0"
+				PrecompiledHeaderFile=".\Release\MaxNifImport.pch"
+				AssemblerListingLocation=".\Release\"
+				ObjectFile=".\Release\"
+				ProgramDataBaseFileName=".\Release\"
+				WarningLevel="3"
+				SuppressStartupBanner="true"
+				CompileAs="0"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+				PreprocessorDefinitions="NDEBUG"
+				Culture="1033"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLinkerTool"
+				AdditionalOptions="/MACHINE:I386"
+				AdditionalDependencies="odbc32.lib odbccp32.lib comctl32.lib shlwapi.lib core.lib geom.lib gfx.lib mesh.lib maxutil.lib maxscrpt.lib paramblk2.lib biped.lib bmm.lib niflib.lib"
+				OutputFile="C:\3dsmax8\plugins\MaxNifImport.dli"
+				LinkIncremental="1"
+				SuppressStartupBanner="true"
+				AdditionalLibraryDirectories="C:\3dsmax8\maxsdk\lib"
+				ModuleDefinitionFile=".\MaxNifImport.def"
+				ProgramDatabaseFile=".\Release\MaxNifImport.pdb"
+				SubSystem="2"
+				SetChecksum="true"
+				BaseAddress="0x16860000"
+				ImportLibrary=".\Release\MaxNifImport.lib"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCManifestTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCAppVerifierTool"
+			/>
+			<Tool
+				Name="VCWebDeploymentTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+				CommandLine="if exist &quot;C:\3dsmax8\plugcfg&quot; (&#x0D;&#x0A;if not exist &quot;C:\3dsmax8\plugcfg\MaxNifTools.ini&quot; (&#x0D;&#x0A;copy &quot;$(ProjectDir)MaxNifTools.ini&quot; &quot;C:\3dsmax8\plugcfg\MaxNifTools.ini&quot;&#x0D;&#x0A;)&#x0D;&#x0A;)&#x0D;&#x0A;"
+			/>
+		</Configuration>
+		<Configuration
+			Name="Debug|Win32"
+			OutputDirectory=".\Debug"
+			IntermediateDirectory=".\Debug"
+			ConfigurationType="2"
+			InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
+			UseOfMFC="0"
+			ATLMinimizesCRunTimeLibraryUsage="false"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+				PreprocessorDefinitions="_DEBUG"
+				MkTypLibCompatible="true"
+				SuppressStartupBanner="true"
+				TargetEnvironment="1"
+				TypeLibraryName=".\Debug\MaxNifImport.tlb"
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				AdditionalOptions="/LD "
+				Optimization="0"
+				AdditionalIncludeDirectories=""
+				PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;USE_NIFLIB_TEMPLATE_HELPERS;_CRT_SECURE_NO_DEPRECATE;USE_NIFLIB_TEMPLATE_HELPERS;_USE_MATH_DEFINES"
+				GeneratePreprocessedFile="0"
+				ExceptionHandling="2"
+				RuntimeLibrary="1"
+				ForceConformanceInForLoopScope="false"
+				UsePrecompiledHeader="0"
+				PrecompiledHeaderFile=".\Debug\MaxNifImport.pch"
+				AssemblerListingLocation=".\Debug\"
+				ObjectFile=".\Debug\"
+				ProgramDataBaseFileName=".\Debug\"
+				WarningLevel="3"
+				SuppressStartupBanner="true"
+				DebugInformationFormat="4"
+				CompileAs="0"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+				PreprocessorDefinitions="_DEBUG"
+				Culture="1033"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLinkerTool"
+				AdditionalOptions="/MACHINE:I386"
+				AdditionalDependencies="odbc32.lib odbccp32.lib comctl32.lib shlwapi.lib core.lib geom.lib gfx.lib mesh.lib maxutil.lib maxscrpt.lib paramblk2.lib biped.lib bmm.lib niflibd.lib"
+				OutputFile="C:\3dsmax8\plugins\MaxNifImport.dli"
+				LinkIncremental="2"
+				SuppressStartupBanner="true"
+				AdditionalLibraryDirectories="C:\3dsmax8\maxsdk\lib"
+				IgnoreDefaultLibraryNames="msvcrtd.lib"
+				ModuleDefinitionFile=".\MaxNifImport.def"
+				GenerateDebugInformation="true"
+				ProgramDatabaseFile=".\Debug\MaxNifImport.pdb"
+				SubSystem="2"
+				BaseAddress="0x16860000"
+				ImportLibrary=".\Debug\MaxNifImport.lib"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCManifestTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCAppVerifierTool"
+			/>
+			<Tool
+				Name="VCWebDeploymentTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+				CommandLine="if exist &quot;C:\3dsmax8\plugcfg&quot; (&#x0D;&#x0A;if not exist &quot;C:\3dsmax8\plugcfg\MaxNifTools.ini&quot; (&#x0D;&#x0A;copy &quot;$(ProjectDir)MaxNifTools.ini&quot; &quot;C:\3dsmax8\plugcfg\MaxNifTools.ini&quot;&#x0D;&#x0A;)&#x0D;&#x0A;)&#x0D;&#x0A;"
+			/>
+		</Configuration>
+	</Configurations>
+	<References>
+	</References>
+	<Files>
+		<Filter
+			Name="Source Files"
+			Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+			>
+			<File
+				RelativePath=".\DllEntry.cpp"
+				>
+			</File>
+			<File
+				RelativePath=".\MaxNifImport.def"
+				>
+			</File>
+			<File
+				RelativePath=".\stdafx.cpp"
+				>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						UsePrecompiledHeader="1"
+					/>
+				</FileConfiguration>
+			</File>
+		</Filter>
+		<Filter
+			Name="Header Files"
+			Filter="h;hpp;hxx;hm;inl"
+			>
+			<File
+				RelativePath=".\niutils.h"
+				>
+			</File>
+			<File
+				RelativePath=".\objectParams.h"
+				>
+			</File>
+			<File
+				RelativePath=".\resource.h"
+				>
+			</File>
+			<File
+				RelativePath=".\stdafx.h"
+				>
+			</File>
+		</Filter>
+		<Filter
+			Name="Resource Files"
+			Filter="ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+			>
+			<File
+				RelativePath=".\MaxNifImport.rc"
+				>
+			</File>
+		</Filter>
+		<Filter
+			Name="Importer"
+			>
+			<File
+				RelativePath=".\AppSettings.cpp"
+				>
+			</File>
+			<File
+				RelativePath=".\AppSettings.h"
+				>
+			</File>
+			<File
+				RelativePath=".\MaxNifImport.cpp"
+				>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						UsePrecompiledHeader="2"
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath=".\MaxNifImport.h"
+				>
+			</File>
+			<File
+				RelativePath=".\niutils.cpp"
+				>
+			</File>
+		</Filter>
+		<File
+			RelativePath=".\MaxNifImport_Readme.txt"
+			>
+		</File>
+		<File
+			RelativePath=".\MaxNifTools.ini"
+			>
+		</File>
+	</Files>
+	<Globals>
+	</Globals>
+</VisualStudioProject>
diff --git a/MaxNifImport/MaxNifImport_Readme.txt b/MaxNifImport/MaxNifImport_Readme.txt
new file mode 100644
index 0000000..6026a04
--- /dev/null
+++ b/MaxNifImport/MaxNifImport_Readme.txt
@@ -0,0 +1,75 @@
+3ds Max Nif Importer v0.1
+
+=========================
+ABOUT
+=========================
+This is an importer for the NIF file format for 3ds Max 8.
+
+It uses the NIF File Format Library and Tools library.
+
+See the plugin INI file for advanced configuration.
+
+=========================
+INSTALL
+=========================
+Extract file to 3dsmax8 installation directory.
+
+Edit the MaxNifTools.ini file to adjust paths and other settings.
+
+=========================
+REQUIREMENTS
+=========================
+3ds Max 8
+
+=========================
+UNINSTALLING
+=========================
+Delete the installed files from 3dsmax directory. See the list below in the 
+Installed Files section.
+
+=========================
+CHANGE LIST
+=========================
+v 0.1
+ o Initial Revision
+
+=========================
+KNOWN ISSUES
+=========================
+None
+
+=========================
+INSTALLED FILES
+=========================
+MaxNifImport_Readme.txt
+plugins\MaxNifImport.dli
+plugcfg\MaxNifTools.ini
+
+
+=========================
+WEBSITES
+=========================
+http://niftools.sourceforge.net/forum/index.php
+http://niftools.sourceforge.net/forum/viewforum.php?f=6&sid=a7b7b59ba39233760ac22590ea8382e1
+
+
+=========================
+COPYRIGHT
+=========================
+Copyright (c) 2006, NIF File Format Library and Tools. All rights reserved.
+
+=========================
+LEGAL
+=========================
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/MaxNifImport/MaxNifTools.ini b/MaxNifImport/MaxNifTools.ini
new file mode 100644
index 0000000..71bd9d8
--- /dev/null
+++ b/MaxNifImport/MaxNifTools.ini
@@ -0,0 +1,81 @@
+;
+; Niftools Max tools configuration file
+;
+[System]
+; ShortDescription used in the 
+ShortDescription=Netimmerse/Gamebryo
+
+; KnownApplications - Used to indicate which sections in the ini file point 
+;    to "Applications" which have their own settings in a section below.
+KnownApplications=Oblivion;Morrowind;Civ4
+
+; Reparse the Applications (and therefore Texture directory cache) on every import/export
+Reparse=0
+
+[MaxNifImport]
+; Current Application to get setting/directory information from.  Should match KnownApps above
+CurrentApp=Oblivion
+; Wildcard to use on all NiNodes to test whether a biped may be used. Default: Bip*
+SkeletonCheck=Bip*
+; Use Bones or Biped.  Biped is broken right now so use bones or nothing.  Default: 0
+UseBiped=0
+; Flip the V coordinate. Default: 1
+FlipUVTextures=1
+; Show Textures in the Viewport. Default: 1
+ShowTextures=1
+; Enable AutoSmooth on meshes. Default: 1
+EnableAutoSmooth=1
+; AutoSmooth angle. Default: 30
+AutoSmoothAngle=30.0
+; Remove Double/Illegal faces on meshes on import
+RemoveDoubleFaces=1
+RemoveIllegalFaces=1
+
+[BipedImport]
+; Biped Height. Default: 131.90
+BipedHeight=131.90
+; Biped initial rotation. Default: 90.0
+BipedAngle=90.0
+; Biped Ankle Attach. Default: 0.2
+BipedAnkleAttach=0.2
+; Use Triangle Pelvis.  Default: 0
+BipedTrianglePelvis=0
+; Remove unused bones from the biped on import of a mesh. Default: 1
+RemoveUnusedImportedBones=1
+
+
+;; [Applications]
+; TextureRootPaths - Semicolon separated list of base directories to look for files
+; TextureExtensions - Extensions to scan for when using TextureSearchPaths
+; TextureSeachPaths - Semicolon separated list of directories to look recursively for files in
+; Skeleton - Default Skeleton to use when importing oblivion meshes
+
+[Oblivion]
+; Installation Folder
+InstallPath=[HKLM\SOFTWARE\Bethesda Softworks\Oblivion]=@"Installed Path"
+RootPath=${InstallPath}\Data
+ExtractFolder=E:\Nifs\Oblivion
+MeshRootPath=${ExtractFolder}\Oblivion - Meshes
+TextureRootPath=${ExtractFolder}\Oblivion - Textures - Compressed
+
+Skeleton=${MeshRootPath}\meshes\characters\_male\skeleton.nif
+TextureRootPaths=${RootPath};${TextureRootPath}
+TextureExtensions=.dds;
+TextureSearchPaths=${RootPath}\Textures;${TextureRootPath}\Textures\Characters;${TextureRootPath}\Textures\Armor
+
+[Morrowind]
+InstallPath=[HKLM\SOFTWARE\Bethesda Softworks\Morrowind]=@"Installed Path"
+RootPath=${InstallPath}\Data Files
+ExtractFolder=E:\Nifs\Morrowind\Data Files
+TextureRootPaths=${RootPath}\Textures;${ExtractFolder}\Textures
+TextureExtensions=.tga;
+TextureSearchPaths=${RootPath}\Textures
+
+[Civ4]
+InstallPath=[HKEY_LOCAL_MACHINE\SOFTWARE\Firaxis Games\Sid Meier's Civilization 4]=@"INSTALLDIR"
+RootPath=
+ExtractFolder=C:\Projects\Main\Civilization4\assets
+TextureRootPaths=$(ExtractFolder)\art\shared\
+TextureExtensions=.dds;.bmp
+TextureSearchPaths=
+
diff --git a/MaxNifImport/niutils.cpp b/MaxNifImport/niutils.cpp
new file mode 100644
index 0000000..f730127
--- /dev/null
+++ b/MaxNifImport/niutils.cpp
@@ -0,0 +1,511 @@
+/**********************************************************************
+*<
+FILE: NIUtils.cpp
+
+DESCRIPTION:	NifImporter Utilities
+
+CREATED BY: tazpn (Theo)
+
+HISTORY: 
+
+*>	Copyright (c) 2006, All Rights Reserved.
+**********************************************************************/
+#include "stdafx.h"
+#include "niutils.h"
+#include <string.h>
+#include <ctype.h>
+#include <locale.h>
+#include <malloc.h>
+#include <sstream>
+#include <modstack.h>
+#include <iskin.h>
+#include <cs/Biped8Api.h>
+#include <cs/OurExp.h> 
+using namespace std;
+using namespace Niflib;
+
+// Macro to create a dynamically allocated strdup on the stack
+#define STRDUPA(p) (_tcscpy((TCHAR*)alloca((_tcslen(p)+1)*sizeof(*p)),p))
+
+// sprintf for TSTR without having to worry about buffer size.
+TSTR FormatText(const TCHAR* format,...)
+{
+   TCHAR buffer[512];
+   TSTR text;
+   va_list args;
+   va_start(args, format);
+   int nChars = _vsntprintf(buffer, _countof(buffer), format, args);
+   if (nChars != -1) {
+      text = buffer;
+   } else {
+      size_t Size = _vsctprintf(format, args);
+      text.Resize(Size);
+      nChars = _vsntprintf(text.data(), Size, format, args);
+   }
+   va_end(args);
+   return text;
+}
+
+// sprintf for std::string without having to worry about buffer size.
+std::string FormatString(const TCHAR* format,...)
+{
+   TCHAR buffer[512];
+   std::string text;
+   va_list args;
+   va_start(args, format);
+   int nChars = _vsntprintf(buffer, _countof(buffer), format, args);
+   if (nChars != -1) {
+      text = buffer;
+   } else {
+      size_t Size = _vsctprintf(format, args);
+      TCHAR* pbuf = (TCHAR*)_alloca(Size);
+      nChars = _vsntprintf(pbuf, Size, format, args);
+      text = pbuf;
+   }
+   va_end(args);
+   return text;
+}
+
+// Tokenize a string using strtok and return it as a stringlist
+stringlist TokenizeString(LPCTSTR str, LPCTSTR delims)
+{
+   stringlist values;
+   LPTSTR buf = STRDUPA(str);
+   for (LPTSTR p = _tcstok(buf, delims); p && *p; p = _tcstok(NULL, delims)){
+      values.push_back(string(p));
+   }
+   return values;
+}
+
+// Parse and ini file section and return the results as s NameValueCollection.
+NameValueCollection ReadIniSection(LPCTSTR Section, LPCTSTR iniFileName )
+{
+   NameValueCollection map;
+   DWORD len = 2048 * sizeof(TCHAR);
+   LPTSTR buf = (LPTSTR)calloc(len+2, 1);
+   while(NULL != buf) {
+      DWORD rlen = GetPrivateProfileSection(Section, buf, len, iniFileName);
+      if (rlen != (len-2)) break;
+      len += 2;
+      buf = (LPTSTR)realloc(buf, len);
+   }
+   if (NULL != buf) {
+      for (LPTSTR line = buf, next = line + strlen(line) + 1; *line; line = next, next = line + strlen(line) + 1){
+         Trim(line);
+         if (line[0] == ';' || line[0] == 0) 
+            continue;
+         if (LPTSTR equals = _tcschr(line, TEXT('='))){
+            *equals++ = 0;
+            Trim(line), Trim(equals);
+            map[string(line)] = string(equals);
+         }
+      }
+   }
+   return map;
+}
+
+// Expand Qualifiers in string using a ${Name} syntax.  Name will be looked up in the
+//    NameValueCollection and expand in place.  Missing names will expand to empty.
+string ExpandQualifiers(const string& src, const NameValueCollection& map)
+{
+   string value;
+   bool hasPercent = false;
+   string::size_type end = src.length();
+   value.reserve(src.length());
+   for (string::size_type i = 0; i < end; ++i) {
+      if (src[i] == TEXT('$')) {
+         if (++i < end) {
+            if (src[i] == TEXT('$')){
+               value.append(1, src[i]);
+            } else if (src[i] == TEXT('{')) {
+               string::size_type term = src.find_first_of(TEXT('}'), i);
+               if (term == string::npos) {
+                  i = end;
+               } else {
+                  string key = src.substr(i+1, term-i-1);
+                  NameValueCollection::const_iterator kvp = map.find(key);
+                  if (kvp != map.end()) {
+                     value.append(kvp->second);
+                  } 
+                  i = term;
+               }
+            } else if (src[i] == TEXT('(')) {
+               string::size_type term = src.find_first_of(TEXT(')'), i);
+               if (term == string::npos) {
+                  i = end;
+               } else {
+                  string key = src.substr(i+1, term-i-1);
+                  NameValueCollection::const_iterator kvp = map.find(key);
+                  if (kvp != map.end()) {
+                     value.append(kvp->second);
+                  } 
+                  i = term;
+               }
+            }
+         } else {
+            value.append(1, TEXT('$'));
+         }
+      } else {
+         value.append(1, src[i]);
+      }
+   }
+   return value;
+}
+
+// Call ExpandEnvironmentStrings but with std string instead
+string ExpandEnvironment(const string& src)
+{
+   DWORD Size = ExpandEnvironmentStrings(src.c_str(), NULL, 0);
+   if (TCHAR* pbuf = (TCHAR*)_alloca(Size+2)) {
+      pbuf[0] = 0;
+      ExpandEnvironmentStrings(src.c_str(), pbuf, Size+2);
+      return string(pbuf);
+   }
+   return src;
+}
+
+
+// Helper struct and method for dealing with standard registry handles
+struct str2hdl
+{
+   const TCHAR *str;
+   HANDLE key;
+};
+const static struct str2hdl RegKeyMap[] = {
+   {TEXT("HKLM"), HKEY_LOCAL_MACHINE},
+   {TEXT("HKCU"), HKEY_CURRENT_USER},
+   {TEXT("HKCR"), HKEY_CLASSES_ROOT},
+   {TEXT("HKEY_LOCAL_MACHINE"), HKEY_LOCAL_MACHINE},
+   {TEXT("HKEY_CURRENT_USER"), HKEY_CURRENT_USER},
+   {TEXT("HKEY_CLASSES_ROOT"), HKEY_CLASSES_ROOT},
+};
+static HANDLE GetRegKey(LPCTSTR key) {
+   for (int i=0; i<_countof(RegKeyMap); ++i)
+      if (0 == _tcscmp(RegKeyMap[i].str, key))
+         return RegKeyMap[i].key;
+   return 0;
+}
+
+// Returns value from indirect source
+//  Currently only supports registry string values using '[HKEY\Key]=@"Value"' 
+//  where  HKEY is HKLM,HKCU,HKCR  
+//         Key is the registry key to lookup
+//         Value is the data value to lookup.  
+string GetIndirectValue(LPCSTR path)
+{
+   if (!path || !*path)
+      return string();
+   string value;
+   LPTSTR p = STRDUPA(path);
+   Trim(p);
+   if (*p == '['){
+      LPTSTR end = _tcschr(++p, ']');
+      if (end != NULL) {
+         *end++ = 0;
+         // Advance unsafely past unnecessary qualifiers
+         LPTSTR valueName = end;
+         end = valueName + strlen(end) - 1;
+         if (*valueName == '=') ++valueName;
+         if (*valueName == '@') ++valueName;
+         if (*valueName == '\"' || *valueName == '\'') ++valueName;
+         if (*end == '\"' || *end == '\'') *end-- = 0;
+         Trim(valueName);
+
+         LPTSTR keyEnd = _tcschr(p, '\\');
+         if (keyEnd != NULL) {
+            *keyEnd++ = 0;
+            HANDLE hRoot = GetRegKey(p);
+            if (hRoot != 0) {
+               HKEY hKey = NULL;
+               if (ERROR_SUCCESS == RegOpenKeyEx((HKEY)hRoot, keyEnd, 0, KEY_READ, &hKey)) {
+                  BYTE buffer[MAX_PATH*sizeof(*path)];
+                  DWORD dwLen = _countof(buffer);
+                  if (ERROR_SUCCESS == RegQueryValueEx(hKey, valueName, NULL, NULL, (LPBYTE)buffer, &dwLen) && dwLen > 0) {
+                     value = (TCHAR*)buffer;
+                  }
+                  RegCloseKey(hKey);
+               }
+            }
+         }
+      }
+   } else {
+      value = path;
+   }
+   return value;
+}
+
+// Original Source: Jack Handy www.codeproject.com
+int wildcmp(const TCHAR *wild, const TCHAR *string) {
+   const TCHAR *cp, *mp;
+
+   while ((*string) && (*wild != '*')) {
+      if ((*wild != *string) && (*wild != '?')) {
+         return 0;
+      }
+      wild++;
+      string++;
+   }
+
+   while (*string) {
+      if (*wild == '*') {
+         if (!*++wild) {
+            return 1;
+         }
+         mp = wild;
+         cp = string+1;
+      } else if ((*wild == *string) || (*wild == '?')) {
+         wild++;
+         string++;
+      } else {
+         wild = mp;
+         string = cp++;
+      }
+   }
+
+   while (*wild == '*') {
+      wild++;
+   }
+   return !*wild;
+}
+
+// Same as above but case insensitive
+int wildcmpi(const TCHAR *wild, const TCHAR *string) {
+   const TCHAR *cp, *mp;
+   int f,l;
+   while ((*string) && (*wild != '*')) {
+      f = _totlower( (_TUCHAR)(*string) );
+      l = _totlower( (_TUCHAR)(*wild) );
+      if ((f != l) && (l != '?')) {
+         return 0;
+      }
+      wild++, string++;
+   }
+   while (*string) {
+      if (*wild == '*') {
+         if (!*++wild) return 1;
+         mp = wild, cp = string+1;
+      } else {
+         f = _totlower( (_TUCHAR)(*string) );
+         l = _totlower( (_TUCHAR)(*wild) );
+         if ((f == l) || (l == '?')) {
+            wild++, string++;
+         } else {
+            wild = mp, string = cp++;
+         }
+      }
+   }
+   while (*wild == '*') wild++;
+   return !*wild;
+}
+
+//! Renames Max Node if it exists
+void RenameNode(Interface *gi, LPCTSTR SrcName, LPCTSTR DstName)
+{
+   INode *node = gi->GetINodeByName(SrcName);
+   if (node != NULL) node->SetName(const_cast<LPTSTR>(DstName));
+}
+
+// Locate a TriObject in an Object if it exists
+TriObject* GetTriObject(Object *o)
+{
+   if (o && o->CanConvertToType(triObjectClassID))
+      return (TriObject *)o->ConvertToType(0, triObjectClassID);
+   while (o->SuperClassID() == GEN_DERIVOB_CLASS_ID && o)
+   {
+      IDerivedObject* dobj = (IDerivedObject *)(o);
+      o = dobj->GetObjRef();
+      if (o && o->CanConvertToType(triObjectClassID))
+         return (TriObject *)o->ConvertToType(0, triObjectClassID);
+   }
+   return NULL;
+}
+
+// Get or Create the Skin Modifier
+Modifier *GetSkin(INode *node)
+{
+   Object* pObj = node->GetObjectRef();
+   if (!pObj) return NULL;
+   while (pObj->SuperClassID() == GEN_DERIVOB_CLASS_ID)
+   {
+      IDerivedObject* pDerObj = (IDerivedObject *)(pObj);
+      int Idx = 0;
+      while (Idx < pDerObj->NumModifiers())
+      {
+         // Get the modifier. 
+         Modifier* mod = pDerObj->GetModifier(Idx);
+         if (mod->ClassID() == SKIN_CLASSID)
+         {
+            // is this the correct Physique Modifier based on index?
+            return mod;
+         }
+         Idx++;
+      }
+      pObj = pDerObj->GetObjRef();
+   }
+
+   IDerivedObject *dobj = CreateDerivedObject(node->GetObjectRef());
+
+   //create a skin modifier and add it
+   Modifier *skinMod = (Modifier*) CreateInstance(OSM_CLASS_ID, SKIN_CLASSID);
+   dobj->SetAFlag(A_LOCK_TARGET);
+   dobj->AddModifier(skinMod);
+   dobj->ClearAFlag(A_LOCK_TARGET);
+   node->SetObjectRef(dobj);
+   return skinMod;
+}
+
+// Set Position and Rotation on a standard controller will need to handle bipeds
+//   Always in World Transform coordinates
+void PositionAndRotateNode(INode *n, Point3 p, Quat& q, TimeValue t)
+{
+   if (Control *c = n->GetTMController()) {
+
+      // Bipeds are special.  And will crash if you dont treat them with care
+      if ( (c->ClassID() == BIPSLAVE_CONTROL_CLASS_ID) 
+         ||(c->ClassID() == BIPBODY_CONTROL_CLASS_ID) 
+         ||(c->ClassID() == FOOTPRINT_CLASS_ID))
+      {
+         // Get the Biped Export Interface from the controller 
+         //IBipedExport *BipIface = (IBipedExport *) c->GetInterface(I_BIPINTERFACE);
+         IOurBipExport *BipIface = (IOurBipExport *) c->GetInterface(I_OURINTERFACE);
+         BipIface->SetBipedRotation(q, t, n, 0/*???*/);
+         BipIface->SetBipedPosition(p, t, n);
+      }
+      else
+      {
+         if (Control *rotCtrl = c->GetRotationController())
+            rotCtrl->SetValue(t, &q, 1, CTRL_ABSOLUTE);
+         if (Control *posCtrl = c->GetPositionController())
+            posCtrl->SetValue(t, &p, 1, CTRL_ABSOLUTE);
+      }
+   }
+}
+
+// Search NiNode collection for a specific name
+NiNodeRef FindNodeByName( const vector<NiNodeRef>& blocks, const string& name )
+{
+   for (vector<NiNodeRef>::const_iterator itr = blocks.begin(), end = blocks.end(); itr != end; ++itr)
+   {
+      const NiNodeRef& block = (*itr);
+      if (name == block->GetName())
+         return block;
+   }
+   return NiNodeRef();
+}
+
+// Search NiNode collection names that match a wildcard 
+vector<NiNodeRef> SelectNodesByName( const vector<NiNodeRef>& blocks, LPCTSTR match)
+{
+   vector<NiNodeRef> nodes;
+   for (vector<NiNodeRef>::const_iterator itr = blocks.begin(), end = blocks.end(); itr != end; ++itr)
+   {
+      const NiNodeRef& block = (*itr);
+      if (wildcmpi(match, block->GetName().c_str()))
+         nodes.insert(nodes.end(), block);
+   }
+   return nodes;
+}
+
+// Count number of NiNodes that match a wildcard 
+int CountNodesByName( const vector<NiNodeRef>& blocks, LPCTSTR match )
+{
+   int count = 0;
+   for (vector<NiNodeRef>::const_iterator itr = blocks.begin(), end = blocks.end(); itr != end; ++itr) {
+      const NiNodeRef& block = (*itr);
+      if (wildcmpi(match, block->GetName().c_str()))
+         ++count;
+   }
+   return count;
+}
+
+// Get a vector of names from an NiNode vector
+vector<string> GetNamesOfNodes( const vector<Niflib::NiNodeRef>& nodes )
+{
+   vector<string> slist;
+   for (vector<NiNodeRef>::const_iterator itr = nodes.begin(), end = nodes.end(); itr != end; ++itr) {
+      const NiNodeRef& block = (*itr);
+      slist.push_back(block->GetName());
+   }
+   return slist;
+}
+
+// Recursively search through directories applying a filter on what to return
+template <typename FileMatch>
+void BuildFileNameMap(NameValueCollection & collection, const TCHAR *root, const TCHAR *path, FileMatch pred)
+{
+   TCHAR buffer[MAX_PATH], buffer2[MAX_PATH], search[MAX_PATH];
+   WIN32_FIND_DATA FindFileData;
+   HANDLE hFind;
+   ZeroMemory(&FindFileData, sizeof(FindFileData));
+   if (path == NULL || path[0] == 0)
+      return;
+   PathCanonicalize(search, path);
+   PathAddBackslash(search);
+   _tcscat(search, "*");
+
+   hFind = FindFirstFile(search, &FindFileData);
+   if (hFind != INVALID_HANDLE_VALUE) {
+      stringlist list;
+      for (BOOL ok = TRUE ; ok ; ok = FindNextFile(hFind, &FindFileData)) {
+         if (FindFileData.cFileName[0] == '.' || (FindFileData.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM)))
+            continue;
+         if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+            PathCombine(buffer, path, FindFileData.cFileName);
+            PathAddBackslash(buffer);
+            list.push_back(buffer);
+         } else {
+            if (pred(FindFileData.cFileName)) {
+               if (collection.find(FindFileData.cFileName) == collection.end()) {
+                  PathCombine(buffer, path, FindFileData.cFileName);
+                  GetLongPathName(buffer, buffer, MAX_PATH);
+                  PathRemoveExtension(FindFileData.cFileName);
+                  PathRelativePathTo(buffer2, root, FILE_ATTRIBUTE_DIRECTORY, buffer, FILE_ATTRIBUTE_NORMAL);
+                  TCHAR *p = buffer2; while (*p == '\\') ++p;
+                  collection.insert(KeyValuePair(FindFileData.cFileName, p));					
+               }
+            }
+         }
+      }
+      FindClose(hFind);
+      for (stringlist::iterator itr = list.begin(), end = list.end(); itr != end; ++itr) {
+         BuildFileNameMap(collection, root, (*itr).c_str(), pred);
+      }
+   }
+}
+// Implementation for BuildFileNameMap which will search for a specific set of extensions
+struct ExtensionMatch : public std::unary_function<LPCTSTR, bool>
+{
+   stringlist extns;
+   ExtensionMatch(string extnlist) {
+      extns = TokenizeString(extnlist.c_str(), ";");
+   }
+   ExtensionMatch(const stringlist& extnlist) : extns(extnlist) {
+   }
+   bool operator()(LPCTSTR name) const {
+      LPCSTR ext = PathFindExtension(name);
+      for (stringlist::const_iterator itr = extns.begin(), end = extns.end(); itr != end; ++itr) {
+         if (0 == _tcsicmp(ext, (*itr).c_str()))
+            return true;
+      }
+      return false;      
+   }
+};
+
+// Run through the search paths and add them to the image collection
+void FindImages(NameValueCollection& images, const string& rootPath, const stringlist& searchpaths, const stringlist& extensions)
+{
+   ExtensionMatch ddsMatch(extensions);
+   for (stringlist::const_iterator itr = searchpaths.begin(), end = searchpaths.end(); itr != end; ++itr) {
+      if (PathIsRelative((*itr).c_str()))
+      {
+         TCHAR texPath[MAX_PATH];
+         PathCombine(texPath, rootPath.c_str(), (*itr).c_str());
+         PathAddBackslash(texPath);
+         BuildFileNameMap(images, rootPath.c_str(), texPath, ddsMatch);
+      }
+      else
+      {
+         BuildFileNameMap(images, rootPath.c_str(), (*itr).c_str(), ddsMatch);
+      }
+   }
+}
diff --git a/MaxNifImport/niutils.h b/MaxNifImport/niutils.h
new file mode 100644
index 0000000..2408100
--- /dev/null
+++ b/MaxNifImport/niutils.h
@@ -0,0 +1,221 @@
+/**********************************************************************
+*<
+FILE: NIUtils.h
+
+DESCRIPTION:	NifImporter Utilities
+
+CREATED BY: tazpn (Theo)
+
+HISTORY: 
+
+INFO: See Implementation for minimalist comments
+
+*>	Copyright (c) 2006, All Rights Reserved.
+**********************************************************************/
+#ifndef _NIUTILS_H_
+#define _NIUTILS_H_
+
+#ifndef _WINDOWS_
+#  include <windows.h>
+#endif
+#include <tchar.h>
+#include <string>
+#include <map>
+#include <vector>
+#include <list>
+#include <map>
+
+// Max Headers
+#include <Max.h>
+#include <strclass.h>
+#include <color.h>
+
+// Niflib Headers
+#include <obj\NiNode.h>
+#include <nif_math.h>
+
+#ifndef _countof
+#define _countof(x) (sizeof(x)/sizeof((x)[0]))
+#endif
+
+extern int wildcmp(const TCHAR *wild, const TCHAR *string);
+extern int wildcmpi(const TCHAR *wild, const TCHAR *string);
+
+// Trim whitespace before and after a string
+inline TCHAR *Trim(TCHAR*&p) { 
+   while(_istspace(*p)) *p++ = 0; 
+   TCHAR *e = p + _tcslen(p) - 1;
+   while (e > p && _istspace(*e)) *e-- = 0;
+   return p;
+}
+
+// Case insensitive string equivalence test for collections
+struct ltstr
+{
+   bool operator()(const TCHAR* s1, const TCHAR* s2) const
+   { return _tcsicmp(s1, s2) < 0; }
+
+   bool operator()(const std::string& s1, const std::string& s2) const
+   { return s1.compare(s2) < 0; }
+
+   bool operator()(const std::string& s1, const TCHAR * s2) const
+   { return s1.compare(s2) < 0; }
+
+   bool operator()(const TCHAR * s1, const std::string& s2) const
+   { return s2.compare(s1) >= 0; }
+};
+
+// Case insensitive string equivalence but numbers are sorted together
+struct NumericStringEquivalence
+{
+   bool operator()(const TCHAR* s1, const TCHAR* s2) const
+   { return numstrcmp(s1, s2) < 0; }
+
+   bool operator()(const std::string& s1, const TCHAR* s2) const
+   { return numstrcmp(s1.c_str(), s2) < 0; }
+
+   bool operator()(const TCHAR* s1, const std::string& s2) const
+   { return numstrcmp(s1, s2.c_str()) < 0; }
+
+   bool operator()(const std::string& s1, const std::string& s2) const
+   { return numstrcmp(s1.c_str(), s2.c_str()) < 0; }
+
+   static int numstrcmp(const TCHAR *str1, const TCHAR *str2)
+   {
+      TCHAR *p1, *p2;
+      int c1, c2, lcmp;
+      for(;;)
+      {
+         c1 = tolower(*str1), c2 = tolower(*str2);
+         if ( c1 == 0 || c2 == 0 )
+            break;
+         else if (isdigit(c1) && isdigit(c2))
+         {			
+            lcmp = strtol(str1, &p1, 10) - strtol(str2, &p2, 10);
+            if ( lcmp == 0 )
+               lcmp = (p2 - str2) - (p1 - str1);
+            if ( lcmp != 0 )
+               return (lcmp > 0 ? 1 : -1);
+            str1 = p1, str2 = p2;
+         }
+         else
+         {
+            lcmp = (c1 - c2);
+            if (lcmp != 0)
+               return (lcmp > 0 ? 1 : -1);
+            ++str1, ++str2;
+         }
+      }
+      lcmp = (c1 - c2);
+      return ( lcmp < 0 ) ? -1 : (lcmp > 0 ? 1 : 0);
+   }
+};
+
+// Generic IniFile reading routine
+template<typename T>
+inline T GetIniValue(LPCTSTR Section, LPCTSTR Setting, T Default, LPCTSTR iniFileName){
+   T v;
+   TCHAR buffer[1024];
+   stringstream sstr;
+   sstr << Default;
+   buffer[0] = 0;
+   if (0 < GetPrivateProfileString(Section, Setting, sstr.str().c_str(), buffer, sizeof(buffer), iniFileName)){
+      stringstream sstr(buffer);
+      sstr >> v;
+      return v;
+   }
+   return Default;
+}
+
+// Specific override for int values
+template<>
+inline int GetIniValue<int>(LPCTSTR Section, LPCTSTR Setting, int Default, LPCTSTR iniFileName){
+   return GetPrivateProfileInt(Section, Setting, Default, iniFileName);
+}
+
+// Specific override for string values
+template<>
+inline std::string GetIniValue<std::string>(LPCTSTR Section, LPCTSTR Setting, std::string Default, LPCTSTR iniFileName){
+   TCHAR buffer[1024];
+   buffer[0] = 0;
+   if (0 < GetPrivateProfileString(Section, Setting, Default.c_str(), buffer, sizeof(buffer), iniFileName)){
+      return std::string(buffer);
+   }
+   return Default;
+}
+
+// Specific override for TSTR values
+template<>
+inline TSTR GetIniValue<TSTR>(LPCTSTR Section, LPCTSTR Setting, TSTR Default, LPCTSTR iniFileName){
+   TCHAR buffer[1024];
+   buffer[0] = 0;
+   if (0 < GetPrivateProfileString(Section, Setting, Default.data(), buffer, sizeof(buffer), iniFileName)){
+      return TSTR(buffer);
+   }
+   return Default;
+}
+
+// Generic IniFile reading routine
+template<typename T>
+inline void SetIniValue(LPCTSTR Section, LPCTSTR Setting, T value, LPCTSTR iniFileName){
+   stringstream sstr;
+   sstr << value;
+   WritePrivateProfileString(Section, Setting, sstr.str().c_str(), iniFileName);
+}
+
+// Specific override for string values
+template<>
+inline void SetIniValue<std::string>(LPCTSTR Section, LPCTSTR Setting, std::string value, LPCTSTR iniFileName){
+   WritePrivateProfileString(Section, Setting, value.c_str(), iniFileName);
+}
+
+// Specific override for TSTR values
+template<>
+inline void SetIniValue<TSTR>(LPCTSTR Section, LPCTSTR Setting, TSTR value, LPCTSTR iniFileName){
+   WritePrivateProfileString(Section, Setting, value.data(), iniFileName);
+}
+
+// Common collections that I use
+typedef std::map<std::string, std::string, ltstr> NameValueCollection;
+typedef std::pair<std::string, std::string> KeyValuePair;
+typedef std::list<std::string> stringlist;
+
+extern TSTR FormatText(const TCHAR* format,...);
+extern std::string FormatString(const TCHAR* format,...);
+
+extern stringlist TokenizeString(LPCTSTR str, LPCTSTR delims);
+extern string GetIndirectValue(LPCSTR path);
+extern NameValueCollection ReadIniSection(LPCTSTR Section, LPCTSTR iniFileName );
+extern string ExpandQualifiers(const string& src, const NameValueCollection& map);
+extern string ExpandEnvironment(const string& src);
+
+extern void FindImages(NameValueCollection& images, const string& rootPath, const stringlist& searchpaths, const stringlist& extensions);
+
+extern void RenameNode(Interface *gi, LPCTSTR SrcName, LPCTSTR DstName);
+extern TriObject* GetTriObject(Object *o);
+extern Modifier *GetSkin(INode *node);
+extern void PositionAndRotateNode(INode *n, Point3 p, Quat& q, TimeValue t = 0);
+
+extern Niflib::NiNodeRef FindNodeByName( const vector<Niflib::NiNodeRef>& blocks, const string& name );
+extern std::vector<Niflib::NiNodeRef> SelectNodesByName( const vector<Niflib::NiNodeRef>& blocks, LPCTSTR match);
+extern int CountNodesByName( const vector<Niflib::NiNodeRef>& blocks, LPCTSTR match );
+extern std::vector<std::string> GetNamesOfNodes( const vector<Niflib::NiNodeRef>& blocks );
+
+// Simple conversion helpers
+static inline float TODEG(float x) { return x * 180.0f / PI; }
+static inline float TORAD(float x) { return x * PI / 180.0f; }
+
+static inline Color TOCOLOR(const Niflib::Color3& c3) {
+   return Color(c3.r, c3.g, c3.b);
+}
+
+static inline Matrix3 TOMATRIX3(const Niflib::Matrix44 &tm, bool invert = true){
+   Niflib::Vector3 pos; Niflib::Matrix33 rot; float scale;
+   tm.Decompose(pos, rot, scale);
+   Matrix3 m(rot.rows[0].data, rot.rows[1].data, rot.rows[2].data, Point3());
+   if (invert) m.Invert();
+   m.SetTrans(Point3(pos.x, pos.y, pos.z));
+   return m;
+}
+
+#endif // _NIUTILS_H_
\ No newline at end of file
diff --git a/MaxNifImport/objectParams.h b/MaxNifImport/objectParams.h
new file mode 100644
index 0000000..d0367e6
--- /dev/null
+++ b/MaxNifImport/objectParams.h
@@ -0,0 +1,336 @@
+/*===========================================================================*\
+ | 
+ |  FILE:	objectParams.h
+ |			Skeleton project and code for a Utility
+ |			3D Studio MAX R3.0
+ | 
+ |  AUTH:   Cleve Ard
+ |			Render Group
+ |			Copyright(c) Discreet 2000
+ |
+ |  HIST:	Started 9-8-00
+ | 
+\*===========================================================================*/
+
+#ifndef _OBJECTPARAMS_H_
+#define _OBJECTPARAMS_H_
+
+#include "maxscrpt/maxscrpt.h"
+#include "maxscrpt/Numbers.h"
+#include "maxscrpt/Name.h"
+#include "maxscrpt/ColorVal.h"
+#include "maxscrpt/MAXObj.h"
+#include "assert1.h"
+
+template <class T>
+class ConvertMAXScriptToC;
+inline Value* make_maxscript_value(bool v);
+inline Value* make_maxscript_value(int v);
+inline Value* make_maxscript_value(float v);
+inline Value* make_maxscript_value(COLORREF rgb);
+inline Value* make_maxscript_value(const Color& rgb);
+inline Value* make_maxscript_value(LPCTSTR str);
+inline Value* make_maxscript_value(ReferenceTarget* rtarg);
+
+// Not all of the paramters for materials and shaders
+// are published in the interfaces. ObjectParams class
+// is used to access the properties of objects the same
+// way the scriptor does, so you can get to properties.
+
+// Set the array parameter named name to the value at time t.
+template<class T>
+bool setMAXScriptValue(ReferenceTarget* obj, LPTSTR name, TimeValue t, T value, int tabIndex)
+{
+	bool rval = false;					// Return false if fails
+	assert(obj != NULL);
+	init_thread_locals();
+	push_alloc_frame();
+	two_value_locals(prop, result);		// Keep two local variables
+	save_current_frames();
+	trace_back_active = FALSE;
+
+	try {
+		// Get the name of the parameter and then retrieve
+		// the array holding the value we want to set.
+		vl.prop = Name::intern(name);
+		vl.result = MAXWrapper::get_property(obj, vl.prop, NULL);
+
+		// Make sure it is the right type.
+		if (vl.result != NULL && vl.result->tag == class_tag(MAXPB2ArrayParam)) {
+			// OK. Now we make sure the tabIndex is within the array bounds.
+			MAXPB2ArrayParam* array = static_cast<MAXPB2ArrayParam*>(vl.result);
+			if (array->pblock != NULL && array->pdef != NULL
+					&& tabIndex >= 0 && tabIndex < array->pblock->Count(array->pdef->ID)) {
+				// Set the value in the array.
+				array->pblock->SetValue(array->pdef->ID, 0, value, tabIndex);
+				rval = true;		// Succeeded
+			}
+		}
+	} catch ( ... ) {
+		// Failed.
+		clear_error_source_data();
+		restore_current_frames();
+		MAXScript_signals = 0;
+		if (progress_bar_up)
+			MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE;
+	}
+	pop_value_locals();
+	pop_alloc_frame();
+	return rval;
+}
+
+// Set the parameter. Cannot be an array entry
+template<class T>
+bool setMAXScriptValue(ReferenceTarget* obj, LPTSTR name, TimeValue t, T& value)
+{
+	bool rval = false;					// return false if fails
+	assert(obj != NULL);
+	init_thread_locals();
+	push_alloc_frame();
+	two_value_locals(prop, val);			// Keep two local variables
+	save_current_frames();
+	trace_back_active = FALSE;
+
+	try {
+		// Get the name and value to set
+		vl.prop = Name::intern(name);
+		vl.val = make_maxscript_value(value);
+
+		// Set the value.
+		Value* val = MAXWrapper::set_property(obj, vl.prop, vl.val);
+		if (val != NULL)
+			rval = true;				// Succeeded
+	} catch ( ... ) {
+		// Failed.
+		clear_error_source_data();
+		restore_current_frames();
+		MAXScript_signals = 0;
+		if (progress_bar_up)
+			MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE;
+	}
+	pop_value_locals();
+	pop_alloc_frame();
+	return rval;
+}
+
+// Get the parameter from an array
+template<class T>
+bool getMAXScriptValue(ReferenceTarget* obj, LPTSTR name, TimeValue t, T& value, int tabIndex)
+{
+	bool rval = false;					// Return false if fails
+	assert(obj != NULL);
+	init_thread_locals();
+	push_alloc_frame();
+	two_value_locals(prop, result);		// Keep two local variables
+	save_current_frames();
+	trace_back_active = FALSE;
+
+	try {
+		// Get the name and the array holding the roperty.
+		vl.prop = Name::intern(name);
+		vl.result = MAXWrapper::get_property(obj, vl.prop, NULL);
+
+		// Make sure it is they right type.
+		if (vl.result != NULL && is_tab_param(vl.result)) {
+			// OK. Now we make sure the tabIndex is within the array bounds.
+			MAXPB2ArrayParam* array = static_cast<MAXPB2ArrayParam*>(vl.result);
+			if (array->pblock != NULL && array->pdef != NULL
+					&& tabIndex >= 0 && tabIndex < array->pblock->Count(array->pdef->ID)) {
+				// Good. Get the value
+				array->pblock->GetValue(array->pdef->ID, 0, value, Interval(0,0), tabIndex);
+				rval = true;			// Succeeded
+			}
+		}
+	} catch ( ... ) {
+		// Failed.
+		clear_error_source_data();
+		restore_current_frames();
+		MAXScript_signals = 0;
+		if (progress_bar_up)
+			MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE;
+	}
+	pop_value_locals();
+	pop_alloc_frame();
+	return rval;
+}
+
+// Get the parameter
+template<class T>
+bool getMAXScriptValue(ReferenceTarget* obj, LPTSTR name, TimeValue t, T& value)
+{
+	bool rval = false;				// Return false if fails
+	assert(obj != NULL);
+	init_thread_locals();
+	push_alloc_frame();
+	two_value_locals(prop, result);	// Keep two local varaibles
+	save_current_frames();
+	trace_back_active = FALSE;
+
+	try {
+		// Get the name and the parameter value
+		vl.prop = Name::intern(name);
+		vl.result = MAXWrapper::get_property(obj, vl.prop, NULL);
+
+		// Make sure it is valid.
+		if (vl.result != NULL && vl.result != &undefined && vl.result != &unsupplied) {
+			// Convert to C++ type.
+			value = ConvertMAXScriptToC<T>::cvt(vl.result);
+			rval = true;				// Succeeded
+		}
+	} catch ( ... ) {
+		clear_error_source_data();
+		restore_current_frames();
+		MAXScript_signals = 0;
+		if (progress_bar_up)
+			MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE;
+	}
+	pop_value_locals();
+	pop_alloc_frame();
+	return rval;
+}
+
+// Get the parameter controller
+Control* getMAXScriptController(ReferenceTarget* obj, LPTSTR name, ParamDimension*& dim)
+{
+	Control* rval = NULL;
+	assert(obj != NULL);
+	init_thread_locals();
+	push_alloc_frame();
+	one_value_local(prop);			// Keep one local varaibles
+	save_current_frames();
+	trace_back_active = FALSE;
+
+	try {
+		// Get the name and the parameter value
+		vl.prop = Name::intern(name);
+		rval = MAXWrapper::get_max_prop_controller(obj, vl.prop, &dim);
+	} catch ( ... ) {
+		clear_error_source_data();
+		restore_current_frames();
+		MAXScript_signals = 0;
+		if (progress_bar_up)
+			MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE;
+	}
+	pop_value_locals();
+	pop_alloc_frame();
+	return rval;
+}
+
+// Set the parameter controller
+bool setMAXScriptController(ReferenceTarget* obj, LPTSTR name, Control* control, ParamDimension* dim)
+{
+	bool rval = false;
+	assert(obj != NULL);
+	init_thread_locals();
+	push_alloc_frame();
+	two_value_locals(prop, maxControl);			// Keep two local varaibles
+	save_current_frames();
+	trace_back_active = FALSE;
+
+	try {
+		// Get the name and the parameter value
+		vl.prop = Name::intern(name);
+		vl.maxControl = MAXControl::intern(control, dim);
+		rval = MAXWrapper::set_max_prop_controller(obj, vl.prop,
+			static_cast<MAXControl*>(vl.maxControl)) != 0;
+	} catch ( ... ) {
+		clear_error_source_data();
+		restore_current_frames();
+		MAXScript_signals = 0;
+		if (progress_bar_up)
+			MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE;
+	}
+	pop_value_locals();
+	pop_alloc_frame();
+	return rval;
+}
+
+
+// These helpers are used to convert C++ values to
+// their MAXScript value. Maxscript uses different
+// methods to handle the conversion, and these helpers
+// allow the conversion to be templated. You may need
+// to add more converter, if you need to access other
+// parameter types.
+inline Value* make_maxscript_value(bool v)
+{
+	return_protected(v ? &true_value : &false_value);
+}
+
+inline Value* make_maxscript_value(int v)
+{
+	return_protected(Integer::intern(v));
+}
+
+inline Value* make_maxscript_value(float v)
+{
+	return_protected(Float::intern(v));
+}
+
+inline Value* make_maxscript_value(COLORREF rgb)
+{
+	return new ColorValue(rgb);
+}
+
+inline Value* make_maxscript_value(const Color& rgb)
+{
+	return new ColorValue(rgb);
+}
+
+inline Value* make_maxscript_value(LPCTSTR str)
+{
+	return Name::intern(const_cast<LPTSTR>(str));
+}
+
+inline Value* make_maxscript_value(ReferenceTarget* rtarg)
+{
+	return MAXClass::make_wrapper_for(rtarg);
+}
+
+// These helpers convert MAXScript values to C++ values.
+// MAXScript uses different methods for this conversion,
+// and these helpers template the conversion. You will
+// need to add more converters if you need more types.
+template <class T>
+class ConvertMAXScriptToC {
+public:
+	static T cvt(Value* val);
+};
+
+inline bool ConvertMAXScriptToC<bool>::cvt(Value* val)
+{
+	return val->to_bool() != 0;
+}
+
+inline int ConvertMAXScriptToC<int>::cvt(Value* val)
+{
+	return val->to_int();
+}
+
+inline float ConvertMAXScriptToC<float>::cvt(Value* val)
+{
+	return val->to_float();
+}
+
+inline Color ConvertMAXScriptToC<Color>::cvt(Value* val)
+{
+	return val->to_point3();
+}
+
+inline LPTSTR ConvertMAXScriptToC<LPTSTR>::cvt(Value* val)
+{
+	return val->to_string();
+}
+
+inline Texmap* ConvertMAXScriptToC<Texmap*>::cvt(Value* val)
+{
+	return val->to_texmap();
+}
+
+inline ReferenceTarget* ConvertMAXScriptToC<ReferenceTarget*>::cvt(Value* val)
+{
+	return val->to_reftarg();
+}
+
+
+#endif
diff --git a/MaxNifImport/resource.h b/MaxNifImport/resource.h
new file mode 100644
index 0000000..01805df
--- /dev/null
+++ b/MaxNifImport/resource.h
@@ -0,0 +1,28 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by MaxNifImport.rc
+//
+#define IDS_LIBDESCRIPTION              1
+#define IDS_CATEGORY                    2
+#define IDS_CLASS_NAME                  3
+#define IDS_PARAMS                      4
+#define IDS_SPIN                        5
+#define IDD_PANEL                       101
+#define IDC_CLOSEBUTTON                 1000
+#define IDC_DOSTUFF                     1000
+#define IDC_EDITHEIGHT                  1001
+#define IDC_BUTTON1                     1002
+#define IDC_COLOR                       1456
+#define IDC_EDIT                        1490
+#define IDC_SPIN                        1496
+
+// Next default values for new objects
+// 
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE        101
+#define _APS_NEXT_COMMAND_VALUE         40001
+#define _APS_NEXT_CONTROL_VALUE         1003
+#define _APS_NEXT_SYMED_VALUE           101
+#endif
+#endif
diff --git a/MaxNifImport/stdafx.cpp b/MaxNifImport/stdafx.cpp
new file mode 100644
index 0000000..1577c4e
--- /dev/null
+++ b/MaxNifImport/stdafx.cpp
@@ -0,0 +1 @@
+#include "stdafx.h"
\ No newline at end of file
diff --git a/MaxNifImport/stdafx.h b/MaxNifImport/stdafx.h
new file mode 100644
index 0000000..9f724ac
--- /dev/null
+++ b/MaxNifImport/stdafx.h
@@ -0,0 +1,28 @@
+#include <stdio.h>
+#include <tchar.h>
+#include <iomanip>
+#include <cmath>
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <map>
+#include <exception>
+#include <stdexcept>
+#include <algorithm>
+#include <functional>
+#include <numeric>
+#include <vector>
+#include <list>
+#include <map>
+
+// _WIN32 will detect windows on most compilers
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <commctrl.h>
+#include <shlwapi.h>
+
+#include "Max.h"
+#include "resource.h"
+#include "istdplug.h"
+#include "iparamb2.h"
+#include "iparamm2.h"
-- 
GitLab