Skip to content

Commit b7d57f9

Browse files
committed
Added FindWindow Hooks
1 parent 4c9d67d commit b7d57f9

6 files changed

Lines changed: 371 additions & 75 deletions

File tree

WinSplitPlusApp/Examples/BatFileExample.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ WinSplitPlus.exe ^
55
-A ^
66
-WinClass ^
77
-WinName ^
8+
-FindWindow ^
89
-Mutex "MutexGame" ^
910
-Width 960 ^
1011
-Height 1080 ^
1112
-Posx 0 ^
1213
-Posy 0 ^
13-
"C:\Game\Game.exe" -window
14+
"Game.exe" -window

WinSplitPlusApp/Examples/BatFileExample64.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ WinSplitPlus64.exe ^
55
-W ^
66
-WinClass ^
77
-WinName ^
8+
-FindWindow ^
89
-Mutex "MutexGame" ^
910
-Width 960 ^
1011
-Height 1080 ^
1112
-Posx 0 ^
1213
-Posy 0 ^
13-
"C:\Game\Game.exe" -window
14+
"Game.exe" -window

WinSplitPlusApp/main.cpp

Lines changed: 101 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,14 @@ And modified by @SAM1430B from splitscreen.me .
1616

1717
#include "InjectionInfo.h"
1818

