diff --git a/thprac/src/thprac/main.cpp b/thprac/src/thprac/main.cpp index 7797682..3f2f5b5 100644 --- a/thprac/src/thprac/main.cpp +++ b/thprac/src/thprac/main.cpp @@ -3,11 +3,15 @@ #include "thprac_init.h" #include "thprac_launcher_main.h" #include "thprac_launcher_cfg.h" +#include "thprac_launcher_games.h" #include "thprac_main.h" #include "thprac_gui_locale.h" #include "thprac_hook.h" +#include "thprac_load_exe.h" +#include "thprac_utils.h" #include #include +#include #include using namespace THPrac; @@ -29,6 +33,120 @@ bool PrivilegeCheck() return fRet; } +void ApplyToProcById(DWORD pid) { + auto hProc = OpenProcess( + // PROCESS_SUSPEND_RESUME | + PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, + FALSE, + pid); + if (!hProc) { + fprintf(stderr, "Error: failed to open process %d\n", pid); + return; + } + + uintptr_t base = GetGameModuleBase(hProc); + if (!base) { + fprintf(stderr, "Error: failed to determine image base for process %d\n", pid); + } + + if (!WriteTHPracSig(hProc, base) || !LoadSelf(hProc)) { + fprintf(stderr, "Error: failed to inject into %d\n", pid); + } +} + +bool DetectGame(const wchar_t* const exe, THGameSig** sigIn) { + MappedFile f(exe); + if(!f.fileMapView) { + return false; + } + + ExeSig exeSig; + if (!GetExeInfo(f.fileMapView, f.fileSize, exeSig)) { + return false; + } + + for (auto& sig : gGameDefs) { + if (sig.exeSig.textSize == exeSig.textSize && sig.exeSig.timeStamp == exeSig.timeStamp) { + *sigIn = &sig; + return true; + } + } + + return false; + +} + +bool doCmdLineStuff(PWSTR cmdLine) { + int argc; + LPWSTR* argv = CommandLineToArgvW(cmdLine, &argc); + defer(LocalFree(argv)); + + enum CURRENT_CMD { + CMD_NONE, + CMD_ATTACH, + } cur_cmd = CMD_NONE; + + bool withVpatch = true; + const wchar_t* exeFn = nullptr; + + if (argc > 0) { + if (AttachConsole(ATTACH_PARENT_PROCESS)) { + freopen("conin$", "r", stdin); + freopen("conout$", "w", stdout); + freopen("conout$", "w", stderr); + } + } else { + return false; + } + THGameSig* sig = nullptr; + + for (int i = 0; i < argc;) { + if (cur_cmd == CMD_ATTACH) { + DWORD pid = wcstoul(argv[i], nullptr, 10); + if (!pid) { + cur_cmd = CMD_NONE; + fwprintf(stderr, L"Warning: failed to detect %s as process ID\n", argv[i]); + continue; + } else { + ApplyToProcById(pid); + return true; + } + } + + if (wcscmp(argv[i], L"--attach") == 0) { + cur_cmd = CMD_ATTACH; + i++; + continue; + } + + if (wcscmp(argv[i], L"--without-vpatch") == 0) { + withVpatch = false; + i++; + continue; + } + + // No actual command line parameter detected, so let's see if it's an exe name + if (DetectGame(argv[i], &sig)) { + exeFn = argv[i]; + } else { + fwprintf(stderr, L"Warning: invalid filename %s\n", argv[i]); + } + i++; + } + + if (exeFn) { + __assume(sig); + wchar_t exeDir[MAX_PATH + 1] = {}; + wcsncpy(exeDir, exeFn, MAX_PATH); + PathRemoveFileSpecW(exeDir); + SetCurrentDirectoryW(exeDir); + RunGameWithTHPrac(*sig, exeFn, withVpatch); + return true; + } else { + return false; + } +} + int WINAPI wWinMain( [[maybe_unused]] HINSTANCE hInstance, [[maybe_unused]] HINSTANCE hPrevInstance, @@ -43,6 +161,10 @@ int WINAPI wWinMain( RemoteInit(); auto thpracMutex = OpenMutexW(SYNCHRONIZE, FALSE, L"thprac - Touhou Practice Tool##mutex"); + if (doCmdLineStuff(pCmdLine)) { + return 0; + } + int launchBehavior = 0; bool dontFindOngoingGame = false; bool adminRights = false; diff --git a/thprac/src/thprac/thprac_load_exe.cpp b/thprac/src/thprac/thprac_load_exe.cpp index 6296ec2..bdce31e 100644 --- a/thprac/src/thprac/thprac_load_exe.cpp +++ b/thprac/src/thprac/thprac_load_exe.cpp @@ -1,6 +1,6 @@ #include "thprac_load_exe.h" #include "utils/utils.h" -#include +#include "utils/wininternal.h" namespace THPrac { @@ -58,6 +58,43 @@ unsigned char INJECT_SHELLCODE[] = { static_assert(sizeof(INJECT_SHELLCODE) % 16 == 0); +uintptr_t GetGameModuleBase(HANDLE hProc) +{ + PROCESS_BASIC_INFORMATION pbi; + NtQueryInformationProcess(hProc, ProcessBasicInformation, &pbi, sizeof(pbi), nullptr); + + LPVOID based = (LPVOID)((uintptr_t)pbi.PebBaseAddress + offsetof(PEB, ImageBaseAddress)); + + uintptr_t ret = 0; + DWORD byteRet; + ReadProcessMemory(hProc, based, &ret, sizeof(ret), &byteRet); + + return ret; +} + +bool WriteTHPracSig(HANDLE hProc, uintptr_t base) +{ + DWORD sigAddr = 0; + DWORD bytesReadRPM; + ReadProcessMemory(hProc, (void*)(base + 0x3c), &sigAddr, 4, &bytesReadRPM); + if (bytesReadRPM != 4 || !sigAddr) + return false; + sigAddr += base; + sigAddr -= 4; + + constexpr DWORD thpracSig = 'CARP'; + DWORD bytesWrote; + DWORD oldProtect; + if (!VirtualProtectEx(hProc, (void*)sigAddr, 4, PAGE_EXECUTE_READWRITE, &oldProtect)) + return false; + if (!WriteProcessMemory(hProc, (void*)sigAddr, &thpracSig, 4, &bytesWrote)) + return false; + if (!VirtualProtectEx(hProc, (void*)sigAddr, 4, oldProtect, &oldProtect)) + return false; + + return true; +} + bool LoadSelf(HANDLE hProcess, void* userdata, size_t userdataSize) { remote_param lModule = { diff --git a/thprac/src/thprac/thprac_load_exe.h b/thprac/src/thprac/thprac_load_exe.h index dc7f349..e89e05a 100644 --- a/thprac/src/thprac/thprac_load_exe.h +++ b/thprac/src/thprac/thprac_load_exe.h @@ -28,6 +28,8 @@ struct InjectResult { } error; WORD lastError; }; +uintptr_t GetGameModuleBase(HANDLE hProc); +bool WriteTHPracSig(HANDLE hProc, uintptr_t base); bool LoadSelf(HANDLE hProcess, void* userdata = nullptr, size_t userdataSize = 0); void** GetUserData(); } diff --git a/thprac/src/thprac/thprac_main.cpp b/thprac/src/thprac/thprac_main.cpp index 8c92a7a..39f1f8e 100644 --- a/thprac/src/thprac/thprac_main.cpp +++ b/thprac/src/thprac/thprac_main.cpp @@ -101,20 +101,7 @@ bool PromptUser(thprac_prompt_t info, THGameSig* gameSig = nullptr) return false; } -uintptr_t GetGameModuleBase(HANDLE hProc) { - PROCESS_BASIC_INFORMATION pbi; - NtQueryInformationProcess(hProc, ProcessBasicInformation, &pbi, sizeof(pbi), nullptr); - - LPVOID based = (LPVOID)((uintptr_t)pbi.PebBaseAddress + offsetof(PEB, ImageBaseAddress)); - - uintptr_t ret = 0; - DWORD byteRet; - ReadProcessMemory(hProc, based, &ret, sizeof(ret), &byteRet); - - return ret; -} - -THGameSig* CheckOngoingGame(PROCESSENTRY32W& proc, uintptr_t& base) +THGameSig* CheckOngoingGame(PROCESSENTRY32W& proc, uintptr_t* base, HANDLE* pOutHandle = nullptr) { // Eliminate impossible process if ( wcscmp(L"東方紅魔郷.exe", proc.szExeFile) && wcscmp(L"alcostg.exe", proc.szExeFile)) { @@ -146,8 +133,8 @@ THGameSig* CheckOngoingGame(PROCESSENTRY32W& proc, uintptr_t& base) if (!hProc) return nullptr; - base = GetGameModuleBase(hProc); - if (!base) { + *base = GetGameModuleBase(hProc); + if (!*base) { return nullptr; } @@ -155,19 +142,19 @@ THGameSig* CheckOngoingGame(PROCESSENTRY32W& proc, uintptr_t& base) DWORD sigAddr = 0; DWORD sigCheck = 0; DWORD bytesReadRPM; - ReadProcessMemory(hProc, (void*)(base + 0x3c), &sigAddr, 4, &bytesReadRPM); + ReadProcessMemory(hProc, (void*)(*base + 0x3c), &sigAddr, 4, &bytesReadRPM); if (bytesReadRPM != 4 || !sigAddr) { CloseHandle(hProc); return nullptr; } - ReadProcessMemory(hProc, (void*)(base + sigAddr - 4), &sigCheck, 4, &bytesReadRPM); + ReadProcessMemory(hProc, (void*)(*base + sigAddr - 4), &sigCheck, 4, &bytesReadRPM); if (bytesReadRPM != 4 || sigCheck) { CloseHandle(hProc); return nullptr; } ExeSig sig; - if (GetExeInfoEx((size_t)hProc, base, sig)) { + if (GetExeInfoEx((size_t)hProc, *base, sig)) { for (auto& gameDef : gGameDefs) { if (gameDef.catagory != CAT_MAIN && gameDef.catagory != CAT_SPINOFF_STG) { continue; @@ -178,50 +165,15 @@ THGameSig* CheckOngoingGame(PROCESSENTRY32W& proc, uintptr_t& base) return &gameDef; } } - CloseHandle(hProc); + // I should not have to do this... + if (pOutHandle) { + *pOutHandle = hProc; + } else { + CloseHandle(hProc); + } return nullptr; } -bool WriteTHPracSig(HANDLE hProc, uintptr_t base) -{ - DWORD sigAddr = 0; - DWORD bytesReadRPM; - ReadProcessMemory(hProc, (void*)(base + 0x3c), &sigAddr, 4, &bytesReadRPM); - if (bytesReadRPM != 4 || !sigAddr) - return false; - sigAddr += base; - sigAddr -= 4; - - constexpr DWORD thpracSig = 'CARP'; - DWORD bytesWrote; - DWORD oldProtect; - if (!VirtualProtectEx(hProc, (void*)sigAddr, 4, PAGE_EXECUTE_READWRITE, &oldProtect)) - return false; - if (!WriteProcessMemory(hProc, (void*)sigAddr, &thpracSig, 4, &bytesWrote)) - return false; - if (!VirtualProtectEx(hProc, (void*)sigAddr, 4, oldProtect, &oldProtect)) - return false; - - return true; -} - -bool ApplyTHPracToProc(PROCESSENTRY32W& proc, uintptr_t base) -{ - // Open the related process - auto hProc = OpenProcess( - //PROCESS_SUSPEND_RESUME | - PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, - FALSE, - proc.th32ProcessID); - if (!hProc) - return false; - - auto result = (WriteTHPracSig(hProc, base) && THPrac::LoadSelf(hProc)); - CloseHandle(hProc); - - return result; -} - bool CheckIfGameExistEx(THGameSig& gameSig, const wchar_t* name) { MappedFile file(name); @@ -264,15 +216,15 @@ bool CheckVpatch(THGameSig& gameSig) return true; } -bool RunGameWithTHPrac(THGameSig& gameSig, std::wstring& name) +bool RunGameWithTHPrac(THGameSig& gameSig, const wchar_t* const name, bool withVpatch) { - auto isVpatchValid = CheckDLLFunction(gameSig.vPatchStr, "_Initialize@4"); + auto isVpatchValid = withVpatch && CheckDLLFunction(gameSig.vPatchStr, "_Initialize@4"); STARTUPINFOW startup_info; PROCESS_INFORMATION proc_info; memset(&startup_info, 0, sizeof(startup_info)); startup_info.cb = sizeof(startup_info); - CreateProcessW(name.c_str(), nullptr, nullptr, nullptr, false, CREATE_SUSPENDED, nullptr, nullptr, &startup_info, &proc_info); + CreateProcessW(name, nullptr, nullptr, nullptr, false, CREATE_SUSPENDED, nullptr, nullptr, &startup_info, &proc_info); uintptr_t base = GetGameModuleBase(proc_info.hProcess); if (isVpatchValid) { @@ -309,21 +261,24 @@ bool FindOngoingGame(bool prompt) if (Process32FirstW(snapshot, &entry)) { do { uintptr_t base; - gameSig = CheckOngoingGame(entry, base); - if (gameSig) { - hasPrompted = true; - if (PromptUser(PR_ASK_IF_ATTACH, gameSig)) { - if (ApplyTHPracToProc(entry, base)) { - PromptUser(PR_INFO_ATTACHED); - CloseHandle(snapshot); - return true; - } else { - PromptUser(PR_ERR_ATTACH_FAILED); - CloseHandle(snapshot); - return true; - } - } + HANDLE hProc = 0; + if (!(gameSig = CheckOngoingGame(entry, &base, &hProc))) + continue; + + hasPrompted = true; + if (!PromptUser(PR_ASK_IF_ATTACH, gameSig)) + continue; + + if (WriteTHPracSig(hProc, base) && LoadSelf(hProc)) { + PromptUser(PR_INFO_ATTACHED); + CloseHandle(snapshot); + return true; + } else { + PromptUser(PR_ERR_ATTACH_FAILED); + CloseHandle(snapshot); + return true; } + } while (Process32NextW(snapshot, &entry)); } } @@ -355,7 +310,7 @@ bool FindAndRunGame(bool prompt) return true; } - if (RunGameWithTHPrac(sig, name)) { + if (RunGameWithTHPrac(sig, name.c_str())) { return true; } else { PromptUser(PR_FAILED); diff --git a/thprac/src/thprac/thprac_main.h b/thprac/src/thprac/thprac_main.h index 670e618..72798ca 100644 --- a/thprac/src/thprac/thprac_main.h +++ b/thprac/src/thprac/thprac_main.h @@ -14,9 +14,8 @@ #endif namespace THPrac { -uintptr_t GetGameModuleBase(HANDLE hProc); bool CheckIfAnyGame(); -bool WriteTHPracSig(HANDLE hProc, uintptr_t base); bool FindOngoingGame(bool prompt = false); bool FindAndRunGame(bool prompt = false); +bool RunGameWithTHPrac(THGameSig& gameSig, const wchar_t* const name, bool withVpatch = true); }