diff --git a/CubeModLauncher/main.cpp b/CubeModLauncher/main.cpp index c61f8d5..d9cc518 100644 --- a/CubeModLauncher/main.cpp +++ b/CubeModLauncher/main.cpp @@ -3,20 +3,13 @@ #include #include #include "Process.h" -#include "crc.h" - -#define CUBE_VERSION "1.0.0-1" -#define CUBE_PACKED_CRC 0xC7682619 -#define CUBE_UNPACKED_CRC 0xBA092543 - -#define MODLOADER_CRC 0x39D18E98 char* CUBE_EXECUTABLE = "cubeworld.exe"; char* MODLOADER_DLL = "CubeModLoader.dll"; using namespace std; -bool FileExists(char* fileName) { +bool FileExists(const char* fileName) { DWORD dwAttrib = GetFileAttributes(fileName); return (dwAttrib != INVALID_FILE_ATTRIBUTES && !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); } @@ -28,13 +21,8 @@ int Bail(int result){ } int main(int argc, char** argv) { - bool testMode = false; - if (argc >= 2 && !strcmp(argv[1], "test")) { - testMode = true; - printf("Test mode enabled. CRC checks will be bypassed.\n"); - if (argc >= 3) { - CUBE_EXECUTABLE = argv[2]; - } + if (argc >= 2) { + CUBE_EXECUTABLE = argv[1]; } //Cube world is obviously required @@ -43,44 +31,12 @@ int main(int argc, char** argv) { return Bail(1); } - unsigned int checksum = crc32_file(CUBE_EXECUTABLE); - - if (testMode) { - printf("%s CRC: %08X\n", CUBE_EXECUTABLE, checksum); - } - - // Check if the game is still packed - if (checksum == CUBE_PACKED_CRC && !testMode) { - printf("Cube World was found, but it is not unpacked.\n" - "Use Steamless to unpack %s.\n", CUBE_EXECUTABLE); - return Bail(1); - } - - - if (checksum != CUBE_UNPACKED_CRC && !testMode) { - printf("Cube World was found, but it is not version %s.\n" - "(Found CRC %08X, expected %08X)\n" - "Please update your game.\n", - CUBE_VERSION, checksum, CUBE_UNPACKED_CRC); - return Bail(1); - } - //Inject our dll if ( !FileExists(MODLOADER_DLL) ) { printf("%s not found.\n", MODLOADER_DLL); return Bail(1); } - unsigned int loaderChecksum = crc32_file(MODLOADER_DLL); - if (loaderChecksum != MODLOADER_CRC && !testMode) { - printf("%s is the wrong version (%08X)\n", MODLOADER_DLL, loaderChecksum); - return Bail(1); - } - - if (testMode) { - printf("%s CRC: %08X\n", MODLOADER_DLL, loaderChecksum); - } - Process process(CUBE_EXECUTABLE); //Create game in suspended state diff --git a/CubeModLauncher/main.h b/CubeModLauncher/main.h index b204edf..689df4a 100644 --- a/CubeModLauncher/main.h +++ b/CubeModLauncher/main.h @@ -1,7 +1,7 @@ #ifndef MAIN_H_INCLUDED #define MAIN_H_INCLUDED -bool FileExists(char* fileName); +bool FileExists(const char* fileName); int Bail(int result); int main(int argc, char** argv); diff --git a/CubeModLauncher/crc.cpp b/CubeModLoader/crc.cpp similarity index 100% rename from CubeModLauncher/crc.cpp rename to CubeModLoader/crc.cpp diff --git a/CubeModLauncher/crc.h b/CubeModLoader/crc.h similarity index 100% rename from CubeModLauncher/crc.h rename to CubeModLoader/crc.h diff --git a/CubeModLoader/main.cpp b/CubeModLoader/main.cpp index 7e4a3b2..d6f375a 100644 --- a/CubeModLoader/main.cpp +++ b/CubeModLoader/main.cpp @@ -2,9 +2,16 @@ #include #include #include "DLL.h" +#include "crc.h" #define MOD_MAJOR_VERSION 4 -#define MOD_MINOR_VERSION 1 +#define MOD_MINOR_VERSION 2 + +#define CUBE_VERSION "1.0.0-1" +#define CUBE_PACKED_CRC 0xC7682619 +#define CUBE_UNPACKED_CRC 0xBA092543 + +#define MODLOADER_NAME "CubeModLoader" #define no_optimize __attribute__((optimize("O0"))) @@ -30,8 +37,12 @@ dllname->name = GetProcAddress(dllname->handle, #name); using namespace std; -void* base; -vector modDLLs; +void* base; // Module base +vector modDLLs; // Every mod we've loaded +HMODULE hSelf; // A handle to ourself, to prevent being unloaded +void* initterm_e; // A pointer to a function which is run extremely soon after starting, or after being unpacked +const size_t BYTES_TO_MOVE = 14; // The size of a far jump +char initterm_e_remember[BYTES_TO_MOVE]; // We'll use this to store the code we overwrite in initterm_e, so we can put it back later. void WriteFarJMP(void* source, void* destination) { DWORD dwOldProtection; @@ -52,65 +63,69 @@ void WriteFarJMP(void* source, void* destination) { #include "callbacks/ChatHandler.h" #include "callbacks/P2PRequestHandler.h" #include "callbacks/CheckInventoryFullHandler.h" -#include "callbacks/CheckMapIconVisibilityHandler.h" void SetupHandlers() { SetupChatHandler(); SetupP2PRequestHandler(); SetupCheckInventoryFullHandler(); - SetupCheckMapIconVisibilityHandler(); } -void Popup(const char* title, char* msg ){ +void Popup(const char* title, const char* msg ) { MessageBoxA(0, msg, title, MB_OK | MB_ICONINFORMATION); } -extern "C" __declspec(dllexport) BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { - switch (fdwReason) { - case DLL_PROCESS_ATTACH: - base = GetModuleHandle(NULL); +void PrintLoadedMods() { + std::string mods("Mods Loaded:\n"); + for (DLL* dll : modDLLs) { + mods += dll->fileName; + mods += "\n"; + } + Popup("Loaded Mods", mods.c_str()); +} - SetupHandlers(); +// Handles injecting callbacks and the mods +void StartMods() { + char msg[256] = {0}; - //Find mods - HANDLE hFind; - WIN32_FIND_DATA data; + SetupHandlers(); - CreateDirectory("Mods", NULL); - hFind = FindFirstFile("Mods\\*.dll", &data); - if (hFind != INVALID_HANDLE_VALUE) { - do { - // We should be loaded into the application's address space, so we can just LoadLibraryA - DLL* dll = new DLL(string("Mods\\") + data.cFileName); - dll->Load(); - printf("Loaded %s\n", dll->fileName.c_str()); - modDLLs.push_back(dll); - } while (FindNextFile(hFind, &data)); - FindClose(hFind); - } + //Find mods + HANDLE hFind; + WIN32_FIND_DATA data; - // Find all the functions the mods may export - for (DLL* dll: modDLLs) { - MUST_IMPORT(dll, ModMajorVersion); - MUST_IMPORT(dll, ModMinorVersion); - MUST_IMPORT(dll, ModPreInitialize); - IMPORT(dll, ModInitialize); - IMPORT(dll, HandleChat); - IMPORT(dll, HandleP2PRequest); - IMPORT(dll, HandleCheckInventoryFull); - IMPORT(dll, HandleCheckMapIconVisibility); - } + CreateDirectory("Mods", NULL); + hFind = FindFirstFile("Mods\\*.dll", &data); + if (hFind != INVALID_HANDLE_VALUE) { + do { + // We should be loaded into the application's address space, so we can just LoadLibraryA + DLL* dll = new DLL(string("Mods\\") + data.cFileName); + dll->Load(); + printf("Loaded %s\n", dll->fileName.c_str()); + modDLLs.push_back(dll); + } while (FindNextFile(hFind, &data)); + FindClose(hFind); + } - // Ensure version compatibility - char msg[512] = {0}; - for (DLL* dll: modDLLs) { - int majorVersion = ((int(*)())dll->ModMajorVersion)(); - int minorVersion = ((int(*)())dll->ModMinorVersion)(); - if (majorVersion != MOD_MAJOR_VERSION) { - sprintf(msg, "%s has major version %d but requires %d.\n", dll->fileName.c_str(), majorVersion, MOD_MAJOR_VERSION); - Popup("Error", msg); - exit(1); - } + // Find all the functions the mods may export + for (DLL* dll: modDLLs) { + MUST_IMPORT(dll, ModMajorVersion); + MUST_IMPORT(dll, ModMinorVersion); + MUST_IMPORT(dll, ModPreInitialize); + IMPORT(dll, ModInitialize); + IMPORT(dll, HandleChat); + IMPORT(dll, HandleP2PRequest); + IMPORT(dll, HandleCheckInventoryFull); + IMPORT(dll, HandleCheckMapIconVisibility); + } + + // Ensure version compatibility + for (DLL* dll: modDLLs) { + int majorVersion = ((int(*)())dll->ModMajorVersion)(); + int minorVersion = ((int(*)())dll->ModMinorVersion)(); + if (majorVersion != MOD_MAJOR_VERSION) { + sprintf(msg, "%s has major version %d but requires %d.\n", dll->fileName.c_str(), majorVersion, MOD_MAJOR_VERSION); + Popup("Error", msg); + exit(1); if (minorVersion > MOD_MINOR_VERSION) { sprintf(msg, "%s has minor version %d but requires %d or lower.\n", dll->fileName.c_str(), minorVersion, MOD_MINOR_VERSION); @@ -129,7 +144,108 @@ extern "C" __declspec(dllexport) BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD ((void(*)())dll->ModInitialize)(); } } + } + if (hSelf) PrintLoadedMods(); + return; +} +void* StartMods_ptr = (void*)&StartMods; +void no_optimize ASMStartMods() { + asm(PUSH_ALL + PREPARE_STACK + + // Initialize mods and callbacks + "call [StartMods_ptr] \n" + + // We can put initterm_e back how we found it. + "call [CopyInitializationBack_ptr] \n" + + RESTORE_STACK + POP_ALL + + // Run initterm_e properly this time. + "jmp [initterm_e] \n" + ); +} + +void PatchFreeImage(){ + // Thanks to frognik for showing off this method! + DWORD oldProtect; + void* patchaddr = (void*)GetModuleHandleA("FreeImage.dll") + 0x1E8C12; + VirtualProtect((LPVOID)patchaddr, 8, PAGE_EXECUTE_READWRITE, &oldProtect); + *(uint64_t*)patchaddr = 0x909090000000A8E9; +} + +void InitializationPatch() { + // Get pointer to initterm_e + initterm_e = *(void**)(base + 0x42CBD8); + + // Store old code, we'll copy it back once we regain control. + memcpy(initterm_e_remember, initterm_e, BYTES_TO_MOVE); + + // Write a jump to our code + WriteFarJMP(initterm_e, (void*)&ASMStartMods); +} + +// This restores initterm_e to how it was before we hijacked it. +void CopyInitializationBack() { + DWORD dwOldProtection; + VirtualProtect(initterm_e, BYTES_TO_MOVE, PAGE_EXECUTE_READWRITE, &dwOldProtection); + + memcpy(initterm_e, initterm_e_remember, BYTES_TO_MOVE); + + VirtualProtect(initterm_e, BYTES_TO_MOVE, dwOldProtection, &dwOldProtection); + + return; +} +void* CopyInitializationBack_ptr = (void*)&CopyInitializationBack; + +bool already_ran = false; +extern "C" __declspec(dllexport) BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { + switch (fdwReason) { + case DLL_PROCESS_ATTACH: + base = GetModuleHandle(NULL); + + // Don't allow this to run more than once + if (already_ran) + return true; + + already_ran = true; + + char msg[256] = {0}; + + // This serves to prevent ourself from being unloaded, if we are a .fip + hSelf = LoadLibrary(MODLOADER_NAME ".fip"); + + // The user could also inject this as a DLL, so if we can't find our .fip, we were probably launched with a launcher. + // Therefore, we don't need to prompt the user. + if (hSelf) { + if (MessageBoxA(NULL, "Would you like to run with mods?", "Cube World Mod Loader", MB_YESNO) != IDYES) { + PatchFreeImage(); + return true; + } + } + + base = GetModuleHandle(NULL); + + + // Figure out where the executable is and verify its checksum + char cubePath[_MAX_PATH+1]; + GetModuleFileName(NULL, cubePath, _MAX_PATH); + + uint32_t checksum = crc32_file(cubePath); + if (checksum == CUBE_PACKED_CRC || checksum == CUBE_UNPACKED_CRC) { + // Patch some code to run StartMods. This method makes it work with AND without SteamStub. + InitializationPatch(); + } else { + sprintf(msg, "%s does not seem to be version %s. CRC %08X", cubePath, CUBE_VERSION, checksum); + Popup("Error", msg); + PatchFreeImage(); + return true; + } + + Sleep(250); + PatchFreeImage(); } return true; } diff --git a/README.md b/README.md index 51aaa6a..d2268bc 100644 --- a/README.md +++ b/README.md @@ -3,20 +3,15 @@ Supports injecting Cube World mods. DLLs must go in a folder called "Mods". ## Installing -Get the latest executable from Releases and place it in the same folder as cubeworld.exe +Get the latest .fib from Releases and place it in the same folder as cubeworld.exe https://github.com/ChrisMiuchiz/Cube-World-Mod-Launcher/releases ## Installing Mods A "Mods" folder should be created in the same folder as cubeworld.exe, and mods should be installed by moving them into the Mods folder. - -## Preparing cubeworld.exe -Since Cube World is using SteamStub obfuscation, you need to remove the obfuscation using [Steamless](https://github.com/atom0s/Steamless). - - ## Building the launcher or mods -This project will only ever support GCC. This program will not even build using MSVC, not only because the inline assembly syntax is different, but because support for inline assembly was removed for x86-64. +This project currently only supports GCC, but will likely move to clang soon. This program will not even build using MSVC, not only because the inline assembly syntax is different, but because support for inline assembly was removed for x86-64. All mods MUST include cwmods.h from [cwsdk](https://github.com/ChrisMiuchiz/CWSDK) to function.