Merge branch 'HEAD' of https://github.com/ChrisMiuchiz/Cube-World-Mod-Launcher.git
# Conflicts: # CubeModLoader/main.cpp
This commit is contained in:
commit
9b837e0a91
|
@ -3,20 +3,13 @@
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "Process.h"
|
#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* CUBE_EXECUTABLE = "cubeworld.exe";
|
||||||
char* MODLOADER_DLL = "CubeModLoader.dll";
|
char* MODLOADER_DLL = "CubeModLoader.dll";
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
bool FileExists(char* fileName) {
|
bool FileExists(const char* fileName) {
|
||||||
DWORD dwAttrib = GetFileAttributes(fileName);
|
DWORD dwAttrib = GetFileAttributes(fileName);
|
||||||
return (dwAttrib != INVALID_FILE_ATTRIBUTES && !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
|
return (dwAttrib != INVALID_FILE_ATTRIBUTES && !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
|
||||||
}
|
}
|
||||||
|
@ -28,13 +21,8 @@ int Bail(int result){
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
bool testMode = false;
|
if (argc >= 2) {
|
||||||
if (argc >= 2 && !strcmp(argv[1], "test")) {
|
CUBE_EXECUTABLE = argv[1];
|
||||||
testMode = true;
|
|
||||||
printf("Test mode enabled. CRC checks will be bypassed.\n");
|
|
||||||
if (argc >= 3) {
|
|
||||||
CUBE_EXECUTABLE = argv[2];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Cube world is obviously required
|
//Cube world is obviously required
|
||||||
|
@ -43,44 +31,12 @@ int main(int argc, char** argv) {
|
||||||
return Bail(1);
|
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
|
//Inject our dll
|
||||||
if ( !FileExists(MODLOADER_DLL) ) {
|
if ( !FileExists(MODLOADER_DLL) ) {
|
||||||
printf("%s not found.\n", MODLOADER_DLL);
|
printf("%s not found.\n", MODLOADER_DLL);
|
||||||
return Bail(1);
|
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);
|
Process process(CUBE_EXECUTABLE);
|
||||||
|
|
||||||
//Create game in suspended state
|
//Create game in suspended state
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#ifndef MAIN_H_INCLUDED
|
#ifndef MAIN_H_INCLUDED
|
||||||
#define MAIN_H_INCLUDED
|
#define MAIN_H_INCLUDED
|
||||||
|
|
||||||
bool FileExists(char* fileName);
|
bool FileExists(const char* fileName);
|
||||||
int Bail(int result);
|
int Bail(int result);
|
||||||
int main(int argc, char** argv);
|
int main(int argc, char** argv);
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,16 @@
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "DLL.h"
|
#include "DLL.h"
|
||||||
|
#include "crc.h"
|
||||||
|
|
||||||
#define MOD_MAJOR_VERSION 4
|
#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")))
|
#define no_optimize __attribute__((optimize("O0")))
|
||||||
|
@ -30,8 +37,12 @@ dllname->name = GetProcAddress(dllname->handle, #name);
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
void* base;
|
void* base; // Module base
|
||||||
vector <DLL*> modDLLs;
|
vector <DLL*> 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) {
|
void WriteFarJMP(void* source, void* destination) {
|
||||||
DWORD dwOldProtection;
|
DWORD dwOldProtection;
|
||||||
|
@ -52,65 +63,69 @@ void WriteFarJMP(void* source, void* destination) {
|
||||||
#include "callbacks/ChatHandler.h"
|
#include "callbacks/ChatHandler.h"
|
||||||
#include "callbacks/P2PRequestHandler.h"
|
#include "callbacks/P2PRequestHandler.h"
|
||||||
#include "callbacks/CheckInventoryFullHandler.h"
|
#include "callbacks/CheckInventoryFullHandler.h"
|
||||||
#include "callbacks/CheckMapIconVisibilityHandler.h"
|
|
||||||
|
|
||||||
void SetupHandlers() {
|
void SetupHandlers() {
|
||||||
SetupChatHandler();
|
SetupChatHandler();
|
||||||
SetupP2PRequestHandler();
|
SetupP2PRequestHandler();
|
||||||
SetupCheckInventoryFullHandler();
|
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);
|
MessageBoxA(0, msg, title, MB_OK | MB_ICONINFORMATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" __declspec(dllexport) BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
|
void PrintLoadedMods() {
|
||||||
switch (fdwReason) {
|
std::string mods("Mods Loaded:\n");
|
||||||
case DLL_PROCESS_ATTACH:
|
for (DLL* dll : modDLLs) {
|
||||||
base = GetModuleHandle(NULL);
|
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
|
SetupHandlers();
|
||||||
HANDLE hFind;
|
|
||||||
WIN32_FIND_DATA data;
|
|
||||||
|
|
||||||
CreateDirectory("Mods", NULL);
|
//Find mods
|
||||||
hFind = FindFirstFile("Mods\\*.dll", &data);
|
HANDLE hFind;
|
||||||
if (hFind != INVALID_HANDLE_VALUE) {
|
WIN32_FIND_DATA data;
|
||||||
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 all the functions the mods may export
|
CreateDirectory("Mods", NULL);
|
||||||
for (DLL* dll: modDLLs) {
|
hFind = FindFirstFile("Mods\\*.dll", &data);
|
||||||
MUST_IMPORT(dll, ModMajorVersion);
|
if (hFind != INVALID_HANDLE_VALUE) {
|
||||||
MUST_IMPORT(dll, ModMinorVersion);
|
do {
|
||||||
MUST_IMPORT(dll, ModPreInitialize);
|
// We should be loaded into the application's address space, so we can just LoadLibraryA
|
||||||
IMPORT(dll, ModInitialize);
|
DLL* dll = new DLL(string("Mods\\") + data.cFileName);
|
||||||
IMPORT(dll, HandleChat);
|
dll->Load();
|
||||||
IMPORT(dll, HandleP2PRequest);
|
printf("Loaded %s\n", dll->fileName.c_str());
|
||||||
IMPORT(dll, HandleCheckInventoryFull);
|
modDLLs.push_back(dll);
|
||||||
IMPORT(dll, HandleCheckMapIconVisibility);
|
} while (FindNextFile(hFind, &data));
|
||||||
}
|
FindClose(hFind);
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure version compatibility
|
// Find all the functions the mods may export
|
||||||
char msg[512] = {0};
|
for (DLL* dll: modDLLs) {
|
||||||
for (DLL* dll: modDLLs) {
|
MUST_IMPORT(dll, ModMajorVersion);
|
||||||
int majorVersion = ((int(*)())dll->ModMajorVersion)();
|
MUST_IMPORT(dll, ModMinorVersion);
|
||||||
int minorVersion = ((int(*)())dll->ModMinorVersion)();
|
MUST_IMPORT(dll, ModPreInitialize);
|
||||||
if (majorVersion != MOD_MAJOR_VERSION) {
|
IMPORT(dll, ModInitialize);
|
||||||
sprintf(msg, "%s has major version %d but requires %d.\n", dll->fileName.c_str(), majorVersion, MOD_MAJOR_VERSION);
|
IMPORT(dll, HandleChat);
|
||||||
Popup("Error", msg);
|
IMPORT(dll, HandleP2PRequest);
|
||||||
exit(1);
|
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) {
|
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);
|
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)();
|
((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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,20 +3,15 @@
|
||||||
Supports injecting Cube World mods. DLLs must go in a folder called "Mods".
|
Supports injecting Cube World mods. DLLs must go in a folder called "Mods".
|
||||||
|
|
||||||
## Installing
|
## 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
|
https://github.com/ChrisMiuchiz/Cube-World-Mod-Launcher/releases
|
||||||
|
|
||||||
## Installing Mods
|
## 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.
|
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
|
## 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.
|
All mods MUST include cwmods.h from [cwsdk](https://github.com/ChrisMiuchiz/CWSDK) to function.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue