diff --git a/Globals.h b/Globals.h new file mode 100644 index 0000000..5265921 --- /dev/null +++ b/Globals.h @@ -0,0 +1,5 @@ +#pragma once + +#define VERSION L"1.2.0" +#define APPNAME L"UniversalPauseButton" +#define MAX_PROCESSES 8 diff --git a/Main.c b/Main.c index c8c681b..d947648 100644 --- a/Main.c +++ b/Main.c @@ -4,6 +4,8 @@ // ryanries09@gmail.com // https://github.com/ryanries/UniversalPauseButton/ +#pragma warning(disable: 5045) // Spectre memory vulnerability warnings + #ifndef UNICODE #define UNICODE #endif @@ -18,14 +20,15 @@ #include #include "Main.h" #include "resource.h" +#include "set.h" CONFIG gConfig; HANDLE gDbgConsole = INVALID_HANDLE_VALUE; -BOOL gIsRunning = TRUE; -HANDLE gMutex; +BOOL gIsRunning = TRUE; // Tracks if the application is running +HANDLE gMutex; // Ensures only a single instance of the app is running NOTIFYICONDATA gTrayNotifyIconData; -BOOL gIsPaused; -u32 gPreviouslyPausedProcessId; +BOOL gIsPaused = FALSE; // Global pause state for all processes +Set gPids; // Process PIDs int WINAPI wWinMain(_In_ HINSTANCE Instance, _In_opt_ HINSTANCE PrevInstance, _In_ PWSTR CmdLine, _In_ int CmdShow) { @@ -37,6 +40,7 @@ int WINAPI wWinMain(_In_ HINSTANCE Instance, _In_opt_ HINSTANCE PrevInstance, _I MSG WndMsg = { 0 }; //HHOOK KeyboardHook = NULL; + initializeSet(&gPids); if (LoadRegistrySettings() != ERROR_SUCCESS) { goto Exit; @@ -143,10 +147,8 @@ int WINAPI wWinMain(_In_ HINSTANCE Instance, _In_opt_ HINSTANCE PrevInstance, _I { HandlePauseKeyPress(); } - DispatchMessageW(&WndMsg); } - Sleep(5); } @@ -154,135 +156,205 @@ int WINAPI wWinMain(_In_ HINSTANCE Instance, _In_opt_ HINSTANCE PrevInstance, _I return(0); } -void HandlePauseKeyPress(void) -{ - HANDLE ProcessHandle = NULL; +// returns PID or 0 on error +u32 PidLookup(wchar_t* ProcessName) { + HANDLE ProcessSnapshot = NULL; PROCESSENTRY32W ProcessEntry = { sizeof(PROCESSENTRY32W) }; u32 ProcessId = 0; - - // Either we configured it to pause/un-pause a specified process, or we will pause/un-pause - // the currently in-focus foreground window. - if (wcslen(gConfig.ProcessNameToPause) > 0) + + ProcessSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (ProcessSnapshot == INVALID_HANDLE_VALUE) { - if (gIsPaused) + MsgBox(L"Failed to create snapshot of running processes! Error 0x%08lx", APPNAME L" Error", MB_OK | MB_ICONERROR, GetLastError()); + return 0; + } + + if (Process32FirstW(ProcessSnapshot, &ProcessEntry) == FALSE) + { + MsgBox(L"Failed to retrieve list of running processes! Error 0x%08lx", APPNAME L" Error", MB_OK | MB_ICONERROR, GetLastError()); + return 0; + } + + do + { + if (_wcsicmp(ProcessEntry.szExeFile, ProcessName) == 0) { - // Process currently paused, need to un-pause it. - UnpausePreviouslyPausedProcess(); + ProcessId = ProcessEntry.th32ProcessID; + DbgPrint(L"Found process %s with PID %d.", ProcessEntry.szExeFile, ProcessId); + + break; } - else - { - // Process needs to be paused. - DbgPrint(L"Pause key pressed. Attempting to pause named process %s...", gConfig.ProcessNameToPause); - ProcessSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (ProcessSnapshot == INVALID_HANDLE_VALUE) - { - MsgBox(L"Failed to create snapshot of running processes! Error 0x%08lx", APPNAME L" Error", MB_OK | MB_ICONERROR, GetLastError()); - goto Exit; - } + } while (Process32NextW(ProcessSnapshot, &ProcessEntry)); - if (Process32FirstW(ProcessSnapshot, &ProcessEntry) == FALSE) - { - MsgBox(L"Failed to retrieve list of running processes! Error 0x%08lx", APPNAME L" Error", MB_OK | MB_ICONERROR, GetLastError()); - goto Exit; - } + CloseHandle(ProcessSnapshot); + + if (ProcessId == 0) + { + DbgPrint(L"Unable to locate any process with the name %s!", ProcessName); + return 0; + } - do - { - if (_wcsicmp(ProcessEntry.szExeFile, gConfig.ProcessNameToPause) == 0) - { - ProcessId = ProcessEntry.th32ProcessID; - DbgPrint(L"Found process %s with PID %d.", ProcessEntry.szExeFile, ProcessId); - break; - } - } while (Process32NextW(ProcessSnapshot, &ProcessEntry)); + return ProcessId; +} - if (ProcessId == 0) - { - DbgPrint(L"Unable to locate any process with the name %s!", gConfig.ProcessNameToPause); - goto Exit; - } - ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId); - if (ProcessHandle == NULL) - { - MsgBox(L"Failed to open process %d! Error 0x%08lx", APPNAME L" Error", MB_OK | MB_ICONERROR, ProcessId, GetLastError()); - goto Exit; - } - NtSuspendProcess(ProcessHandle); - gIsPaused = TRUE; - gPreviouslyPausedProcessId = ProcessId; - DbgPrint(L"Process paused!"); - } +// returns 0 on success, -1 on error +i8 PauseProcess(u32 Pid) { + HANDLE ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid); + if (ProcessHandle == NULL) + { + MsgBox(L"Failed to open process %d! Error 0x%08lx", APPNAME L" Error", MB_OK | MB_ICONERROR, Pid, GetLastError()); + return -1; } - else + + i32 SuspendResult = NtSuspendProcess(ProcessHandle); + DbgPrint(L"NtSuspendProcess() Result Code: %d", SuspendResult); + CloseHandle(ProcessHandle); + gIsPaused = TRUE; + addToSet(&gPids, Pid); + DbgPrint(L"Process %d paused!", Pid); + return 0; +} + +u32 PidOfForegroundProcess(void) { + u32 ProcessId; + HWND ForegroundWindow = GetForegroundWindow(); + if (ForegroundWindow == NULL) { - if (gIsPaused) - { - // Process currently paused, need to un-pause it. - UnpausePreviouslyPausedProcess(); + MsgBox(L"Failed to detect foreground window!", APPNAME L" Error", MB_OK | MB_ICONERROR); + return 0; + } + GetWindowThreadProcessId(ForegroundWindow, &ProcessId); + if (ProcessId == 0) + { + MsgBox(L"Failed to get PID from foreground window! Error code 0x%08lx", APPNAME L" Error", MB_OK | MB_ICONERROR, GetLastError()); + return 0; + } + + HANDLE ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId); + if (ProcessHandle == NULL) + { + MsgBox(L"Failed to open process %d! Error 0x%08lx", APPNAME L" Error", MB_OK | MB_ICONERROR, ProcessId, GetLastError()); + CloseHandle(ProcessHandle); + return 0; + } + CloseHandle(ProcessHandle); + return ProcessId; +} + +void FindRegistryPids(void) { + if (wcslen(gConfig.ProcessNameListToPause) > 0) + { + u32 pid = 0; + // A required pointer which tracks the context between successive calls to wcstok_s(). + wchar_t* context = NULL; + wchar_t delim[] = L", "; + wchar_t myCopy[sizeof(gConfig.ProcessNameListToPause) / sizeof(wchar_t)]; + + wcscpy_s(myCopy, _countof(myCopy), gConfig.ProcessNameListToPause); + + // strtok modifies the original string during the parsing process + wchar_t* token = wcstok_s(myCopy, delim, &context); + // loop through all process names + while (token != NULL) { + TrimWhitespaces(token); + pid = PidLookup(token); + DbgPrint(L"Found \"%s\" via the windows registry settings", token); + if (pid) addToSet(&gPids, pid); + // Get the next token + token = wcstok_s(NULL, delim, &context); } - else - { - // Process needs to be paused. - DbgPrint(L"Pause key pressed. Attempting to pause current foreground window..."); - HWND ForegroundWindow = GetForegroundWindow(); - if (ForegroundWindow == NULL) - { - MsgBox(L"Failed to detect foreground window!", APPNAME L" Error", MB_OK | MB_ICONERROR); - goto Exit; - } - GetWindowThreadProcessId(ForegroundWindow, &ProcessId); - if (ProcessId == 0) - { - MsgBox(L"Failed to get PID from foreground window! Error code 0x%08lx", APPNAME L" Error", MB_OK | MB_ICONERROR, GetLastError()); - goto Exit; - } - ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId); - if (ProcessHandle == NULL) - { - MsgBox(L"Failed to open process %d! Error 0x%08lx", APPNAME L" Error", MB_OK | MB_ICONERROR, ProcessId, GetLastError()); - goto Exit; - } - NtSuspendProcess(ProcessHandle); - gIsPaused = TRUE; - gPreviouslyPausedProcessId = ProcessId; - DbgPrint(L"Process paused!"); - } } +} -Exit: - if (ProcessSnapshot && ProcessSnapshot != INVALID_HANDLE_VALUE) +void FindForegroundPid(void) { + u32 pid = PidOfForegroundProcess(); + if (pid) addToSet(&gPids, pid); +} + +void TogglePause(void) { + // Iterate through Set of PIDs and pause/unpause as needed + + if (gIsPaused) { - CloseHandle(ProcessSnapshot); + DbgPrint(L"Resuming PIDs..."); + for (size_t i = 0; i < gPids.size; i++) { + u32 p = gPids.elements[i]; + ResumeProcess(p); + } + initializeSet(&gPids); + gIsPaused = false; } - if (ProcessHandle) + else { - CloseHandle(ProcessHandle); + DbgPrint(L"Pausing PIDs..."); + size_t length = gPids.size; + for (size_t i = 0; i < length; i++) { + u32 p = gPids.elements[i]; + PauseProcess(p); + addToSet(&gPids, p); + } + gIsPaused = true; + } +} + + +void HandlePauseKeyPress(void) +{ + // Either we configured it to pause/un-pause a specified process via the registry, + // or we will pause/un-pause the currently in-focus foreground window. + DbgPrint(L"Pause key pressed."); + + if (isSetEmpty(&gPids)) { + DbgPrint(L"Attempting to pause registry configured named processes %s...", gConfig.ProcessNameListToPause); + FindRegistryPids(); + + // If nothing is in the registry, use the foreground window + if (isSetEmpty(&gPids)) { + FindForegroundPid(); + DbgPrint(L"Attempting to pause current foreground window..."); + } + } + + if (!isSetEmpty(&gPids)) { + TogglePause(); } } -void UnpausePreviouslyPausedProcess(void) +// returns 0 on success, -1 on failure +i8 ResumeProcess(u32 Pid) { HANDLE ProcessHandle = NULL; - DbgPrint(L"Pause key pressed. Attempting to un-pause previously paused PID %d.", gPreviouslyPausedProcessId); + DbgPrint(L"Attempting to resume previously paused PID %d.", Pid); - ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, gPreviouslyPausedProcessId); + ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid); if (ProcessHandle == NULL) { // Maybe the previously paused process was killed, no longer exists? - MsgBox(L"Failed to open process %d! Error 0x%08lx", APPNAME L" Error", MB_OK | MB_ICONERROR, gPreviouslyPausedProcessId, GetLastError()); + MsgBox(L"Failed to open process %d! Error 0x%08lx", APPNAME L" Error", MB_OK | MB_ICONERROR, Pid, GetLastError()); + return -1; } - else + + NtResumeProcess(ProcessHandle); + CloseHandle(ProcessHandle); + DbgPrint(L"Resume of %d successful.", Pid); + return 0; +} + +void ShowDebugConsole(void) { + if (AllocConsole() == FALSE) { - NtResumeProcess(ProcessHandle); - DbgPrint(L"Un-pause successful."); - } - gIsPaused = FALSE; - gPreviouslyPausedProcessId = 0; - if (ProcessHandle) + MsgBox(L"Failed to allocate debug console!\nError: 0x%08lx", L"Error", MB_OK | MB_ICONERROR, GetLastError()); + return; + } + gDbgConsole = GetStdHandle(STD_OUTPUT_HANDLE); + if (gDbgConsole == INVALID_HANDLE_VALUE) { - CloseHandle(ProcessHandle); + MsgBox(L"Failed to get stdout debug console handle!\nError: 0x%08lx", L"Error", MB_OK | MB_ICONERROR, GetLastError()); + return; } + DbgPrint(L"%s version %s.", APPNAME, VERSION); + DbgPrint(L"To disable this debug console, delete the 'Debug' reg setting at HKCU\\SOFTWARE\\%s", APPNAME); } u32 LoadRegistrySettings(void) @@ -332,6 +404,14 @@ u32 LoadRegistrySettings(void) .MinValue = NULL, .MaxValue = NULL, .Destination = &gConfig.ProcessNameToPause + }, + { + .Name = L"ProcessNameListToPause", + .DataType = REG_SZ, + .DefaultValue = &(wchar_t[1024]) { L"" }, + .MinValue = NULL, + .MaxValue = NULL, + .Destination = &gConfig.ProcessNameListToPause } }; @@ -355,7 +435,7 @@ u32 LoadRegistrySettings(void) Settings[s].Name, RRF_RT_DWORD, NULL, - Settings[s].Destination, + Settings[s].Destination, // Registry Value is stored here &BytesRead); if (Result != ERROR_SUCCESS) { @@ -383,22 +463,7 @@ u32 LoadRegistrySettings(void) // This is so the debug console can report on the other registry settings. if (Settings[s].Destination == &gConfig.Debug) { - if (gConfig.Debug) - { - if (AllocConsole() == FALSE) - { - MsgBox(L"Failed to allocate debug console!\nError: 0x%08lx", L"Error", MB_OK | MB_ICONERROR, GetLastError()); - goto Exit; - } - gDbgConsole = GetStdHandle(STD_OUTPUT_HANDLE); - if (gDbgConsole == INVALID_HANDLE_VALUE) - { - MsgBox(L"Failed to get stdout debug console handle!\nError: 0x%08lx", L"Error", MB_OK | MB_ICONERROR, GetLastError()); - goto Exit; - } - DbgPrint(L"%s version %s.", APPNAME, VERSION); - DbgPrint(L"To disable this debug console, delete the 'Debug' reg setting at HKCU\\SOFTWARE\\%s", APPNAME); - } + if (gConfig.Debug) ShowDebugConsole(); } DbgPrint(L"Using value 0n%d (0x%x) for registry setting '%s'.", *(u32*)Settings[s].Destination, *(u32*)Settings[s].Destination, Settings[s].Name); @@ -414,7 +479,7 @@ u32 LoadRegistrySettings(void) Settings[s].Name, RRF_RT_REG_SZ, NULL, - Settings[s].Destination, + Settings[s].Destination, // Registry Value is stored here &BytesRead); if (Result != ERROR_SUCCESS) { @@ -441,6 +506,15 @@ u32 LoadRegistrySettings(void) } } + // Combine ProcessNameToPause and ProcessNameListToPause + if (wcslen(gConfig.ProcessNameListToPause) > 0) + { + wcscat_s(gConfig.ProcessNameListToPause, sizeof(gConfig.ProcessNameListToPause), L", "); + } + wcscat_s(gConfig.ProcessNameListToPause, sizeof(gConfig.ProcessNameListToPause), gConfig.ProcessNameToPause); + + DbgPrint(L"Final List of Processes: %s.", gConfig.ProcessNameListToPause); + Exit: if (RegKey != NULL) { @@ -520,4 +594,23 @@ LRESULT CALLBACK SysTrayCallback(_In_ HWND Window, _In_ UINT Message, _In_ WPARA } } return(Result); +} + +// Function to trim leading and trailing whitespaces from a wide string +void TrimWhitespaces(wchar_t* str) { + wchar_t* end; + + // Trim leading whitespaces + while (iswspace(*str)) { // iswspace returns zero (false) if the char is not a whitespace + str++; // move pointer until we find the first whitespace char + } + + // Trim trailing whitespaces + end = str + wcslen(str) - 1; + while (end > str && iswspace(*end)) { + end--; + } + + // Null-terminate the trimmed string + *(end + 1) = L'\0'; } \ No newline at end of file diff --git a/Main.h b/Main.h index ee8cbd3..15e09fd 100644 --- a/Main.h +++ b/Main.h @@ -1,9 +1,9 @@ #pragma once #pragma warning(disable:4820) // padding in structures +#pragma warning(disable:4711) // automatic inline expansion -#define VERSION L"1.1.5" -#define APPNAME L"UniversalPauseButton" +#include "Globals.h" // The Lord's data types. typedef unsigned char u8; @@ -29,10 +29,11 @@ _HungWindowFromGhostWindow HungWindowFromGhostWindow; // Configurable registry settings. typedef struct _CONFIG { - u32 Debug; - u32 TrayIcon; - u32 PauseKey; - wchar_t ProcessNameToPause[128]; + u32 Debug; + u32 TrayIcon; + u32 PauseKey; + wchar_t ProcessNameToPause[128]; + wchar_t ProcessNameListToPause[MAX_PROCESSES * 128]; } CONFIG; // Function declarations. @@ -41,4 +42,12 @@ void MsgBox(const wchar_t* Message, const wchar_t* Caption, u32 Flags, ...); void DbgPrint(const wchar_t* Message, ...); LRESULT CALLBACK SysTrayCallback(_In_ HWND Window, _In_ UINT Message, _In_ WPARAM WParam, _In_ LPARAM LParam); void HandlePauseKeyPress(void); -void UnpausePreviouslyPausedProcess(void); \ No newline at end of file +i8 ResumeProcess(u32 Pid); +u32 PidLookup(wchar_t* ProcessName); +i8 PauseProcess(u32 Pid); +u32 PidOfForegroundProcess(void); +void TrimWhitespaces(wchar_t* str); +void FindRegistryPids(void); +void FindForegroundPid(void); +void TogglePause(void); +void ShowDebugConsole(void); diff --git a/README.md b/README.md index cf670e2..309b0be 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,19 @@ If TrayIcon is enabled, which it is by default, there will be a small system tra If ProcessNameToPause is defined, then the app will only pause that process by name, regardless of foreground window. E.g., notepad.exe or mycoolgame.exe. Include the .exe file extension. It expects process name, not Window text. WARNING: In case there are multiple processes with the same name, only the first instance found will be paused. Please don't try dumb things like trying to pause svchost.exe or lsass.exe or csrss.exe... +**ProcessNameListToPause** + + Type: REG_SZ (String) + + Default: "" (Empty string) + + Minimum: n/a + + Maximum: n/a + + +If ProcessNameListToPause is defined, then the app will pause and resume all of the processes listed at the same time. Follow the same guidelines as ProcessNameToPause. Seperate each process by a comma. E.g., "VRising.exe, VRisingServer.exe, Spotify.exe". Note: You only need to use either ProcessNameToPause or ProcessNameListToPause, but both can be used simultaneously. As always, please try it out, and let me know if you find any bugs or have any feature requests. -Thanks! \ No newline at end of file +Thanks! diff --git a/UniversalPauseButton.vcxproj b/UniversalPauseButton.vcxproj index 2998abb..60f3ee2 100644 --- a/UniversalPauseButton.vcxproj +++ b/UniversalPauseButton.vcxproj @@ -145,10 +145,13 @@ + + + diff --git a/UniversalPauseButton.vcxproj.filters b/UniversalPauseButton.vcxproj.filters index fc03361..368662d 100644 --- a/UniversalPauseButton.vcxproj.filters +++ b/UniversalPauseButton.vcxproj.filters @@ -21,6 +21,12 @@ Header Files + + Header Files + + + Header Files + @@ -36,5 +42,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/set.c b/set.c new file mode 100644 index 0000000..f06ba03 --- /dev/null +++ b/set.c @@ -0,0 +1,67 @@ +#pragma once +#pragma warning(disable: 5045) // Spectre memory vulnerability warnings + +#include "set.h" +#include + +void initializeSet(Set* set) { + for (size_t i = 0; i < MAX_PROCESSES; i++) { + set->elements[i] = 0; + } + set->size = 0; +} + +bool addToSet(Set* set, u32 element) { + // Check if the element is already in the set + for (size_t i = 0; i < set->size; i++) { + if (set->elements[i] == element) { + return false; // Element is already in the set + } + } + + // Add the element to the set + if (set->size < MAX_PROCESSES) { + set->elements[set->size++] = element; + return true; // Element added successfully + } + else { + return false; // Set is full + } +} + +bool removeFromSet(Set* set, u32 element) { + for (size_t i = 0; i < MAX_PROCESSES; i++) { + if (set->elements[i] == element) { + set->elements[i] = 0; + // Shift elements to the left to fill the gap + for (size_t j = i; j < MAX_PROCESSES - 1; j++) { + set->elements[j] = set->elements[j + 1]; + } + set->size--; + return true; // Element removed successfully + } + } + return false; // Element is not in the set +} + +bool isInSet(const Set* set, u32 element) { + for (size_t i = 0; i < set->size; i++) { + if (set->elements[i] == element) { + return true; // Element is in the set + } + } + return false; // Element is not in the set +} + +void printSet(const Set* set) { + printf("Set: "); + for (size_t i = 0; i < set->size; i++) { + printf("%ld ", set->elements[i]); + } + printf("\n"); +} + +bool isSetEmpty(const Set* set) +{ + return set->size == 0; +} diff --git a/set.h b/set.h new file mode 100644 index 0000000..cc36ab5 --- /dev/null +++ b/set.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include "Globals.h" + +// #define MAX_PROCESSES 8 // todo: centralize +typedef unsigned long u32; + +typedef struct { + u32 elements[MAX_PROCESSES]; + size_t size; +} Set; + +void initializeSet(Set* set); +bool addToSet(Set* set, u32 element); +bool removeFromSet(Set* set, u32 element); +bool isInSet(const Set* set, u32 element); +void printSet(const Set* set); +bool isSetEmpty(const Set* set);