[Closed] A little challenge… Max File Opener
Here’s a little challenge for those that can make EXE’s…
It would be nice if there was an application you could associate .max files with that would inspect the max file and see which version of max the file was saved in and then gave you options of which version of max to open the file with.
The current max file opening system is such a mess when you have multiple versions of max installed!
getting the version is pretty trivial, this should do it…
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>
#include <conio.h>
#define PROPSET_SUMINFO 0x000000ff
#define PROPSET_DOCSUMINFO 0x0000ff00
#define PROPSET_USERDEF 0x00ff0000
#define ALL_PROPERTIES 0xffffffff // All props
#define TITLE_PROP 0x00000001 // Summary Info
#define SUBJECT_PROP 0x00000002 // Summary Info
#define AUTHOR_PROP 0x00000004 // Summary Info
#define KEYWORDS_PROP 0x00000008 // Summary Info
#define COMMENTS_PROP 0x00000010 // Summary Info
#define MANAGER_PROP 0x00000100 // Document Summary Info
#define COMPANY_PROP 0x00000200 // Document Summary Info
#define CATEGORY_PROP 0x00000400 // Document Summary Info
#define EXT_DEPEND_PROP 0x00000800 // Document Summary Info
#define PLUGINS_PROP 0x00001000 // Document Summary Info
#define OBJECTS_PROP 0x00002000 // Document Summary Info
#define MATERIALS_PROP 0x00004000 // Document Summary Info
#define USER_PROP 0x00010000 // User Defined Properties
#define PID_TITLE 0x00000002
#define PID_SUBJECT 0x00000003
#define PID_AUTHOR 0x00000004
#define PID_KEYWORDS 0x00000005
#define PID_COMMENTS 0x00000006
#define PID_MANAGER 0x0000000E
#define PID_COMPANY 0x0000000F
#define PID_CATEGORY 0x00000002
#define PID_HEADINGPAIR 0x0000000C
#define PID_DOCPARTS 0x0000000D
#define testfile "D:/Max/Scenes/Golf/models/default/ball.max"
int main(int argc, char* argv[])
{
LPSTORAGE pStorage = NULL;
IPropertySetStorage* pPropertySetStorage = NULL;
IPropertyStorage* pDocumentSummaryInfoStorage = NULL;
wchar_t wfilename[_MAX_PATH];
MultiByteToWideChar(CP_ACP, 0, (char*)argv[1], -1, wfilename, _MAX_PATH);
HRESULT res = StgOpenStorage(wfilename, (LPSTORAGE)0, STGM_DIRECT|STGM_READ|STGM_SHARE_EXCLUSIVE, NULL,0,&pStorage);
if (res!=S_OK) return 0;
if(S_OK != pStorage->QueryInterface(IID_IPropertySetStorage, (void**)&pPropertySetStorage))
{
pStorage->Release();
return 0;
}
if (S_OK == pPropertySetStorage->Open(FMTID_DocSummaryInformation, STGM_READ|STGM_SHARE_EXCLUSIVE, &pDocumentSummaryInfoStorage))
{
PROPSPEC PropSpec[2] = {{PRSPEC_PROPID,PID_HEADINGPAIR},{PRSPEC_PROPID,PID_DOCPARTS}};
PROPVARIANT PropVar[2];
HRESULT hr = pDocumentSummaryInfoStorage->ReadMultiple(2, PropSpec, PropVar);
if (S_OK == hr)
{
if ((PropVar[0].vt == (VT_VARIANT | VT_VECTOR)) && (PropVar[1].vt == (VT_LPSTR | VT_VECTOR)))
{
CAPROPVARIANT* pHeading = &PropVar[0].capropvar;
CALPSTR* pDocPart = &PropVar[1].calpstr;
printf("%s
", pDocPart->pElems[0]); // prints the maxversion string
printf("%s
", pDocPart->pElems[2]); // prints the build version string
}
}
FreePropVariantArray(2, PropVar);
pDocumentSummaryInfoStorage->Release();
}
pPropertySetStorage->Release();
pStorage->Release();
_getch();
return 0;
}
you can associate max files with the above command line app and it will read and display the version (though early max version files don’t display the build version but the number of verts). you can use sscanf to extract the version from the string. find the version of max or higher that the file was create with (a list of file strings for each version should do it, just checking they exist ) then use then use createprocess window proc to launch max passing the filename as the command line arg.
this will get you most the way there…
// MaxVersion.cpp : Defines the entry point for the console application.
//
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>
#include <conio.h>
#define PROPSET_SUMINFO 0x000000ff
#define PROPSET_DOCSUMINFO 0x0000ff00
#define PROPSET_USERDEF 0x00ff0000
#define ALL_PROPERTIES 0xffffffff // All props
#define TITLE_PROP 0x00000001 // Summary Info
#define SUBJECT_PROP 0x00000002 // Summary Info
#define AUTHOR_PROP 0x00000004 // Summary Info
#define KEYWORDS_PROP 0x00000008 // Summary Info
#define COMMENTS_PROP 0x00000010 // Summary Info
#define MANAGER_PROP 0x00000100 // Document Summary Info
#define COMPANY_PROP 0x00000200 // Document Summary Info
#define CATEGORY_PROP 0x00000400 // Document Summary Info
#define EXT_DEPEND_PROP 0x00000800 // Document Summary Info
#define PLUGINS_PROP 0x00001000 // Document Summary Info
#define OBJECTS_PROP 0x00002000 // Document Summary Info
#define MATERIALS_PROP 0x00004000 // Document Summary Info
#define USER_PROP 0x00010000 // User Defined Properties
#define PID_TITLE 0x00000002
#define PID_SUBJECT 0x00000003
#define PID_AUTHOR 0x00000004
#define PID_KEYWORDS 0x00000005
#define PID_COMMENTS 0x00000006
#define PID_MANAGER 0x0000000E
#define PID_COMPANY 0x0000000F
#define PID_CATEGORY 0x00000002
#define PID_HEADINGPAIR 0x0000000C
#define PID_DOCPARTS 0x0000000D
//const char* maxversions
inline bool FileExists(const char* filename)
{
if(FILE *file = fopen(filename, "r"))
{
fclose(file);
return true;
}
else
return false;
}
const wchar_t* max12 = L"C:/Program Files (x86)/Autodesk/3ds Max 2010/3dsmax.exe";
bool launchMax(const wchar_t* max, wchar_t* filename)
{
STARTUPINFO StartupInfo;
PROCESS_INFORMATION ProcessInfo;
memset(&StartupInfo, 0, sizeof(StartupInfo));
StartupInfo.cb = sizeof(StartupInfo);
memset(&ProcessInfo, 0, sizeof(ProcessInfo));
wchar_t cmdline[512];
wcscpy(cmdline,max);
wcscat(cmdline,L" ");
wcscat(cmdline,filename);
if(!CreateProcess(NULL, cmdline, NULL, NULL, NULL, NULL, NULL, NULL, &StartupInfo, &ProcessInfo))
return false;
return true;
}
//********************************************************************************************
int main(int argc, char* argv[])
{
LPSTORAGE pStorage = NULL;
IPropertySetStorage* pPropertySetStorage = NULL;
IPropertyStorage* pDocumentSummaryInfoStorage = NULL;
float version;
wchar_t wfilename[_MAX_PATH];
char* filename = (char*)argv[1];
MultiByteToWideChar(CP_ACP, 0, (char*)argv[1], -1, wfilename, _MAX_PATH);
HRESULT res = StgOpenStorage(wfilename, (LPSTORAGE)0, STGM_DIRECT|STGM_READ|STGM_SHARE_EXCLUSIVE, NULL,0,&pStorage);
if(res != S_OK)
return 0;
if(S_OK != pStorage->QueryInterface(IID_IPropertySetStorage, (void**)&pPropertySetStorage))
{
pStorage->Release();
return 0;
}
if (S_OK == pPropertySetStorage->Open(FMTID_DocSummaryInformation, STGM_READ|STGM_SHARE_EXCLUSIVE, &pDocumentSummaryInfoStorage))
{
PROPSPEC PropSpec[2] = {{PRSPEC_PROPID,PID_HEADINGPAIR},{PRSPEC_PROPID,PID_DOCPARTS}};
PROPVARIANT PropVar[2];
HRESULT hr = pDocumentSummaryInfoStorage->ReadMultiple(2, PropSpec, PropVar);
if(S_OK == hr)
{
if((PropVar[0].vt == (VT_VARIANT | VT_VECTOR)) && (PropVar[1].vt == (VT_LPSTR | VT_VECTOR)))
{
CAPROPVARIANT* pHeading = &PropVar[0].capropvar;
CALPSTR* pDocPart = &PropVar[1].calpstr;
version = (float)atof(&pDocPart->pElems[0][16]); // hoping this doesn't change in future versions
printf("%s
", pDocPart->pElems[0]); // prints the maxversion string
printf("%s
", pDocPart->pElems[2]); // prints the build version string
}
}
FreePropVariantArray(2, PropVar);
pDocumentSummaryInfoStorage->Release();
}
pPropertySetStorage->Release();
pStorage->Release();
// choose max here etc (the easy version as this should be quite involved process) :)
if((int)version == 12)
launchMax(max12, wfilename);
//_getch();
return 0;
}
edited as earlier verions use “3ds max Version:” and later versions use “3ds Max Version:” :hmm:
this seems to work ok
// MaxVersion.cpp
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>
#include <conio.h>
#pragma warning(disable : 4996)
#define PID_HEADINGPAIR 0x0000000C
#define PID_DOCPARTS 0x0000000D
//********************************************************************************************
inline bool FileExists(const wchar_t* filename)
{
if(FILE* file = _wfopen(filename, L"r"))
{
fclose(file);
return true;
}
else
return false;
}
//********************************************************************************************
// max apps on you machine go here
const wchar_t* maxversions[] = {
L"C:/Program Files (x86)/Autodesk/3ds Max 2010/3dsmax.exe",
L"C:/Program Files (x86)/Autodesk/3ds Max 2011/3dsmax.exe",
L"C:/Program Files (x86)/Autodesk/3ds Max 2012/3dsmax.exe",
L"C:/Program Files/Autodesk/3ds Max 2014/3dsmax.exe",
L"C:/Program Files/Autodesk/3ds Max 2015/3dsmax.exe" };
// look up table for preferred usage app eg 0-12 by max 2010, 2013 by 2014 etc
static int versionprefs[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,3,4 };
//********************************************************************************************
bool launchMax(const wchar_t* max, wchar_t* filename)
{
STARTUPINFO StartupInfo;
PROCESS_INFORMATION ProcessInfo;
memset(&StartupInfo, 0, sizeof(StartupInfo));
StartupInfo.cb = sizeof(StartupInfo);
memset(&ProcessInfo, 0, sizeof(ProcessInfo));
wchar_t cmdline[512];
wcscpy_s(cmdline,max);
wcscat_s(cmdline,L" ");
wcscat_s(cmdline,filename);
// add any cmdline customization here
if(!CreateProcess(NULL, cmdline, NULL, NULL, NULL, NULL, NULL, NULL, &StartupInfo, &ProcessInfo))
return false;
return true;
}
//********************************************************************************************
int main(int argc, char* argv[])
{
LPSTORAGE pStorage = NULL;
IPropertySetStorage* pPropertySetStorage = NULL;
IPropertyStorage* pDocumentSummaryInfoStorage = NULL;
int version;
wchar_t wfilename[_MAX_PATH];
MultiByteToWideChar(CP_ACP, 0, argv[1], -1, wfilename, _MAX_PATH);
HRESULT res = StgOpenStorage(wfilename, (LPSTORAGE)0, STGM_DIRECT|STGM_READ|STGM_SHARE_EXCLUSIVE, NULL,0,&pStorage);
if(res != S_OK)
return 0;
if(S_OK != pStorage->QueryInterface(IID_IPropertySetStorage, (void**)&pPropertySetStorage))
{
pStorage->Release();
return 0;
}
if (S_OK == pPropertySetStorage->Open(FMTID_DocSummaryInformation, STGM_READ|STGM_SHARE_EXCLUSIVE, &pDocumentSummaryInfoStorage))
{
PROPSPEC PropSpec[2] = {{PRSPEC_PROPID,PID_HEADINGPAIR},{PRSPEC_PROPID,PID_DOCPARTS}};
PROPVARIANT PropVar[2];
HRESULT hr = pDocumentSummaryInfoStorage->ReadMultiple(2, PropSpec, PropVar);
if(S_OK == hr)
{
if((PropVar[0].vt == (VT_VARIANT | VT_VECTOR)) && (PropVar[1].vt == (VT_LPSTR | VT_VECTOR)))
{
CAPROPVARIANT* pHeading = &PropVar[0].capropvar;
CALPSTR* pDocPart = &PropVar[1].calpstr;
version = (int)atof(&pDocPart->pElems[0][16]);
}
}
FreePropVariantArray(2, PropVar);
pDocumentSummaryInfoStorage->Release();
}
pPropertySetStorage->Release();
pStorage->Release();
// dispatch to the preferred max version
if(FileExists(maxversions[versionprefs[version]]))
launchMax(maxversions[versionprefs[version]], wfilename);
return 0;
}
I don’t think i’d have as the default app though is nice to have it as a right click open with option.
slight variation… the original is based on a console app by changing the build properties
Linker::system::subsystem to Windows (/SUBSYSTEM:WINDOWS) and changing the code to…
// MaxVersion.cpp
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>
#include <conio.h>
#pragma warning(disable : 4996)
#define PID_HEADINGPAIR 0x0000000C
#define PID_DOCPARTS 0x0000000D
//********************************************************************************************
inline bool FileExists(const wchar_t* filename)
{
if(FILE* file = _wfopen(filename, L"r"))
{
fclose(file);
return true;
}
else
return false;
}
//********************************************************************************************
// max apps on your machine go here
const wchar_t* maxversions[] = {
L"C:/Program Files (x86)/Autodesk/3ds Max 2010/3dsmax.exe",
L"C:/Program Files (x86)/Autodesk/3ds Max 2011/3dsmax.exe",
L"C:/Program Files (x86)/Autodesk/3ds Max 2012/3dsmax.exe",
L"C:/Program Files/Autodesk/3ds Max 2014/3dsmax.exe",
L"C:/Program Files/Autodesk/3ds Max 2015/3dsmax.exe" };
// look up table for preferred usage app eg 0-12 by max 2010, 2013 by 2014 etc
static int versionprefs[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,3,4 };
//********************************************************************************************
bool launchMax(const wchar_t* max, wchar_t* filename)
{
STARTUPINFO StartupInfo;
PROCESS_INFORMATION ProcessInfo;
memset(&StartupInfo, 0, sizeof(StartupInfo));
StartupInfo.cb = sizeof(StartupInfo);
memset(&ProcessInfo, 0, sizeof(ProcessInfo));
wchar_t cmdline[512];
wcscpy_s(cmdline,max);
wcscat_s(cmdline,L" ");
wcscat_s(cmdline,filename);
if(!CreateProcess(NULL, cmdline, NULL, NULL, NULL, NULL, NULL, NULL, &StartupInfo, &ProcessInfo))
return false;
return true;
}
//********************************************************************************************
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
LPWSTR *szArgList;
int argCount;
szArgList = CommandLineToArgvW(GetCommandLine(), &argCount);
LPSTORAGE pStorage = NULL;
IPropertySetStorage* pPropertySetStorage = NULL;
IPropertyStorage* pDocumentSummaryInfoStorage = NULL;
int version;
wchar_t* wfilename = szArgList[1];
HRESULT res = StgOpenStorage(wfilename, (LPSTORAGE)0, STGM_DIRECT|STGM_READ|STGM_SHARE_EXCLUSIVE, NULL,0,&pStorage);
if(res != S_OK)
return 0;
if(S_OK != pStorage->QueryInterface(IID_IPropertySetStorage, (void**)&pPropertySetStorage))
{
pStorage->Release();
return 0;
}
if (S_OK == pPropertySetStorage->Open(FMTID_DocSummaryInformation, STGM_READ|STGM_SHARE_EXCLUSIVE, &pDocumentSummaryInfoStorage))
{
PROPSPEC PropSpec[2] = {{PRSPEC_PROPID,PID_HEADINGPAIR},{PRSPEC_PROPID,PID_DOCPARTS}};
PROPVARIANT PropVar[2];
HRESULT hr = pDocumentSummaryInfoStorage->ReadMultiple(2, PropSpec, PropVar);
if(S_OK == hr)
{
if((PropVar[0].vt == (VT_VARIANT | VT_VECTOR)) && (PropVar[1].vt == (VT_LPSTR | VT_VECTOR)))
{
CAPROPVARIANT* pHeading = &PropVar[0].capropvar;
CALPSTR* pDocPart = &PropVar[1].calpstr;
version = (int)atof(&pDocPart->pElems[0][16]);
}
}
FreePropVariantArray(2, PropVar);
pDocumentSummaryInfoStorage->Release();
}
pPropertySetStorage->Release();
pStorage->Release();
// dispatch to the preferred max version
if(FileExists(maxversions[versionprefs[version]]))
launchMax(maxversions[versionprefs[version]], wfilename);
return 0;
}
there will be no console window opened so it appears more “seamless”
an exe (was compiled on windows 7 should work on xp & vista) for anyone to try, with the the “prefs” moved into an inifile (so win98 )
to use copy the exe & ini into the same folder edit the inifile for your system e.g. which max exes you want to use and which version they will open with. right click on any max file an select open with… choose default program and browse for the maxversion.exe ( i reset the default back to max afterwards) the app should then open the file in the “correct” version of max.
the source code, moved some of it into stl strings as it’s a bit “cleaner”…
// MaxVersion.cpp
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>
#include <conio.h>
#include <string>
#include <sstream>
#include <vector>
using namespace std;
#pragma warning(disable : 4996)
#define PID_HEADINGPAIR 0x0000000C
#define PID_DOCPARTS 0x0000000D
const wchar_t* INIFILE = L"./MaxVersion.ini";
template<typename T> std::wstring toString(const T& t)
{
std::wstringstream stringStream;
stringStream << t;
return stringStream.str();
}
//********************************************************************************************
inline bool FileExists(const wstring& filename)
{
if(FILE* file = _wfopen(filename.c_str(), L"r"))
{
fclose(file);
return true;
}
else
return false;
}
//*****************************************************************************************
// max apps on your machine go here
static vector<wstring> maxexes;
static vector<int> versions;
//**************************************************************************************
bool launchMax(const wstring& max, const wstring& filename)
{
STARTUPINFO StartupInfo;
PROCESS_INFORMATION ProcessInfo;
memset(&StartupInfo, 0, sizeof(StartupInfo));
StartupInfo.cb = sizeof(StartupInfo);
memset(&ProcessInfo, 0, sizeof(ProcessInfo));
wstring cmdline = max;
cmdline += L" ";
cmdline += filename;
if(!CreateProcess(NULL,(LPWSTR)cmdline.c_str(), NULL, NULL, NULL, NULL, NULL, NULL, &StartupInfo, &ProcessInfo))
return false;
return true;
}
//**************************************************************************************
bool readInifile(const wstring& inifile)
{
if(!FileExists(inifile))
return false;
wchar_t buf[MAX_PATH];
int count = GetPrivateProfileString(L"exe",L"execount",L"0", buf, MAX_PATH,inifile.c_str());
int execount = _wtoi(buf);
maxexes.resize(execount);
for(int i = 0; i < execount; ++i)
{
wstring key(L"exe_");
key += toString(i);
count = GetPrivateProfileString(L"exe",key.c_str(),L"", buf, MAX_PATH,inifile.c_str());
maxexes[i] = buf;
}
count = GetPrivateProfileString(L"versions",L"numversions",L"0", buf, MAX_PATH,inifile.c_str());
int vercount = _wtoi(buf);
versions.resize(vercount);
for(int i = 0; i < vercount; ++i)
{
wstring key(L"ver_");
key += toString(i);
count = GetPrivateProfileString(L"versions",key.c_str(),L"", buf, MAX_PATH,inifile.c_str());
versions[i] = _wtoi(buf);
}
return true;
}
//*****************************************************************************************
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
if(!readInifile(INIFILE))
return 0;
LPWSTR *szArgList;
int argCount, version;
szArgList = CommandLineToArgvW(GetCommandLine(), &argCount);
LPSTORAGE pStorage = NULL;
IPropertySetStorage* pPropertySetStorage = NULL;
IPropertyStorage* pDocumentSummaryInfoStorage = NULL;
wstring wfilename = szArgList[1];
HRESULT res = StgOpenStorage(wfilename.c_str(), (LPSTORAGE)0, STGM_DIRECT|STGM_READ|STGM_SHARE_EXCLUSIVE, NULL,0,&pStorage);
if(res != S_OK)
return 0;
if(S_OK != pStorage->QueryInterface(IID_IPropertySetStorage, (void**)&pPropertySetStorage))
{
pStorage->Release();
return 0;
}
if (S_OK == pPropertySetStorage->Open(FMTID_DocSummaryInformation, STGM_READ|STGM_SHARE_EXCLUSIVE, &pDocumentSummaryInfoStorage))
{
PROPSPEC PropSpec[2] = {{PRSPEC_PROPID,PID_HEADINGPAIR},{PRSPEC_PROPID,PID_DOCPARTS}};
PROPVARIANT PropVar[2];
HRESULT hr = pDocumentSummaryInfoStorage->ReadMultiple(2, PropSpec, PropVar);
if(S_OK == hr)
{
if((PropVar[0].vt == (VT_VARIANT | VT_VECTOR)) && (PropVar[1].vt == (VT_LPSTR | VT_VECTOR)))
{
CAPROPVARIANT* pHeading = &PropVar[0].capropvar;
CALPSTR* pDocPart = &PropVar[1].calpstr;
version = (int)atof(&pDocPart->pElems[0][16]);
}
}
FreePropVariantArray(2, PropVar);
pDocumentSummaryInfoStorage->Release();
}
pPropertySetStorage->Release();
pStorage->Release();
// dispatch to the preferred max version
if(FileExists(maxexes[versions[version]]))
launchMax(maxexes[versions[version]], wfilename);
return 0;
}
just curios… what is the reason to open a max file with lowest available max version?
we render on a network where max 2012 is installed with all the needed plugins. Sometimes we download files for interiors/exteriors made in 2013/2014 version. We have to open them in 2014 then save them as 2012 version. And sometimes, after conversion to lower version of max, and opening in 2012, I get an error – missing plugins – mass FX or smth like this. then I use this script: romevemissingplugins and only after this I can copy the object from this max file to the project max file where I work. It may happen I repeat these steps 5-15 times each day.