Created
April 25, 2020 19:34
-
-
Save ppoffice/5c5f3dd3d88d971b9d0239d7b09fae16 to your computer and use it in GitHub Desktop.
Convert Empire Earth CEM v1 model to v2 model
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2020 ppoffice | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this softwareand associated documentation files(the "Software"), to | |
// deal in the Software without restriction, including without limitation the | |
// rights to use, copy, modify, merge, publish, distribute, sublicense, and /or | |
// sell copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions : | |
// | |
// The above copyright noticeand this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
// IN THE SOFTWARE. | |
#include <windows.h> | |
#include <iostream> | |
#include <fstream> | |
#define EE_ENGINE_API __declspec (dllimport) | |
class EE_ENGINE_API UString { | |
// Add these fields here for better debugging. Can be removed | |
int unknown1; | |
char* mStr; | |
int mLength; | |
int mMaxLength; | |
public: | |
UString() {} | |
UString(char const* str) {}; | |
}; | |
typedef void (WINAPI* UStringCTOR) (char const*); | |
class EE_ENGINE_API FSBasicFile { | |
// Add these fields here for better debugging. Can be removed | |
int unknown1; | |
UString volume; | |
UString directory; | |
UString file; | |
int unknown2; | |
int unknown3; | |
byte unknown4; | |
public: | |
FSBasicFile() {}; | |
}; | |
class EE_ENGINE_API FSFileSpec { | |
// Add these fields here for better debugging. Can be removed | |
FSBasicFile file; | |
DWORD unknown1; | |
int unknown2; | |
int unknown3; | |
int unknown4; | |
public: | |
FSFileSpec(UString*) {}; | |
}; | |
typedef void (WINAPI* FSFileSpecCTOR) (UString*); | |
typedef int (WINAPI* AddDirectoryFunc) (FSFileSpec*, bool); | |
typedef int (WINAPI* OpenFileSpecFunc) (unsigned int); | |
class EE_ENGINE_API FSPath { | |
public: | |
FSPath() {}; | |
}; | |
typedef void (WINAPI* FSPathCTOR) (); | |
class EE_ENGINE_API GEModel { | |
}; | |
typedef int (WINAPI* SaveGEModelFunc) (FSFileSpec*); | |
class EE_ENGINE_API GEModelManager { | |
public: | |
GEModelManager() {}; | |
}; | |
typedef void (WINAPI* GEModelManagerCTOR) (FSPath*); | |
typedef GEModel* (WINAPI* LoadModelFunc) (UString*, FSFileSpec*); | |
typedef int(WINAPIV* GEInitializeFunc) (FSFileSpec*, HINSTANCE); | |
typedef int(WINAPIV* GetRasterizerFunc) (unsigned int); | |
typedef int(WINAPI* ActivateRasterizerFunc) (); | |
// Taken from https://stackoverflow.com/a/17387176 | |
std::string GetLastErrorAsString() { | |
DWORD errorMessageID = ::GetLastError(); | |
if (errorMessageID == 0) { | |
return std::string(); | |
} | |
LPSTR messageBuffer = nullptr; | |
size_t size = FormatMessageA( | |
FORMAT_MESSAGE_ALLOCATE_BUFFER | | |
FORMAT_MESSAGE_FROM_SYSTEM | | |
FORMAT_MESSAGE_IGNORE_INSERTS, | |
NULL, | |
errorMessageID, | |
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), | |
(LPSTR)&messageBuffer, | |
0, | |
NULL | |
); | |
std::string message(messageBuffer, size); | |
LocalFree(messageBuffer); | |
return message; | |
} | |
// Taken from https://stackoverflow.com/a/27296 | |
std::wstring s2ws(const std::string& s) | |
{ | |
int len; | |
int slength = (int)s.length() + 1; | |
len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0); | |
wchar_t* buf = new wchar_t[len]; | |
MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len); | |
std::wstring r(buf); | |
delete[] buf; | |
return r; | |
} | |
// Load the Low-Level Engine.dll | |
HINSTANCE loadEngineLib(std::string gameDir) { | |
SetDllDirectory(s2ws(gameDir).c_str()); | |
HINSTANCE engineLib = LoadLibrary( | |
s2ws(gameDir + "Low-Level Engine.dll").c_str() | |
); | |
if (!engineLib) { | |
std::cout << "could not load the dynamic library" << std::endl; | |
std::cout << GetLastErrorAsString() << std::endl; | |
return NULL; | |
} | |
return engineLib; | |
} | |
// The approach to load class constructors from DLL is taken from | |
// https://www.codeproject.com/articles/9405/using-classes-exported-from-a-dll-using-loadlibrar | |
// call "new UString(char const *)" | |
UString* new_UString(HINSTANCE engineLib, char const* str) { | |
UStringCTOR constructor = (UStringCTOR)GetProcAddress( | |
engineLib, | |
"??0UString@@QAE@PBD@Z" | |
); | |
if (!constructor) { | |
std::cout << "could not locate the constructor for UString" << std::endl; | |
return NULL; | |
} | |
// allocate "maybe" enough memory for the object | |
UString* obj = (UString*)malloc(0xFFFF); | |
if (!obj) { | |
std::cout << "could not allocate memory for UString" << std::endl; | |
return NULL; | |
} | |
// pass "this" context to the register | |
__asm { MOV ECX, obj } | |
constructor(str); | |
return obj; | |
} | |
// call "new FSFileSpec::FSFileSpec(UString *)" | |
FSFileSpec* new_FSFileSpec(HINSTANCE engineLib, UString* filepath) { | |
FSFileSpecCTOR constructor = (FSFileSpecCTOR)GetProcAddress( | |
engineLib, | |
"??0FSFileSpec@@QAE@ABVUString@@@Z" | |
); | |
if (!constructor) { | |
std::cout << "could not locate the constructor for FSFileSpec" << std::endl; | |
return NULL; | |
} | |
// allocate "maybe" enough memory for the object | |
FSFileSpec* obj = (FSFileSpec*)malloc(0xFFFF); | |
if (!obj) { | |
std::cout << "could not allocate memory for FSFileSpec" << std::endl; | |
return NULL; | |
} | |
// pass "this" context to the register | |
__asm { MOV ECX, obj } | |
constructor(filepath); | |
return obj; | |
} | |
// call "FSFileSpec::Open(int)" | |
int FSFileSpec_Open( | |
HINSTANCE engineLib, | |
FSFileSpec* file, | |
int flag | |
) { | |
OpenFileSpecFunc func = (OpenFileSpecFunc)GetProcAddress( | |
engineLib, | |
"?Open@FSFileSpec@@UAEJK@Z" | |
); | |
if (!func) { | |
std::cout << "could not locate FSFileSpec::Open(int)" << std::endl; | |
return NULL; | |
} | |
// pass "this" context to the register | |
__asm { MOV ECX, file } | |
return func(flag); | |
} | |
// call "new FSPath::FSPath()" | |
FSPath* new_FSPath(HINSTANCE engineLib) { | |
FSPathCTOR constructor = (FSPathCTOR)GetProcAddress( | |
engineLib, | |
"??0FSPath@@QAE@XZ" | |
); | |
if (!constructor) { | |
std::cout << "could not locate the constructor for FSPath" << std::endl; | |
return NULL; | |
} | |
// allocate "maybe" enough memory for the object | |
FSPath* obj = (FSPath*)malloc(0xFFFF); | |
if (!obj) { | |
std::cout << "could not allocate memory for FSPath" << std::endl; | |
return NULL; | |
} | |
// pass "this" context to the register | |
__asm { MOV ECX, obj } | |
constructor(); | |
return obj; | |
} | |
// call "FSPath::AddDirectory(FSFileSpec *, bool)" | |
int FSPath_AddDirectory( | |
HINSTANCE engineLib, | |
FSPath* path, | |
FSFileSpec* directory | |
) { | |
AddDirectoryFunc func = (AddDirectoryFunc)GetProcAddress( | |
engineLib, | |
"?AddDirectory@FSPath@@QAEXABVFSFileSpec@@_N@Z" | |
); | |
if (!func) { | |
std::cout << "could not locate FSPath::AddDirectory(FSFileSpec *, bool)" << std::endl; | |
return EXIT_FAILURE; | |
} | |
// pass "this" context to the register | |
__asm { MOV ECX, path } | |
return func(directory, true); | |
} | |
// call "new GEModelManager::GEModelManager(FSPath *)" | |
GEModelManager* new_GEModelManager(HINSTANCE engineLib, FSPath* modelDir) { | |
GEModelManagerCTOR constructor = (GEModelManagerCTOR)GetProcAddress( | |
engineLib, | |
"??0GEModelManager@@QAE@PAVFSPath@@@Z" | |
); | |
if (!constructor) { | |
std::cout << "could not locate the constructor for GEModelManager" << std::endl; | |
return NULL; | |
} | |
// allocate "maybe" enough memory for the object | |
GEModelManager* obj = (GEModelManager*)malloc(0xFFFF); | |
if (!obj) { | |
std::cout << "could not allocate memory for GEModelManager" << std::endl; | |
return NULL; | |
} | |
// pass "this" context to the register | |
__asm { MOV ECX, obj } | |
constructor(modelDir); | |
return obj; | |
} | |
// call "GEModelManager::LoadModel(UString *, FSFileSpec **)" | |
GEModel* GEModelManager_LoadModel( | |
HINSTANCE engineLib, | |
GEModelManager* manager, | |
UString* filepath, | |
FSFileSpec** placeholders | |
) { | |
LoadModelFunc func = (LoadModelFunc)GetProcAddress( | |
engineLib, | |
"?LoadModel@GEModelManager@@QAEPAVGEModel@@ABVUString@@AAV?$map@KJU?$less@K@std@@V?$allocator@U?$pair@$$CBKJ@std@@@2@@std@@@Z" | |
); | |
if (!func) { | |
std::cout << "could not locate GEModelManager::LoadModel(UString *, FSFileSpec **)" << std::endl; | |
return NULL; | |
} | |
GEModel* model = NULL; | |
__asm { mov esi, esp } | |
__asm { mov eax, placeholders } | |
__asm { push eax } | |
__asm { mov ecx, filepath} | |
__asm { push ecx} | |
__asm { MOV ECX, manager } | |
__asm { call func} | |
__asm { mov model, eax} | |
return model; | |
} | |
int GEModel_Save( | |
HINSTANCE engineLib, | |
GEModel* model, | |
FSFileSpec* path | |
) { | |
SaveGEModelFunc func = (SaveGEModelFunc)GetProcAddress( | |
engineLib, | |
"?Save@GEModel@@UAEJAAVFSFileSpec@@@Z" | |
); | |
if (!func) { | |
std::cout << "could not locate GEModel::Save(FSFileSpec *)" << std::endl; | |
return EXIT_FAILURE; | |
} | |
// pass "this" context to the register | |
__asm { MOV ECX, model } | |
return func(path); | |
} | |
// call "GEInitialize(FSFileSpec*, HINSTANCE)" | |
int GEInitialize(HINSTANCE engineLib, FSFileSpec* gameDir) { | |
GEInitializeFunc GEInitialize = (GEInitializeFunc)GetProcAddress( | |
engineLib, | |
"?GEInitialize@@YAJAAVFSFileSpec@@QAUHINSTANCE__@@@Z" | |
); | |
if (!GEInitialize) { | |
std::cout << "could not locate GEInitialize(FSFileSpec*, HINSTANCE)" << std::endl; | |
return EXIT_FAILURE; | |
} | |
return GEInitialize(gameDir, GetModuleHandle(NULL)); | |
} | |
// call "GERasterizer::GetRasterizer(unsigned int)" | |
// call "GERasterizer::Activate()" | |
int activateRasterizer(HINSTANCE engineLib) { | |
GetRasterizerFunc GetRasterizer = (GetRasterizerFunc)GetProcAddress( | |
engineLib, | |
"?GetRasterizer@GERasterizer@@SAPAV1@K@Z" | |
); | |
if (!GetRasterizer) { | |
std::cout << "could not locate GERasterizer::GetRasterizer(unsigned int)" << std::endl; | |
return EXIT_FAILURE; | |
} | |
void* rasterizer = (void*)GetRasterizer(0); | |
ActivateRasterizerFunc Activate = (ActivateRasterizerFunc)GetProcAddress( | |
engineLib, | |
"?Activate@GERasterizer@@QAEJXZ" | |
); | |
if (!Activate) { | |
std::cout << "could not locate the GERasterizer::Activate()" << std::endl; | |
return EXIT_FAILURE; | |
} | |
// pass "this" context to the register | |
__asm { MOV ECX, rasterizer } | |
Activate(); | |
return EXIT_SUCCESS; | |
} | |
int main() | |
{ | |
// tail backslash is required for directories | |
// the game directory | |
std::string gameDir = "C:\\Program Files (x86)\\GOG Galaxy\\Games\\Empire Earth Gold\\Empire Earth\\"; | |
// the directory where you put all CEM files | |
std::string modelDir = "C:\\Users\\ppoffice\\Downloads\\ee\\extracted_decompressed\\models\\"; | |
// the file name without ".cem" extension of the model file in the modelDir | |
std::string modelName = "air_corsair_10"; | |
std::string modelPath = modelDir + modelName + ".cem"; | |
// put the output model file right beside the model file with the ".2.cem" extension | |
std::string saveModelPath = modelDir + modelName + ".2.cem"; | |
HINSTANCE engineLib = loadEngineLib(gameDir); | |
if (!engineLib) { | |
std::cout << "could not load the dynamic library" << std::endl; | |
std::cout << GetLastErrorAsString() << std::endl; | |
return EXIT_FAILURE; | |
} | |
UString* uStrGameDir = new_UString(engineLib, gameDir.c_str()); | |
UString* uStrModelDir = new_UString(engineLib, modelDir.c_str()); | |
UString* uStrModelName = new_UString(engineLib, modelName.c_str()); | |
UString* uStrModelPath = new_UString(engineLib, modelPath.c_str()); | |
UString* uStrSaveModelPath = new_UString(engineLib, saveModelPath.c_str()); | |
FSFileSpec* fSpecGameDir = new_FSFileSpec(engineLib, uStrGameDir); | |
FSFileSpec* fSpecModelDir = new_FSFileSpec(engineLib, uStrModelDir); | |
FSFileSpec* fSpecModelPath = new_FSFileSpec(engineLib, uStrModelPath); | |
FSFileSpec* fSpecSaveModelPath = new_FSFileSpec(engineLib, uStrSaveModelPath); | |
FSPath* fPathModelDir = new_FSPath(engineLib); | |
FSPath_AddDirectory(engineLib, fPathModelDir, fSpecModelDir); | |
GEInitialize(engineLib, fSpecGameDir); | |
activateRasterizer(engineLib); | |
GEModelManager* modelManager = new_GEModelManager(engineLib, fPathModelDir); | |
// I don't know what the purpose of this array is | |
// or how it should be created, but the code works | |
FSFileSpec** fSpecModels = (FSFileSpec**)malloc(sizeof(void*) * 4); | |
fSpecModels[0] = fSpecModelPath; | |
fSpecModels[1] = fSpecModelPath; | |
fSpecModels[2] = fSpecModelPath; | |
fSpecModels[3] = fSpecModelPath; | |
GEModel* model = GEModelManager_LoadModel( | |
engineLib, | |
modelManager, | |
uStrModelName, | |
fSpecModels | |
); | |
// create an empty saved model file if it does not exist | |
std::fstream fs; | |
fs.open(saveModelPath, std::ios::out); | |
fs.close(); | |
// I don't know why flag has to be 6, but it works | |
FSFileSpec_Open(engineLib, fSpecSaveModelPath, 6); | |
GEModel_Save(engineLib, model, fSpecSaveModelPath); | |
FreeLibrary(engineLib); | |
return EXIT_SUCCESS; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment