From 74a23c7f6331406e3ea0b260fc5a53632d81a0e8 Mon Sep 17 00:00:00 2001 From: Sylmir Date: Wed, 3 Jan 2024 23:22:56 +0100 Subject: [PATCH 1/9] CreateRemoteThread half working --- CMakeLists.txt | 20 ++++--- injector/injector.c | 127 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 injector/injector.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 68ab82eaa..f7404e88b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,11 +93,11 @@ target_compile_options(libzhl PUBLIC "/MD" "/wd4251" "/wd4996") # Injected DLL FILE(GLOB LAUNCHER_SRC ${CMAKE_SOURCE_DIR}/launcher/*.cpp) -add_library(dsound SHARED ${LAUNCHER_SRC}) -target_include_directories(dsound PUBLIC ${CURL_INCLUDE_DIR}) -target_link_libraries(dsound libcurl cryptopp) -target_compile_options(dsound PUBLIC "/MD" "/wd4996") -target_link_libraries(dsound Imagehlp) +add_library(launcher SHARED ${LAUNCHER_SRC}) +target_include_directories(launcher PUBLIC ${CURL_INCLUDE_DIR}) +target_link_libraries(launcher libcurl cryptopp) +target_compile_options(launcher PUBLIC "/MD" "/wd4996") +target_link_libraries(launcher Imagehlp) # ImGUI enable_language(RC) @@ -188,10 +188,13 @@ add_custom_command(TARGET zhlREPENTOGON POST_BUILD COMMAND ${CMAKE_COMMAND} -E c add_custom_command(TARGET zhlREPENTOGON POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${PROJECT_SOURCE_DIR}/repentogon/resources-repentogon" "$/resources-repentogon") # add_custom_command(TARGET zhlDelirium POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${PROJECT_SOURCE_DIR}/delirium/resources-delirium" "$/resources-delirium") +# Injector +add_executable(injector injector/injector.c) if(NOT ISAAC_DIRECTORY STREQUAL "") message (STATUS "Files will be installed to " ${ISAAC_DIRECTORY}) - add_custom_command(TARGET Lua5.4 POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "$/Lua5.4.dll" "${ISAAC_DIRECTORY}") - add_custom_command(TARGET dsound POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "$/dsound.dll" "${ISAAC_DIRECTORY}") + add_custom_command(TARGET Lua5.4 POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "$/Lua5.4.dll" "${ISAAC_DIRECTORY}") + add_custom_command(TARGET injector POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "$/injector.exe" "${ISAAC_DIRECTORY}") + add_custom_command(TARGET launcher POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "$/launcher.dll" "${ISAAC_DIRECTORY}") add_custom_command(TARGET libzhl POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "$/libzhl.dll" "${ISAAC_DIRECTORY}") add_custom_command(TARGET zhlREPENTOGON POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "$/freetype.dll" "${ISAAC_DIRECTORY}") add_custom_command(TARGET zhlREPENTOGON POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "$/zhlREPENTOGON.dll" "${ISAAC_DIRECTORY}") @@ -218,4 +221,5 @@ target_link_libraries(libzhl Lua5.4 "Zydis" dbghelp) set_property(GLOBAL PROPERTY USE_FOLDERS ON) set_target_properties(freetype antlr4_shared imgui Lua5.4 stb stbc Zydis Zycore cryptopp libcurl_object libcurl_static curl_uninstall PROPERTIES FOLDER "External Libs") set_target_properties(libzhl zhlparser PROPERTIES FOLDER "libzhl") -set_target_properties(dsound zhlDelirium zhlREPENTOGON PROPERTIES FOLDER "Repentogon") +set_target_properties(launcher zhlDelirium zhlREPENTOGON PROPERTIES FOLDER "Repentogon") + diff --git a/injector/injector.c b/injector/injector.c new file mode 100644 index 000000000..03e39aad9 --- /dev/null +++ b/injector/injector.c @@ -0,0 +1,127 @@ +#include + +#include +#include + +void Log(const char* fmt, ...) { + va_list va; + va_start(va, fmt); + + FILE* f = fopen("injector.log", "a"); + if (!f) { + f = stderr; + } + + vfprintf(f, fmt, va); + va_end(va); +} + +int main() { + STARTUPINFOA startupInfo; + memset(&startupInfo, 0, sizeof(startupInfo)); + + PROCESS_INFORMATION processInfo; + memset(&processInfo, 0, sizeof(processInfo)); + + DWORD result = CreateProcess("isaac-ng.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &startupInfo, &processInfo); + if (result == 0) { + Log("Failed to create process: %d\n", GetLastError()); + return -1; + } + + HANDLE process = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, + FALSE, processInfo.dwProcessId); + if (!process) { + Log("Failed to open process: %d\n", GetLastError()); + return -1; + } + + void* remotePage = VirtualAllocEx(process, NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE); + if (!remotePage) { + Log("Failed to allocate memory in isaac-ng.exe to load the dsound DLL: %d\n", GetLastError()); + return -1; + } + + size_t bytesWritten = 0; + char zeroBuffer[4096]; + memset(zeroBuffer, 0, sizeof(zeroBuffer)); + WriteProcessMemory(process, remotePage, zeroBuffer, 4096, &bytesWritten); + + HMODULE kernel32 = GetModuleHandle("kernel32.dll"); + if (!kernel32) { + Log("Unable to find kernel32.dll, WTF\n"); + return -1; + } + + FARPROC getProcAddress = GetProcAddress(kernel32, "GetProcAddress"); + FARPROC loadLibraryA = GetProcAddress(kernel32, "LoadLibraryA"); + + if (!getProcAddress) { + Log("Unable to find GetProcAddress\n"); + return -1; + } + + if (!loadLibraryA) { + Log("Unable to find LoadLibraryA\n"); + return -1; + } + + const char* dllName = "launcher.dll"; + // 0x0 + WriteProcessMemory(process, remotePage, &getProcAddress, sizeof(getProcAddress), &bytesWritten); + // 0x4 + WriteProcessMemory(process, (char*)remotePage + 4, &loadLibraryA, sizeof(loadLibraryA), &bytesWritten); + // 0x8 + WriteProcessMemory(process, (char*)remotePage + 8, dllName, strlen(dllName), &bytesWritten); + /* 0x16 (0x15 is a '\0') + * Call LoadLibraryA in the remote thread. + * The thread will push the name of the DLL from its stack. + * It will then call LoadLibraryA. + * + * This function is a THREAD_START_ROUTINE, with the following signature : + * DWORD WINAPI(LPVOID); + * + * WINAPI is __stdcall: arguments are pushed in reverse order on the stack and callee cleans the stack. + */ + char hook[128] = { + "\x55" // push ebp + "\x89\xe5" // mov ebp, esp + "\x53" // push ebx + "\x56" // push esi + "\x57" // push edi + "\x3e\x8b\x5d\x08" // mov ebx, dword ptr ds:[ebp+8], put parameter in ebx + "\x8b\x73\x04" // mov esi, dword ptr ds:[ebx+4], put load library a in esi + "\x8b\x7b\x08" // mov edi, dword ptr ds:[ebx+8], put dllname in edi + "\x57" // push edi, push dllname on stack + "\xff\xd6" // call LoadLibraryA + "\x5f" // pop edi + "\x5e" // pop esi + "\x5b" // pop ebx + "\x89\xec" // mov esp, ebp + "\xb8\x01\x00\x00\x00" // mov eax, 1 + "\x5d" // pop ebp + "\xc3" // ret + }; + WriteProcessMemory(process, (char*)remotePage + 0x15, hook, 128, &bytesWritten); + + HANDLE remoteThread = CreateRemoteThread(process, NULL, 0, (char*)remotePage + 0x15, remotePage, 0, NULL); + if (!remoteThread) { + Log("Error while creating remote thread: %d\n", GetLastError()); + return -1; + } + + result = ResumeThread(processInfo.hThread); + if (result == -1) { + Log("Failed to resume isaac-ng.exe main thread: %d\n", GetLastError()); + return -1; + } + else { + Log("ResumeThread: %d\n", result); + } + + WaitForSingleObject(processInfo.hProcess, INFINITE); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + + return 0; +} \ No newline at end of file From b79ef3ae3a8165526b3662390e6224f4a5872b4e Mon Sep 17 00:00:00 2001 From: Sylmir Date: Wed, 3 Jan 2024 23:28:45 +0100 Subject: [PATCH 2/9] Use lea instead of move for dll name --- injector/injector.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/injector/injector.c b/injector/injector.c index 03e39aad9..ea66c2c04 100644 --- a/injector/injector.c +++ b/injector/injector.c @@ -91,7 +91,7 @@ int main() { "\x57" // push edi "\x3e\x8b\x5d\x08" // mov ebx, dword ptr ds:[ebp+8], put parameter in ebx "\x8b\x73\x04" // mov esi, dword ptr ds:[ebx+4], put load library a in esi - "\x8b\x7b\x08" // mov edi, dword ptr ds:[ebx+8], put dllname in edi + "\x8d\x7b\x08" // lea edi, [ebx+8], put dllname in edi "\x57" // push edi, push dllname on stack "\xff\xd6" // call LoadLibraryA "\x5f" // pop edi From b6585495bf9ba76a62604205e14b64549c281e09 Mon Sep 17 00:00:00 2001 From: Sylmir Date: Thu, 4 Jan 2024 09:10:13 +0100 Subject: [PATCH 3/9] ret 4 in injected function --- injector/injector.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/injector/injector.c b/injector/injector.c index ea66c2c04..a44581e81 100644 --- a/injector/injector.c +++ b/injector/injector.c @@ -100,7 +100,7 @@ int main() { "\x89\xec" // mov esp, ebp "\xb8\x01\x00\x00\x00" // mov eax, 1 "\x5d" // pop ebp - "\xc3" // ret + "\xc2\x04\x00" // ret 4 }; WriteProcessMemory(process, (char*)remotePage + 0x15, hook, 128, &bytesWritten); From 2c37e40dd540f3ed9bd1cb534d52d3bbf8652c60 Mon Sep 17 00:00:00 2001 From: Sylmir Date: Thu, 4 Jan 2024 21:04:08 +0100 Subject: [PATCH 4/9] It should work but it doesn't --- injector/injector.c | 48 ++++++++++-- launcher/dllmain.cpp | 31 +++++--- libzhl/dllmain.cpp | 4 + repentogon/ImGuiFeatures/ImGui.cpp | 2 +- repentogon/LuaInterfaces/CustomCallbacks.cpp | 80 ++++++++++---------- 5 files changed, 106 insertions(+), 59 deletions(-) diff --git a/injector/injector.c b/injector/injector.c index a44581e81..d2033cd10 100644 --- a/injector/injector.c +++ b/injector/injector.c @@ -67,13 +67,22 @@ int main() { } const char* dllName = "launcher.dll"; + const char* functionName = "LaunchZHL"; + size_t offset = 0; // 0x0 WriteProcessMemory(process, remotePage, &getProcAddress, sizeof(getProcAddress), &bytesWritten); + offset += bytesWritten; // 0x4 - WriteProcessMemory(process, (char*)remotePage + 4, &loadLibraryA, sizeof(loadLibraryA), &bytesWritten); + WriteProcessMemory(process, (char*)remotePage + offset, &loadLibraryA, sizeof(loadLibraryA), &bytesWritten); + offset += bytesWritten; // 0x8 - WriteProcessMemory(process, (char*)remotePage + 8, dllName, strlen(dllName), &bytesWritten); - /* 0x16 (0x15 is a '\0') + WriteProcessMemory(process, (char*)remotePage + offset, dllName, strlen(dllName), &bytesWritten); + offset += (bytesWritten + 1); + // 0x16 (0x15 is a '\0') + WriteProcessMemory(process, (char*)remotePage + offset, functionName, strlen(functionName), &bytesWritten); + offset += (bytesWritten + 1); + size_t functionOffset = offset; + /* 0x21 (0x20 is a '\0') * Call LoadLibraryA in the remote thread. * The thread will push the name of the DLL from its stack. * It will then call LoadLibraryA. @@ -90,10 +99,16 @@ int main() { "\x56" // push esi "\x57" // push edi "\x3e\x8b\x5d\x08" // mov ebx, dword ptr ds:[ebp+8], put parameter in ebx - "\x8b\x73\x04" // mov esi, dword ptr ds:[ebx+4], put load library a in esi + "\x8b\x73\x04" // mov esi, dword ptr ds:[ebx+4], put LoadLibraryA in esi "\x8d\x7b\x08" // lea edi, [ebx+8], put dllname in edi "\x57" // push edi, push dllname on stack - "\xff\xd6" // call LoadLibraryA + "\xff\xd6" // call esi, call LoadLibraryA("launcher.dll") + "\x8d\x7b\x15" // lea edi, [ebx + 0x15], put function name in edi + "\x57" // push edi, put function name on stack + "\x50" // push eax, put library on stack + "\x8b\x33" // mov esi, dword ptr ds:[ebx], put GetProcAddress in esi + "\xff\xd6" // call esi, call GetProcAddress(lib, "LaunchZHL") + "\xff\xd0" // call eax, call LaunchZHL() "\x5f" // pop edi "\x5e" // pop esi "\x5b" // pop ebx @@ -102,14 +117,33 @@ int main() { "\x5d" // pop ebp "\xc2\x04\x00" // ret 4 }; - WriteProcessMemory(process, (char*)remotePage + 0x15, hook, 128, &bytesWritten); + WriteProcessMemory(process, (char*)remotePage + offset, hook, 128, &bytesWritten); - HANDLE remoteThread = CreateRemoteThread(process, NULL, 0, (char*)remotePage + 0x15, remotePage, 0, NULL); + HANDLE remoteThread = CreateRemoteThread(process, NULL, 0, (char*)remotePage + functionOffset, remotePage, 0, NULL); if (!remoteThread) { Log("Error while creating remote thread: %d\n", GetLastError()); return -1; } + result = WaitForSingleObject(remoteThread, 60 * 1000); + switch (result) { + case WAIT_OBJECT_0: + Log("RemoteThread completed\n"); + break; + + case WAIT_ABANDONED: + Log("This shouldn't happened: RemoteThread returned WAIT_ABANDONNED\n"); + break; + + case WAIT_TIMEOUT: + Log("RemoteThread timeout\n"); + break; + + case WAIT_FAILED: + Log("WaitForSingleObject on RemoteThread failed: %d\n", GetLastError()); + break; + } + result = ResumeThread(processInfo.hThread); if (result == -1) { Log("Failed to resume isaac-ng.exe main thread: %d\n", GetLastError()); diff --git a/launcher/dllmain.cpp b/launcher/dllmain.cpp index f4b632ae7..9eb4d979c 100644 --- a/launcher/dllmain.cpp +++ b/launcher/dllmain.cpp @@ -117,13 +117,12 @@ DWORD RedirectLua(HMODULE* outLua) { static HMODULE luaHandle = NULL; -BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) -{ - if(ul_reason_for_call == DLL_PROCESS_ATTACH) - { +extern "C" { + void __declspec(dllexport) LaunchZHL() { sLogger->SetOutputFile("dsound.log", "w", true); sLogger->SetFlushOnLog(true); sLogger->Info("Loaded REPENTOGON dsound.dll\n"); + if (HasCommandLineArgument("-repentogonoff")) { FILE* f = fopen("repentogon.log", "a"); if (f) { @@ -132,7 +131,7 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReser fprintf(f, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"); fclose(f); } - return TRUE; + return; } sLogger->Info("dsound: Overriding Lua 5.3.3 with Lua 5.4\n"); @@ -162,6 +161,11 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReser if (!HasCommandLineArgument("-skipupdates")) { sLogger->Info("dsound: Checking for updates\n"); + + if (HasCommandLineArgument("-console")) + ConsoleWindow::Init(); + + if (!HasCommandLineArgument("-skipupdates")) CheckForUpdates(); sLogger->Info("dsound: Update checking done\n"); } @@ -181,16 +185,17 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReser WIN32_FIND_DATA findData; HANDLE hFind = FindFirstFile("*", &findData); - if(hFind != INVALID_HANDLE_VALUE) + LoadLibrary("libzhl.dll"); + if (hFind != INVALID_HANDLE_VALUE) { do { - if(!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { - const char *fileName = findData.cFileName; + const char* fileName = findData.cFileName; int n = strlen(fileName); - if(n >= 4 && !_stricmp(fileName + n - 4, ".dll") && !_strnicmp(fileName, "zhl", 3)) + if (n >= 4 && !_stricmp(fileName + n - 4, ".dll") && !_strnicmp(fileName, "zhl", 3)) { HMODULE library = LoadLibraryA(fileName); if (!library) @@ -206,7 +211,7 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReser //printf("loaded mod %s\n", fileName); } } - } while(FindNextFile(hFind, &findData)); + } while (FindNextFile(hFind, &findData)); FindClose(hFind); } @@ -226,7 +231,11 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReser sLogger->Info("dsound: Loaded all ZHL mods in %d msecs\n", GetTickCount() - startTime); } - else if(ul_reason_for_call == DLL_PROCESS_DETACH) +} + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) +{ + if(ul_reason_for_call == DLL_PROCESS_DETACH) { sLogger->Info("dsound: Unloading dsound.dll\n"); /* printf("unloading libs\n"); diff --git a/libzhl/dllmain.cpp b/libzhl/dllmain.cpp index 41134226e..27383f36a 100644 --- a/libzhl/dllmain.cpp +++ b/libzhl/dllmain.cpp @@ -18,6 +18,10 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReser { if(ul_reason_for_call == DLL_PROCESS_ATTACH) { + FILE* f = fopen("testzhl.log", "w"); + fprintf(f, "Kill me\n"); + fclose(f); + ZHL::Log("libzhl: entering DllMain\n"); InitializeSymbolHandler(); #ifdef ZHL_LOG_FILE diff --git a/repentogon/ImGuiFeatures/ImGui.cpp b/repentogon/ImGuiFeatures/ImGui.cpp index afa598572..47617db40 100644 --- a/repentogon/ImGuiFeatures/ImGui.cpp +++ b/repentogon/ImGuiFeatures/ImGui.cpp @@ -274,7 +274,7 @@ static std::vector pressedKeys; LRESULT CALLBACK windowProc_hook(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - if (shutdownInitiated) + if (shutdownInitiated || !g_Game) return CallWindowProc(windowProc, hWnd, uMsg, wParam, lParam); // Enable the overlay using the grave key, disable using ESC diff --git a/repentogon/LuaInterfaces/CustomCallbacks.cpp b/repentogon/LuaInterfaces/CustomCallbacks.cpp index e224fd59b..910450f15 100644 --- a/repentogon/LuaInterfaces/CustomCallbacks.cpp +++ b/repentogon/LuaInterfaces/CustomCallbacks.cpp @@ -2530,46 +2530,46 @@ HOOK_METHOD(GridEntity, hurt_func, (Entity* ent, int Damage, int DamageFlags, fl } // MC_POST_LEVEL_LAYOUT_GENERATED -HOOK_METHOD(LevelGenerator, Generate, (int unk, bool unk2, bool unk3, bool unk4, unsigned int const& allowedShapes, unsigned int numDeadEnds, LevelGenerator_Room* startRoom) -> void) { - super(unk, unk2, unk3, unk4, allowedShapes, numDeadEnds, startRoom); - - const int callbackId = 1099; - if (!CallbackState.test(callbackId - 1000)) { - return; - } - - lua_State* L = g_LuaEngine->_state; - lua::LuaStackProtector protector(L); - - lua_rawgeti(L, LUA_REGISTRYINDEX, g_LuaEngine->runCallbackRegistry->key); - lua::LuaResults result = lua::LuaCaller(L).push(callbackId) - .push(12) - .push(this, lua::metatables::LevelGeneratorMT) - .call(0); - - /* std::ofstream stream("repentogon.log", std::ios::app); - stream << "After the call to Generate: " << std::endl; - stream << " - Dead ends (" << GetDeadEnds()->size() << "): "; - for (int idx : *GetDeadEnds()) { - stream << idx << " "; - } - stream << std::endl; - stream << " - Non dead ends (" << GetNonDeadEnds()->size() << "): "; - for (int idx : *GetNonDeadEnds()) { - stream << idx << " "; - } - stream << std::endl; - stream << " - Rooms are as follows : " << std::endl; - for (LevelGenerator_Room const& room : *allRooms) { - stream << "\tRoom " << room._generationIndex << " at (" << room._gridColIdx << ", " << room._gridLineIdx << ") of shape " << room._shape << " with allowed door slots " << room._doors << " connects to "; - for (auto const& neighbor : room._neighbors) { - stream << neighbor << " "; - } - stream << std::endl; - } - - stream.flush(); */ -} +//HOOK_METHOD(LevelGenerator, Generate, (int unk, bool unk2, bool unk3, bool unk4, unsigned int const& allowedShapes, unsigned int numDeadEnds, LevelGenerator_Room* startRoom) -> void) { +// super(unk, unk2, unk3, unk4, allowedShapes, numDeadEnds, startRoom); +// +// const int callbackId = 1099; +// if (!CallbackState.test(callbackId - 1000)) { +// return; +// } +// +// lua_State* L = g_LuaEngine->_state; +// lua::LuaStackProtector protector(L); +// +// lua_rawgeti(L, LUA_REGISTRYINDEX, g_LuaEngine->runCallbackRegistry->key); +// lua::LuaResults result = lua::LuaCaller(L).push(callbackId) +// .push(12) +// .push(this, lua::metatables::LevelGeneratorMT) +// .call(0); +// +// /* std::ofstream stream("repentogon.log", std::ios::app); +// stream << "After the call to Generate: " << std::endl; +// stream << " - Dead ends (" << GetDeadEnds()->size() << "): "; +// for (int idx : *GetDeadEnds()) { +// stream << idx << " "; +// } +// stream << std::endl; +// stream << " - Non dead ends (" << GetNonDeadEnds()->size() << "): "; +// for (int idx : *GetNonDeadEnds()) { +// stream << idx << " "; +// } +// stream << std::endl; +// stream << " - Rooms are as follows : " << std::endl; +// for (LevelGenerator_Room const& room : *allRooms) { +// stream << "\tRoom " << room._generationIndex << " at (" << room._gridColIdx << ", " << room._gridLineIdx << ") of shape " << room._shape << " with allowed door slots " << room._doors << " connects to "; +// for (auto const& neighbor : room._neighbors) { +// stream << neighbor << " "; +// } +// stream << std::endl; +// } +// +// stream.flush(); */ +//} //POST_NIGHTMARE_SCENE_RENDER (1102) /*HOOK_METHOD(NightmareScene, Render, () -> void) { From f781e6281f63f95fc2e03855092ad45e83e96f05 Mon Sep 17 00:00:00 2001 From: Sylmir Date: Thu, 4 Jan 2024 22:37:57 +0100 Subject: [PATCH 5/9] Revert changes made to debug the level gen freeze --- libzhl/dllmain.cpp | 4 - repentogon/LuaInterfaces/CustomCallbacks.cpp | 80 ++++++++++---------- 2 files changed, 40 insertions(+), 44 deletions(-) diff --git a/libzhl/dllmain.cpp b/libzhl/dllmain.cpp index 27383f36a..41134226e 100644 --- a/libzhl/dllmain.cpp +++ b/libzhl/dllmain.cpp @@ -18,10 +18,6 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReser { if(ul_reason_for_call == DLL_PROCESS_ATTACH) { - FILE* f = fopen("testzhl.log", "w"); - fprintf(f, "Kill me\n"); - fclose(f); - ZHL::Log("libzhl: entering DllMain\n"); InitializeSymbolHandler(); #ifdef ZHL_LOG_FILE diff --git a/repentogon/LuaInterfaces/CustomCallbacks.cpp b/repentogon/LuaInterfaces/CustomCallbacks.cpp index 910450f15..e224fd59b 100644 --- a/repentogon/LuaInterfaces/CustomCallbacks.cpp +++ b/repentogon/LuaInterfaces/CustomCallbacks.cpp @@ -2530,46 +2530,46 @@ HOOK_METHOD(GridEntity, hurt_func, (Entity* ent, int Damage, int DamageFlags, fl } // MC_POST_LEVEL_LAYOUT_GENERATED -//HOOK_METHOD(LevelGenerator, Generate, (int unk, bool unk2, bool unk3, bool unk4, unsigned int const& allowedShapes, unsigned int numDeadEnds, LevelGenerator_Room* startRoom) -> void) { -// super(unk, unk2, unk3, unk4, allowedShapes, numDeadEnds, startRoom); -// -// const int callbackId = 1099; -// if (!CallbackState.test(callbackId - 1000)) { -// return; -// } -// -// lua_State* L = g_LuaEngine->_state; -// lua::LuaStackProtector protector(L); -// -// lua_rawgeti(L, LUA_REGISTRYINDEX, g_LuaEngine->runCallbackRegistry->key); -// lua::LuaResults result = lua::LuaCaller(L).push(callbackId) -// .push(12) -// .push(this, lua::metatables::LevelGeneratorMT) -// .call(0); -// -// /* std::ofstream stream("repentogon.log", std::ios::app); -// stream << "After the call to Generate: " << std::endl; -// stream << " - Dead ends (" << GetDeadEnds()->size() << "): "; -// for (int idx : *GetDeadEnds()) { -// stream << idx << " "; -// } -// stream << std::endl; -// stream << " - Non dead ends (" << GetNonDeadEnds()->size() << "): "; -// for (int idx : *GetNonDeadEnds()) { -// stream << idx << " "; -// } -// stream << std::endl; -// stream << " - Rooms are as follows : " << std::endl; -// for (LevelGenerator_Room const& room : *allRooms) { -// stream << "\tRoom " << room._generationIndex << " at (" << room._gridColIdx << ", " << room._gridLineIdx << ") of shape " << room._shape << " with allowed door slots " << room._doors << " connects to "; -// for (auto const& neighbor : room._neighbors) { -// stream << neighbor << " "; -// } -// stream << std::endl; -// } -// -// stream.flush(); */ -//} +HOOK_METHOD(LevelGenerator, Generate, (int unk, bool unk2, bool unk3, bool unk4, unsigned int const& allowedShapes, unsigned int numDeadEnds, LevelGenerator_Room* startRoom) -> void) { + super(unk, unk2, unk3, unk4, allowedShapes, numDeadEnds, startRoom); + + const int callbackId = 1099; + if (!CallbackState.test(callbackId - 1000)) { + return; + } + + lua_State* L = g_LuaEngine->_state; + lua::LuaStackProtector protector(L); + + lua_rawgeti(L, LUA_REGISTRYINDEX, g_LuaEngine->runCallbackRegistry->key); + lua::LuaResults result = lua::LuaCaller(L).push(callbackId) + .push(12) + .push(this, lua::metatables::LevelGeneratorMT) + .call(0); + + /* std::ofstream stream("repentogon.log", std::ios::app); + stream << "After the call to Generate: " << std::endl; + stream << " - Dead ends (" << GetDeadEnds()->size() << "): "; + for (int idx : *GetDeadEnds()) { + stream << idx << " "; + } + stream << std::endl; + stream << " - Non dead ends (" << GetNonDeadEnds()->size() << "): "; + for (int idx : *GetNonDeadEnds()) { + stream << idx << " "; + } + stream << std::endl; + stream << " - Rooms are as follows : " << std::endl; + for (LevelGenerator_Room const& room : *allRooms) { + stream << "\tRoom " << room._generationIndex << " at (" << room._gridColIdx << ", " << room._gridLineIdx << ") of shape " << room._shape << " with allowed door slots " << room._doors << " connects to "; + for (auto const& neighbor : room._neighbors) { + stream << neighbor << " "; + } + stream << std::endl; + } + + stream.flush(); */ +} //POST_NIGHTMARE_SCENE_RENDER (1102) /*HOOK_METHOD(NightmareScene, Render, () -> void) { From 84fa2cba56566ae4c56f6c71e08cb301f12d2109 Mon Sep 17 00:00:00 2001 From: Sylmir Date: Fri, 5 Jan 2024 08:35:32 +0100 Subject: [PATCH 6/9] Log the hell out of the injector, this thing will work ! --- injector/injector.c | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/injector/injector.c b/injector/injector.c index d2033cd10..dcfd3731d 100644 --- a/injector/injector.c +++ b/injector/injector.c @@ -2,6 +2,7 @@ #include #include +#include void Log(const char* fmt, ...) { va_list va; @@ -12,11 +13,25 @@ void Log(const char* fmt, ...) { f = stderr; } + char buffer[4096]; + time_t now = time(NULL); + struct tm* tm = localtime(&now); + strftime(buffer, 4095, "%Y-%m-%d %H:%M:%S", tm); + fprintf(f, "[%s] ", buffer); vfprintf(f, fmt, va); va_end(va); } int main() { + { + FILE* f = fopen("injector.log", "w"); + if (f) { + fclose(f); + } + } + + Log("Starting injector\n"); + STARTUPINFOA startupInfo; memset(&startupInfo, 0, sizeof(startupInfo)); @@ -28,6 +43,9 @@ int main() { Log("Failed to create process: %d\n", GetLastError()); return -1; } + else { + Log("Started isaac-ng.exe in suspended state, processID = %d\n", processInfo.dwProcessId); + } HANDLE process = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, processInfo.dwProcessId); @@ -35,12 +53,18 @@ int main() { Log("Failed to open process: %d\n", GetLastError()); return -1; } + else { + Log("Acquired handle to isaac-ng.exe, process ID = %d\n", processInfo.dwProcessId); + } void* remotePage = VirtualAllocEx(process, NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (!remotePage) { Log("Failed to allocate memory in isaac-ng.exe to load the dsound DLL: %d\n", GetLastError()); return -1; } + else { + Log("Allocated memory for remote thread at %p\n", remotePage); + } size_t bytesWritten = 0; char zeroBuffer[4096]; @@ -52,6 +76,9 @@ int main() { Log("Unable to find kernel32.dll, WTF\n"); return -1; } + else { + Log("Acquired kernel32.dll at %p\n", kernel32); + } FARPROC getProcAddress = GetProcAddress(kernel32, "GetProcAddress"); FARPROC loadLibraryA = GetProcAddress(kernel32, "LoadLibraryA"); @@ -66,6 +93,8 @@ int main() { return -1; } + Log("Acquired GetProcAddress at %p, LoadLibraryA at %p\n", getProcAddress, loadLibraryA); + const char* dllName = "launcher.dll"; const char* functionName = "LaunchZHL"; size_t offset = 0; @@ -124,7 +153,11 @@ int main() { Log("Error while creating remote thread: %d\n", GetLastError()); return -1; } + else { + Log("Created remote thread in isaac-ng.exe\n"); + } + Log("Waiting for remote thread to complete\n"); result = WaitForSingleObject(remoteThread, 60 * 1000); switch (result) { case WAIT_OBJECT_0: @@ -136,7 +169,7 @@ int main() { break; case WAIT_TIMEOUT: - Log("RemoteThread timeout\n"); + Log("RemoteThread timed out\n"); break; case WAIT_FAILED: @@ -150,10 +183,13 @@ int main() { return -1; } else { - Log("ResumeThread: %d\n", result); + Log("Resumed main thread of isaac-ng.exe, previous supend count was %d\n", result); } + Log("Waiting for isaac-ng.exe main thread to return\n"); WaitForSingleObject(processInfo.hProcess, INFINITE); + Log("isaac-ng.exe completed, shutting down injector\n"); + CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hThread); From 5df0ab5773c7d46bafe26ec016aa027d7e915a01 Mon Sep 17 00:00:00 2001 From: Sylmir Date: Sun, 14 Jan 2024 13:36:09 +0100 Subject: [PATCH 7/9] Split into multiple functions to try and reduce AV detection --- injector/injector.c | 206 ++++++++++++++++++++++++++++++++++--------- launcher/dllmain.cpp | 5 -- libzhl/SigScan.cpp | 7 ++ 3 files changed, 169 insertions(+), 49 deletions(-) diff --git a/injector/injector.c b/injector/injector.c index dcfd3731d..127c732c6 100644 --- a/injector/injector.c +++ b/injector/injector.c @@ -1,62 +1,95 @@ +#define _CRT_SECURE_NO_WARNINGS + #include +#include #include #include #include -void Log(const char* fmt, ...) { - va_list va; - va_start(va, fmt); +struct IsaacOptions { + // Repentogon options + int updates; + int console; + + // Game options + int lua_debug; + int level_stage; + int stage_type; + const char* lua_heap_size; +}; - FILE* f = fopen("injector.log", "a"); - if (!f) { - f = stderr; +/* Perform the early setup for the injection: create the Isaac process, + * allocate memory for the remote thread function etc. + * + * No extra thread is created in the process. ImGui should be initialized + * afterwards to setup the injector, and then the remote thread can be + * created. + * + * Return true of the initialization was sucessful, false otherwise. + */ +static int FirstStageInit(struct IsaacOptions const* options, HANDLE* process, void** page, size_t* functionOffset, PROCESS_INFORMATION* processInfo); + +static void Log(const char* fmt, ...); + +static void GenerateCLI(const struct IsaacOptions* options, char cli[256]); + +void GenerateCLI(const struct IsaacOptions* options, char cli[256]) { + memset(cli, 0, sizeof(cli)); + if (options->console) { + strcat(cli, "--console "); } - char buffer[4096]; - time_t now = time(NULL); - struct tm* tm = localtime(&now); - strftime(buffer, 4095, "%Y-%m-%d %H:%M:%S", tm); - fprintf(f, "[%s] ", buffer); - vfprintf(f, fmt, va); - va_end(va); -} + if (!options->updates) { + strcat(cli, "--skipupdates "); + } -int main() { - { - FILE* f = fopen("injector.log", "w"); - if (f) { - fclose(f); - } + if (options->lua_debug) { + strcat(cli, "--luadebug "); } - Log("Starting injector\n"); + if (options->level_stage) { + strcat(cli, "--set-stage="); + char buffer[13]; // 11 chars for a max int (including sign) + 1 char for space + 1 char for '\0' + sprintf(buffer, "%d ", options->level_stage); + strcat(cli, buffer); + } + + if (options->stage_type) { + strcat(cli, "--set-stage-type="); + char buffer[13]; // 11 chars for a max int (including sign) + 1 char for space + 1 char for '\0' + sprintf(buffer, "%d ", options->stage_type); + strcat(cli, buffer); + } + + if (options->lua_heap_size) { + strcat(cli, "--luaheapsize="); + strcat(cli, options->lua_heap_size); + } +} +DWORD CreateIsaac(struct IsaacOptions const* options, PROCESS_INFORMATION* processInfo) { STARTUPINFOA startupInfo; memset(&startupInfo, 0, sizeof(startupInfo)); - PROCESS_INFORMATION processInfo; - memset(&processInfo, 0, sizeof(processInfo)); + memset(processInfo, 0, sizeof(*processInfo)); + + char cli[256]; + GenerateCLI(options, cli); - DWORD result = CreateProcess("isaac-ng.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &startupInfo, &processInfo); + DWORD result = CreateProcess("isaac-ng.exe", cli, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &startupInfo, processInfo); if (result == 0) { Log("Failed to create process: %d\n", GetLastError()); return -1; } else { - Log("Started isaac-ng.exe in suspended state, processID = %d\n", processInfo.dwProcessId); + Log("Started isaac-ng.exe in suspended state, processID = %d\n", processInfo->dwProcessId); } - HANDLE process = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, - FALSE, processInfo.dwProcessId); - if (!process) { - Log("Failed to open process: %d\n", GetLastError()); - return -1; - } - else { - Log("Acquired handle to isaac-ng.exe, process ID = %d\n", processInfo.dwProcessId); - } + return result; +} +int UpdateMemory(HANDLE process, PROCESS_INFORMATION const* processInfo, void** page, size_t* functionOffset) { void* remotePage = VirtualAllocEx(process, NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (!remotePage) { Log("Failed to allocate memory in isaac-ng.exe to load the dsound DLL: %d\n", GetLastError()); @@ -66,7 +99,7 @@ int main() { Log("Allocated memory for remote thread at %p\n", remotePage); } - size_t bytesWritten = 0; + SIZE_T bytesWritten = 0; char zeroBuffer[4096]; memset(zeroBuffer, 0, sizeof(zeroBuffer)); WriteProcessMemory(process, remotePage, zeroBuffer, 4096, &bytesWritten); @@ -110,15 +143,17 @@ int main() { // 0x16 (0x15 is a '\0') WriteProcessMemory(process, (char*)remotePage + offset, functionName, strlen(functionName), &bytesWritten); offset += (bytesWritten + 1); - size_t functionOffset = offset; + + *functionOffset = offset; + /* 0x21 (0x20 is a '\0') * Call LoadLibraryA in the remote thread. * The thread will push the name of the DLL from its stack. * It will then call LoadLibraryA. - * + * * This function is a THREAD_START_ROUTINE, with the following signature : * DWORD WINAPI(LPVOID); - * + * * WINAPI is __stdcall: arguments are pushed in reverse order on the stack and callee cleans the stack. */ char hook[128] = { @@ -148,7 +183,42 @@ int main() { }; WriteProcessMemory(process, (char*)remotePage + offset, hook, 128, &bytesWritten); - HANDLE remoteThread = CreateRemoteThread(process, NULL, 0, (char*)remotePage + functionOffset, remotePage, 0, NULL); + *page = remotePage; + return 0; +} + +int FirstStageInit(struct IsaacOptions const* options, HANDLE* outProcess, void** page, size_t* functionOffset, PROCESS_INFORMATION* processInfo) { + { + FILE* f = fopen("injector.log", "w"); + if (f) { + fclose(f); + } + } + + Log("Starting injector\n"); + DWORD processId = CreateIsaac(options, processInfo); + + HANDLE process = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, + FALSE, processInfo->dwProcessId); + if (!process) { + Log("Failed to open process: %d\n", GetLastError()); + return -1; + } + else { + Log("Acquired handle to isaac-ng.exe, process ID = %d\n", processInfo->dwProcessId); + } + + if (UpdateMemory(process, processInfo, page, functionOffset)) { + return -1; + } + + *outProcess = process; + + return 0; +} + +int CreateAndWait(HANDLE process, void* remotePage, size_t functionOffset) { + HANDLE remoteThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)((char*)remotePage + functionOffset), remotePage, 0, NULL); if (!remoteThread) { Log("Error while creating remote thread: %d\n", GetLastError()); return -1; @@ -158,7 +228,7 @@ int main() { } Log("Waiting for remote thread to complete\n"); - result = WaitForSingleObject(remoteThread, 60 * 1000); + DWORD result = WaitForSingleObject(remoteThread, 60 * 1000); switch (result) { case WAIT_OBJECT_0: Log("RemoteThread completed\n"); @@ -166,18 +236,61 @@ int main() { case WAIT_ABANDONED: Log("This shouldn't happened: RemoteThread returned WAIT_ABANDONNED\n"); - break; + return -1; case WAIT_TIMEOUT: Log("RemoteThread timed out\n"); - break; + return -1; case WAIT_FAILED: Log("WaitForSingleObject on RemoteThread failed: %d\n", GetLastError()); - break; + return -1; } - result = ResumeThread(processInfo.hThread); + return 0; +} + +void Log(const char* fmt, ...) { + va_list va; + va_start(va, fmt); + + FILE* f = fopen("injector.log", "a"); + if (!f) { + f = stderr; + } + + char buffer[4096]; + time_t now = time(NULL); + struct tm* tm = localtime(&now); + strftime(buffer, 4095, "%Y-%m-%d %H:%M:%S", tm); + fprintf(f, "[%s] ", buffer); + vfprintf(f, fmt, va); + va_end(va); +} + +int __declspec(dllexport) InjectIsaac(int updates, int console, int lua_debug, int level_stage, int stage_type, const char* lua_heap_size) { + HANDLE process; + void* remotePage; + size_t functionOffset; + PROCESS_INFORMATION processInfo; + + struct IsaacOptions options; + options.updates = updates; + options.console = console; + options.lua_debug = lua_debug; + options.level_stage = level_stage; + options.stage_type = stage_type; + options.lua_heap_size = lua_heap_size; + + if (FirstStageInit(&options, &process, &remotePage, &functionOffset, &processInfo)) { + return -1; + } + + if (CreateAndWait(process, remotePage, functionOffset)) { + return -1; + } + + DWORD result = ResumeThread(processInfo.hThread); if (result == -1) { Log("Failed to resume isaac-ng.exe main thread: %d\n", GetLastError()); return -1; @@ -193,5 +306,10 @@ int main() { CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hThread); + return 0; +} + +int main(int argc, char** argv) { + InjectIsaac(1, 1, 0, 0, 0, NULL); return 0; } \ No newline at end of file diff --git a/launcher/dllmain.cpp b/launcher/dllmain.cpp index 9eb4d979c..d42e2e7ef 100644 --- a/launcher/dllmain.cpp +++ b/launcher/dllmain.cpp @@ -161,11 +161,6 @@ extern "C" { if (!HasCommandLineArgument("-skipupdates")) { sLogger->Info("dsound: Checking for updates\n"); - - if (HasCommandLineArgument("-console")) - ConsoleWindow::Init(); - - if (!HasCommandLineArgument("-skipupdates")) CheckForUpdates(); sLogger->Info("dsound: Update checking done\n"); } diff --git a/libzhl/SigScan.cpp b/libzhl/SigScan.cpp index 008ad4598..9b6fe79b5 100644 --- a/libzhl/SigScan.cpp +++ b/libzhl/SigScan.cpp @@ -1,3 +1,4 @@ +#include "Log.h" #include "SigScan.h" #include #include @@ -197,6 +198,12 @@ bool SigScan::Scan(Callback callback) } s_pLastAddress = s_pBase; + ZHL::Logger logger; + logger.Log("[ERROR] Unable to find signature "); + FILE* f = logger.GetFile(); + for (size_t i = 0; i < m_iLength; ++i) + fprintf(f, "%hhx", m_sig[i]); + fprintf(f, "\n"); return false; } From e064af3470d2b792eda2d77bc7e03a178c0cd859 Mon Sep 17 00:00:00 2001 From: Sylmir Date: Mon, 22 Jan 2024 21:17:10 +0100 Subject: [PATCH 8/9] Start adding Tcl/Tk to injector --- CMakeLists.txt | 9 +++++++++ injector/injector.c | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f7404e88b..477d9dd9b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,12 @@ option(ZYDIS_BUILD_DOXYGEN "" OFF) add_subdirectory("libs/zydis") target_compile_options("Zydis" PUBLIC "/MD") +# Tcl/Tk +add_subdirectory ("libs/tcl8.6.13") +set (TCL_MAIN_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libs/tcl8.6.13") +add_subdirectory ("libs/tk8.6.13") +set (TK_MAIN_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libs/tk8.6.13") + # ANTLR option(WITH_STATIC_CRT "" OFF) option(ANTLR_BUILD_CPP_TESTS "" OFF) @@ -189,7 +195,10 @@ add_custom_command(TARGET zhlREPENTOGON POST_BUILD COMMAND ${CMAKE_COMMAND} -E c # add_custom_command(TARGET zhlDelirium POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${PROJECT_SOURCE_DIR}/delirium/resources-delirium" "$/resources-delirium") # Injector +include_directories("${TCL_MAIN_DIR}/generic" "${TCL_MAIN_DIR}/win" "${TK_MAIN_DIR}/generic" "${TK_MAIN_DIR}/win" "${TK_MAIN_DIR}/xlib") add_executable(injector injector/injector.c) +target_compile_definitions(injector PRIVATE STATIC_BUILD) +target_link_libraries(injector tk tcl) if(NOT ISAAC_DIRECTORY STREQUAL "") message (STATUS "Files will be installed to " ${ISAAC_DIRECTORY}) add_custom_command(TARGET Lua5.4 POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "$/Lua5.4.dll" "${ISAAC_DIRECTORY}") diff --git a/injector/injector.c b/injector/injector.c index 127c732c6..27ebe397e 100644 --- a/injector/injector.c +++ b/injector/injector.c @@ -7,6 +7,12 @@ #include #include +#undef BUILD_tcl + +#include "tcl.h" +#include "tk.h" +#include "tkWin.h" + struct IsaacOptions { // Repentogon options int updates; @@ -310,6 +316,10 @@ int __declspec(dllexport) InjectIsaac(int updates, int console, int lua_debug, i } int main(int argc, char** argv) { + Tcl_Interp* interp = Tcl_CreateInterp(); + Tcl_AppInit(interp); + Tk_Window mainWindow = Tk_MainWindow(interp); + Tk_MainLoop(); InjectIsaac(1, 1, 0, 0, 0, NULL); return 0; } \ No newline at end of file From 528c55b4e4e3a831775f81c66e1f76980be128d5 Mon Sep 17 00:00:00 2001 From: Sylmir Date: Sat, 10 Feb 2024 08:54:45 +0100 Subject: [PATCH 9/9] More work on launcher --- .gitmodules | 6 + CMakeLists.txt | 27 +- injector/{injector.c => injector.cpp} | 50 +-- injector/window.cpp | 499 ++++++++++++++++++++++++++ injector/window.h | 91 +++++ libs/libui-ng | 1 + libs/wxWidgets | 1 + libzhl/Version.cpp | 5 + repentogon/Version.cpp | 3 + 9 files changed, 641 insertions(+), 42 deletions(-) rename injector/{injector.c => injector.cpp} (90%) create mode 100755 injector/window.cpp create mode 100755 injector/window.h create mode 160000 libs/libui-ng create mode 160000 libs/wxWidgets create mode 100755 libzhl/Version.cpp create mode 100755 repentogon/Version.cpp diff --git a/.gitmodules b/.gitmodules index 5ba57aa2d..e043508bb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -38,3 +38,9 @@ [submodule "libs/cryptopp-cmake"] path = libs/cryptopp-cmake url = https://github.com/abdes/cryptopp-cmake +[submodule "libs/libui-ng"] + path = libs/libui-ng + url = git@github.com:libui-ng/libui-ng.git +[submodule "libs/wxWidgets"] + path = libs/wxWidgets + url = git@github.com:wxWidgets/wxWidgets.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 477d9dd9b..2af1a0655 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,8 @@ project(RepExtender) find_package(OpenGL REQUIRED) +include_directories ("${CMAKE_CURRENT_SOURCE_DIR}") + include_directories(${CMAKE_SOURCE_DIR}/include "${CMAKE_SOURCE_DIR}/libs/lua" "${CMAKE_SOURCE_DIR}/libs/rapidxml" "${CMAKE_SOURCE_DIR}/libs/rapidjson/include/rapidjson" "${CMAKE_SOURCE_DIR}/libs/LuaBridge/Source/LuaBridge" @@ -23,11 +25,18 @@ option(ZYDIS_BUILD_DOXYGEN "" OFF) add_subdirectory("libs/zydis") target_compile_options("Zydis" PUBLIC "/MD") -# Tcl/Tk -add_subdirectory ("libs/tcl8.6.13") -set (TCL_MAIN_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libs/tcl8.6.13") -add_subdirectory ("libs/tk8.6.13") -set (TK_MAIN_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libs/tk8.6.13") +# wxWidgets +set(wxBUILD_CXX_STANDARD 17) +option(wxBUILD_INSTALL "" OFF) +option(wxBUILD_OPTIMISE "" ON) +option(wxBUILD_PIC "" OFF) +option(wxBUILD_SHARED "" OFF) +option(wxBUILD_STRIPPED_RELEASE "" ON) +option(wxBUILD_VENDOR "" "repentogon") +add_subdirectory ("libs/wxWidgets") +include_directories ("${CMAKE_CURRENT_SOURCE_DIR}/libs/wxWidgets/" "${CMAKE_CURRENT_SOURCE_DIR}/libs/wxWidgets/include") +target_compile_options(wxbase PUBLIC "/MD") +target_compile_options(wxcore PUBLIC "/MD") # ANTLR option(WITH_STATIC_CRT "" OFF) @@ -195,10 +204,10 @@ add_custom_command(TARGET zhlREPENTOGON POST_BUILD COMMAND ${CMAKE_COMMAND} -E c # add_custom_command(TARGET zhlDelirium POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${PROJECT_SOURCE_DIR}/delirium/resources-delirium" "$/resources-delirium") # Injector -include_directories("${TCL_MAIN_DIR}/generic" "${TCL_MAIN_DIR}/win" "${TK_MAIN_DIR}/generic" "${TK_MAIN_DIR}/win" "${TK_MAIN_DIR}/xlib") -add_executable(injector injector/injector.c) -target_compile_definitions(injector PRIVATE STATIC_BUILD) -target_link_libraries(injector tk tcl) +add_executable(injector WIN32 injector/injector.cpp injector/window.cpp injector/window.h) +target_compile_options(injector PUBLIC "/MD") +target_link_libraries(injector wxbase wxcore cryptopp bcrypt) + if(NOT ISAAC_DIRECTORY STREQUAL "") message (STATUS "Files will be installed to " ${ISAAC_DIRECTORY}) add_custom_command(TARGET Lua5.4 POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "$/Lua5.4.dll" "${ISAAC_DIRECTORY}") diff --git a/injector/injector.c b/injector/injector.cpp similarity index 90% rename from injector/injector.c rename to injector/injector.cpp index 27ebe397e..fc9767bc8 100644 --- a/injector/injector.c +++ b/injector/injector.cpp @@ -1,29 +1,13 @@ #define _CRT_SECURE_NO_WARNINGS -#include +#include +#include +#include -#include -#include -#include -#include +#include -#undef BUILD_tcl - -#include "tcl.h" -#include "tk.h" -#include "tkWin.h" - -struct IsaacOptions { - // Repentogon options - int updates; - int console; - - // Game options - int lua_debug; - int level_stage; - int stage_type; - const char* lua_heap_size; -}; +#include +#include "injector/window.h" /* Perform the early setup for the injection: create the Isaac process, * allocate memory for the remote thread function etc. @@ -83,7 +67,7 @@ DWORD CreateIsaac(struct IsaacOptions const* options, PROCESS_INFORMATION* proce char cli[256]; GenerateCLI(options, cli); - DWORD result = CreateProcess("isaac-ng.exe", cli, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &startupInfo, processInfo); + DWORD result = CreateProcessA("isaac-ng.exe", cli, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &startupInfo, processInfo); if (result == 0) { Log("Failed to create process: %d\n", GetLastError()); return -1; @@ -110,7 +94,7 @@ int UpdateMemory(HANDLE process, PROCESS_INFORMATION const* processInfo, void** memset(zeroBuffer, 0, sizeof(zeroBuffer)); WriteProcessMemory(process, remotePage, zeroBuffer, 4096, &bytesWritten); - HMODULE kernel32 = GetModuleHandle("kernel32.dll"); + HMODULE kernel32 = GetModuleHandleA("kernel32.dll"); if (!kernel32) { Log("Unable to find kernel32.dll, WTF\n"); return -1; @@ -274,7 +258,7 @@ void Log(const char* fmt, ...) { va_end(va); } -int __declspec(dllexport) InjectIsaac(int updates, int console, int lua_debug, int level_stage, int stage_type, const char* lua_heap_size) { +int InjectIsaac(int updates, int console, int lua_debug, int level_stage, int stage_type, const char* lua_heap_size) { HANDLE process; void* remotePage; size_t functionOffset; @@ -315,11 +299,11 @@ int __declspec(dllexport) InjectIsaac(int updates, int console, int lua_debug, i return 0; } -int main(int argc, char** argv) { - Tcl_Interp* interp = Tcl_CreateInterp(); - Tcl_AppInit(interp); - Tk_Window mainWindow = Tk_MainWindow(interp); - Tk_MainLoop(); - InjectIsaac(1, 1, 0, 0, 0, NULL); - return 0; -} \ No newline at end of file +bool IsaacLauncher::App::OnInit() { + MainFrame* frame = new MainFrame(); + frame->Show(); + frame->PostInit(); + return true; +} + +wxIMPLEMENT_APP(IsaacLauncher::App); \ No newline at end of file diff --git a/injector/window.cpp b/injector/window.cpp new file mode 100755 index 000000000..88ba3cf75 --- /dev/null +++ b/injector/window.cpp @@ -0,0 +1,499 @@ +// #include + +#include +#include + +// #include +// #include + +#include "injector/window.h" + +wxBEGIN_EVENT_TABLE(IsaacLauncher::MainFrame, wxFrame) + EVT_COMBOBOX(IsaacLauncher::WINDOW_COMBOBOX_LEVEL, IsaacLauncher::MainFrame::OnLevelSelect) + EVT_COMBOBOX(IsaacLauncher::WINDOW_COMBOBOX_LAUNCH_MODE, IsaacLauncher::MainFrame::OnLauchModeSelect) + EVT_CHECKBOX(IsaacLauncher::WINDOW_CHECKBOX_REPENTOGON_CONSOLE, IsaacLauncher::MainFrame::OnOptionSelected) + EVT_CHECKBOX(IsaacLauncher::WINDOW_CHECKBOX_REPENTOGON_UPDATES, IsaacLauncher::MainFrame::OnOptionSelected) + EVT_CHECKBOX(IsaacLauncher::WINDOW_CHECKBOX_VANILLA_LUADEBUG, IsaacLauncher::MainFrame::OnOptionSelected) + EVT_TEXT(IsaacLauncher::WINDOW_TEXT_VANILLA_LUAHEAPSIZE, IsaacLauncher::MainFrame::OnCharacterWritten) + EVT_BUTTON(IsaacLauncher::WINDOW_BUTTON_LAUNCH_BUTTON, IsaacLauncher::MainFrame::Launch) +wxEND_EVENT_TABLE() + +class LuaHeapSizeValidator : public wxValidator { +public: + LuaHeapSizeValidator() { } + LuaHeapSizeValidator(const LuaHeapSizeValidator& ) { } + + wxObject* Clone() const override { + return new LuaHeapSizeValidator(); + } + +private: + void OnCharacterWritten(wxKeyEvent& event) { + wxTextCtrl* ctrl = dynamic_cast(event.GetEventObject()); + wxString original = ctrl->GetValue(); + + } + + wxDECLARE_EVENT_TABLE(); +}; + +wxBEGIN_EVENT_TABLE(LuaHeapSizeValidator, wxValidator) + // EVT_CHAR(LuaHeapSizeValidator::OnCharacterWritten) +wxEND_EVENT_TABLE() + +namespace IsaacLauncher { + static std::tuple MakeBoldFont(wxFrame* frame); + static wxComboBox* CreateLevelsComboBox(wxFrame* frame); + + static const char* mandatoryFileNames[] = { + "libzhl.dll", + "zhlRepentogon.dll", + "freetype.dll", + NULL + }; + + Version const knownVersions[] = { + { "04469d0c3d3581936fcf85bea5f9f4f3a65b2ccf96b36310456c9626bac36dc6", "v1.7.9b.J835 (Steam)", true }, + { NULL, NULL, false } + }; + + Version const* const validVersions[] = { + NULL + }; + + Version const* MainFrame::GetVersion(const char* hash) { + Version const* version = knownVersions; + while (version->version) { + if (!strcmp(hash, version->hash)) { + return version; + } + + ++version; + } + + return NULL; + } + + bool MainFrame::FileExists(const char* name) { + WIN32_FIND_DATAA data; + memset(&data, 0, sizeof(data)); + HANDLE result = FindFirstFileA(name, &data); + bool ret = result != INVALID_HANDLE_VALUE; + if (ret) { + FindClose(result); + } + + return ret; + } + + MainFrame::MainFrame() : wxFrame(nullptr, wxID_ANY, "REPENTOGON Launcher") { + memset(&_options, 0, sizeof(_options)); + _grid = new wxGridBagSizer(0, 20); + _updates = _console = nullptr; + _luaHeapSize = nullptr; + _enabledRepentogon = false; + + AddLaunchOptions(); + AddRepentogonOptions(); + AddVanillaOptions(); + + wxSizer* horizontalSizer = new wxBoxSizer(wxHORIZONTAL); + horizontalSizer->Add(_grid, 0, wxLEFT, 20); + + wxSizer* verticalSizer = new wxBoxSizer(wxVERTICAL); + wxTextCtrl* logWindow = new wxTextCtrl(this, -1, wxEmptyString, wxDefaultPosition, wxSize(-1, 200), wxTE_READONLY | wxTE_MULTILINE | wxTE_RICH); + logWindow->SetBackgroundColour(*wxWHITE); + _logWindow = logWindow; + verticalSizer->Add(logWindow, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 20); + verticalSizer->Add(horizontalSizer); + SetSizer(verticalSizer); + + SetBackgroundColour(wxColour(237, 237, 237)); + } + + void MainFrame::AddLaunchOptions() { + auto [sourceFont, boldFont] = MakeBoldFont(this); + boldFont.SetPointSize(14); + SetFont(boldFont); + wxStaticText* text = new wxStaticText(this, -1, "Launch mode"); + SetFont(sourceFont); + + wxSizer* box = new wxBoxSizer(wxHORIZONTAL); + box->Add(new wxStaticText(this, -1, "Launch mode: ")); + + wxComboBox* modes = new wxComboBox(this, WINDOW_COMBOBOX_LAUNCH_MODE); + modes->Insert("Repentogon", 0, (void*)nullptr); + modes->Insert("Vanilla", 0, (void*)nullptr); + modes->SetValue("Repentogon"); + + box->Add(modes); + + _grid->Add(text, wxGBPosition(0, 2), wxDefaultSpan, wxALIGN_CENTER); + _grid->Add(box, wxGBPosition(1, 2)); + + wxButton* launchButton = new wxButton(this, WINDOW_BUTTON_LAUNCH_BUTTON, "Launch game"); + _grid->Add(launchButton, wxGBPosition(2, 2), wxDefaultSpan, wxEXPAND); + } + + void MainFrame::AddRepentogonOptions() { + auto [sourceFont, boldFont] = MakeBoldFont(this); + boldFont.SetPointSize(14); + SetFont(boldFont); + wxStaticText* text = new wxStaticText(this, -1, "REPENTOGON Options"); + SetFont(sourceFont); + + wxCheckBox* updates = new wxCheckBox(this, WINDOW_CHECKBOX_REPENTOGON_UPDATES, "Check for updates"); + updates->SetValue(true); + wxCheckBox* console = new wxCheckBox(this, WINDOW_CHECKBOX_REPENTOGON_CONSOLE, "Enable console window"); + console->SetValue(false); + + _updates = updates; + _console = console; + + _grid->Add(text, wxGBPosition(0, 0), wxDefaultSpan, wxALIGN_CENTER); + _grid->Add(updates, wxGBPosition(1, 0)); + _grid->Add(console, wxGBPosition(2, 0)); + } + + void MainFrame::AddVanillaOptions() { + auto [sourceFont, boldFont] = MakeBoldFont(this); + boldFont.SetPointSize(14); + SetFont(boldFont); + wxStaticText* text = new wxStaticText(this, -1, "Universal Options"); + SetFont(sourceFont); + + wxSizer* levelSelectSizer = new wxBoxSizer(wxHORIZONTAL); + levelSelectSizer->Add(new wxStaticText(this, -1, "Starting stage: ")); + wxComboBox* levelSelect = CreateLevelsComboBox(this); + levelSelectSizer->Add(levelSelect); + + _grid->Add(text, wxGBPosition(0, 1), wxDefaultSpan, wxALIGN_CENTER); + _grid->Add(levelSelectSizer, wxGBPosition(1, 1)); + _grid->Add(new wxCheckBox(this, WINDOW_CHECKBOX_VANILLA_LUADEBUG, "Enable luadebug (unsafe)"), wxGBPosition(2, 1)); + + LuaHeapSizeValidator validator; + wxSizer* heapSizeBox = new wxBoxSizer(wxHORIZONTAL); + wxTextCtrl* heapSizeCtrl = new wxTextCtrl(this, WINDOW_TEXT_VANILLA_LUAHEAPSIZE, "1024M"); + heapSizeCtrl->SetValidator(validator); + _luaHeapSize = heapSizeCtrl; + wxStaticText* heapSizeText = new wxStaticText(this, -1, "Lua heap size: "); + heapSizeBox->Add(heapSizeText); + heapSizeBox->Add(heapSizeCtrl); + _grid->Add(heapSizeBox, wxGBPosition(3, 1)); + } + + void MainFrame::OnLevelSelect(wxCommandEvent& event) { + wxComboBox* box = dynamic_cast(event.GetEventObject()); + wxString string = box->GetValue(); + std::cmatch match; + if (std::regex_search(string.c_str().AsChar(), match, std::basic_regex("([0-9])\\.([0-9])"))) { + int stage = std::stoi(match[1].str(), NULL, 0); + int type = std::stoi(match[2].str(), NULL, 0); + _options.level_stage = stage; + _options.stage_type = type; + } + } + + void MainFrame::OnLauchModeSelect(wxCommandEvent& event) { + wxComboBox* box = dynamic_cast(event.GetEventObject()); + if (box->GetValue() == "Vanilla") { + _updates->Disable(); + _console->Disable(); + _options.mode = LAUNCH_MODE_VANILLA; + } + else { + _updates->Enable(); + _console->Enable(); + _options.mode = LAUNCH_MODE_REPENTOGON; + } + } + + void MainFrame::OnCharacterWritten(wxCommandEvent& event) { + wxTextCtrl* object = dynamic_cast(event.GetEventObject()); + wxString content = object->GetValue(); + } + + void MainFrame::OnOptionSelected(wxCommandEvent& event) { + wxCheckBox* box = dynamic_cast(event.GetEventObject()); + switch (box->GetId()) { + case WINDOW_CHECKBOX_REPENTOGON_CONSOLE: + _options.console = box->GetValue(); + break; + + case WINDOW_CHECKBOX_REPENTOGON_UPDATES: + _options.updates = box->GetValue(); + break; + + case WINDOW_CHECKBOX_VANILLA_LUADEBUG: + _options.lua_debug = box->GetValue(); + break; + + default: + return; + } + } + + void MainFrame::Launch(wxCommandEvent& event) { + + } + + void MainFrame::PostInit() { + Log("Welcome to the REPENTOGON Launcher version %s", IsaacLauncher::version); + if (!CheckIsaacVersion()) { + return; + } + + if (!CheckRepentogonInstallation()) { + return; + } + + if (!CheckRepentogonVersion()) { + return; + } + } + + bool MainFrame::CheckIsaacVersion() { + Log("Checking Isaac version..."); + WIN32_FIND_DATAA isaac; + memset(&isaac, 0, sizeof(isaac)); + HANDLE isaacFile = FindFirstFileA("isaac-ng.exe", &isaac); + if (isaacFile == INVALID_HANDLE_VALUE) { + LogError("Unable to find isaac-ng.exe"); + return false; + } + FindClose(isaacFile); + + DWORD size = isaac.nFileSizeLow; + char* content = (char*)malloc(size + 2); + if (!content) { + LogError("Unable to allocate memory to read game's executable, aborting"); + return false; + } + + FILE* exe = fopen(isaac.cFileName, "rb"); + fread(content, 1, size, exe); + content[size] = '\0'; + /* std::string sha256Str; + CryptoPP::SHA256 sha256; + CryptoPP::StringSource(content, true, + new CryptoPP::HashFilter(sha256, + new CryptoPP::HexEncoder( + new CryptoPP::StringSink(sha256Str) + ) + ) + ); */ + BCRYPT_ALG_HANDLE alg; + NTSTATUS err = BCryptOpenAlgorithmProvider(&alg, BCRYPT_SHA256_ALGORITHM, NULL, 0); + DWORD buffSize; + DWORD dummy; + err = BCryptGetProperty(alg, BCRYPT_OBJECT_LENGTH, (unsigned char*)&buffSize, sizeof(buffSize), &dummy, 0); + BCRYPT_HASH_HANDLE hashHandle; + unsigned char* hashBuffer = (unsigned char*)malloc(buffSize); + err = BCryptCreateHash(alg, &hashHandle, hashBuffer, buffSize, NULL, 0, 0); + err = BCryptHashData(hashHandle, (unsigned char*)content, size, 0); + DWORD hashSize; + err = BCryptGetProperty(alg, BCRYPT_HASH_LENGTH, (unsigned char*)&hashSize, sizeof(hashSize), &dummy, 0); + unsigned char* hash = (unsigned char*)malloc(hashSize); + char* hashHex = (char*)malloc(hashSize * 2 + 1); + err = BCryptFinishHash(hashHandle, hash, hashSize, 0); + free(hashBuffer); + err = BCryptCloseAlgorithmProvider(&alg, 0); + + for (int i = 0; i < hashSize; ++i) { + sprintf(hashHex + 2 * i, "%02hhx", hash[i]); + } + + // const char* sha256p = sha256Str.c_str(); + Log("\tFound isaac-ng.exe. Hash: %s", hashHex); + Version const* version = GetVersion(hashHex); + if (!version) { + LogError("Unknown version. REPENTOGON will not launch."); + return false; + } + else if (!version->valid) { + LogError("This version of the game does not support REPENTOGON."); + return false; + } + + free(hash); + + Log("\tIdentified REPENTOGON compatible version %s", version->version); + + return true; + } + + bool MainFrame::CheckRepentogonInstallation() { + Log("Checking Repentogon installation..."); + const char** file = mandatoryFileNames; + bool ok = true; + while (*file) { + if (FileExists(*file)) { + Log("\t%s: found", *file); + } + else { + ok = false; + LogError("\t%s: not found", *file); + } + + ++file; + } + + return ok; + } + + bool MainFrame::CheckRepentogonVersion() { + Log("Checking Repentogon version..."); + HMODULE repentogon = LoadLibraryExA("zhlRepentogon.dll", NULL, DONT_RESOLVE_DLL_REFERENCES); + if (!repentogon) { + LogError("Unable to open zhlRepentogon.dll"); + return false; + } + + HMODULE zhl = LoadLibraryExA("libzhl.dll", NULL, DONT_RESOLVE_DLL_REFERENCES); + if (!zhl) { + FreeLibrary(repentogon); + LogError("Unable to open libzhl.dll"); + return false; + } + + bool result = false; + FARPROC repentogonVersion = GetProcAddress(repentogon, "__REPENTOGON_VERSION"); + FARPROC zhlVersion = GetProcAddress(zhl, "__ZHL_VERSION"); + + if (!repentogonVersion) { + LogError("Unable to get Repentogon's version (%d)", GetLastError()); + result = false; + goto end; + } + + if (!zhlVersion) { + LogError("Unable to get ZHL's version"); + result = false; + goto end; + } + + const char* repentogonVersionStr = *(const char**)repentogonVersion; + const char* zhlVersionStr = *(const char**)zhlVersion; + + Log("\tFound Repentogon version %s", repentogonVersionStr); + Log("\tFound ZHL version %s", zhlVersionStr); + + if (strcmp(repentogonVersionStr, zhlVersionStr)) { + LogError("Repentogon/ZHL version mismatch"); + result = false; + goto end; + } + + Log("\tRepentogon and ZHL versions match"); + result = true; + + end: + FreeLibrary(repentogon); + FreeLibrary(zhl); + return result; + } + + void MainFrame::Log(const char* fmt, ...) { + char buffer[4096]; + va_list va; + va_start(va, fmt); + int count = vsnprintf(buffer, 4096, fmt, va); + va_end(va); + + if (count == 0) + return; + + wxString text(buffer); + if (buffer[count] != '\n') + text += '\n'; + _logWindow->AppendText(text); + } + + void MainFrame::LogError(const char* fmt, ...) { + char buffer[4096]; + va_list va; + va_start(va, fmt); + int count = vsnprintf(buffer, 4096, fmt, va); + va_end(va); + + if (count == 0) + return; + + wxString text(buffer); + text.Prepend("[ERROR] "); + if (buffer[count] != '\n') + text += '\n'; + wxTextAttr attr = _logWindow->GetDefaultStyle(); + _logWindow->SetDefaultStyle(wxTextAttr(*wxRED)); + _logWindow->AppendText(text); + _logWindow->SetDefaultStyle(attr); + } + + std::tuple MakeBoldFont(wxFrame* frame) { + wxFont source = frame->GetFont(); + wxFont bold = source; + bold.MakeBold(); + return std::make_tuple(source, bold); + } + + wxComboBox* CreateLevelsComboBox(wxFrame* frame) { + wxComboBox* box = new wxComboBox(frame, WINDOW_COMBOBOX_LEVEL, "Start level"); + const char* names[] = { + "Basement", + "Cellar", + "Burning Basement", + "Downpour", + "Dross", + "Caves", + "Catacombs", + "Flooded Caves", + "Mines", + "Ashpit", + "Depths", + "Necropolis", + "Dank Depths", + "Mausoleum", + "Gehenna", + "Womb", + "Utero", + "Scarred Womb", + "Corpse", + NULL + }; + + const char* uniqueNames[] = { + "??? (9.0)", + "Sheol (10.0)", + "Cathedral (10.1)", + "Dark Room (11.0)", + "Chest (11.1)", + "The Void (12.0)", + "Home (13.0)", + NULL + }; + + int pos = 0; + box->Insert(wxString("--"), pos++); + box->SetValue("--"); + int variant = 0; + for (const char* name : names) { + if (!name) + continue; + + wxString s; + int level = 1 + 2 * (variant / 5); + box->Insert(s.Format("%s I (%d.%d)", name, level, variant % 5), pos++, (void*)nullptr); + box->Insert(s.Format("%s II (%d.%d)", name, level + 1, variant % 5), pos++, (void*)nullptr); + + ++variant; + } + + for (const char* name : uniqueNames) { + if (!name) + continue; + box->Insert(wxString(name), pos++, (void*)nullptr); + } + + return box; + } +} \ No newline at end of file diff --git a/injector/window.h b/injector/window.h new file mode 100755 index 000000000..f1d3f58cc --- /dev/null +++ b/injector/window.h @@ -0,0 +1,91 @@ +#pragma once + +#include +#include + +enum LaunchMode { + LAUNCH_MODE_VANILLA, + LAUNCH_MODE_REPENTOGON +}; + +struct IsaacOptions { + LaunchMode mode; + + // Repentogon options + bool updates; + bool console; + + // Game options + bool lua_debug; + int level_stage; + int stage_type; + const char* lua_heap_size; +}; + +namespace IsaacLauncher { + static constexpr const char* version = "alpha"; + + struct Version { + const char* hash; + const char* version; + bool valid; + }; + + extern Version const knownVersions[]; + + enum Windows { + WINDOW_COMBOBOX_LEVEL, + WINDOW_COMBOBOX_LAUNCH_MODE, + WINDOW_CHECKBOX_REPENTOGON_CONSOLE, + WINDOW_CHECKBOX_REPENTOGON_UPDATES, + WINDOW_CHECKBOX_VANILLA_LUADEBUG, + WINDOW_TEXT_VANILLA_LUAHEAPSIZE, + WINDOW_BUTTON_LAUNCH_BUTTON + }; + + class App : public wxApp { + public: + bool OnInit() override; + }; + + class MainFrame : public wxFrame { + public: + MainFrame(); + + void Log(const char* fmt, ...); + void LogError(const char* fmt, ...); + void PostInit(); + + static Version const* GetVersion(const char* hash); + static bool FileExists(const char* name); + + private: + /* Window building. */ + void AddLaunchOptions(); + void AddRepentogonOptions(); + void AddVanillaOptions(); + + /* Event handlers. */ + void OnLevelSelect(wxCommandEvent& event); + void OnLauchModeSelect(wxCommandEvent& event); + void OnCharacterWritten(wxCommandEvent& event); + void OnOptionSelected(wxCommandEvent& event); + void Launch(wxCommandEvent& event); + + /* Post init stuff. */ + bool CheckIsaacVersion(); + bool CheckRepentogonInstallation(); + bool CheckRepentogonVersion(); + + IsaacOptions _options; + wxGridBagSizer* _grid; + wxCheckBox* _updates; + wxCheckBox* _console; + wxTextCtrl* _luaHeapSize; + wxTextCtrl* _logWindow; + + bool _enabledRepentogon; + + wxDECLARE_EVENT_TABLE(); + }; +} \ No newline at end of file diff --git a/libs/libui-ng b/libs/libui-ng new file mode 160000 index 000000000..8de4a5c83 --- /dev/null +++ b/libs/libui-ng @@ -0,0 +1 @@ +Subproject commit 8de4a5c8336f82310df1c6dad51cb732113ea114 diff --git a/libs/wxWidgets b/libs/wxWidgets new file mode 160000 index 000000000..ac3cea5bc --- /dev/null +++ b/libs/wxWidgets @@ -0,0 +1 @@ +Subproject commit ac3cea5bcd5a3fc3d52648efb4dceacbcc9d0698 diff --git a/libzhl/Version.cpp b/libzhl/Version.cpp new file mode 100755 index 000000000..217046485 --- /dev/null +++ b/libzhl/Version.cpp @@ -0,0 +1,5 @@ +#include "libzhl.h" + +extern "C" { + LIBZHL_API const char* __ZHL_VERSION = "1.1.0"; +} \ No newline at end of file diff --git a/repentogon/Version.cpp b/repentogon/Version.cpp new file mode 100755 index 000000000..25601948b --- /dev/null +++ b/repentogon/Version.cpp @@ -0,0 +1,3 @@ +extern "C" { + __declspec(dllexport) const char* __REPENTOGON_VERSION = "1.1.0"; +} \ No newline at end of file