Skip to content

Instantly share code, notes, and snippets.

@ppoffice
Created April 25, 2020 19:34
Show Gist options
  • Save ppoffice/5c5f3dd3d88d971b9d0239d7b09fae16 to your computer and use it in GitHub Desktop.
Save ppoffice/5c5f3dd3d88d971b9d0239d7b09fae16 to your computer and use it in GitHub Desktop.
Convert Empire Earth CEM v1 model to v2 model
// 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