Skip to content

Commit

Permalink
add command line options
Browse files Browse the repository at this point in the history
use --attach <pid> to attach to an existing process, specify an exe
filename to run that game, and optionally specify --without-vpatch to
prevent vpatch from being loaded
  • Loading branch information
32th-System committed Nov 20, 2024
1 parent 21eae3b commit 0b8d25e
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 81 deletions.
122 changes: 122 additions & 0 deletions thprac/src/thprac/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <Windows.h>
#include <psapi.h>
#include <shlwapi.h>
#include <tlhelp32.h>

using namespace THPrac;
Expand All @@ -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,
Expand All @@ -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;
Expand Down
39 changes: 38 additions & 1 deletion thprac/src/thprac/thprac_load_exe.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "thprac_load_exe.h"
#include "utils/utils.h"
#include <windows.h>
#include "utils/wininternal.h"

namespace THPrac {

Expand Down Expand Up @@ -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 = {
Expand Down
2 changes: 2 additions & 0 deletions thprac/src/thprac/thprac_load_exe.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
111 changes: 33 additions & 78 deletions thprac/src/thprac/thprac_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -146,28 +133,28 @@ THGameSig* CheckOngoingGame(PROCESSENTRY32W& proc, uintptr_t& base)
if (!hProc)
return nullptr;

base = GetGameModuleBase(hProc);
if (!base) {
*base = GetGameModuleBase(hProc);
if (!*base) {
return nullptr;
}

// Check THPrac signature
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;
Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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));
}
}
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 1 addition & 2 deletions thprac/src/thprac/thprac_main.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

0 comments on commit 0b8d25e

Please sign in to comment.