diff --git a/mhw-cs-plugin-loader.slnx b/mhw-cs-plugin-loader.slnx index ecc1f8c..25e930f 100644 --- a/mhw-cs-plugin-loader.slnx +++ b/mhw-cs-plugin-loader.slnx @@ -62,6 +62,10 @@ + + + + diff --git a/mhw-cs-plugin-loader/Launcher/Launcher.c b/mhw-cs-plugin-loader/Launcher/Launcher.c new file mode 100644 index 0000000..29b68de --- /dev/null +++ b/mhw-cs-plugin-loader/Launcher/Launcher.c @@ -0,0 +1,94 @@ +// cl.exe Launcher.c kernel32.lib user32.lib /link /SUBSYSTEM:windows /NODEFAULTLIB /ENTRY:WinMainCRTStartup + +#include +#include + +#define TARGET_EXE "MonsterHunterWorld.exe" +#define TARGET_DLL "winmm.dll" + +static DWORD (*K32GetModuleBaseNameA)(IN HANDLE hProcess, IN HMODULE hModule, OUT LPSTR lpBaseName, IN DWORD nSize); +static BOOL (*K32EnumProcessModules)(IN HANDLE hProcess, OUT HMODULE *lphModule, IN DWORD cb, OUT LPDWORD lpcbNeeded); + +// MonsterHunterWorld.exe -> ntdll.dll -> KERNEL32.dll -> KERNELBASE.dll -> (apphelp.dll) -> TARGET_DLL + dependencies. +// () = Present on Windows and not Wine (Windows 11, Proton 11.0). +// Total modules varies between Windows and Wine, 64 is plenty in the case of MHW and likely most scenarios. +static HMODULE moduleHandles[64]; +static CHAR moduleName[MAX_MODULE_NAME32 + 1]; +static CHAR dllPath[MAX_PATH]; + +// https://git.musl-libc.org/cgit/musl/tree/src/string/strncmp.c +static int Strncmp(const char *_l, const char *_r, size_t n) +{ + const unsigned char *l=(void *)_l, *r=(void *)_r; + if (!n--) return 0; + for (; *l && *r && n && *l == *r ; l++, r++, n--); + return *l - *r; +} + +static BOOL DllWasInjected(HANDLE process) +{ + DWORD cbNeeded; + if (K32EnumProcessModules(process, moduleHandles, sizeof(moduleHandles), &cbNeeded)) { + DWORD processCount = cbNeeded / sizeof(HMODULE); + for (DWORD i = 0; i < processCount; i++) { + K32GetModuleBaseNameA(process, moduleHandles[i], moduleName, sizeof(moduleName)); + if (Strncmp(TARGET_DLL, moduleName, sizeof(TARGET_DLL)) == 0) { + return TRUE; + } + } + } + return FALSE; +} + +void __stdcall WinMainCRTStartup() +{ + HMODULE k32 = GetModuleHandleA("kernel32.dll"); + LPVOID LoadLibraryAddr = (LPVOID)GetProcAddress(k32, "LoadLibraryA"); + K32GetModuleBaseNameA = (LPVOID)GetProcAddress(k32, "K32GetModuleBaseNameA"); + K32EnumProcessModules = (LPVOID)GetProcAddress(k32, "K32EnumProcessModules"); + + STARTUPINFOA si = { .cb = sizeof(si) }; + PROCESS_INFORMATION pi = { 0 }; + if (CreateProcessA(NULL, TARGET_EXE, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi) == 0) { + MessageBoxA(NULL, + "Failed to create process. Is '" TARGET_EXE "' contained within this folder?", + "Launch Error", + MB_ICONERROR | MB_OK); + ExitProcess(1); + } + + DWORD arglen = GetFullPathNameA(TARGET_DLL, sizeof(dllPath), dllPath, NULL); + LPVOID buf = VirtualAllocEx(pi.hProcess, NULL, arglen, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); + WriteProcessMemory(pi.hProcess, buf, dllPath, arglen, NULL); + + HANDLE hThread = CreateRemoteThread(pi.hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryAddr, buf, 0, NULL); + if (!hThread) { + MessageBoxA(NULL, + "Failed to CreateRemoteThread(), make sure the architecture of this exe matches " TARGET_EXE ".", + "Injection Failed", + MB_ICONWARNING | MB_OK); + } else { + WaitForSingleObject(hThread, INFINITE); + // The value of GetExitCodeThread() isn't usable, so try to manually check + // if the DLL got loaded (LoadLibrary() succeeded). + if (!DllWasInjected(pi.hProcess)) { + MessageBoxA(NULL, + "Failed to inject DLL. Is '" TARGET_DLL "' contained within this folder?", + "Injection Failed", + MB_ICONWARNING | MB_OK); + } + CloseHandle(hThread); + } + + VirtualFreeEx(pi.hProcess, buf, 0, MEM_RELEASE); + + ResumeThread(pi.hThread); + + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + FreeLibrary(k32); + + ExitProcess(0); +} diff --git a/mhw-cs-plugin-loader/Launcher/Launcher.ico b/mhw-cs-plugin-loader/Launcher/Launcher.ico new file mode 100644 index 0000000..eac7be3 Binary files /dev/null and b/mhw-cs-plugin-loader/Launcher/Launcher.ico differ diff --git a/mhw-cs-plugin-loader/Launcher/Launcher.rc b/mhw-cs-plugin-loader/Launcher/Launcher.rc new file mode 100755 index 0000000..799a459 Binary files /dev/null and b/mhw-cs-plugin-loader/Launcher/Launcher.rc differ diff --git a/mhw-cs-plugin-loader/Launcher/Launcher.vcxproj b/mhw-cs-plugin-loader/Launcher/Launcher.vcxproj new file mode 100755 index 0000000..662c6e8 --- /dev/null +++ b/mhw-cs-plugin-loader/Launcher/Launcher.vcxproj @@ -0,0 +1,100 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {1fa71114-1187-4059-9523-196a234c1b94} + Launcher + 10.0 + + + + Application + true + v143 + NotSet + + + Application + false + v143 + true + NotSet + + + + + + + + + + + + + + + SPLLauncher + false + + + SPLLauncher + false + + + + Level3 + false + _DEBUG;%(PreprocessorDefinitions);WIN32_LEAN_AND_MEAN + true + false + Default + + + Windows + true + Yes + + + + + Level3 + true + true + false + NDEBUG;%(PreprocessorDefinitions);WIN32_LEAN_AND_MEAN + true + false + + + Windows + true + Yes + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mhw-cs-plugin-loader/Launcher/Launcher.vcxproj.filters b/mhw-cs-plugin-loader/Launcher/Launcher.vcxproj.filters new file mode 100755 index 0000000..ab6603e --- /dev/null +++ b/mhw-cs-plugin-loader/Launcher/Launcher.vcxproj.filters @@ -0,0 +1,33 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + + + Resource Files + + + + + Resource Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/mhw-cs-plugin-loader/Launcher/create_icon.sh b/mhw-cs-plugin-loader/Launcher/create_icon.sh new file mode 100755 index 0000000..5815f00 --- /dev/null +++ b/mhw-cs-plugin-loader/Launcher/create_icon.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env sh +# -define icon:auto-resize=256,128,96,64,48,32,16 +magick -density 512 -define icon:auto-resize=32 -background none ../../docs/images/SPL.svg Launcher.ico diff --git a/mhw-cs-plugin-loader/Launcher/resource.h b/mhw-cs-plugin-loader/Launcher/resource.h new file mode 100755 index 0000000..79dc64e --- /dev/null +++ b/mhw-cs-plugin-loader/Launcher/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Launcher.rc +// +#define IDI_ICON1 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif