feat: prevent recursive self-launching

- Prevent the launcher from starting itself to avoid infinite loops caused by misconfiguration.
- Refactor and clean up related code.
This commit is contained in:
Kuriko Moe
2026-04-15 15:16:09 +08:00
parent e4a5a164ef
commit 491c3e82d5
4 changed files with 123 additions and 54 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ target := "main"
build:
xmake f -m {{XMAKE_TYPE}} -y
xmake
xmake -j
clean:
rm -rf build
+44 -33
View File
@@ -1,11 +1,8 @@
//#pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
// #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
#include <windows.h>
#include <string>
#include <filesystem>
#include <fstream>
#include <shellapi.h>
#include <locale>
#include <codecvt>
#include <detours/detours.h>
#include <inipp.h>
@@ -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<typename T, typename ... Args>
template <typename T, typename... Args>
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;
}
+11 -15
View File
@@ -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);
+67 -5
View File
@@ -31,7 +31,6 @@ DWORD __stdcall hGetModuleFileNameA(
const std::vector<std::string> 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<size_t>(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<size_t>(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<size_t>(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;
}