From 491c3e82d554f7595da0753a2c07ba320ed6d518 Mon Sep 17 00:00:00 2001 From: Kuriko Moe Date: Wed, 15 Apr 2026 15:16:09 +0800 Subject: [PATCH] feat: prevent recursive self-launching - Prevent the launcher from starting itself to avoid infinite loops caused by misconfiguration. - Refactor and clean up related code. --- justfile | 2 +- launcher/main.cpp | 77 ++++++++++++++++++++++++++------------------- src/dllmain.cpp | 26 +++++++-------- src/hooks_exename.h | 72 +++++++++++++++++++++++++++++++++++++++--- 4 files changed, 123 insertions(+), 54 deletions(-) diff --git a/justfile b/justfile index 4e0b541..48978b4 100644 --- a/justfile +++ b/justfile @@ -11,7 +11,7 @@ target := "main" build: xmake f -m {{XMAKE_TYPE}} -y - xmake + xmake -j clean: rm -rf build diff --git a/launcher/main.cpp b/launcher/main.cpp index 229deb2..e9f3b9b 100644 --- a/launcher/main.cpp +++ b/launcher/main.cpp @@ -1,11 +1,8 @@ -//#pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") +// #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") #include #include #include -#include #include -#include -#include #include #include @@ -14,44 +11,54 @@ namespace fs = std::filesystem; -bool debug = false; -std::wstring game_name = L"left4dead2.exe"; +const LPCSTR dll_path = "kpatch.dll"; +std::wstring gameName = L"left4dead2.exe"; void init_cfg() { LoadIni(); if (cfg::Redirect::enable) { - game_name = cfg::Redirect::target; + gameName = cfg::Redirect::target; } else { - game_name = L"left4dead2.exe"; + gameName = L"left4dead2.exe"; } } -template +template void debugPrint(T fmt, Args... args) { std::wstring errMsg = std::wstring(L"[L4D2Fix] ") + std::vformat(fmt, std::make_wformat_args(args...)); OutputDebugStringW(errMsg.c_str()); } int WINAPI wWinMain( - _In_ HINSTANCE hInstance, - _In_opt_ HINSTANCE hPrevInstance, + _In_ HINSTANCE /*hInstance*/, + _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ LPWSTR lpwCmdLine, - _In_ int nShowCmd -) { - std::wstring errMsg; + _In_ int /*nShowCmd*/) { init_cfg(); - WCHAR working_path[MAX_PATH]; - GetModuleFileNameW(nullptr, working_path, MAX_PATH); + WCHAR buffer[MAX_PATH]; + GetModuleFileNameW(nullptr, buffer, MAX_PATH); // Change the working directory to the directory containing the DLL. - fs::path path(working_path); - debugPrint(L"working_path {}\n", working_path); + fs::path selfPath(buffer); + fs::path workingPath = selfPath.parent_path(); + debugPrint(L"working_path {}\n", workingPath.c_str()); - LPCSTR dll_path = "kpatch.dll"; + LPCWSTR targetExe = gameName.c_str(); + debugPrint(L"target_exe_path {}\n", targetExe); - LPCWSTR target_exe_path = game_name.c_str(); - debugPrint(L"target_exe_path {}\n", target_exe_path); + if (selfPath.filename() == gameName) { + debugPrint(L"Avoid launching self, exiting.\n"); + MessageBoxW( + NULL, + L"警告:配置错误,补丁尝试启动的目标为自身,请检查配置!\n" + L"为防止卡死系统 L4D2Fix 将强制退出。\n" + L"Fatal:Invalid config or setup!\n" + L"The patch is trying to launch itself, please check the config!\n" + L"Forcing exit to prevent system freeze.", + L"L4D2Fix", MB_OK|MB_SYSTEMMODAL|MB_ICONERROR); + ExitProcess(0); + } STARTUPINFOW si; PROCESS_INFORMATION pi; @@ -65,18 +72,19 @@ int WINAPI wWinMain( SetLastError(0); if (TRUE != DetourCreateProcessWithDllExW( - target_exe_path, - lpwCmdLine, - nullptr, - nullptr, - TRUE, - dwFlags, - nullptr, - nullptr, - &si, - &pi, - dll_path, - nullptr)) { + targetExe, + lpwCmdLine, + nullptr, + nullptr, + TRUE, + dwFlags, + nullptr, + nullptr, + &si, + &pi, + dll_path, + nullptr + )) { auto dwError = GetLastError(); debugPrint(L"DetourCreateProcessWithDllEx failed with error {}\n", dwError); @@ -87,5 +95,8 @@ int WINAPI wWinMain( WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + return 0; } diff --git a/src/dllmain.cpp b/src/dllmain.cpp index c9adb27..2a577f5 100644 --- a/src/dllmain.cpp +++ b/src/dllmain.cpp @@ -59,10 +59,10 @@ void Logging() { auto start_time = std::chrono::system_clock::now(); if (cfg::System::debug) { - spdlog::set_level(spdlog::level::debug); + spdlog::set_level(spdlog::level::debug); } else { - spdlog::set_level(spdlog::level::info); + spdlog::set_level(spdlog::level::info); } spdlog::flush_on(spdlog::level::debug); @@ -88,13 +88,13 @@ void FirstRunCheck() { std::filesystem::path startup_check = sLogFile; if (!std::filesystem::exists(startup_check)) { - auto ret = MessageBoxW( + MessageBoxW( NULL, L"这是一个启动测试,用于检验补丁是否正常运行。\n" - "请注意由于修改内存,请不要进 VAC 服,后果自负。\n" - "本弹窗仅首次启动出现,后续运行情况参见 L4D2Fix.log 日志文件。\n\n" - "项目地址: https://github.com/kurikomoe/L4D2Fix\n" - "关注B站 5050 直播间,谢谢喵! --KurikoMoe", + L"请注意由于修改内存,请不要进 VAC 服,后果自负。\n" + L"本弹窗仅首次启动出现,后续运行情况参见 L4D2Fix.log 日志文件。\n\n" + L"项目地址: https://github.com/kurikomoe/L4D2Fix\n" + L"关注B站 5050 直播间,谢谢喵! --KurikoMoe", pMsgboxTitle, MB_OK|MB_SYSTEMMODAL); } @@ -135,13 +135,11 @@ DWORD __stdcall Main(void*) { spdlog::info(L"Startup commandline: {}", cmdline.c_str()); std::wstring dllName = L"shaderapidx9.dll"; - bool isVulkan = false; - std::transform(cmdline.cbegin(), cmdline.cend(), cmdline.begin(), ::tolower); + std::transform(cmdline.cbegin(), cmdline.cend(), cmdline.begin(), ::towlower); if (cmdline.find(L"-vulkan") != std::wstring::npos) { dllName = L"shaderapivk.dll"; spdlog::info(L"Detected Vulkan: using {}", dllName); - isVulkan = true; } else { // Preload d3d9.dll LoadLibraryW(L"d3d9.dll"); @@ -161,7 +159,7 @@ DWORD __stdcall Main(void*) { if (hDll == nullptr) { auto errCode = GetLastError(); - wchar_t buf[255]; + wchar_t buf[MAX_PATH]; FormatMessageW( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), @@ -178,10 +176,8 @@ DWORD __stdcall Main(void*) { // Fix exe name initExeNameHook(); - auto* buf = new char[255]; - GetModuleFileNameA(NULL, buf, 255); // Trigger the hook once - - // Not known + auto* buf = new char[MAX_PATH]; + GetModuleFileNameA(NULL, buf, MAX_PATH); // Trigger the hook once auto ret = 0; ret += PatchIndices(hDll, dllName); diff --git a/src/hooks_exename.h b/src/hooks_exename.h index ba65366..415dca5 100644 --- a/src/hooks_exename.h +++ b/src/hooks_exename.h @@ -31,7 +31,6 @@ DWORD __stdcall hGetModuleFileNameA( const std::vector moduleNameList = { "launcher.dll", }; - spdlog::info("GetModuleFileNameA called by module: {}", moduleName); for (auto& name : moduleNameList) { if (!strstr(moduleName, name.c_str())) { return ret; @@ -43,11 +42,74 @@ DWORD __stdcall hGetModuleFileNameA( std::wstring exeName = filePath.filename().wstring(); if (cfg::Redirect::enable && exeName == cfg::Redirect::target) { spdlog::info("GetModuleFileNameA called by module: {}", moduleName); - std::string newExeName = std::string( - cfg::Redirect::origin.begin(), cfg::Redirect::origin.end()); + std::wstring newExeName = cfg::Redirect::origin; newFilePath = newFilePath.replace_filename(newExeName); - std::string newFilePathStr = newFilePath.string(); - memcpy_s(lpFilename, nSize, newFilePath.string().c_str(), newFilePathStr.size() + 1); + std::wstring newFilePathStr = newFilePath.wstring(); + + BOOL usedDefaultChar = FALSE; + int requiredSize = WideCharToMultiByte( + CP_ACP, + WC_NO_BEST_FIT_CHARS, + newFilePathStr.c_str(), + -1, + nullptr, + 0, + "?", + &usedDefaultChar + ); + + if (requiredSize <= 0) { + spdlog::warn("WideCharToMultiByte size query failed, error={}", GetLastError()); + return ret; + } + + std::string ansiPath(static_cast(requiredSize), '\0'); + int convertedSize = WideCharToMultiByte( + CP_ACP, + WC_NO_BEST_FIT_CHARS, + newFilePathStr.c_str(), + -1, + ansiPath.data(), + requiredSize, + "?", + &usedDefaultChar + ); + + if (convertedSize <= 0) { + spdlog::warn("WideCharToMultiByte conversion failed, error={}", GetLastError()); + return ret; + } + + if (nSize == 0) { + spdlog::warn("Cannot write redirected path: lpFilename buffer size is 0"); + return ret; + } + + const size_t ansiLen = strnlen_s(ansiPath.c_str(), ansiPath.size()); + if (ansiLen + 1 > static_cast(nSize)) { + spdlog::warn( + "Redirected path exceeds caller buffer: required={} bytes, capacity={} bytes; path will be truncated", + ansiLen + 1, + nSize + ); + } + + size_t copyLen = ansiLen; + const size_t maxWritable = static_cast(nSize - 1); + if (copyLen > maxWritable) { + copyLen = maxWritable; + } + + errno_t copyErr = memcpy_s(lpFilename, nSize, ansiPath.c_str(), copyLen); + if (copyErr != 0) { + spdlog::warn("memcpy_s failed while writing redirected path, errno={}", copyErr); + return ret; + } + lpFilename[copyLen] = '\0'; + + if (usedDefaultChar) { + spdlog::warn("Path conversion used fallback characters; non-ANSI characters may be lost"); + } } return ret; }