19-
// Helper function to convert a string to lowercase
20-
std::wstring to_lower(const std::wstring& str) {
21-
std::wstring lower_str = str;
22-
std::transform(lower_str.begin(), lower_str.end(), lower_str.begin(), ::towlower);
23-
return lower_str;
24-
}
25-
2619
// Command-line usage information
2720
void print_usage() {
2821
std::wcout << L"Usage: WinSplitPlus.exe [options] C:\\path\\to\\game.exe [game arguments]\n"
2922
<< L"Options:\n"
3023
<< L" -Player <Number> Identifier.\n"
3124
<< L" -WinClass Enable Window class hook.\n"
3225
<< L" -WinName Enable Window Name hook.\n"
26+
<< L" -FindWindow Enable FindWindow hook.\n"
3327
<< L" -Mutex <Name> Hook Mutex.\n"
3428
<< L" -Width <W> -Height <H> Set Window Size.\n"
3529
<< L" -Posx <X> -Posy <Y> Set Window Position.\n\n"
@@ -38,9 +32,52 @@ void print_usage() {
3832
<< L" -W Enable Unicode hooks (e.g. RegisterClassW).\n"
3933
<< L" -Std Enable Standard hooks (e.g. RegisterClass).\n"
4034
<< L" -Ex Enable Extended hooks (e.g. RegisterClassEx).\n\n"
35+
<< L" -IgnoreWinClass <Name> Ignore specific Class Name (partial match).\n"
36+
<< L" -IgnoreWinName <Name> Ignore specific Window Name (partial match).\n"
4137
<< L" (If no filters are provided, ALL are enabled by default.)\n\n";
4238
}
4339

40+
bool InjectStandardDLL(HANDLE hProcess, const std::wstring& dllPath)
41+
{
42+
HMODULE hKernel32 = GetModuleHandleW(L"Kernel32");
43+
if (!hKernel32) return false;
44+
45+
LPVOID pLoadLibrary = (LPVOID)GetProcAddress(hKernel32, "LoadLibraryW");
46+
if (!pLoadLibrary) return false;
47+
48+
size_t bytesNeeded = (dllPath.length() + 1) * sizeof(wchar_t);
49+
LPVOID pRemoteString = VirtualAllocEx(hProcess, NULL, bytesNeeded, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
50+
if (!pRemoteString) return false;
51+
52+
if (!WriteProcessMemory(hProcess, pRemoteString, dllPath.c_str(), bytesNeeded, NULL))
53+
{
54+
VirtualFreeEx(hProcess, pRemoteString, 0, MEM_RELEASE);
55+
return false;
56+
}
57+
58+
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLoadLibrary, pRemoteString, 0, NULL);
59+
if (!hThread)
60+
{
61+
VirtualFreeEx(hProcess, pRemoteString, 0, MEM_RELEASE);
62+
return false;
63+
}
64+
65+
WaitForSingleObject(hThread, INFINITE);
66+
67+
CloseHandle(hThread);
68+
VirtualFreeEx(hProcess, pRemoteString, 0, MEM_RELEASE);
69+
70+
std::wcout << L"Standard Injection: " << dllPath.substr(dllPath.find_last_of(L"\\/") + 1) << L" - OK" << std::endl;
71+
return true;
72+
}
73+
74+
// Helper function to convert a string to lowercase
75+
std::wstring to_lower(const std::wstring& str) {
76+
std::wstring lower_str = str;
77+
std::transform(lower_str.begin(), lower_str.end(), lower_str.begin(), ::towlower);
78+
return lower_str;
79+
}
80+
4481
int wmain(int argc, wchar_t* argv[])
4582
{
4683
InjectionInfo injectionInfo = {};
@@ -83,6 +120,9 @@ int wmain(int argc, wchar_t* argv[])
83120
injectionInfo.injectionFlags = injectionInfo.injectionFlags | InjectionFlags::HOOK_CREATE_WINDOW;
84121
changeWindowName = true;
85122
}
123+
else if (lower_arg == L"-findwindow") {
124+
injectionInfo.injectionFlags = injectionInfo.injectionFlags | InjectionFlags::HOOK_FIND_WINDOW;
125+
}
86126
else if (lower_arg == L"-mutex" && i + 1 < argc) {
87127
injectionInfo.injectionFlags = injectionInfo.injectionFlags | InjectionFlags::HOOK_CREATE_MUTEX;
88128
baseMutexName = argv[++i];
@@ -118,6 +158,14 @@ int wmain(int argc, wchar_t* argv[])
118158
injectionInfo.injectionFlags = injectionInfo.injectionFlags | InjectionFlags::HOOK_EXTENDED;
119159
hasVariantFilter = true;
120160
}
161+
else if (lower_arg == L"-ignorewinclass" && i + 1 < argc) {
162+
std::wstring val = argv[++i];
163+
wcscpy_s(injectionInfo.customIgnoreClassName, IGNORE_MAX_LENGTH, val.c_str());
164+
}
165+
else if (lower_arg == L"-ignorewinname" && i + 1 < argc) {
166+
std::wstring val = argv[++i];
167+
wcscpy_s(injectionInfo.customIgnoreWindowName, IGNORE_MAX_LENGTH, val.c_str());
168+
}
121169
else {
122170
if (gamePath.empty()) gamePath = original_arg;
123171
else {
@@ -133,7 +181,7 @@ int wmain(int argc, wchar_t* argv[])
133181
system("pause");
134182
return 1;
135183
}
136-
184+
137185
// Construct final names based on Player number
138186
if (!hasCharsetFilter) {
139187
injectionInfo.injectionFlags = injectionInfo.injectionFlags | InjectionFlags::HOOK_ANSI | InjectionFlags::HOOK_UNICODE;
@@ -166,7 +214,7 @@ int wmain(int argc, wchar_t* argv[])
166214
wcscpy_s(injectionInfo.mutexNewName, MUTEX_NAME_MAX_LENGTH, finalMutexName.c_str());
167215
}
168216

169-
// Inject the DLL
217+
// Prepare DLLs
170218
WCHAR exePath[MAX_PATH];
171219
GetModuleFileName(NULL, exePath, MAX_PATH);
172220
std::wstring exeDir = exePath;
@@ -175,8 +223,6 @@ int wmain(int argc, wchar_t* argv[])
175223
exeDir = exeDir.substr(0, lastSlash);
176224
}
177225

178-
std::vector<std::wstring> dllsToInject;
179-
180226
std::wstring coreDllName;
181227

182228
#if defined(_WIN64)
@@ -189,45 +235,49 @@ int wmain(int argc, wchar_t* argv[])
189235
coreDllName = L"WinSplitPlusIJ32.dll";
190236
#endif
191237

192-
// Add the Core DLL to the top of the list
193238
std::wstring coreDllPath = exeDir + L"\\" + coreDllName;
194-
195-
if (GetFileAttributesW(coreDllPath.c_str()) != INVALID_FILE_ATTRIBUTES) {
196-
dllsToInject.push_back(coreDllPath);
197-
std::wcout << L"Added Core DLL: " << coreDllName << std::endl;
198-
}
199-
else {
239+
if (GetFileAttributesW(coreDllPath.c_str()) == INVALID_FILE_ATTRIBUTES) {
200240
std::wcerr << L"CRITICAL ERROR: Core DLL not found at: " << coreDllPath << std::endl;
201241
Sleep(10000);
202242
return 1;
203243
}
204244

245+
// Plugin DLLs
246+
std::vector<std::wstring> pluginDlls;
205247
std::wstring pluginsDir = exeDir + L"\\plugins\\";
206248
std::wstring searchPath = pluginsDir + L"*.dll";
207249
WIN32_FIND_DATA wfd;
208250
HANDLE hFind = FindFirstFile(searchPath.c_str(), &wfd);
209251

210252
if (hFind != INVALID_HANDLE_VALUE) {
211253
do {
212-
std::wstring dllName = wfd.cFileName;
213-
dllsToInject.push_back(pluginsDir + dllName);
254+
std::wstring filename = wfd.cFileName;
255+
std::wstring lowerName = to_lower(filename);
256+
if (lowerName.length() >= 4 &&
257+
lowerName.substr(lowerName.length() - 4) == L".dll")
258+
{
259+
pluginDlls.push_back(pluginsDir + filename);
260+
}
261+
else
262+
{
263+
//std::wcout << L"Skipping invalid match: " << filename << std::endl;
264+
}
265+
214266
} while (FindNextFile(hFind, &wfd));
215267
FindClose(hFind);
216268
}
217269
else {
218-
std::wcout << L"No plugins found in " << pluginsDir << L" (Only Core DLL will be injected)." << std::endl;
270+
std::wcout << L"No plugins found in " << pluginsDir << std::endl;
219271
}
220272

221-
222-
// Suspended state is needed for Window hooks
273+
// Launch Process
223274
PROCESS_INFORMATION pi = {};
224275
STARTUPINFOW si = {};
225276
si.cb = sizeof(si);
226277

227278
std::wstring fullCommandLine = L"\"" + gamePath + L"\" " + gameArgs;
228279

229280
std::wcout << L"Launching game suspended: " << gamePath << std::endl;
230-
std::wcout << L"Injecting " << dllsToInject.size() << L" DLLs..." << std::endl;
231281

232282
if (!CreateProcess(
233283
NULL,
@@ -242,39 +292,39 @@ int wmain(int argc, wchar_t* argv[])
242292
&pi // ProcessInfo
243293
)) {
244294
std::wcerr << L"Failed to create process: " << GetLastError() << std::endl;
245-
std::wcerr << L"Command: " << fullCommandLine << std::endl;
246295
Sleep(7000);
247296
return 1;
248297
}
249298

250299
DWORD processId = pi.dwProcessId;
251-
bool allInjectionsSucceeded = true;
252-
253-
for (const auto& dllPath : dllsToInject) {
254-
std::wcout << L"Injecting " << dllPath.substr(dllPath.find_last_of(L"\\/") + 1) << L"..." << std::endl;
255-
256-
NTSTATUS nt = RhInjectLibrary(
257-
processId,
258-
0,
259-
EASYHOOK_INJECT_DEFAULT,
260-
const_cast<wchar_t*>(dllPath.c_str()),
261-
const_cast<wchar_t*>(dllPath.c_str()),
262-
&injectionInfo,
263-
sizeof(InjectionInfo)
264-
);
265-
266-
if (nt != 0) {
267-
std::wcerr << L"Failed to inject DLL " << dllPath << L": " << RtlGetLastErrorString() << std::endl;
268-
allInjectionsSucceeded = false;
269-
}
270-
}
271300

272-
if (!allInjectionsSucceeded) {
273-
std::wcerr << L"Warning: One or more DLLs failed to inject." << std::endl;
274-
Sleep(5000);
301+
302+
std::wcout << L"Injecting Core DLL via EasyHook..." << std::endl;
303+
NTSTATUS nt = RhInjectLibrary(
304+
processId,
305+
0,
306+
EASYHOOK_INJECT_DEFAULT,
307+
#if defined(_WIN64)
308+
NULL, const_cast<wchar_t*>(coreDllPath.c_str()),
309+
#else
310+
const_cast<wchar_t*>(coreDllPath.c_str()), NULL,
311+
#endif
312+
& injectionInfo, sizeof(InjectionInfo)
313+
);
314+
315+
if (nt != 0) {
316+
std::wcerr << L"Failed to inject Core DLL: " << RtlGetLastErrorString() << std::endl;
317+
std::wcerr << L"Terminating..." << std::endl;
318+
TerminateProcess(pi.hProcess, 1);
319+
return 1;
275320
}
276-
else {
277-
std::wcout << L"All injections successful." << std::endl;
321+
322+
if (!pluginDlls.empty())
323+
{
324+
std::wcout << L"Injecting " << pluginDlls.size() << L" plugins via LoadLibrary..." << std::endl;
325+
for (const auto& dllPath : pluginDlls) {
326+
InjectStandardDLL(pi.hProcess, dllPath);
327+
}
278328
}
279329

280330
std::wcout << L"Resuming game process..." << std::endl;
@@ -283,7 +333,7 @@ int wmain(int argc, wchar_t* argv[])
283333
CloseHandle(pi.hProcess);
284334
CloseHandle(pi.hThread);
285335

286-
std::wcout << L"Successfully launched and injected into process " << processId << L". The launcher will now exit." << std::endl;
336+
std::wcout << L"Success. Launcher exiting." << std::endl;
287337

288338
return 0;
289339
}

WinSplitPlusIJ/Version.rc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
#define PROD_NAME "WinSplitPlusIJ (32-bit)"
99
#endif
1010

11-
#define VER_FILEVERSION 1,2,0,0
12-
#define VER_FILEVERSION_STR "1.2.0.0\0"
11+
#define VER_FILEVERSION 1,3,0,0
12+
#define VER_FILEVERSION_STR "1.3.0.0\0"
1313

14-
#define VER_PRODUCTVERSION 1,2,0,0
15-
#define VER_PRODUCTVERSION_STR "1.2.0.0\0"
14+
#define VER_PRODUCTVERSION 1,3,0,0
15+
#define VER_PRODUCTVERSION_STR "1.3.0.0\0"
1616

1717
VS_VERSION_INFO VERSIONINFO
1818
FILEVERSION VER_FILEVERSION
@@ -36,7 +36,7 @@ VALUE "CompanyName", "https://github.com/SAM1430B/WinSplitPlus"
3636
VALUE "FileDescription", "Window Hooks to run multi instances"
3737
VALUE "FileVersion", VER_FILEVERSION_STR
3838
VALUE "InternalName", FILE_NAME
39-
VALUE "LegalCopyright", "Copyright (C) 2025"
39+
VALUE "LegalCopyright", "Copyright (C) 2026"
4040
VALUE "OriginalFilename", FILE_NAME
4141
VALUE "ProductName", PROD_NAME
4242
VALUE "ProductVersion", VER_PRODUCTVERSION_STR

0 commit comments

Comments
 (0)