From 91b014b8983ff028619a34d9995aae38f98f533d Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Wed, 8 Jul 2026 23:47:32 +0200 Subject: [PATCH 01/25] Fix Explorer restart issue --- mods/settings-to-control-panel.wh.cpp | 35 +++++++++++++++------------ 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index 81bb2bbb45..d7fae8b61b 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -2,7 +2,7 @@ // @id settings-to-control-panel // @name Redirect Settings to Control Panel // @description Forces classic Control Panel to open instead of Windows 10/11 Settings app using native components. Primarily designed for Windows 10; Windows 11 support is limited due to Microsoft's shell architecture changes. -// @version 10.0.20 +// @version 10.0.21 // @author babamohammed // @github https://github.com/babamohammed2022 // @include explorer.exe @@ -357,6 +357,16 @@ static bool InitTrayDllInfo() { static int GetTrayButtonType(HWND hToolbar, int buttonIndex) { if (buttonIndex < 0) return 0; + // Retry capturing the DLL base addresses here too: SetupTraySubclass() + // only calls InitTrayDllInfo() once (guarded by g_hTrayToolbar), so if + // pnidui.dll (network) wasn't loaded yet at that point (e.g. right after + // an Explorer restart, while SndVolSSO.dll for audio was already + // resident), g_pniduiBase would stay null forever and network tray + // detection would silently break until the mod was reloaded. Re-checking + // here is cheap (InitTrayDllInfo short-circuits once both are set) and + // lets it self-heal as soon as pnidui.dll actually loads. + InitTrayDllInfo(); + TBBUTTON tb{}; if (!SendMessageW(hToolbar, TB_GETBUTTON, buttonIndex, (LPARAM)&tb)) return 0; if (!tb.dwData) return 0; @@ -631,26 +641,21 @@ BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND h Wh_Log(L"[TRAY-HOOK] %s menu target index=%d, ID=%u", menuKind, targetIndex, GetMenuItemID(hMenu, targetIndex)); - UINT customId = isAudioMenu ? TRAY_CUSTOM_ID_AUDIO - : isNetworkMenu ? TRAY_CUSTOM_ID_NETWORK - : TRAY_CUSTOM_ID_DEVICES; UINT originalId = GetMenuItemID(hMenu, targetIndex); - MENUITEMINFOW mii = { sizeof(MENUITEMINFOW) }; - mii.fMask = MIIM_ID; - mii.wID = customId; - SetMenuItemInfoW(hMenu, targetIndex, TRUE, &mii); - + // NOTE: we intentionally do NOT swap the item's ID (via SetMenuItemInfoW) + // anymore. This menu item still gets its text/icon painted by pnidui.dll + // through WM_DRAWITEM, which is keyed off the item's real ID — replacing + // it with our own custom ID left pnidui.dll unable to look up what to + // draw for it, so the item rendered with no text (it still worked, + // since selection is ID-driven, but visually looked broken). Comparing + // directly against the original ID avoids touching the item at all. bool callerWantedReturnCmd = (uFlags & TPM_RETURNCMD) != 0; uFlags |= TPM_RETURNCMD; BOOL result = g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); int selectedId = (int)result; - // Restore original ID - mii.wID = originalId; - SetMenuItemInfoW(hMenu, targetIndex, TRUE, &mii); - - if (selectedId == (int)customId) { + if (selectedId == (int)originalId) { Wh_Log(L"[TRAY-HOOK] User selected target item, redirecting"); if (isAudioMenu) OpenClassicSoundPanel(); else if (isNetworkMenu) OpenClassicNetworkConnections(); @@ -1249,7 +1254,7 @@ HWND WINAPI CreateWindowExW_Hook( } BOOL Wh_ModInit() { - Wh_Log(L"Redirect Settings to Control Panel v10.0.20"); + Wh_Log(L"Redirect Settings to Control Panel v10.0.21"); DetectWindowsVersion(); LoadSettings(); From 26518f6bc597611606c70260a8ee01b493f92c10 Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Thu, 9 Jul 2026 00:22:34 +0200 Subject: [PATCH 02/25] Attempt to fix system tray redirect for the network context menu after explorer restart --- mods/settings-to-control-panel.wh.cpp | 147 ++++++++++++++++++++++++-- 1 file changed, 138 insertions(+), 9 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index d7fae8b61b..19e7e6bf08 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -2,7 +2,7 @@ // @id settings-to-control-panel // @name Redirect Settings to Control Panel // @description Forces classic Control Panel to open instead of Windows 10/11 Settings app using native components. Primarily designed for Windows 10; Windows 11 support is limited due to Microsoft's shell architecture changes. -// @version 10.0.21 +// @version 10.0.22 // @author babamohammed // @github https://github.com/babamohammed2022 // @include explorer.exe @@ -104,7 +104,10 @@ using ICMH_CAODTM_t = bool(__fastcall*)(HMENU, HWND); static ICMH_CAODTM_t g_icmhOrig_SndVolSSO = nullptr; static ICMH_CAODTM_t g_icmhOrig_pnidui = nullptr; static ICMH_CAODTM_t g_icmhOrig_Shell32Devices = nullptr; - +static bool g_pniduiHookInstalled = false; +static std::mutex g_pniduiHookMutex; +static HANDLE g_pniduiRetryThread = nullptr; +static volatile bool g_pniduiRetryStop = false; static bool __fastcall ICMH_CAODTM_hook(HMENU, HWND); @@ -1177,21 +1180,109 @@ BOOL WINAPI CreateProcessW_hook(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); } +static volatile bool g_pniduiRetryRunning = false; + + +static bool TryInstallPniduiHook() { + std::lock_guard lk(g_pniduiHookMutex); + + // Se già installato, non fare nulla + if (g_pniduiHookInstalled) { + return true; + } + + // Prima verifica se la DLL è già caricata nel processo + HMODULE hMod = GetModuleHandleW(L"pnidui.dll"); + if (!hMod) { + // Non è caricata, prova a caricarla (potrebbe fallire se non disponibile) + hMod = LoadLibraryExW(L"pnidui.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (!hMod) { + Wh_Log(L"[PNIDUI-HOOK] pnidui.dll not loaded yet"); + return false; + } + } + + Wh_Log(L"[PNIDUI-HOOK] pnidui.dll loaded at 0x%p", hMod); + + WindhawkUtils::SYMBOL_HOOK hook = {{ + L"bool " +#ifdef _WIN64 + L"__cdecl" +#else + L"__stdcall" +#endif + L" ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu" + L"(struct HMENU__ *,struct HWND__ *)" + }, + (void**)&g_icmhOrig_pnidui, + (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook}; + + bool result = WindhawkUtils::HookSymbols(hMod, &hook, 1); + if (result) { + Wh_Log(L"[PNIDUI-HOOK] Successfully installed pnidui.dll hook"); + g_pniduiHookInstalled = true; + } else { + Wh_Log(L"[PNIDUI-HOOK] Failed to install pnidui.dll hook"); + } + return result; +} + +static DWORD WINAPI PniduiRetryThread(LPVOID) { + // Evita che più thread di retry partano contemporaneamente + if (g_pniduiRetryRunning) { + Wh_Log(L"[PNIDUI-HOOK] Retry thread already running, exiting"); + return 0; + } + g_pniduiRetryRunning = true; + + Wh_Log(L"[PNIDUI-HOOK] Retry thread started - waiting for pnidui.dll to load"); + + // Aspetta che la DLL venga caricata - controlla ogni 500ms per max 30 secondi + const int MAX_WAIT_CHECKS = 60; // 30 secondi + const DWORD CHECK_INTERVAL = 500; + bool dllLoaded = false; + + for (int i = 0; i < MAX_WAIT_CHECKS && !g_pniduiRetryStop; i++) { + if (GetModuleHandleW(L"pnidui.dll") != nullptr) { + dllLoaded = true; + Wh_Log(L"[PNIDUI-HOOK] pnidui.dll detected after %dms", i * CHECK_INTERVAL); + break; + } + Sleep(CHECK_INTERVAL); + } + + if (!dllLoaded) { + Wh_Log(L"[PNIDUI-HOOK] pnidui.dll not loaded after timeout, giving up"); + g_pniduiRetryRunning = false; + return 0; + } + + // Ora prova ad installare l'hook + if (TryInstallPniduiHook()) { + Wh_Log(L"[PNIDUI-HOOK] Hook installed successfully after waiting for DLL"); + } else { + Wh_Log(L"[PNIDUI-HOOK] Failed to install hook even though DLL is loaded"); + } + + g_pniduiRetryRunning = false; + Wh_Log(L"[PNIDUI-HOOK] Retry thread exiting"); + return 0; +} + static void InstallImmersiveMenuHooks() { + // Installa SndVolSSO (sempre) struct DllHook { const wchar_t* dll; ICMH_CAODTM_t* orig; } targets[] = { { L"SndVolSSO.dll", &g_icmhOrig_SndVolSSO }, - { L"pnidui.dll", &g_icmhOrig_pnidui }, }; for (auto& t : targets) { HMODULE hMod = LoadLibraryExW(t.dll, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if (!hMod) continue; - // SndVolSSO.dll, pnidui.dll - WindhawkUtils::SYMBOL_HOOK sndVolSSO_pnidui_hooks[] = { + WindhawkUtils::SYMBOL_HOOK hooks[] = { {{ L"bool " #ifdef _WIN64 @@ -1206,13 +1297,29 @@ static void InstallImmersiveMenuHooks() { (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook} }; - WindhawkUtils::HookSymbols(hMod, sndVolSSO_pnidui_hooks, 1); + WindhawkUtils::HookSymbols(hMod, hooks, 1); + } + + // Prova pnidui.dll - se fallisce, avvia il thread di retry + if (!TryInstallPniduiHook()) { + // Controlla che il thread non sia già in esecuzione + if (!g_pniduiRetryRunning && !g_pniduiRetryThread) { + g_pniduiRetryStop = false; + g_pniduiRetryThread = CreateThread(nullptr, 0, PniduiRetryThread, nullptr, 0, nullptr); + if (g_pniduiRetryThread) { + Wh_Log(L"[PNIDUI-HOOK] Retry thread created"); + } else { + Wh_Log(L"[PNIDUI-HOOK] Failed to create retry thread"); + } + } else { + Wh_Log(L"[PNIDUI-HOOK] Retry thread already running or hook already installed"); + } } + // Shell32 per dispositivi (solo Win11) if (g_isWin11) { HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); if (hShell32) { - // shell32.dll WindhawkUtils::SYMBOL_HOOK shell32_hooks[] = { {{ L"bool " @@ -1254,7 +1361,7 @@ HWND WINAPI CreateWindowExW_Hook( } BOOL Wh_ModInit() { - Wh_Log(L"Redirect Settings to Control Panel v10.0.21"); + Wh_Log(L"Redirect Settings to Control Panel v10.0.22"); DetectWindowsVersion(); LoadSettings(); @@ -1297,8 +1404,21 @@ BOOL Wh_ModInit() { return TRUE; } - void Wh_ModUninit() { + // Ferma il thread di retry se attivo + if (g_pniduiRetryThread) { + g_pniduiRetryStop = true; + Wh_Log(L"[PNIDUI-HOOK] Stopping retry thread..."); + DWORD waitResult = WaitForSingleObject(g_pniduiRetryThread, 3000); + if (waitResult == WAIT_TIMEOUT) { + Wh_Log(L"[PNIDUI-HOOK] Retry thread timeout, terminating"); + TerminateThread(g_pniduiRetryThread, 0); + } + CloseHandle(g_pniduiRetryThread); + g_pniduiRetryThread = nullptr; + g_pniduiRetryRunning = false; + } + RemoveTraySubclass(); } @@ -1308,9 +1428,18 @@ void Wh_ModSettingsChanged() { g_sndVolSSOEnd = nullptr; g_pniduiBase = nullptr; g_pniduiEnd = nullptr; + + // Resetta lo stato degli hook per riprovare + { + std::lock_guard lk(g_pniduiHookMutex); + g_pniduiHookInstalled = false; + } + LoadSettings(); InitMappings(); if (g_settings.redirectSystemTray) { SetupTraySubclass(); + // Reinstalla gli hook se necessario + InstallImmersiveMenuHooks(); } } From 40b384c6c0e4a76993cb9466255efe2a8009ac07 Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Thu, 9 Jul 2026 00:35:03 +0200 Subject: [PATCH 03/25] Added another fallback method for network system tray redirect --- mods/settings-to-control-panel.wh.cpp | 184 ++++++++++++++++++++++++-- 1 file changed, 173 insertions(+), 11 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index 19e7e6bf08..b11d1a9a48 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -2,7 +2,7 @@ // @id settings-to-control-panel // @name Redirect Settings to Control Panel // @description Forces classic Control Panel to open instead of Windows 10/11 Settings app using native components. Primarily designed for Windows 10; Windows 11 support is limited due to Microsoft's shell architecture changes. -// @version 10.0.22 +// @version 10.0.23 // @author babamohammed // @github https://github.com/babamohammed2022 // @include explorer.exe @@ -129,6 +129,9 @@ using ShellExecuteW_t = HINSTANCE(WINAPI*)(HWND, LPCWSTR, LPCWSTR, LPCWSTR, LPCW using ShellExecuteExW_t = BOOL(WINAPI*)(SHELLEXECUTEINFOW*); static ShellExecuteExW_t ShellExecuteExW_orig = nullptr; static ShellExecuteW_t ShellExecuteW_orig = nullptr; +// NtUserTrackPopupMenuEx fallback (syscall level) +using NtUserTrackPopupMenuEx_t = BOOL(WINAPI*)(HMENU, UINT, int, int, HWND, const TPMPARAMS*); +static NtUserTrackPopupMenuEx_t g_origNtUserTrackPopupMenuEx = nullptr; struct ResolveResult { std::wstring target; @@ -673,7 +676,138 @@ BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND h return result; } - +// Hook a livello syscall per catturare menu che bypassano TrackPopupMenuEx +BOOL WINAPI NtUserTrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND hWnd, const TPMPARAMS* lptpm) { + // Se non è un menu della system tray, passa all'originale + if (!g_settings.redirectSystemTray || !g_settings.enableRedirects) { + return g_origNtUserTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + } + + HookGuard guard; + if (guard.IsReentrant()) { + return g_origNtUserTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + } + + // --- Rilevamento menu usando la stessa logica di TrackPopupMenuEx_Hook --- + int contextType; + { + std::lock_guard lk(g_trayContextMutex); + contextType = g_trayContextType; + DWORD tick = g_trayContextTick; + g_trayContextType = 0; + g_trayContextTick = 0; + if (contextType != 0 && GetTickCount() - tick > TRAY_CONTEXT_MAX_AGE_MS) { + contextType = 0; + } + } + + bool isAudioMenu = (contextType == 1); + bool isNetworkMenu = (contextType == 2); + bool isDeviceMenu = false; + + // Fallback: DLL return-address detection + if (!isAudioMenu && !isNetworkMenu) { + void* retAddr = GetReturnAddress(); + int itemCount = GetMenuItemCount(hMenu); + if (itemCount > 0) { + if (IsAddressInModule(retAddr, L"SndVolSSO.dll")) { + if (itemCount <= 6) isAudioMenu = true; + } + else if (IsAddressInModule(retAddr, L"pnidui.dll")) { + if (itemCount <= 6) isNetworkMenu = (itemCount >= 2 && itemCount <= 5); + } + else if (IsAddressInModule(retAddr, L"dxgi.dll")) { + if (itemCount == 2 && GetMenuItemID(hMenu, 0) == 3107 && GetMenuItemID(hMenu, 1) == 3109) { + isNetworkMenu = true; + } + else if (GetMenuItemID(hMenu, 0) == 215) { + isDeviceMenu = true; + } + } + else if (IsAddressInModule(retAddr, L"shell32.dll")) { + for (int i = 0; i < itemCount; i++) { + if (GetMenuItemID(hMenu, i) == 215) { + isDeviceMenu = true; + break; + } + } + } + else if (IsAddressInModule(retAddr, L"hotplug.dll")) { + isDeviceMenu = true; + } + } + } + + if (!isAudioMenu && !isNetworkMenu && !isDeviceMenu) { + return g_origNtUserTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + } + + // Trova l'item target + int itemCount = GetMenuItemCount(hMenu); + int targetIndex = -1; + + if (isAudioMenu) { + targetIndex = 0; + } + else if (isNetworkMenu) { + for (int i = itemCount - 1; i >= 0; i--) { + MENUITEMINFOW miiCheck = { sizeof(MENUITEMINFOW) }; + miiCheck.fMask = MIIM_FTYPE; + if (GetMenuItemInfoW(hMenu, i, TRUE, &miiCheck)) { + if (!(miiCheck.fType & MFT_SEPARATOR)) { + targetIndex = i; + break; + } + } + } + } + else if (isDeviceMenu) { + for (int i = 0; i < itemCount; i++) { + if (GetMenuItemID(hMenu, i) == 215) { + targetIndex = i; + break; + } + } + if (targetIndex == -1) { + for (int i = 0; i < itemCount; i++) { + MENUITEMINFOW miiCheck = { sizeof(MENUITEMINFOW) }; + miiCheck.fMask = MIIM_FTYPE; + if (GetMenuItemInfoW(hMenu, i, TRUE, &miiCheck)) { + if (!(miiCheck.fType & MFT_SEPARATOR)) { + targetIndex = i; + break; + } + } + } + } + } + + if (targetIndex == -1) { + return g_origNtUserTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + } + + UINT originalId = GetMenuItemID(hMenu, targetIndex); + bool callerWantedReturnCmd = (uFlags & TPM_RETURNCMD) != 0; + uFlags |= TPM_RETURNCMD; + + BOOL result = g_origNtUserTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + int selectedId = (int)result; + + if (selectedId == (int)originalId) { + Wh_Log(L"[SYSCALL-HOOK] User selected target item, redirecting"); + if (isAudioMenu) OpenClassicSoundPanel(); + else if (isNetworkMenu) OpenClassicNetworkConnections(); + else OpenClassicDevicesAndPrinters(); + return 0; + } + + if (selectedId != 0 && !callerWantedReturnCmd) { + PostMessageW(hWnd, WM_COMMAND, MAKEWPARAM((WORD)selectedId, 0), 0); + return TRUE; + } + + return result; +} static std::unordered_map g_mappings; static void InitMappings() { @@ -1204,7 +1338,7 @@ static bool TryInstallPniduiHook() { Wh_Log(L"[PNIDUI-HOOK] pnidui.dll loaded at 0x%p", hMod); - WindhawkUtils::SYMBOL_HOOK hook = {{ + WindhawkUtils::SYMBOL_HOOK pniduiHook = {{ L"bool " #ifdef _WIN64 L"__cdecl" @@ -1217,7 +1351,7 @@ static bool TryInstallPniduiHook() { (void**)&g_icmhOrig_pnidui, (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook}; - bool result = WindhawkUtils::HookSymbols(hMod, &hook, 1); + bool result = WindhawkUtils::HookSymbols(hMod, &pniduiHook, 1); if (result) { Wh_Log(L"[PNIDUI-HOOK] Successfully installed pnidui.dll hook"); g_pniduiHookInstalled = true; @@ -1282,7 +1416,8 @@ static void InstallImmersiveMenuHooks() { HMODULE hMod = LoadLibraryExW(t.dll, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if (!hMod) continue; - WindhawkUtils::SYMBOL_HOOK hooks[] = { + // RINOMINATO: sndVolSsoHooks invece di hooks + WindhawkUtils::SYMBOL_HOOK sndVolSsoHooks[] = { {{ L"bool " #ifdef _WIN64 @@ -1297,7 +1432,7 @@ static void InstallImmersiveMenuHooks() { (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook} }; - WindhawkUtils::HookSymbols(hMod, hooks, 1); + WindhawkUtils::HookSymbols(hMod, sndVolSsoHooks, 1); } // Prova pnidui.dll - se fallisce, avvia il thread di retry @@ -1320,7 +1455,8 @@ static void InstallImmersiveMenuHooks() { if (g_isWin11) { HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); if (hShell32) { - WindhawkUtils::SYMBOL_HOOK shell32_hooks[] = { + // RINOMINATO: shell32Hooks invece di shell32_hooks + WindhawkUtils::SYMBOL_HOOK shell32Hooks[] = { {{ L"bool " #ifdef _WIN64 @@ -1335,9 +1471,34 @@ static void InstallImmersiveMenuHooks() { (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook} }; - WindhawkUtils::HookSymbols(hShell32, shell32_hooks, 1); + WindhawkUtils::HookSymbols(hShell32, shell32Hooks, 1); + } + } +} +static void InstallSyscallFallback() { + HMODULE hWin32u = GetModuleHandleW(L"win32u.dll"); + if (!hWin32u) { + hWin32u = LoadLibraryW(L"win32u.dll"); + if (!hWin32u) { + Wh_Log(L"[SYSCALL-HOOK] win32u.dll not found"); + return; } } + + // Prova prima con NtUserTrackPopupMenuEx (nome esatto) + FARPROC pNtUserTrackPopupMenuEx = GetProcAddress(hWin32u, "NtUserTrackPopupMenuEx"); + if (!pNtUserTrackPopupMenuEx) { + Wh_Log(L"[SYSCALL-HOOK] NtUserTrackPopupMenuEx not found by name"); + return; + } + + if (Wh_SetFunctionHook((void*)pNtUserTrackPopupMenuEx, + (void*)NtUserTrackPopupMenuEx_Hook, + (void**)&g_origNtUserTrackPopupMenuEx)) { + Wh_Log(L"[SYSCALL-HOOK] NtUserTrackPopupMenuEx hooked successfully"); + } else { + Wh_Log(L"[SYSCALL-HOOK] Failed to hook NtUserTrackPopupMenuEx"); + } } using CreateWindowExW_t = decltype(&CreateWindowExW); CreateWindowExW_t CreateWindowExW_Original; @@ -1360,8 +1521,9 @@ HWND WINAPI CreateWindowExW_Hook( return hwnd; } + BOOL Wh_ModInit() { - Wh_Log(L"Redirect Settings to Control Panel v10.0.22"); + Wh_Log(L"Redirect Settings to Control Panel v10.0.23"); DetectWindowsVersion(); LoadSettings(); @@ -1391,8 +1553,8 @@ BOOL Wh_ModInit() { SetupTraySubclass(); } - InstallImmersiveMenuHooks(); - + InstallImmersiveMenuHooks(); + InstallSyscallFallback(); HMODULE hUser32 = GetModuleHandleW(L"user32.dll"); if (!hUser32) hUser32 = LoadLibraryW(L"user32.dll"); if (hUser32) { From 5cc6484743c615433a8b3e0530489271ff4cc1de Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Thu, 9 Jul 2026 00:39:11 +0200 Subject: [PATCH 04/25] Tried to fix validation problem --- mods/settings-to-control-panel.wh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index b11d1a9a48..86d937f5bc 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -1455,7 +1455,7 @@ static void InstallImmersiveMenuHooks() { if (g_isWin11) { HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); if (hShell32) { - // RINOMINATO: shell32Hooks invece di shell32_hooks + // RINOMINATO: shell32Hooks invece di shell32Hooks WindhawkUtils::SYMBOL_HOOK shell32Hooks[] = { {{ L"bool " From 5e197d45d9677548d2fefe8c3f35bc96f03f21d5 Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Thu, 9 Jul 2026 09:48:35 +0200 Subject: [PATCH 05/25] Attempt to fix the issue with explorer restarting --- mods/settings-to-control-panel.wh.cpp | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index 86d937f5bc..3d4212c6de 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -2,7 +2,7 @@ // @id settings-to-control-panel // @name Redirect Settings to Control Panel // @description Forces classic Control Panel to open instead of Windows 10/11 Settings app using native components. Primarily designed for Windows 10; Windows 11 support is limited due to Microsoft's shell architecture changes. -// @version 10.0.23 +// @version 10.0.24 // @author babamohammed // @github https://github.com/babamohammed2022 // @include explorer.exe @@ -1338,7 +1338,8 @@ static bool TryInstallPniduiHook() { Wh_Log(L"[PNIDUI-HOOK] pnidui.dll loaded at 0x%p", hMod); - WindhawkUtils::SYMBOL_HOOK pniduiHook = {{ + // pnidui.dll + WindhawkUtils::SYMBOL_HOOK pniduiDllHook = {{ L"bool " #ifdef _WIN64 L"__cdecl" @@ -1351,7 +1352,7 @@ static bool TryInstallPniduiHook() { (void**)&g_icmhOrig_pnidui, (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook}; - bool result = WindhawkUtils::HookSymbols(hMod, &pniduiHook, 1); + bool result = WindhawkUtils::HookSymbols(hMod, &pniduiDllHook, 1); if (result) { Wh_Log(L"[PNIDUI-HOOK] Successfully installed pnidui.dll hook"); g_pniduiHookInstalled = true; @@ -1416,8 +1417,8 @@ static void InstallImmersiveMenuHooks() { HMODULE hMod = LoadLibraryExW(t.dll, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if (!hMod) continue; - // RINOMINATO: sndVolSsoHooks invece di hooks - WindhawkUtils::SYMBOL_HOOK sndVolSsoHooks[] = { + // SndVolSSO.dll + WindhawkUtils::SYMBOL_HOOK sndVolSsoDllHooks[] = { {{ L"bool " #ifdef _WIN64 @@ -1432,7 +1433,7 @@ static void InstallImmersiveMenuHooks() { (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook} }; - WindhawkUtils::HookSymbols(hMod, sndVolSsoHooks, 1); + WindhawkUtils::HookSymbols(hMod, sndVolSsoDllHooks, 1); } // Prova pnidui.dll - se fallisce, avvia il thread di retry @@ -1455,8 +1456,8 @@ static void InstallImmersiveMenuHooks() { if (g_isWin11) { HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); if (hShell32) { - // RINOMINATO: shell32Hooks invece di shell32Hooks - WindhawkUtils::SYMBOL_HOOK shell32Hooks[] = { + // shell32.dll + WindhawkUtils::SYMBOL_HOOK shell32DllHooks[] = { {{ L"bool " #ifdef _WIN64 @@ -1471,7 +1472,7 @@ static void InstallImmersiveMenuHooks() { (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook} }; - WindhawkUtils::HookSymbols(hShell32, shell32Hooks, 1); + WindhawkUtils::HookSymbols(hShell32, shell32DllHooks, 1); } } } @@ -1523,7 +1524,7 @@ HWND WINAPI CreateWindowExW_Hook( BOOL Wh_ModInit() { - Wh_Log(L"Redirect Settings to Control Panel v10.0.23"); + Wh_Log(L"Redirect Settings to Control Panel v10.0.24"); DetectWindowsVersion(); LoadSettings(); @@ -1591,7 +1592,6 @@ void Wh_ModSettingsChanged() { g_pniduiBase = nullptr; g_pniduiEnd = nullptr; - // Resetta lo stato degli hook per riprovare { std::lock_guard lk(g_pniduiHookMutex); g_pniduiHookInstalled = false; @@ -1601,7 +1601,6 @@ void Wh_ModSettingsChanged() { InitMappings(); if (g_settings.redirectSystemTray) { SetupTraySubclass(); - // Reinstalla gli hook se necessario InstallImmersiveMenuHooks(); } } From caac728389c6d87e28e92c369a6c1b87e3388922 Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Thu, 9 Jul 2026 09:50:40 +0200 Subject: [PATCH 06/25] Fix validation --- mods/settings-to-control-panel.wh.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index 3d4212c6de..bbf708b435 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -1338,7 +1338,6 @@ static bool TryInstallPniduiHook() { Wh_Log(L"[PNIDUI-HOOK] pnidui.dll loaded at 0x%p", hMod); - // pnidui.dll WindhawkUtils::SYMBOL_HOOK pniduiDllHook = {{ L"bool " #ifdef _WIN64 @@ -1417,7 +1416,6 @@ static void InstallImmersiveMenuHooks() { HMODULE hMod = LoadLibraryExW(t.dll, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if (!hMod) continue; - // SndVolSSO.dll WindhawkUtils::SYMBOL_HOOK sndVolSsoDllHooks[] = { {{ L"bool " @@ -1456,7 +1454,6 @@ static void InstallImmersiveMenuHooks() { if (g_isWin11) { HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); if (hShell32) { - // shell32.dll WindhawkUtils::SYMBOL_HOOK shell32DllHooks[] = { {{ L"bool " @@ -1592,6 +1589,7 @@ void Wh_ModSettingsChanged() { g_pniduiBase = nullptr; g_pniduiEnd = nullptr; + // Resetta lo stato degli hook per riprovare { std::lock_guard lk(g_pniduiHookMutex); g_pniduiHookInstalled = false; @@ -1601,6 +1599,7 @@ void Wh_ModSettingsChanged() { InitMappings(); if (g_settings.redirectSystemTray) { SetupTraySubclass(); + // Reinstalla gli hook se necessario InstallImmersiveMenuHooks(); } } From 3d0758c1d2c5a6305c5061cd4da9380e4ad1a6fd Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Fri, 10 Jul 2026 09:05:19 +0200 Subject: [PATCH 07/25] Add a function that detects System tray after explorer restart --- mods/settings-to-control-panel.wh.cpp | 205 +++++++++++++++++++++++++- 1 file changed, 204 insertions(+), 1 deletion(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index bbf708b435..350391dac4 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -489,11 +489,21 @@ static HWND FindTrayToolbar() { static void SetupTraySubclass() { if (g_hTrayToolbar) return; - if (!InitTrayDllInfo()) return; + // NOTE: we no longer bail out early if InitTrayDllInfo() fails. Right + // after an Explorer restart the tray's ToolbarWindow32 is created well + // before pnidui.dll/SndVolSSO.dll get loaded: bailing out here used to + // skip installing the subclass entirely, and since CreateWindowExW_Hook + // only fires once (at window creation), the subclass would never be + // installed for the rest of the process's lifetime. The subclass should + // be installed regardless: GetTrayButtonType() already retries + // InitTrayDllInfo() on every click, so it self-heals once the DLLs + // become available. + InitTrayDllInfo(); HWND hToolbar = FindTrayToolbar(); if (!hToolbar) return; if (WindhawkUtils::SetWindowSubclassFromAnyThread(hToolbar, TrayToolbarSubclassProc, 0)) { g_hTrayToolbar = hToolbar; + Wh_Log(L"[TRAY-SUBCLASS] Tray toolbar subclass installed (hwnd=0x%p)", hToolbar); } } @@ -1223,6 +1233,44 @@ static bool IsControlSystemCommand(const std::wstring& cmdLine) { return (arg == L"system" || arg == L"microsoft.system"); } +// Explorer routes some navigations (notably the classic Control Panel's +// "See also" links) by spawning a brand new "explorer.exe " process +// via CreateProcessW, rather than calling ShellExecuteW/ShellExecuteExW. +// This extracts the target URI (ms-settings:... or shell:::{...}) from such +// a command line, so CreateProcessW_hook can redirect it the same way the +// ShellExecute hooks already do. Returns an empty string if the command +// line isn't an "explorer.exe " launch. +static std::wstring ExtractExplorerLaunchUri(const std::wstring& cmdLine) { + size_t i = 0, n = cmdLine.size(); + while (i < n && cmdLine[i] == L' ') i++; + + std::wstring exeToken; + if (i < n && cmdLine[i] == L'"') { + size_t end = cmdLine.find(L'"', i + 1); + if (end == std::wstring::npos) return L""; + exeToken = cmdLine.substr(i + 1, end - i - 1); + i = end + 1; + } else { + size_t start = i; + while (i < n && cmdLine[i] != L' ') i++; + exeToken = cmdLine.substr(start, i - start); + } + + if (BaseNameLower(exeToken) != L"explorer.exe") return L""; + + while (i < n && cmdLine[i] == L' ') i++; + std::wstring rest = cmdLine.substr(i); + while (!rest.empty() && rest.back() == L' ') rest.pop_back(); + if (rest.size() >= 2 && rest.front() == L'"' && rest.back() == L'"') { + rest = rest.substr(1, rest.size() - 2); + } + if (rest.empty()) return L""; + + if (IsMsSettings(rest.c_str())) return NormalizeUri(rest); + if (IsShellClsid(rest.c_str())) return ToLower(rest); + return L""; +} + BOOL WINAPI ShellExecuteExW_hook(SHELLEXECUTEINFOW* pei) { if (IsChildProcess()) return ShellExecuteExW_orig(pei); HookGuard guard; @@ -1309,6 +1357,21 @@ BOOL WINAPI CreateProcessW_hook(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, SetLastError(ERROR_SUCCESS); return TRUE; } + + // Handles cases like the classic Control Panel's "See also" links, + // which Explorer launches as a new "explorer.exe ms-settings:..." + // (or "explorer.exe shell:::{...}") process via CreateProcessW + // instead of going through ShellExecuteW/ShellExecuteExW. + std::wstring uri = ExtractExplorerLaunchUri(cmdLine); + if (!uri.empty()) { + auto result = ResolveUri(uri, nullptr); + if (result.intercept) { + if (!result.target.empty()) LaunchTarget(result.target); + if (lpProcessInformation) ZeroMemory(lpProcessInformation, sizeof(PROCESS_INFORMATION)); + SetLastError(ERROR_SUCCESS); + return TRUE; + } + } } return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); @@ -1498,6 +1561,120 @@ static void InstallSyscallFallback() { Wh_Log(L"[SYSCALL-HOOK] Failed to hook NtUserTrackPopupMenuEx"); } } + +// --------------------------------------------------------------------------- +// Tray subclass watchdog +// +// Covers two scenarios that the CreateWindowExW hook alone doesn't handle: +// +// 1) Startup race condition: the tray's ToolbarWindow32 can be created +// before pnidui.dll/SndVolSSO.dll are loaded. With the SetupTraySubclass() +// fix this no longer blocks subclass installation, but the watchdog is +// still a useful second safety net in case FindTrayToolbar() fails on the +// first attempt (e.g. window not fully parented yet). +// +// 2) Tray "restart" WITHOUT the whole explorer.exe process dying: sometimes +// the taskbar/tray gets recreated internally by Explorer (taskbar-only +// crash and recovery, some "Restart" actions from Task Manager that only +// recreate Shell_TrayWnd, resolution/DPI changes that recreate the tray, +// etc). In these cases Wh_ModInit() is never called again (the process +// itself hasn't changed), so nothing else would reinstall the subclass or +// redo the pnidui.dll hook. That's what the dedicated HasTrayBeenRecreated() +// function detects, by tracking changes to the Shell_TrayWnd handle. +// --------------------------------------------------------------------------- + +static HANDLE g_traySubclassWatchdogThread = nullptr; +static volatile bool g_traySubclassWatchdogStop = false; +static HWND g_lastShellTrayWnd = nullptr; + +// Detects whether the tray (Shell_TrayWnd) has been recreated since we last +// checked. Returns true ONLY when it detects an actual change (not on the +// very first call, which simply records the initial state). +static bool HasTrayBeenRecreated() { + HWND hTray = FindWindowW(L"Shell_TrayWnd", nullptr); + if (!hTray) { + // Tray non ancora presente (avvio molto precoce): niente da rilevare. + return false; + } + + bool recreated = (g_lastShellTrayWnd != nullptr && hTray != g_lastShellTrayWnd); + g_lastShellTrayWnd = hTray; + return recreated; +} + +// Resets all state tied to the system tray redirect and retries installing +// the required hooks. Called both on first startup (via Wh_ModInit) and +// whenever the watchdog detects that the tray has been recreated while the +// mod was already active. +static void ReinitializeTrayRedirect() { + Wh_Log(L"[TRAY-WATCHDOG] Reinitializing tray redirect state"); + + RemoveTraySubclass(); + + g_sndVolSSOBase = nullptr; + g_sndVolSSOEnd = nullptr; + g_pniduiBase = nullptr; + g_pniduiEnd = nullptr; + + { + std::lock_guard lk(g_pniduiHookMutex); + g_pniduiHookInstalled = false; + } + + if (g_settings.redirectSystemTray) { + SetupTraySubclass(); + } + + if (!TryInstallPniduiHook()) { + if (!g_pniduiRetryRunning && !g_pniduiRetryThread) { + g_pniduiRetryStop = false; + g_pniduiRetryThread = CreateThread(nullptr, 0, PniduiRetryThread, nullptr, 0, nullptr); + } + } + + InstallImmersiveMenuHooks(); +} + +static DWORD WINAPI TraySubclassWatchdogThread(LPVOID) { + Wh_Log(L"[TRAY-WATCHDOG] Watchdog thread started"); + + // Initial phase: tight polling (every 500ms) for the first 30s, useful + // right after an Explorer restart while the tray is still settling. + // After that, fall back to a more relaxed periodic check (every 3s) for + // the rest of the mod's lifetime, to also catch later tray recreations. + const int FAST_PHASE_CHECKS = 60; // 60 * 500ms = 30s + const DWORD FAST_INTERVAL_MS = 500; + const DWORD SLOW_INTERVAL_MS = 3000; + + int tick = 0; + while (!g_traySubclassWatchdogStop) { + Sleep(tick < FAST_PHASE_CHECKS ? FAST_INTERVAL_MS : SLOW_INTERVAL_MS); + tick++; + if (g_traySubclassWatchdogStop) break; + + if (HasTrayBeenRecreated()) { + Wh_Log(L"[TRAY-WATCHDOG] Shell_TrayWnd recreation detected, reinitializing"); + ReinitializeTrayRedirect(); + continue; + } + + if (!g_settings.redirectSystemTray) continue; + + // Stale handle (window destroyed without Shell_TrayWnd changing, + // an edge case but cheap to check for). + if (g_hTrayToolbar && !IsWindow(g_hTrayToolbar)) { + Wh_Log(L"[TRAY-WATCHDOG] Tray toolbar handle no longer valid, resetting"); + g_hTrayToolbar = nullptr; + } + + if (!g_hTrayToolbar) { + SetupTraySubclass(); + } + } + + Wh_Log(L"[TRAY-WATCHDOG] Watchdog thread exiting"); + return 0; +} using CreateWindowExW_t = decltype(&CreateWindowExW); CreateWindowExW_t CreateWindowExW_Original; @@ -1562,9 +1739,35 @@ BOOL Wh_ModInit() { } } + // Initialize the Shell_TrayWnd reference for the watchdog (can be + // nullptr if we're too early in Explorer's boot: that's fine, the first + // watchdog tick will populate it without a false "recreation" positive). + g_lastShellTrayWnd = FindWindowW(L"Shell_TrayWnd", nullptr); + + g_traySubclassWatchdogStop = false; + g_traySubclassWatchdogThread = CreateThread(nullptr, 0, TraySubclassWatchdogThread, nullptr, 0, nullptr); + if (g_traySubclassWatchdogThread) { + Wh_Log(L"[TRAY-WATCHDOG] Watchdog thread created"); + } else { + Wh_Log(L"[TRAY-WATCHDOG] Failed to create watchdog thread"); + } + return TRUE; } void Wh_ModUninit() { + // Ferma il thread watchdog della tray, se attivo + if (g_traySubclassWatchdogThread) { + g_traySubclassWatchdogStop = true; + Wh_Log(L"[TRAY-WATCHDOG] Stopping watchdog thread..."); + DWORD waitResult = WaitForSingleObject(g_traySubclassWatchdogThread, 3000); + if (waitResult == WAIT_TIMEOUT) { + Wh_Log(L"[TRAY-WATCHDOG] Timeout, terminating thread"); + TerminateThread(g_traySubclassWatchdogThread, 0); + } + CloseHandle(g_traySubclassWatchdogThread); + g_traySubclassWatchdogThread = nullptr; + } + // Ferma il thread di retry se attivo if (g_pniduiRetryThread) { g_pniduiRetryStop = true; From a592e9c9bc0f83ae62750be6c379e2de421a0267 Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Fri, 10 Jul 2026 16:17:39 +0200 Subject: [PATCH 08/25] Changed the version to 10.0.25 and update dependencies --- mods/settings-to-control-panel.wh.cpp | 309 +++++++++++++++++--------- 1 file changed, 209 insertions(+), 100 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index 350391dac4..5fa83a1ddc 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -2,11 +2,11 @@ // @id settings-to-control-panel // @name Redirect Settings to Control Panel // @description Forces classic Control Panel to open instead of Windows 10/11 Settings app using native components. Primarily designed for Windows 10; Windows 11 support is limited due to Microsoft's shell architecture changes. -// @version 10.0.24 +// @version 10.0.25 // @author babamohammed // @github https://github.com/babamohammed2022 // @include explorer.exe -// @compilerOptions -lcomctl32 -lpsapi +// @compilerOptions -lcomctl32 -lpsapi -lole32 // ==/WindhawkMod== // ==WindhawkModReadme== /* @@ -31,11 +31,11 @@ Panel pages, using only native Windows components. - Anti-loop protection (stops windows from reopening endlessly) - Configurable fallback behavior for unmapped links - Tray menu detection (experimental) ---- ## Limitations - The system tray context menu redirect only supports the Win32 taskbar (the one from Windows 10 and previous versions). If using Windows 11, it might function decently but it is still an experimental feature. - The device & printers system tray redirect may not work on some Windows 11 configurations, as Microsoft hardcoded the redirect to the Settings app in certain shell code paths. This could change in future if correct documentation is found. +- The two new experimental features above (`ComActivationRedirect` and `LegacyNameMappingFix`) are based on undocumented/internal Windows behavior and reverse engineering assumptions. They are disabled by default, are not guaranteed to work on every Windows build, and could stop working (harmlessly) after a Windows update. They are written to always fall back to the original, unmodified behavior whenever something doesn't match what's expected, so enabling them should never break normal functionality — worst case, they simply do nothing. --- @@ -76,6 +76,7 @@ Panel pages, using only native Windows components. #include #include #include +#include #include #include #include @@ -85,6 +86,14 @@ Panel pages, using only native Windows components. #include #include +// Manually defined GUIDs to avoid requiring -luuid / static ole32 linkage. +// {45BA127D-10A8-46EA-8AB7-56EA9078943C} = CLSID_ApplicationActivationManager +static const CLSID CLSID_ApplicationActivationManager_STC = + { 0x45ba127d, 0x10a8, 0x46ea, { 0x8a, 0xb7, 0x56, 0xea, 0x90, 0x78, 0x94, 0x3c } }; +// {2E941141-7F97-4756-BA1D-9DECDE894A3D} = IID_IApplicationActivationManager +static const IID IID_IApplicationActivationManager_STC = + { 0x2e941141, 0x7f97, 0x4756, { 0xba, 0x1d, 0x9d, 0xec, 0xde, 0x89, 0x4a, 0x3d } }; + // Custom IDs for tray menu redirection (TrackPopupMenuEx method) #define TRAY_CUSTOM_ID_AUDIO 65001 #define TRAY_CUSTOM_ID_NETWORK 65002 @@ -108,6 +117,9 @@ static bool g_pniduiHookInstalled = false; static std::mutex g_pniduiHookMutex; static HANDLE g_pniduiRetryThread = nullptr; static volatile bool g_pniduiRetryStop = false; +static HANDLE g_traySubclassWatchdogThread = nullptr; +static volatile bool g_traySubclassWatchdogStop = false; +static HWND g_lastShellTrayWnd = nullptr; static bool __fastcall ICMH_CAODTM_hook(HMENU, HWND); @@ -175,6 +187,8 @@ struct ModSettings { int fallbackMode = 2; bool win11CompatibilityMode = false; int maxLaunchesPerUri = 3; + bool comActivationRedirect = true; + bool legacyNameMappingFix = true; }; static ModSettings g_settings; @@ -202,6 +216,9 @@ static void LoadSettings() { int ml = Wh_GetIntSetting(L"MaxLaunchesPerUri"); g_settings.maxLaunchesPerUri = (ml >= 0 && ml <= 20) ? ml : 3; + + g_settings.comActivationRedirect = Wh_GetIntSetting(L"ComActivationRedirect") != 0; + g_settings.legacyNameMappingFix = Wh_GetIntSetting(L"LegacyNameMappingFix") != 0; } static bool g_isWin11 = false; @@ -362,15 +379,6 @@ static bool InitTrayDllInfo() { static int GetTrayButtonType(HWND hToolbar, int buttonIndex) { if (buttonIndex < 0) return 0; - - // Retry capturing the DLL base addresses here too: SetupTraySubclass() - // only calls InitTrayDllInfo() once (guarded by g_hTrayToolbar), so if - // pnidui.dll (network) wasn't loaded yet at that point (e.g. right after - // an Explorer restart, while SndVolSSO.dll for audio was already - // resident), g_pniduiBase would stay null forever and network tray - // detection would silently break until the mod was reloaded. Re-checking - // here is cheap (InitTrayDllInfo short-circuits once both are set) and - // lets it self-heal as soon as pnidui.dll actually loads. InitTrayDllInfo(); TBBUTTON tb{}; @@ -382,10 +390,6 @@ static int GetTrayButtonType(HWND hToolbar, int buttonIndex) { wchar_t className[256]{}; if (!GetClassNameW(hIconWnd, className, 256)) return 0; - - // Language-independent check: Safely Remove Hardware is a plain Shell_NotifyIcon - // and doesn't use an ATL class wrapper from a specific DLL. We ignore it here - // and let TrackPopupMenuEx identify it via hotplug.dll return address. if (wcsncmp(className, L"ATL:", 4) != 0) { return 0; } @@ -489,21 +493,11 @@ static HWND FindTrayToolbar() { static void SetupTraySubclass() { if (g_hTrayToolbar) return; - // NOTE: we no longer bail out early if InitTrayDllInfo() fails. Right - // after an Explorer restart the tray's ToolbarWindow32 is created well - // before pnidui.dll/SndVolSSO.dll get loaded: bailing out here used to - // skip installing the subclass entirely, and since CreateWindowExW_Hook - // only fires once (at window creation), the subclass would never be - // installed for the rest of the process's lifetime. The subclass should - // be installed regardless: GetTrayButtonType() already retries - // InitTrayDllInfo() on every click, so it self-heals once the DLLs - // become available. - InitTrayDllInfo(); + if (!InitTrayDllInfo()) return; HWND hToolbar = FindTrayToolbar(); if (!hToolbar) return; if (WindhawkUtils::SetWindowSubclassFromAnyThread(hToolbar, TrayToolbarSubclassProc, 0)) { g_hTrayToolbar = hToolbar; - Wh_Log(L"[TRAY-SUBCLASS] Tray toolbar subclass installed (hwnd=0x%p)", hToolbar); } } @@ -1201,6 +1195,167 @@ static ResolveResult ResolveUri(const std::wstring& uri, HWND hwnd) { } return {L"", false}; } +// =========================================================================== +// EXPERIMENTAL: IApplicationActivationManager COM interception +// +// Some Windows 11 shell components (notably the system tray flyouts for +// "Open Devices and Printers") may bypass ShellExecute/CreateProcess entirely +// and instead activate the Settings app through the low-level COM interface +// IApplicationActivationManager::ActivateApplication(). +// +// We install a tiny vtable-style hook on the COM object returned by +// CoCreateInstance(CLSID_ApplicationActivationManager) to inspect every +// ActivateApplication call. When the appUserModelId matches +// "windows.immersivecontrolpanel..." (the Settings app), we: +// 1) map the ms-settings: URI embedded in the arguments to a classic CPL +// 2) launch that CPL ourselves +// 3) return S_OK to the caller (making it believe Settings was launched) +// +// This is entirely best-effort and based on reverse engineering assumptions. +// If anything unexpected happens we fall back to the original vtable entry. +// =========================================================================== + +// Minimal vtable layout for IApplicationActivationManager (3 methods) +struct IApplicationActivationManagerVtbl { + // IUnknown + HRESULT (STDMETHODCALLTYPE *QueryInterface)(IUnknown*, REFIID, void**); + ULONG (STDMETHODCALLTYPE *AddRef)(IUnknown*); + ULONG (STDMETHODCALLTYPE *Release)(IUnknown*); + // IApplicationActivationManager + HRESULT (STDMETHODCALLTYPE *ActivateApplication)( + IUnknown*, + LPCWSTR appUserModelId, + LPCWSTR arguments, + DWORD options, + DWORD* processId); + HRESULT (STDMETHODCALLTYPE *ActivateForFile)(IUnknown*, LPCWSTR, LPCWSTR, DWORD, DWORD*); + HRESULT (STDMETHODCALLTYPE *ActivateForProtocol)(IUnknown*, LPCWSTR, DWORD*, DWORD); +}; + +static IApplicationActivationManagerVtbl g_origAAMVtbl = {}; +static bool g_aamHookInstalled = false; +static std::mutex g_aamHookMutex; + +HRESULT STDMETHODCALLTYPE AAM_ActivateApplication_hook( + IUnknown* pThis, + LPCWSTR appUserModelId, + LPCWSTR arguments, + DWORD options, + DWORD* processId) +{ + Wh_Log(L"[AAM-HOOK] ActivateApplication: appId=%s, args=%s", + appUserModelId ? appUserModelId : L"(null)", + arguments ? arguments : L"(null)"); + + // Is this the Settings app being activated? + if (appUserModelId && arguments && + _wcsnicmp(appUserModelId, L"windows.immersivecontrolpanel", 29) == 0) + { + std::wstring uri = NormalizeUri(arguments); + Wh_Log(L"[AAM-HOOK] Settings activation intercepted: %s", uri.c_str()); + + auto result = ResolveUri(uri, nullptr); + if (result.intercept && !result.target.empty()) { + LaunchTarget(result.target); + if (processId) *processId = GetCurrentProcessId(); + Wh_Log(L"[AAM-HOOK] Redirected to: %s", result.target.c_str()); + return S_OK; + } + Wh_Log(L"[AAM-HOOK] No mapping found, falling back to original"); + } + + // Not a Settings activation we can handle — call original + if (g_origAAMVtbl.ActivateApplication) { + return g_origAAMVtbl.ActivateApplication(pThis, appUserModelId, arguments, options, processId); + } + return E_FAIL; +} + +static void InstallAAMHook() { + std::lock_guard lk(g_aamHookMutex); + if (g_aamHookInstalled) return; + + IUnknown* pAAM = nullptr; + HRESULT hr = CoCreateInstance( + CLSID_ApplicationActivationManager_STC, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IApplicationActivationManager_STC, + (void**)&pAAM); + + if (FAILED(hr) || !pAAM) { + Wh_Log(L"[AAM-HOOK] CoCreateInstance(CLSID_ApplicationActivationManager) failed: 0x%08X", hr); + return; + } + + IApplicationActivationManagerVtbl* vtbl = *(IApplicationActivationManagerVtbl**)pAAM; + if (!vtbl || IsBadReadPtr(vtbl, sizeof(*vtbl))) { + Wh_Log(L"[AAM-HOOK] Invalid vtable pointer"); + pAAM->Release(); + return; + } + + // Save original vtable + g_origAAMVtbl = *vtbl; + + // Make the vtable page writable + DWORD oldProtect; + if (!VirtualProtect(vtbl, sizeof(*vtbl), PAGE_READWRITE, &oldProtect)) { + Wh_Log(L"[AAM-HOOK] VirtualProtect failed"); + pAAM->Release(); + return; + } + + // Patch ActivateApplication entry + vtbl->ActivateApplication = AAM_ActivateApplication_hook; + + VirtualProtect(vtbl, sizeof(*vtbl), oldProtect, &oldProtect); + + g_aamHookInstalled = true; + Wh_Log(L"[AAM-HOOK] Successfully installed"); + pAAM->Release(); +} + +bool (*COpenControlPanel__MapLegacyName_orig)(void*, LPCWSTR, LPWSTR, UINT, bool*); +bool COpenControlPanel__MapLegacyName_hook( + void *pThis, + LPCWSTR pszLegacyName, + LPWSTR pszNewName, + UINT uLen, + bool *nameChanged) +{ + // Always tell the caller the name was NOT changed — this forces + // Explorer to use the original legacy Control Panel path. + *nameChanged = false; + *pszNewName = L'\0'; + Wh_Log(L"[MAP-LEGACY] Suppressed mapping for: %s", + pszLegacyName ? pszLegacyName : L"(null)"); + return false; +} + +static bool InstallLegacyNameHook() { + HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); + if (!hShell32) { + Wh_Log(L"[MAP-LEGACY] shell32.dll not loaded"); + return false; + } + + WindhawkUtils::SYMBOL_HOOK mapLegacyHook = {{ + L"private: bool __cdecl COpenControlPanel::_MapLegacyName" + L"(unsigned short const *,unsigned short *,unsigned int,bool *)" + }, + (void**)&COpenControlPanel__MapLegacyName_orig, + (void*)COpenControlPanel__MapLegacyName_hook, + false}; + + if (WindhawkUtils::HookSymbols(hShell32, &mapLegacyHook, 1)) { + Wh_Log(L"[MAP-LEGACY] Hook installed successfully"); + return true; + } else { + Wh_Log(L"[MAP-LEGACY] Failed to install hook (symbol may differ on this build)"); + return false; + } +} static std::wstring BaseNameLower(const std::wstring& path) { size_t pos = path.rfind(L'\\'); @@ -1232,14 +1387,6 @@ static bool IsControlSystemCommand(const std::wstring& cmdLine) { std::wstring arg = ToLower(tokens[1]); return (arg == L"system" || arg == L"microsoft.system"); } - -// Explorer routes some navigations (notably the classic Control Panel's -// "See also" links) by spawning a brand new "explorer.exe " process -// via CreateProcessW, rather than calling ShellExecuteW/ShellExecuteExW. -// This extracts the target URI (ms-settings:... or shell:::{...}) from such -// a command line, so CreateProcessW_hook can redirect it the same way the -// ShellExecute hooks already do. Returns an empty string if the command -// line isn't an "explorer.exe " launch. static std::wstring ExtractExplorerLaunchUri(const std::wstring& cmdLine) { size_t i = 0, n = cmdLine.size(); while (i < n && cmdLine[i] == L' ') i++; @@ -1270,7 +1417,6 @@ static std::wstring ExtractExplorerLaunchUri(const std::wstring& cmdLine) { if (IsShellClsid(rest.c_str())) return ToLower(rest); return L""; } - BOOL WINAPI ShellExecuteExW_hook(SHELLEXECUTEINFOW* pei) { if (IsChildProcess()) return ShellExecuteExW_orig(pei); HookGuard guard; @@ -1376,22 +1522,18 @@ BOOL WINAPI CreateProcessW_hook(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); } - static volatile bool g_pniduiRetryRunning = false; static bool TryInstallPniduiHook() { std::lock_guard lk(g_pniduiHookMutex); - // Se già installato, non fare nulla if (g_pniduiHookInstalled) { return true; } - // Prima verifica se la DLL è già caricata nel processo HMODULE hMod = GetModuleHandleW(L"pnidui.dll"); if (!hMod) { - // Non è caricata, prova a caricarla (potrebbe fallire se non disponibile) hMod = LoadLibraryExW(L"pnidui.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if (!hMod) { Wh_Log(L"[PNIDUI-HOOK] pnidui.dll not loaded yet"); @@ -1425,7 +1567,6 @@ static bool TryInstallPniduiHook() { } static DWORD WINAPI PniduiRetryThread(LPVOID) { - // Evita che più thread di retry partano contemporaneamente if (g_pniduiRetryRunning) { Wh_Log(L"[PNIDUI-HOOK] Retry thread already running, exiting"); return 0; @@ -1434,8 +1575,7 @@ static DWORD WINAPI PniduiRetryThread(LPVOID) { Wh_Log(L"[PNIDUI-HOOK] Retry thread started - waiting for pnidui.dll to load"); - // Aspetta che la DLL venga caricata - controlla ogni 500ms per max 30 secondi - const int MAX_WAIT_CHECKS = 60; // 30 secondi + const int MAX_WAIT_CHECKS = 60; const DWORD CHECK_INTERVAL = 500; bool dllLoaded = false; @@ -1454,7 +1594,6 @@ static DWORD WINAPI PniduiRetryThread(LPVOID) { return 0; } - // Ora prova ad installare l'hook if (TryInstallPniduiHook()) { Wh_Log(L"[PNIDUI-HOOK] Hook installed successfully after waiting for DLL"); } else { @@ -1467,7 +1606,6 @@ static DWORD WINAPI PniduiRetryThread(LPVOID) { } static void InstallImmersiveMenuHooks() { - // Installa SndVolSSO (sempre) struct DllHook { const wchar_t* dll; ICMH_CAODTM_t* orig; @@ -1497,9 +1635,7 @@ static void InstallImmersiveMenuHooks() { WindhawkUtils::HookSymbols(hMod, sndVolSsoDllHooks, 1); } - // Prova pnidui.dll - se fallisce, avvia il thread di retry if (!TryInstallPniduiHook()) { - // Controlla che il thread non sia già in esecuzione if (!g_pniduiRetryRunning && !g_pniduiRetryThread) { g_pniduiRetryStop = false; g_pniduiRetryThread = CreateThread(nullptr, 0, PniduiRetryThread, nullptr, 0, nullptr); @@ -1513,7 +1649,6 @@ static void InstallImmersiveMenuHooks() { } } - // Shell32 per dispositivi (solo Win11) if (g_isWin11) { HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); if (hShell32) { @@ -1536,6 +1671,7 @@ static void InstallImmersiveMenuHooks() { } } } + static void InstallSyscallFallback() { HMODULE hWin32u = GetModuleHandleW(L"win32u.dll"); if (!hWin32u) { @@ -1546,7 +1682,6 @@ static void InstallSyscallFallback() { } } - // Prova prima con NtUserTrackPopupMenuEx (nome esatto) FARPROC pNtUserTrackPopupMenuEx = GetProcAddress(hWin32u, "NtUserTrackPopupMenuEx"); if (!pNtUserTrackPopupMenuEx) { Wh_Log(L"[SYSCALL-HOOK] NtUserTrackPopupMenuEx not found by name"); @@ -1562,38 +1697,15 @@ static void InstallSyscallFallback() { } } +using CreateWindowExW_t = decltype(&CreateWindowExW); +CreateWindowExW_t CreateWindowExW_Original; // --------------------------------------------------------------------------- // Tray subclass watchdog -// -// Covers two scenarios that the CreateWindowExW hook alone doesn't handle: -// -// 1) Startup race condition: the tray's ToolbarWindow32 can be created -// before pnidui.dll/SndVolSSO.dll are loaded. With the SetupTraySubclass() -// fix this no longer blocks subclass installation, but the watchdog is -// still a useful second safety net in case FindTrayToolbar() fails on the -// first attempt (e.g. window not fully parented yet). -// -// 2) Tray "restart" WITHOUT the whole explorer.exe process dying: sometimes -// the taskbar/tray gets recreated internally by Explorer (taskbar-only -// crash and recovery, some "Restart" actions from Task Manager that only -// recreate Shell_TrayWnd, resolution/DPI changes that recreate the tray, -// etc). In these cases Wh_ModInit() is never called again (the process -// itself hasn't changed), so nothing else would reinstall the subclass or -// redo the pnidui.dll hook. That's what the dedicated HasTrayBeenRecreated() -// function detects, by tracking changes to the Shell_TrayWnd handle. // --------------------------------------------------------------------------- -static HANDLE g_traySubclassWatchdogThread = nullptr; -static volatile bool g_traySubclassWatchdogStop = false; -static HWND g_lastShellTrayWnd = nullptr; - -// Detects whether the tray (Shell_TrayWnd) has been recreated since we last -// checked. Returns true ONLY when it detects an actual change (not on the -// very first call, which simply records the initial state). static bool HasTrayBeenRecreated() { HWND hTray = FindWindowW(L"Shell_TrayWnd", nullptr); if (!hTray) { - // Tray non ancora presente (avvio molto precoce): niente da rilevare. return false; } @@ -1602,10 +1714,6 @@ static bool HasTrayBeenRecreated() { return recreated; } -// Resets all state tied to the system tray redirect and retries installing -// the required hooks. Called both on first startup (via Wh_ModInit) and -// whenever the watchdog detects that the tray has been recreated while the -// mod was already active. static void ReinitializeTrayRedirect() { Wh_Log(L"[TRAY-WATCHDOG] Reinitializing tray redirect state"); @@ -1638,11 +1746,7 @@ static void ReinitializeTrayRedirect() { static DWORD WINAPI TraySubclassWatchdogThread(LPVOID) { Wh_Log(L"[TRAY-WATCHDOG] Watchdog thread started"); - // Initial phase: tight polling (every 500ms) for the first 30s, useful - // right after an Explorer restart while the tray is still settling. - // After that, fall back to a more relaxed periodic check (every 3s) for - // the rest of the mod's lifetime, to also catch later tray recreations. - const int FAST_PHASE_CHECKS = 60; // 60 * 500ms = 30s + const int FAST_PHASE_CHECKS = 60; const DWORD FAST_INTERVAL_MS = 500; const DWORD SLOW_INTERVAL_MS = 3000; @@ -1660,8 +1764,6 @@ static DWORD WINAPI TraySubclassWatchdogThread(LPVOID) { if (!g_settings.redirectSystemTray) continue; - // Stale handle (window destroyed without Shell_TrayWnd changing, - // an edge case but cheap to check for). if (g_hTrayToolbar && !IsWindow(g_hTrayToolbar)) { Wh_Log(L"[TRAY-WATCHDOG] Tray toolbar handle no longer valid, resetting"); g_hTrayToolbar = nullptr; @@ -1675,9 +1777,6 @@ static DWORD WINAPI TraySubclassWatchdogThread(LPVOID) { Wh_Log(L"[TRAY-WATCHDOG] Watchdog thread exiting"); return 0; } -using CreateWindowExW_t = decltype(&CreateWindowExW); -CreateWindowExW_t CreateWindowExW_Original; - HWND WINAPI CreateWindowExW_Hook( DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, @@ -1698,7 +1797,7 @@ HWND WINAPI CreateWindowExW_Hook( BOOL Wh_ModInit() { - Wh_Log(L"Redirect Settings to Control Panel v10.0.24"); + Wh_Log(L"Redirect Settings to Control Panel v10.0.25"); DetectWindowsVersion(); LoadSettings(); @@ -1729,7 +1828,16 @@ BOOL Wh_ModInit() { } InstallImmersiveMenuHooks(); - InstallSyscallFallback(); + InstallSyscallFallback(); + + if (g_settings.comActivationRedirect && g_isWin11) { + InstallAAMHook(); + } + + if (g_settings.legacyNameMappingFix) { + InstallLegacyNameHook(); + } + HMODULE hUser32 = GetModuleHandleW(L"user32.dll"); if (!hUser32) hUser32 = LoadLibraryW(L"user32.dll"); if (hUser32) { @@ -1738,10 +1846,7 @@ BOOL Wh_ModInit() { Wh_SetFunctionHook(pTrackPopupMenuEx, (void*)TrackPopupMenuEx_Hook, (void**)&g_origTrackPopupMenuEx); } } - - // Initialize the Shell_TrayWnd reference for the watchdog (can be - // nullptr if we're too early in Explorer's boot: that's fine, the first - // watchdog tick will populate it without a false "recreation" positive). + // Initialize the Shell_TrayWnd reference for the watchdog g_lastShellTrayWnd = FindWindowW(L"Shell_TrayWnd", nullptr); g_traySubclassWatchdogStop = false; @@ -1751,11 +1856,10 @@ BOOL Wh_ModInit() { } else { Wh_Log(L"[TRAY-WATCHDOG] Failed to create watchdog thread"); } - return TRUE; } + void Wh_ModUninit() { - // Ferma il thread watchdog della tray, se attivo if (g_traySubclassWatchdogThread) { g_traySubclassWatchdogStop = true; Wh_Log(L"[TRAY-WATCHDOG] Stopping watchdog thread..."); @@ -1767,8 +1871,6 @@ void Wh_ModUninit() { CloseHandle(g_traySubclassWatchdogThread); g_traySubclassWatchdogThread = nullptr; } - - // Ferma il thread di retry se attivo if (g_pniduiRetryThread) { g_pniduiRetryStop = true; Wh_Log(L"[PNIDUI-HOOK] Stopping retry thread..."); @@ -1792,7 +1894,6 @@ void Wh_ModSettingsChanged() { g_pniduiBase = nullptr; g_pniduiEnd = nullptr; - // Resetta lo stato degli hook per riprovare { std::lock_guard lk(g_pniduiHookMutex); g_pniduiHookInstalled = false; @@ -1802,7 +1903,15 @@ void Wh_ModSettingsChanged() { InitMappings(); if (g_settings.redirectSystemTray) { SetupTraySubclass(); - // Reinstalla gli hook se necessario InstallImmersiveMenuHooks(); } + + // Re-install experimental hooks if settings changed + if (g_settings.comActivationRedirect && g_isWin11) { + InstallAAMHook(); + } + + if (g_settings.legacyNameMappingFix) { + InstallLegacyNameHook(); + } } From cd59297e35d6223c23bff49d7ea722c7e059369a Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Fri, 10 Jul 2026 16:29:31 +0200 Subject: [PATCH 09/25] Refactor symbol hooks for improved clarity --- mods/settings-to-control-panel.wh.cpp | 54 +++++++++++++++------------ 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index 5fa83a1ddc..5b9c5a8e21 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -1340,7 +1340,7 @@ static bool InstallLegacyNameHook() { return false; } - WindhawkUtils::SYMBOL_HOOK mapLegacyHook = {{ + WindhawkUtils::SYMBOL_HOOK shell32_maplegacy_hook = {{ L"private: bool __cdecl COpenControlPanel::_MapLegacyName" L"(unsigned short const *,unsigned short *,unsigned int,bool *)" }, @@ -1348,7 +1348,7 @@ static bool InstallLegacyNameHook() { (void*)COpenControlPanel__MapLegacyName_hook, false}; - if (WindhawkUtils::HookSymbols(hShell32, &mapLegacyHook, 1)) { + if (WindhawkUtils::HookSymbols(hShell32, &shell32_maplegacy_hook, 1)) { Wh_Log(L"[MAP-LEGACY] Hook installed successfully"); return true; } else { @@ -1543,20 +1543,24 @@ static bool TryInstallPniduiHook() { Wh_Log(L"[PNIDUI-HOOK] pnidui.dll loaded at 0x%p", hMod); - WindhawkUtils::SYMBOL_HOOK pniduiDllHook = {{ - L"bool " + // pnidui.dll + WindhawkUtils::SYMBOL_HOOK pnidui_dll_hooks[] = {{ + { + L"bool " #ifdef _WIN64 - L"__cdecl" + L"__cdecl" #else - L"__stdcall" + L"__stdcall" #endif - L" ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu" - L"(struct HMENU__ *,struct HWND__ *)" - }, - (void**)&g_icmhOrig_pnidui, - (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook}; - - bool result = WindhawkUtils::HookSymbols(hMod, &pniduiDllHook, 1); + L" ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu" + L"(struct HMENU__ *,struct HWND__ *)" + }, + (void**)&g_icmhOrig_pnidui, + (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, + false + }}; + + bool result = WindhawkUtils::HookSymbols(hMod, pnidui_dll_hooks, 1); if (result) { Wh_Log(L"[PNIDUI-HOOK] Successfully installed pnidui.dll hook"); g_pniduiHookInstalled = true; @@ -1617,8 +1621,9 @@ static void InstallImmersiveMenuHooks() { HMODULE hMod = LoadLibraryExW(t.dll, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if (!hMod) continue; - WindhawkUtils::SYMBOL_HOOK sndVolSsoDllHooks[] = { - {{ + // SndVolSSO.dll + WindhawkUtils::SYMBOL_HOOK sndVolSSO_dll_hooks[] = {{ + { L"bool " #ifdef _WIN64 L"__cdecl" @@ -1629,10 +1634,11 @@ static void InstallImmersiveMenuHooks() { L"(struct HMENU__ *,struct HWND__ *)" }, (void**)t.orig, - (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook} - }; + (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, + false + }}; - WindhawkUtils::HookSymbols(hMod, sndVolSsoDllHooks, 1); + WindhawkUtils::HookSymbols(hMod, sndVolSSO_dll_hooks, 1); } if (!TryInstallPniduiHook()) { @@ -1652,8 +1658,9 @@ static void InstallImmersiveMenuHooks() { if (g_isWin11) { HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); if (hShell32) { - WindhawkUtils::SYMBOL_HOOK shell32DllHooks[] = { - {{ + // shell32.dll + WindhawkUtils::SYMBOL_HOOK shell32_dll_hooks[] = {{ + { L"bool " #ifdef _WIN64 L"__cdecl" @@ -1664,10 +1671,11 @@ static void InstallImmersiveMenuHooks() { L"(struct HMENU__ *,unsigned int)" }, (void**)&g_icmhOrig_Shell32Devices, - (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook} - }; + (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, + false + }}; - WindhawkUtils::HookSymbols(hShell32, shell32DllHooks, 1); + WindhawkUtils::HookSymbols(hShell32, shell32_dll_hooks, 1); } } } From 62bd314ca11e87813602862770d1c01c9dbeeb73 Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Fri, 10 Jul 2026 16:40:53 +0200 Subject: [PATCH 10/25] Rename shell32_maplegacy_hook to shell32_dll_hook --- mods/settings-to-control-panel.wh.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index 5b9c5a8e21..ae13c8b102 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -1340,7 +1340,7 @@ static bool InstallLegacyNameHook() { return false; } - WindhawkUtils::SYMBOL_HOOK shell32_maplegacy_hook = {{ + WindhawkUtils::SYMBOL_HOOK shell32_dll_hook = {{ L"private: bool __cdecl COpenControlPanel::_MapLegacyName" L"(unsigned short const *,unsigned short *,unsigned int,bool *)" }, @@ -1348,7 +1348,7 @@ static bool InstallLegacyNameHook() { (void*)COpenControlPanel__MapLegacyName_hook, false}; - if (WindhawkUtils::HookSymbols(hShell32, &shell32_maplegacy_hook, 1)) { + if (WindhawkUtils::HookSymbols(hShell32, &shell32_dll_hook, 1)) { Wh_Log(L"[MAP-LEGACY] Hook installed successfully"); return true; } else { @@ -1543,7 +1543,6 @@ static bool TryInstallPniduiHook() { Wh_Log(L"[PNIDUI-HOOK] pnidui.dll loaded at 0x%p", hMod); - // pnidui.dll WindhawkUtils::SYMBOL_HOOK pnidui_dll_hooks[] = {{ { L"bool " @@ -1621,7 +1620,6 @@ static void InstallImmersiveMenuHooks() { HMODULE hMod = LoadLibraryExW(t.dll, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if (!hMod) continue; - // SndVolSSO.dll WindhawkUtils::SYMBOL_HOOK sndVolSSO_dll_hooks[] = {{ { L"bool " @@ -1658,7 +1656,6 @@ static void InstallImmersiveMenuHooks() { if (g_isWin11) { HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); if (hShell32) { - // shell32.dll WindhawkUtils::SYMBOL_HOOK shell32_dll_hooks[] = {{ { L"bool " From 85ee8468489fac03fec6bdb6622df3f1289ede41 Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Sat, 11 Jul 2026 00:43:52 +0200 Subject: [PATCH 11/25] Add control panel applets (credits to Anixx) and try to enhance the mod and update README --- mods/settings-to-control-panel.wh.cpp | 1979 +++++++++++++++++-------- 1 file changed, 1331 insertions(+), 648 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index ae13c8b102..c36f506fb1 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -1,16 +1,17 @@ // ==WindhawkMod== // @id settings-to-control-panel // @name Redirect Settings to Control Panel -// @description Forces classic Control Panel to open instead of Windows 10/11 Settings app using native components. Primarily designed for Windows 10; Windows 11 support is limited due to Microsoft's shell architecture changes. -// @version 10.0.25 +// @description Forces classic Control Panel to open instead of Windows Settings and restores some classic CPL entries. +// @version 10.0.26 // @author babamohammed // @github https://github.com/babamohammed2022 // @include explorer.exe // @compilerOptions -lcomctl32 -lpsapi -lole32 // ==/WindhawkMod== + // ==WindhawkModReadme== /* -# Redirect Settings → Control Panel +# Redirect Settings → Control Panel This mod intercepts modern `ms-settings:` links (the ones that open the Settings app) and redirects them to their corresponding classic Control @@ -22,54 +23,78 @@ Panel pages, using only native Windows components. - **Windows 10** – Mostly complete support - **Windows 11** – Partial support - +The mod has been tested on Windows 10 21H2, Windows 10 1809, Windows 11 23H2 and Windows 11 24H2. --- ## Features -- Redirects many `ms-settings:` links to the classic Control Panel +- Redirects many `ms-settings:` links to the classic Control Panel where possible - Anti-loop protection (stops windows from reopening endlessly) - Configurable fallback behavior for unmapped links - Tray menu detection (experimental) +- Restore Classic Control Panel Entries (credits to Anixx for the original implementation) +--- ## Limitations - The system tray context menu redirect only supports the Win32 taskbar (the one from Windows 10 and previous versions). If using Windows 11, it might function decently but it is still an experimental feature. - The device & printers system tray redirect may not work on some Windows 11 configurations, as Microsoft hardcoded the redirect to the Settings app in certain shell code paths. This could change in future if correct documentation is found. -- The two new experimental features above (`ComActivationRedirect` and `LegacyNameMappingFix`) are based on undocumented/internal Windows behavior and reverse engineering assumptions. They are disabled by default, are not guaranteed to work on every Windows build, and could stop working (harmlessly) after a Windows update. They are written to always fall back to the original, unmodified behavior whenever something doesn't match what's expected, so enabling them should never break normal functionality — worst case, they simply do nothing. - +- The redirect for the network connection menu in the system tray might not work on very specific taskbar configurations. --- ## Credits - m417z – Code reviews and feedback -- Anixx – Testing on Windows 11 23H2 and the original toolbar subclassing approach +- Anixx – Testing on Windows 11 23H2, the original toolbar subclassing approach and restoring control panel applets +- sebastian08dm08-cpu - Testing on Windows 10 1809 - dbilanoski – CLSID documentation */ // ==/WindhawkModReadme== + // ==WindhawkModSettings== /* - EnableRedirects: true $name: Enable Redirects - $description: "Turns the mod on or off. When disabled, Settings opens normally as usual." + $description: "Turns the mod on or off. When disabled, Settings opens normally." + - RedirectSystemTray: false - $name: Redirect System Tray Audio/Network/Device & Printers (EXPERIMENTAL) - $description: "If enabled, right-clicking the Audio or Network or Device & Printers icon near the clock and choosing 'Open Sound settings' or 'Open Network settings' or 'Open devices and printers' will open the classic panel instead of the Settings app." + $name: Redirect System Tray (EXPERIMENTAL) + $description: "If enabled, right-clicking the Audio, Network, or Devices & Printers tray icons and selecting their Settings item opens the classic Control Panel instead." + - UIOnlyRedirects: false $name: Non-Invasive Mode - $description: "Only redirects clicks made in the UI. Doesn't touch programs that open Settings in other ways." + $description: "Only redirects clicks made in the user interface. Programs that open Settings programmatically are left untouched." + - FallbackMode: "2" - $name: Behavior for Unmapped Links - $description: "What to do when a Settings page has no classic equivalent." + $name: Fallback for Unmapped Links + $description: "What to do when a Settings page has no classic Control Panel equivalent." $options: - - "0": Ignore (silent fail) - - "1": Open the Control Panel (control.exe) - - "2": Pass through to the modern Settings application (ms-settings.exe)" + - "0": Ignore (do nothing) + - "1": Open Control Panel + - "2": Pass through to the modern Settings app + - Win11CompatibilityMode: false $name: Windows 11 Compatibility Mode - $description: "Safer mode for Windows 11. When enabled, only uses proven redirects while everything else opens the standard Control Panel page as a fallback to avoid loops or other issues." + $description: "On Windows 11, only uses proven redirects. Everything else opens the Control Panel as a fallback." + - MaxLaunchesPerUri: 3 - $name: Anti-Loop Limit (per window, every 5 seconds) - $description: "Safety measure: if the same window gets opened too many times within a few seconds, the mod stops reopening it. Set to 0 to disable this limit." + $name: Anti-Loop Limit + $description: "Maximum number of times the same target can be launched within 5 seconds. Set to 0 to disable this safety measure." + +- ComActivationRedirect: true + $name: COM Activation Redirect (EXPERIMENTAL) + $description: "Intercepts IApplicationActivationManager activations of the Settings app on Windows 11." + +- LegacyNameMappingFix: true + $name: Legacy Name Mapping Fix (EXPERIMENTAL) + $description: "Prevents Explorer from remapping classic Control Panel names to the modern Settings app." + +- enableClassicCpls: true + $name: Restore Classic Control Panel Entries + $description: "Restores the classic Personalization, Notification Area Icons, Network Connections, and Printers & Faxes sections to the Control Panel. Implementation by Anixx." + +- suppressCompanySync: true + $name: Hide Company Sync Entry + $description: "Suppresses the Company Sync namespace entry from the Control Panel. Implementation by Anixx." */ // ==/WindhawkModSettings== @@ -85,65 +110,89 @@ Panel pages, using only native Windows components. #include #include #include +#include + +// --------------------------------------------------------------------------- +// GUIDs and constants +// --------------------------------------------------------------------------- -// Manually defined GUIDs to avoid requiring -luuid / static ole32 linkage. -// {45BA127D-10A8-46EA-8AB7-56EA9078943C} = CLSID_ApplicationActivationManager static const CLSID CLSID_ApplicationActivationManager_STC = { 0x45ba127d, 0x10a8, 0x46ea, { 0x8a, 0xb7, 0x56, 0xea, 0x90, 0x78, 0x94, 0x3c } }; -// {2E941141-7F97-4756-BA1D-9DECDE894A3D} = IID_IApplicationActivationManager + static const IID IID_IApplicationActivationManager_STC = { 0x2e941141, 0x7f97, 0x4756, { 0xba, 0x1d, 0x9d, 0xec, 0xde, 0x89, 0x4a, 0x3d } }; -// Custom IDs for tray menu redirection (TrackPopupMenuEx method) #define TRAY_CUSTOM_ID_AUDIO 65001 #define TRAY_CUSTOM_ID_NETWORK 65002 #define TRAY_CUSTOM_ID_DEVICES 65003 -// TrackPopupMenuEx hook (DLL-based fallback method) +static const HINSTANCE SHELL_EXECUTE_SUCCESS = (HINSTANCE)33; + +#define PERS_ROOT L"explorer shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921}" +#define PERS_WALLPAPER L"explorer shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921} -Microsoft.Personalization\\pageWallpaper" +#define PERS_COLORS L"explorer shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921} -Microsoft.Personalization\\pageColorization" +#define SYSTEM_PROPS_CLSID L"shell:::{BB06C0E4-D293-4f75-8A90-CB05B6477EEE}" +#define NOTIF_AREA_CLSID L"shell:::{05d7b0f4-2121-4eff-bf6b-ed3f69b894d9}" +#define WIN11_PASSTHROUGH L"__PASSTHROUGH__" +#define EASE_OF_ACCESS L"explorer shell:::{D555645E-D4F8-4c29-A827-D93C859C4F2A}" + +// --------------------------------------------------------------------------- +// Typedefs and globals +// --------------------------------------------------------------------------- + +using CreateProcessW_t = BOOL(WINAPI*)(LPCWSTR, LPWSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION); +using ShellExecuteW_t = HINSTANCE(WINAPI*)(HWND, LPCWSTR, LPCWSTR, LPCWSTR, LPCWSTR, INT); +using ShellExecuteExW_t = BOOL(WINAPI*)(SHELLEXECUTEINFOW*); using TrackPopupMenuEx_t = BOOL(WINAPI*)(HMENU, UINT, int, int, HWND, const TPMPARAMS*); +using NtUserTrackPopupMenuEx_t = BOOL(WINAPI*)(HMENU, UINT, int, int, HWND, const TPMPARAMS*); +using ICMH_CAODTM_t = bool(__fastcall*)(HMENU, HWND); + +static CreateProcessW_t CreateProcessW_orig = nullptr; +static ShellExecuteExW_t ShellExecuteExW_orig = nullptr; +static ShellExecuteW_t ShellExecuteW_orig = nullptr; static TrackPopupMenuEx_t g_origTrackPopupMenuEx = nullptr; +static NtUserTrackPopupMenuEx_t g_origNtUserTrackPopupMenuEx = nullptr; + +static ICMH_CAODTM_t g_icmhOrig_SndVolSSO = nullptr; +static ICMH_CAODTM_t g_icmhOrig_pnidui = nullptr; +static ICMH_CAODTM_t g_icmhOrig_Shell32Devices = nullptr; -// Set on WM_RBUTTONUP by the subclass proc so TrackPopupMenuEx knows the icon type. static int g_trayContextType = 0; static DWORD g_trayContextTick = 0; static std::mutex g_trayContextMutex; +static bool g_shellExecHooksInstalled = false; +static bool g_createProcessHookInstalled = false; +static bool g_rcplHooksInstalled = false; +static bool g_taskbarListenerInstalled = false; +static std::atomic g_modActive{false}; +static std::mutex g_initMutex; +static std::atomic g_reinitPending{false}; static constexpr DWORD TRAY_CONTEXT_MAX_AGE_MS = 1500; -using ICMH_CAODTM_t = bool(__fastcall*)(HMENU, HWND); -static ICMH_CAODTM_t g_icmhOrig_SndVolSSO = nullptr; -static ICMH_CAODTM_t g_icmhOrig_pnidui = nullptr; -static ICMH_CAODTM_t g_icmhOrig_Shell32Devices = nullptr; static bool g_pniduiHookInstalled = false; static std::mutex g_pniduiHookMutex; static HANDLE g_pniduiRetryThread = nullptr; static volatile bool g_pniduiRetryStop = false; +static volatile bool g_pniduiRetryRunning = false; +static HANDLE g_reinitThread = nullptr; static HANDLE g_traySubclassWatchdogThread = nullptr; static volatile bool g_traySubclassWatchdogStop = false; +static HANDLE g_hWatchdogStopEvent = nullptr; static HWND g_lastShellTrayWnd = nullptr; +static HWND g_hTrayToolbar = nullptr; -static bool __fastcall ICMH_CAODTM_hook(HMENU, HWND); - -// Constants -static const HINSTANCE SHELL_EXECUTE_SUCCESS = (HINSTANCE)33; -#define PERS_ROOT L"explorer shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921}" -#define PERS_WALLPAPER L"explorer shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921} -Microsoft.Personalization\\pageWallpaper" -#define PERS_COLORS L"explorer shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921} -Microsoft.Personalization\\pageColorization" - -#define SYSTEM_PROPS_CLSID L"shell:::{BB06C0E4-D293-4f75-8A90-CB05B6477EEE}" -#define NOTIF_AREA_CLSID L"shell:::{05d7b0f4-2121-4eff-bf6b-ed3f69b894d9}" -#define WIN11_PASSTHROUGH L"__PASSTHROUGH__" -#define EASE_OF_ACCESS L"explorer shell:::{D555645E-D4F8-4c29-A827-D93C859C4F2A}" - -using CreateProcessW_t = BOOL(WINAPI*)(LPCWSTR, LPWSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION); -static CreateProcessW_t CreateProcessW_orig = nullptr; +static BYTE* g_sndVolSSOBase = nullptr; +static BYTE* g_sndVolSSOEnd = nullptr; +static BYTE* g_pniduiBase = nullptr; +static BYTE* g_pniduiEnd = nullptr; -using ShellExecuteW_t = HINSTANCE(WINAPI*)(HWND, LPCWSTR, LPCWSTR, LPCWSTR, LPCWSTR, INT); -using ShellExecuteExW_t = BOOL(WINAPI*)(SHELLEXECUTEINFOW*); -static ShellExecuteExW_t ShellExecuteExW_orig = nullptr; -static ShellExecuteW_t ShellExecuteW_orig = nullptr; -// NtUserTrackPopupMenuEx fallback (syscall level) -using NtUserTrackPopupMenuEx_t = BOOL(WINAPI*)(HMENU, UINT, int, int, HWND, const TPMPARAMS*); -static NtUserTrackPopupMenuEx_t g_origNtUserTrackPopupMenuEx = nullptr; +// --- NEW: TaskbarCreated message-only window, used to detect Explorer/tray +// restarts *immediately* instead of relying only on the polling watchdog. +// This is the standard, Microsoft-documented mechanism apps use to know when +// the shell has recreated the taskbar (see RegisterWindowMessage("TaskbarCreated")). +static UINT g_uTaskbarCreatedMsg = 0; +static HWND g_hTrayMsgWindow = nullptr; +static const wchar_t* TRAY_MSG_WNDCLASS = L"STC_TrayRestartListener"; struct ResolveResult { std::wstring target; @@ -160,15 +209,44 @@ struct HookGuard { static std::wstring g_childEnvBlock; +struct ModSettings { + bool enableRedirects = true; + bool redirectSystemTray = false; + bool uiOnlyRedirects = false; + int fallbackMode = 2; + bool win11CompatibilityMode = false; + int maxLaunchesPerUri = 3; + bool comActivationRedirect = true; + bool legacyNameMappingFix = true; +}; + +static ModSettings g_settings; +static bool g_isWin11 = false; +static std::unordered_map g_mappings; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +static std::wstring ToLower(std::wstring s) { + std::transform(s.begin(), s.end(), s.begin(), ::towlower); + return s; +} + +static std::wstring BaseNameLower(const std::wstring& path) { + size_t pos = path.rfind(L'\\'); + return ToLower((pos != std::wstring::npos) ? path.substr(pos + 1) : path); +} + static void BuildChildEnvironment() { + g_childEnvBlock.clear(); LPWCH curEnv = GetEnvironmentStringsW(); if (curEnv) { LPWCH p = curEnv; while (*p) { std::wstring entry(p); - if (entry.find(L"WH_STC_NOREDIRECT=") != 0) { + if (entry.find(L"WH_STC_NOREDIRECT=") != 0) g_childEnvBlock += entry + L'\0'; - } p += entry.length() + 1; } FreeEnvironmentStringsW(curEnv); @@ -180,22 +258,16 @@ static bool IsChildProcess() { return GetEnvironmentVariableW(L"WH_STC_NOREDIRECT", nullptr, 0) > 0; } -struct ModSettings { - bool enableRedirects = true; - bool redirectSystemTray = false; - bool uiOnlyRedirects = false; - int fallbackMode = 2; - bool win11CompatibilityMode = false; - int maxLaunchesPerUri = 3; - bool comActivationRedirect = true; - bool legacyNameMappingFix = true; -}; - -static ModSettings g_settings; - -static bool __fastcall ICMH_CAODTM_hook(HMENU, HWND) { - if (!g_settings.redirectSystemTray) return true; - return false; +static void DetectWindowsVersion() { + OSVERSIONINFOEXW osvi = {}; + osvi.dwOSVersionInfoSize = sizeof(osvi); + using RtlGetVersion_t = NTSTATUS(WINAPI*)(OSVERSIONINFOEXW*); + HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll"); + if (hNtdll) { + auto fn = (RtlGetVersion_t)GetProcAddress(hNtdll, "RtlGetVersion"); + if (fn) fn(&osvi); + } + g_isWin11 = (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber >= 22000); } static void LoadSettings() { @@ -205,7 +277,7 @@ static void LoadSettings() { WindhawkUtils::StringSetting fallbackSetting(Wh_GetStringSetting(L"FallbackMode")); PCWSTR fallbackStr = fallbackSetting; - if (fallbackStr[0] != L'\0') { + if (fallbackStr && fallbackStr[0] != L'\0') { int mode = _wtoi(fallbackStr); g_settings.fallbackMode = (mode >= 0 && mode <= 2) ? mode : 2; } else { @@ -213,35 +285,25 @@ static void LoadSettings() { } g_settings.win11CompatibilityMode = Wh_GetIntSetting(L"Win11CompatibilityMode") != 0; - int ml = Wh_GetIntSetting(L"MaxLaunchesPerUri"); g_settings.maxLaunchesPerUri = (ml >= 0 && ml <= 20) ? ml : 3; - g_settings.comActivationRedirect = Wh_GetIntSetting(L"ComActivationRedirect") != 0; g_settings.legacyNameMappingFix = Wh_GetIntSetting(L"LegacyNameMappingFix") != 0; } +// === NUOVO: Funzione di reinit iperstabile === -static bool g_isWin11 = false; - -static void DetectWindowsVersion() { - OSVERSIONINFOEXW osvi = {}; - osvi.dwOSVersionInfoSize = sizeof(osvi); - using RtlGetVersion_t = NTSTATUS(WINAPI*)(OSVERSIONINFOEXW*); - HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll"); - if (hNtdll) { - auto fn = (RtlGetVersion_t)GetProcAddress(hNtdll, "RtlGetVersion"); - if (fn) fn(&osvi); - } - g_isWin11 = (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber >= 22000); +static bool __fastcall ICMH_CAODTM_hook(HMENU, HWND) { + if (!g_settings.redirectSystemTray) return true; + return false; } -struct BounceRecord { - DWORD lastRedirectTick = 0; -}; +// --------------------------------------------------------------------------- +// Loop/bounce guards +// --------------------------------------------------------------------------- +struct BounceRecord { DWORD lastRedirectTick = 0; }; static std::mutex g_bounceGuardMtx; static std::unordered_map g_bounceGuard; - static constexpr DWORD BOUNCE_WINDOW_MS = 3000; static void BounceGuardRecord(const std::wstring& uri) { @@ -261,37 +323,32 @@ static bool BounceGuardIsBounce(const std::wstring& uri) { return false; } -struct LaunchRecord { - int count = 0; - DWORD firstTick = 0; -}; - +struct LaunchRecord { int count = 0; DWORD firstTick = 0; }; static std::mutex g_loopGuardMtx; static std::unordered_map g_loopGuard; - static constexpr DWORD LOOP_WINDOW_MS = 5000; static bool LoopGuardAllow(const std::wstring& target) { if (g_settings.maxLaunchesPerUri <= 0) return true; - std::lock_guard lk(g_loopGuardMtx); DWORD now = GetTickCount(); auto& rec = g_loopGuard[target]; - if (rec.count == 0 || (now - rec.firstTick) >= LOOP_WINDOW_MS) { rec.count = 1; rec.firstTick = now; return true; } - if (rec.count < g_settings.maxLaunchesPerUri) { rec.count++; return true; } - return false; } +// --------------------------------------------------------------------------- +// Windows 11 CLSID filters +// --------------------------------------------------------------------------- + static const std::unordered_set g_win11SafeClsids = { L"shell:::{025a5937-a6be-4686-a844-36fe4bec8b6d}", L"shell:::{05d7b0f4-2121-4eff-bf6b-ed3f69b894d9}", @@ -342,16 +399,9 @@ static bool IsClsidLoopOnWin11(const std::wstring& lowerTarget) { return g_win11LoopClsids.count(base) > 0; } -static std::wstring ToLower(std::wstring s) { - std::transform(s.begin(), s.end(), s.begin(), ::towlower); - return s; -} - -static HWND g_hTrayToolbar = nullptr; -static BYTE* g_sndVolSSOBase = nullptr; -static BYTE* g_sndVolSSOEnd = nullptr; -static BYTE* g_pniduiBase = nullptr; -static BYTE* g_pniduiEnd = nullptr; +// --------------------------------------------------------------------------- +// Tray helpers +// --------------------------------------------------------------------------- static bool InitTrayDllInfo() { if (g_sndVolSSOBase && g_pniduiBase) return true; @@ -390,13 +440,10 @@ static int GetTrayButtonType(HWND hToolbar, int buttonIndex) { wchar_t className[256]{}; if (!GetClassNameW(hIconWnd, className, 256)) return 0; - if (wcsncmp(className, L"ATL:", 4) != 0) { - return 0; - } + if (wcsncmp(className, L"ATL:", 4) != 0) return 0; const wchar_t* hexPart = className + 4; ULONG_PTR addr = 0; - while (*hexPart) { wchar_t c = *hexPart; int digit = 0; @@ -409,9 +456,9 @@ static int GetTrayButtonType(HWND hToolbar, int buttonIndex) { } if (g_sndVolSSOBase && addr >= (ULONG_PTR)g_sndVolSSOBase && addr < (ULONG_PTR)g_sndVolSSOEnd) - return 1; // Audio + return 1; if (g_pniduiBase && addr >= (ULONG_PTR)g_pniduiBase && addr < (ULONG_PTR)g_pniduiEnd) - return 2; // Network + return 2; return 0; } @@ -449,56 +496,64 @@ static void OpenClassicDevicesAndPrinters() { ShellExecuteExW_orig(&sei); } -static LRESULT CALLBACK TrayToolbarSubclassProc( - HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, DWORD_PTR dwRefData) -{ +static LRESULT CALLBACK TrayToolbarSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, DWORD_PTR) { if (msg == WM_RBUTTONUP) { POINT pt; pt.x = (int)(short)LOWORD(lParam); pt.y = (int)(short)HIWORD(lParam); int hitIndex = (int)SendMessageW(hwnd, TB_HITTEST, 0, (LPARAM)&pt); - if (hitIndex >= 0) { int buttonType = GetTrayButtonType(hwnd, hitIndex); - if (buttonType == 1) { - std::lock_guard lk(g_trayContextMutex); - g_trayContextType = 1; - g_trayContextTick = GetTickCount(); - } - else if (buttonType == 2) { + if (buttonType == 1 || buttonType == 2) { std::lock_guard lk(g_trayContextMutex); - g_trayContextType = 2; + g_trayContextType = buttonType; g_trayContextTick = GetTickCount(); } } } + return DefSubclassProc(hwnd, msg, wParam, lParam); } static HWND FindTrayToolbar() { HWND hTray = FindWindowW(L"Shell_TrayWnd", nullptr); if (!hTray) return nullptr; + DWORD pid = 0; GetWindowThreadProcessId(hTray, &pid); if (pid != GetCurrentProcessId()) return nullptr; + HWND hNotify = FindWindowExW(hTray, nullptr, L"TrayNotifyWnd", nullptr); if (!hNotify) return nullptr; + HWND hSysPager = FindWindowExW(hNotify, nullptr, L"SysPager", nullptr); if (hSysPager) { HWND hToolbar = FindWindowExW(hSysPager, nullptr, L"ToolbarWindow32", nullptr); if (hToolbar) return hToolbar; } + return FindWindowExW(hNotify, nullptr, L"ToolbarWindow32", nullptr); } static void SetupTraySubclass() { - if (g_hTrayToolbar) return; + if (g_hTrayToolbar) { + // Guard against a stale handle that's no longer the actual current + // toolbar (e.g. after a tray restart the old handle can, in rare + // cases, still test as IsWindow==TRUE for a brief moment while a + // brand new toolbar has already been created alongside it). + HWND hCurrent = FindTrayToolbar(); + if (hCurrent == g_hTrayToolbar) return; + if (IsWindow(g_hTrayToolbar)) + WindhawkUtils::RemoveWindowSubclassFromAnyThread(g_hTrayToolbar, TrayToolbarSubclassProc); + g_hTrayToolbar = nullptr; + } if (!InitTrayDllInfo()) return; + HWND hToolbar = FindTrayToolbar(); if (!hToolbar) return; - if (WindhawkUtils::SetWindowSubclassFromAnyThread(hToolbar, TrayToolbarSubclassProc, 0)) { + + if (WindhawkUtils::SetWindowSubclassFromAnyThread(hToolbar, TrayToolbarSubclassProc, 0)) g_hTrayToolbar = hToolbar; - } } static void RemoveTraySubclass() { @@ -524,15 +579,17 @@ static bool IsAddressInModule(void* address, const wchar_t* moduleName) { } return false; } -BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND hWnd, const TPMPARAMS* lptpm) { + +static BOOL HandleTrayPopupMenu(HMENU hMenu, UINT uFlags, int x, int y, HWND hWnd, const TPMPARAMS* lptpm, bool syscallHook) { + auto orig = syscallHook ? g_origNtUserTrackPopupMenuEx : g_origTrackPopupMenuEx; + if (!g_settings.redirectSystemTray || !g_settings.enableRedirects) - return g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + return orig(hMenu, uFlags, x, y, hWnd, lptpm); HookGuard guard; if (guard.IsReentrant()) - return g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + return orig(hMenu, uFlags, x, y, hWnd, lptpm); - // --- Primary: subclass flag set on WM_RBUTTONUP (language-independent) --- int contextType; { std::lock_guard lk(g_trayContextMutex); @@ -540,139 +597,92 @@ BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND h DWORD tick = g_trayContextTick; g_trayContextType = 0; g_trayContextTick = 0; - if (contextType != 0 && GetTickCount() - tick > TRAY_CONTEXT_MAX_AGE_MS) { + if (contextType != 0 && GetTickCount() - tick > TRAY_CONTEXT_MAX_AGE_MS) contextType = 0; - } } - bool isAudioMenu = (contextType == 1); + bool isAudioMenu = (contextType == 1); bool isNetworkMenu = (contextType == 2); - bool isDeviceMenu = false; + bool isDeviceMenu = false; - // --- Fallback: DLL return-address detection (language-independent) --- if (!isAudioMenu && !isNetworkMenu) { void* retAddr = GetReturnAddress(); int itemCount = GetMenuItemCount(hMenu); if (itemCount > 0) { if (IsAddressInModule(retAddr, L"SndVolSSO.dll")) { if (itemCount <= 6) isAudioMenu = true; - } - else if (IsAddressInModule(retAddr, L"pnidui.dll")) { + } else if (IsAddressInModule(retAddr, L"pnidui.dll")) { if (itemCount <= 6) isNetworkMenu = (itemCount >= 2 && itemCount <= 5); - } - else if (IsAddressInModule(retAddr, L"dxgi.dll")) { - // dxgi.dll handles both Network flyout (IDs 3107/3109) and - // Device/Safely Remove Hardware flyout (ID 215 = "Open Devices and Printers") - if (itemCount == 2 && GetMenuItemID(hMenu, 0) == 3107 && GetMenuItemID(hMenu, 1) == 3109) { + } else if (IsAddressInModule(retAddr, L"dxgi.dll")) { + if (itemCount == 2 && GetMenuItemID(hMenu, 0) == 3107 && GetMenuItemID(hMenu, 1) == 3109) isNetworkMenu = true; - } - // Check for device menu: item ID 215 is the language-independent - // identifier for "Open Devices and Printers" - else if (GetMenuItemID(hMenu, 0) == 215) { + else if (GetMenuItemID(hMenu, 0) == 215) isDeviceMenu = true; - Wh_Log(L"[TRAY-HOOK] Device menu detected via dxgi.dll + ID 215"); - } - } - else if (IsAddressInModule(retAddr, L"shell32.dll")) { - // NUOVO: Rilevamento shell32.dll per Win11 23H2 - // Cerca l'ID 215 in qualsiasi posizione del menu + } else if (IsAddressInModule(retAddr, L"shell32.dll")) { for (int i = 0; i < itemCount; i++) { - UINT itemId = GetMenuItemID(hMenu, i); - if (itemId == 215) { + if (GetMenuItemID(hMenu, i) == 215) { isDeviceMenu = true; - Wh_Log(L"[TRAY-HOOK] Device menu detected via shell32.dll + ID 215 at index %d", i); break; } } - } - else if (IsAddressInModule(retAddr, L"hotplug.dll")) { - // Fallback for older Windows versions where hotplug.dll handles the menu + } else if (IsAddressInModule(retAddr, L"hotplug.dll")) { isDeviceMenu = true; - Wh_Log(L"[TRAY-HOOK] Device menu detected via hotplug.dll"); } } } if (!isAudioMenu && !isNetworkMenu && !isDeviceMenu) - return g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + return orig(hMenu, uFlags, x, y, hWnd, lptpm); int itemCount = GetMenuItemCount(hMenu); - const wchar_t* menuKind = isAudioMenu ? L"AUDIO" : (isNetworkMenu ? L"NETWORK" : L"DEVICE"); - Wh_Log(L"[TRAY-HOOK] %s menu, %d items", menuKind, itemCount); - - // Find target item without any text matching. - // Audio: first item. - // Network: last non-separator item. - // Device: item with ID 215 ("Open Devices and Printers") — language-independent. int targetIndex = -1; - + if (isAudioMenu) { targetIndex = 0; - } - else if (isNetworkMenu) { + } else if (isNetworkMenu) { for (int i = itemCount - 1; i >= 0; i--) { MENUITEMINFOW miiCheck = { sizeof(MENUITEMINFOW) }; miiCheck.fMask = MIIM_FTYPE; - if (GetMenuItemInfoW(hMenu, i, TRUE, &miiCheck)) { - if (!(miiCheck.fType & MFT_SEPARATOR)) { - targetIndex = i; - break; - } + if (GetMenuItemInfoW(hMenu, i, TRUE, &miiCheck) && !(miiCheck.fType & MFT_SEPARATOR)) { + targetIndex = i; + break; } } - } - else if (isDeviceMenu) { - // Find item with ID 215 — the language-independent ID for "Open Devices and Printers" + } else if (isDeviceMenu) { for (int i = 0; i < itemCount; i++) { if (GetMenuItemID(hMenu, i) == 215) { targetIndex = i; break; } } - // Fallback: if ID 215 not found (unlikely), use first non-separator item if (targetIndex == -1) { for (int i = 0; i < itemCount; i++) { MENUITEMINFOW miiCheck = { sizeof(MENUITEMINFOW) }; miiCheck.fMask = MIIM_FTYPE; - if (GetMenuItemInfoW(hMenu, i, TRUE, &miiCheck)) { - if (!(miiCheck.fType & MFT_SEPARATOR)) { - targetIndex = i; - break; - } + if (GetMenuItemInfoW(hMenu, i, TRUE, &miiCheck) && !(miiCheck.fType & MFT_SEPARATOR)) { + targetIndex = i; + break; } } } } - - if (targetIndex == -1) { - Wh_Log(L"[TRAY-HOOK] No valid menu item found for %s menu", menuKind); - return g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); - } - Wh_Log(L"[TRAY-HOOK] %s menu target index=%d, ID=%u", menuKind, targetIndex, GetMenuItemID(hMenu, targetIndex)); + if (targetIndex == -1) + return orig(hMenu, uFlags, x, y, hWnd, lptpm); UINT originalId = GetMenuItemID(hMenu, targetIndex); - - // NOTE: we intentionally do NOT swap the item's ID (via SetMenuItemInfoW) - // anymore. This menu item still gets its text/icon painted by pnidui.dll - // through WM_DRAWITEM, which is keyed off the item's real ID — replacing - // it with our own custom ID left pnidui.dll unable to look up what to - // draw for it, so the item rendered with no text (it still worked, - // since selection is ID-driven, but visually looked broken). Comparing - // directly against the original ID avoids touching the item at all. bool callerWantedReturnCmd = (uFlags & TPM_RETURNCMD) != 0; uFlags |= TPM_RETURNCMD; - BOOL result = g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); - int selectedId = (int)result; + BOOL result = orig(hMenu, uFlags, x, y, hWnd, lptpm); + int selectedId = (int)result; if (selectedId == (int)originalId) { - Wh_Log(L"[TRAY-HOOK] User selected target item, redirecting"); - if (isAudioMenu) OpenClassicSoundPanel(); + Wh_Log(L"%s", syscallHook ? L"[SYSCALL-HOOK] User selected target item, redirecting" : L"[TRAY-HOOK] User selected target item, redirecting"); + if (isAudioMenu) OpenClassicSoundPanel(); else if (isNetworkMenu) OpenClassicNetworkConnections(); - else OpenClassicDevicesAndPrinters(); + else OpenClassicDevicesAndPrinters(); return 0; } - if (selectedId != 0 && !callerWantedReturnCmd) { PostMessageW(hWnd, WM_COMMAND, MAKEWPARAM((WORD)selectedId, 0), 0); return TRUE; @@ -680,143 +690,21 @@ BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND h return result; } -// Hook a livello syscall per catturare menu che bypassano TrackPopupMenuEx + +BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND hWnd, const TPMPARAMS* lptpm) { + return HandleTrayPopupMenu(hMenu, uFlags, x, y, hWnd, lptpm, false); +} + BOOL WINAPI NtUserTrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND hWnd, const TPMPARAMS* lptpm) { - // Se non è un menu della system tray, passa all'originale - if (!g_settings.redirectSystemTray || !g_settings.enableRedirects) { - return g_origNtUserTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); - } - - HookGuard guard; - if (guard.IsReentrant()) { - return g_origNtUserTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); - } - - // --- Rilevamento menu usando la stessa logica di TrackPopupMenuEx_Hook --- - int contextType; - { - std::lock_guard lk(g_trayContextMutex); - contextType = g_trayContextType; - DWORD tick = g_trayContextTick; - g_trayContextType = 0; - g_trayContextTick = 0; - if (contextType != 0 && GetTickCount() - tick > TRAY_CONTEXT_MAX_AGE_MS) { - contextType = 0; - } - } - - bool isAudioMenu = (contextType == 1); - bool isNetworkMenu = (contextType == 2); - bool isDeviceMenu = false; - - // Fallback: DLL return-address detection - if (!isAudioMenu && !isNetworkMenu) { - void* retAddr = GetReturnAddress(); - int itemCount = GetMenuItemCount(hMenu); - if (itemCount > 0) { - if (IsAddressInModule(retAddr, L"SndVolSSO.dll")) { - if (itemCount <= 6) isAudioMenu = true; - } - else if (IsAddressInModule(retAddr, L"pnidui.dll")) { - if (itemCount <= 6) isNetworkMenu = (itemCount >= 2 && itemCount <= 5); - } - else if (IsAddressInModule(retAddr, L"dxgi.dll")) { - if (itemCount == 2 && GetMenuItemID(hMenu, 0) == 3107 && GetMenuItemID(hMenu, 1) == 3109) { - isNetworkMenu = true; - } - else if (GetMenuItemID(hMenu, 0) == 215) { - isDeviceMenu = true; - } - } - else if (IsAddressInModule(retAddr, L"shell32.dll")) { - for (int i = 0; i < itemCount; i++) { - if (GetMenuItemID(hMenu, i) == 215) { - isDeviceMenu = true; - break; - } - } - } - else if (IsAddressInModule(retAddr, L"hotplug.dll")) { - isDeviceMenu = true; - } - } - } - - if (!isAudioMenu && !isNetworkMenu && !isDeviceMenu) { - return g_origNtUserTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); - } - - // Trova l'item target - int itemCount = GetMenuItemCount(hMenu); - int targetIndex = -1; - - if (isAudioMenu) { - targetIndex = 0; - } - else if (isNetworkMenu) { - for (int i = itemCount - 1; i >= 0; i--) { - MENUITEMINFOW miiCheck = { sizeof(MENUITEMINFOW) }; - miiCheck.fMask = MIIM_FTYPE; - if (GetMenuItemInfoW(hMenu, i, TRUE, &miiCheck)) { - if (!(miiCheck.fType & MFT_SEPARATOR)) { - targetIndex = i; - break; - } - } - } - } - else if (isDeviceMenu) { - for (int i = 0; i < itemCount; i++) { - if (GetMenuItemID(hMenu, i) == 215) { - targetIndex = i; - break; - } - } - if (targetIndex == -1) { - for (int i = 0; i < itemCount; i++) { - MENUITEMINFOW miiCheck = { sizeof(MENUITEMINFOW) }; - miiCheck.fMask = MIIM_FTYPE; - if (GetMenuItemInfoW(hMenu, i, TRUE, &miiCheck)) { - if (!(miiCheck.fType & MFT_SEPARATOR)) { - targetIndex = i; - break; - } - } - } - } - } - - if (targetIndex == -1) { - return g_origNtUserTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); - } - - UINT originalId = GetMenuItemID(hMenu, targetIndex); - bool callerWantedReturnCmd = (uFlags & TPM_RETURNCMD) != 0; - uFlags |= TPM_RETURNCMD; - - BOOL result = g_origNtUserTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); - int selectedId = (int)result; - - if (selectedId == (int)originalId) { - Wh_Log(L"[SYSCALL-HOOK] User selected target item, redirecting"); - if (isAudioMenu) OpenClassicSoundPanel(); - else if (isNetworkMenu) OpenClassicNetworkConnections(); - else OpenClassicDevicesAndPrinters(); - return 0; - } - - if (selectedId != 0 && !callerWantedReturnCmd) { - PostMessageW(hWnd, WM_COMMAND, MAKEWPARAM((WORD)selectedId, 0), 0); - return TRUE; - } - - return result; + return HandleTrayPopupMenu(hMenu, uFlags, x, y, hWnd, lptpm, true); } -static std::unordered_map g_mappings; + +// --------------------------------------------------------------------------- +// Mappings and URI resolving +// --------------------------------------------------------------------------- static void InitMappings() { const bool w11 = g_isWin11; - g_mappings = { {L"ms-settings:personalization", PERS_ROOT}, {L"ms-settings:personalization-colors", PERS_COLORS}, @@ -933,7 +821,7 @@ static void InitMappings() { {L"ms-settings:easeofaccess-mouse", EASE_OF_ACCESS}, {L"ms-settings:easeofaccess-keyboard", EASE_OF_ACCESS}, {L"ms-settings:recovery", w11 ? L"control.exe" : L"shell:::{9FE63AFD-59CF-4419-9775-ABCC3849F861}"}, - {L"ms-settings:troubleshoot", w11 ? L"msdt.exe -id DeviceDiagnostic" : L"shell:::{C58C4893-3BE0-4B45-ABB5-A63E4B8C8651}"}, + {L"ms-settings:troubleshoot", w11 ? L"msdt.exe -id DeviceDiagnostic" : L"shell:::{C58C4893-3BE0-4B45-ABB5-A63E4B8C8651}"}, {L"ms-settings:deviceencryption", L"shell:::{D9EF8727-CAC2-4e60-809E-86F80A666C91}"}, {L"ms-settings:gaming-gamebar", L"joy.cpl"}, {L"ms-settings:folders", L"shell:::{6DFD7C5C-2451-11d3-A299-00C04F8EF6AF}"}, @@ -963,7 +851,6 @@ static void InitMappings() { g_mappings[L"ms-settings:backup"] = L"control.exe /name Microsoft.BackupAndRestore"; g_mappings[L"ms-settings:network-advancedsettings"] = L"control.exe /name Microsoft.NetworkAndSharingCenter"; - if (g_isWin11) { g_mappings[L"ms-settings:recovery"] = L"shell:::{26EE0668-A00A-44D7-9371-BEB064C98683}\\0\\::{9FE63AFD-59CF-4419-9775-ABCC3849F861}"; } @@ -973,16 +860,13 @@ static std::wstring NormalizeUri(const std::wstring& uri) { std::wstring result = ToLower(uri); const std::wstring PROTOCOL = L"ms-settings://"; size_t pos = result.find(PROTOCOL); - if (pos != std::wstring::npos) { + if (pos != std::wstring::npos) result = L"ms-settings:" + result.substr(pos + PROTOCOL.length()); - } pos = result.find(L'?'); - if (pos != std::wstring::npos) { + if (pos != std::wstring::npos) result = result.substr(0, pos); - } - while (!result.empty() && result.back() == L'/') { + while (!result.empty() && result.back() == L'/') result.pop_back(); - } return result; } @@ -1000,10 +884,10 @@ static std::wstring ApplyWin11Filter(const std::wstring& target) { if (!g_isWin11) return target; std::wstring lower = ToLower(target); if (lower.find(L"shell:::") != 0 && lower.find(L"explorer shell:::") != 0) return target; - + std::wstring clsPart = lower; if (lower.find(L"explorer ") == 0) clsPart = lower.substr(9); - + if (IsClsidLoopOnWin11(clsPart)) { if (lower.find(L"ed834ed6") != std::wstring::npos) { if (lower.find(L"pagewallpaper") != std::wstring::npos) return PERS_WALLPAPER; @@ -1013,16 +897,19 @@ static std::wstring ApplyWin11Filter(const std::wstring& target) { if (lower.find(L"bb06c0e4") != std::wstring::npos) return L"sysdm.cpl"; return L"control.exe"; } - if (g_settings.win11CompatibilityMode && !IsClsidSafeOnWin11(clsPart)) { + + if (g_settings.win11CompatibilityMode && !IsClsidSafeOnWin11(clsPart)) return L"control.exe"; - } + return target; } -static bool HandleFallback(const std::wstring& uri) { +static bool HandleFallback(const std::wstring&) { switch (g_settings.fallbackMode) { - case 0: return true; + case 0: + return true; case 1: { + if (!CreateProcessW_orig) return true; std::wstring cmd = L"control.exe"; STARTUPINFOW si = {}; si.cb = sizeof(si); @@ -1035,15 +922,15 @@ static bool HandleFallback(const std::wstring& uri) { } return true; } - default: return false; + default: + return false; } } static void LaunchTarget(const std::wstring& command) { if (!LoopGuardAllow(command)) return; - std::wstring lower = ToLower(command); - + if (lower.find(L"explorer shell:::") != std::wstring::npos) { SHELLEXECUTEINFOW sei = {}; sei.cbSize = sizeof(sei); @@ -1055,14 +942,14 @@ static void LaunchTarget(const std::wstring& command) { ShellExecuteExW_orig(&sei); return; } - + if (lower.find(L"rundll32.exe ") == 0) { wchar_t rundll32Path[MAX_PATH]; - if (GetSystemDirectoryW(rundll32Path, MAX_PATH)) { + if (GetSystemDirectoryW(rundll32Path, MAX_PATH)) wcscat_s(rundll32Path, MAX_PATH, L"\\rundll32.exe"); - } else { + else wcscpy_s(rundll32Path, MAX_PATH, L"rundll32.exe"); - } + SHELLEXECUTEINFOW sei = {}; sei.cbSize = sizeof(sei); sei.fMask = SEE_MASK_FLAG_NO_UI; @@ -1073,20 +960,23 @@ static void LaunchTarget(const std::wstring& command) { ShellExecuteExW_orig(&sei); return; } - + bool isFullCmdLine = (lower.find(L"explorer.exe ") != std::wstring::npos) || - (lower.find(L"control.exe /") != std::wstring::npos); + (lower.find(L"control.exe /") != std::wstring::npos) || + (lower.find(L"control.exe ") == 0 && lower.find(L".cpl") != std::wstring::npos) || + (lower.find(L"msdt.exe ") == 0) || + (lower.find(L"sndvol.exe ") == 0); + if (isFullCmdLine) { STARTUPINFOW si = {}; si.cb = sizeof(si); si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_SHOWNORMAL; PROCESS_INFORMATION pi = {}; - std::wstring mutable_cmd = command; - if (!CreateProcessW_orig(nullptr, mutable_cmd.data(), nullptr, nullptr, + std::wstring mutableCmd = command; + if (CreateProcessW_orig && CreateProcessW_orig(nullptr, mutableCmd.data(), nullptr, nullptr, FALSE, CREATE_UNICODE_ENVIRONMENT, (LPVOID)g_childEnvBlock.c_str(), nullptr, &si, &pi)) { - } else { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } @@ -1094,7 +984,9 @@ static void LaunchTarget(const std::wstring& command) { } if (command == L"devmgmt.msc" || command == L"compmgmt.msc" || - command == L"slui.exe" || command == L"OptionalFeatures.exe") { + command == L"slui.exe" || command == L"OptionalFeatures.exe" || + command == L"msconfig.exe" || command == L"netplwiz" || + command == L"colorcpl.exe") { ShellExecuteW_orig(nullptr, L"open", command.c_str(), nullptr, nullptr, SW_SHOWNORMAL); return; } @@ -1129,7 +1021,7 @@ static void LaunchTarget(const std::wstring& command) { cmdLine = L"control.exe " + command; } - if (!cmdLine.empty()) { + if (!cmdLine.empty() && CreateProcessW_orig) { std::wstring mutableCmd = cmdLine; if (!CreateProcessW_orig(nullptr, mutableCmd.data(), nullptr, nullptr, FALSE, CREATE_UNICODE_ENVIRONMENT, @@ -1169,105 +1061,708 @@ static bool ShouldApplyBounceGuard(const std::wstring& uri) { static ResolveResult ResolveUri(const std::wstring& uri, HWND hwnd) { if (uri == L"ms-settings:personalization-background") { - if (BounceGuardIsBounce(uri)) return {L"", true}; + if (BounceGuardIsBounce(uri)) return { L"", true }; std::wstring t = ApplyWin11Filter(ResolvePersonalizationBackground(hwnd)); BounceGuardRecord(uri); - return {t, true}; + return { t, true }; } + auto it = g_mappings.find(uri); if (it != g_mappings.end()) { bool useBounceGuard = ShouldApplyBounceGuard(uri); if (useBounceGuard && BounceGuardIsBounce(uri)) { bool handled = HandleFallback(uri); - return {L"", handled}; + return { L"", handled }; } std::wstring t = ApplyWin11Filter(it->second); if (t == WIN11_PASSTHROUGH) { bool handled = HandleFallback(uri); - return {L"", handled}; + return { L"", handled }; } if (useBounceGuard) BounceGuardRecord(uri); - return {t, true}; + return { t, true }; } + if (uri.find(L"ms-settings:") == 0) { bool handled = HandleFallback(uri); - return {L"", handled}; - } - return {L"", false}; -} -// =========================================================================== -// EXPERIMENTAL: IApplicationActivationManager COM interception -// -// Some Windows 11 shell components (notably the system tray flyouts for -// "Open Devices and Printers") may bypass ShellExecute/CreateProcess entirely -// and instead activate the Settings app through the low-level COM interface -// IApplicationActivationManager::ActivateApplication(). -// -// We install a tiny vtable-style hook on the COM object returned by -// CoCreateInstance(CLSID_ApplicationActivationManager) to inspect every -// ActivateApplication call. When the appUserModelId matches -// "windows.immersivecontrolpanel..." (the Settings app), we: -// 1) map the ms-settings: URI embedded in the arguments to a classic CPL -// 2) launch that CPL ourselves -// 3) return S_OK to the caller (making it believe Settings was launched) -// -// This is entirely best-effort and based on reverse engineering assumptions. -// If anything unexpected happens we fall back to the original vtable entry. -// =========================================================================== - -// Minimal vtable layout for IApplicationActivationManager (3 methods) + return { L"", handled }; + } + + return { L"", false }; +} + +// --------------------------------------------------------------------------- +// Integrated Restore Classic CPLs by Anixx, prefixed with RCPL_ +// --------------------------------------------------------------------------- + +struct RCPL_Settings { + // Consolidated single toggle. Previously this mod exposed 4 separate + // switches (Personalization / Notification Icons / Network Connections / + // Printers and Faxes). They all did the same kind of thing (restore a + // classic CPL entry), so they're now one simple on/off toggle, enabled + // by default. + std::atomic enableClassicCpls = true; + std::atomic suppressCompanySync = true; +} g_rcplSettings; + +static std::wstring g_rcplPersonalizationName; +static std::unordered_map g_rcplKeyPaths; +static std::unordered_set g_rcplFakeHandles; +static std::mutex g_rcplKeyPathsMutex; + +static std::wstring g_rcplPersonalizationGuidLower; +static std::wstring g_rcplNotificationIconsGuidLower; +static std::wstring g_rcplNetworkConnectionsGuidLower; +static std::wstring g_rcplPrintersAndFaxesGuidLower; +static std::wstring g_rcplSuppressedGuidLower; + +static const std::wstring RCPL_kPersonalizationGuid = L"{580722ff-16a7-44c1-bf74-7e1acd00f4f9}"; +static const std::wstring RCPL_kNotificationIconsGuid = L"{05d7b0f4-2121-4eff-bf6b-ed3f69b894d9}"; +static const std::wstring RCPL_kNetworkConnectionsGuid = L"{7007acc7-3202-11d1-aad2-00805fc1270e}"; +static const std::wstring RCPL_kPrintersAndFaxesGuid = L"{2227a280-3aea-1069-a2de-08002b30309d}"; +static const std::wstring RCPL_kSuppressedGuid = L"{98f2ab62-0e29-4e4c-8ee7-b542e66740b1}"; + +static const DWORD RCPL_kCategoryAppearance = 1; +static const DWORD RCPL_kCategoryHardware = 2; +static const DWORD RCPL_kCategoryNetwork = 3; + +static bool RCPL_IsPredefinedHkey(HKEY hKey) { + return hKey == HKEY_CLASSES_ROOT || hKey == HKEY_CURRENT_USER || + hKey == HKEY_LOCAL_MACHINE || hKey == HKEY_USERS || + hKey == HKEY_CURRENT_CONFIG; +} + +static bool RCPL_EndsWith(const std::wstring& str, const std::wstring& suffix) { + if (str.size() < suffix.size()) return false; + return str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; +} + +static bool RCPL_ContainsRelevantKeyword(const std::wstring& lowerPath) { + return lowerPath.find(L"clsid") != std::wstring::npos || + lowerPath.find(L"controlpanel") != std::wstring::npos; +} + +static void RCPL_LoadSettings() { + g_rcplSettings.enableClassicCpls.store(Wh_GetIntSetting(L"enableClassicCpls") != 0); + g_rcplSettings.suppressCompanySync.store(Wh_GetIntSetting(L"suppressCompanySync") != 0); +} + +static void RCPL_InitDisplayNames() { + wchar_t buffer[256] = { 0 }; + HMODULE hTheme = LoadLibraryExW(L"themecpl.dll", nullptr, + LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_SEARCH_SYSTEM32); + if (hTheme) { + if (LoadStringW(hTheme, 1, buffer, 256) && buffer[0]) + g_rcplPersonalizationName = buffer; + else + g_rcplPersonalizationName = L"Personalization"; + FreeLibrary(hTheme); + } else { + g_rcplPersonalizationName = L"Personalization"; + } + + g_rcplPersonalizationGuidLower = ToLower(RCPL_kPersonalizationGuid); + g_rcplNotificationIconsGuidLower = ToLower(RCPL_kNotificationIconsGuid); + g_rcplNetworkConnectionsGuidLower = ToLower(RCPL_kNetworkConnectionsGuid); + g_rcplPrintersAndFaxesGuidLower = ToLower(RCPL_kPrintersAndFaxesGuid); + g_rcplSuppressedGuidLower = ToLower(RCPL_kSuppressedGuid); +} + +static std::wstring RCPL_GetTrackedPath(HKEY hKey) { + if (!hKey) return L""; + if (hKey == HKEY_CLASSES_ROOT) return L"HKEY_CLASSES_ROOT"; + if (hKey == HKEY_CURRENT_USER) return L"HKEY_CURRENT_USER"; + if (hKey == HKEY_LOCAL_MACHINE) return L"HKEY_LOCAL_MACHINE"; + if (hKey == HKEY_USERS) return L"HKEY_USERS"; + if (hKey == HKEY_CURRENT_CONFIG) return L"HKEY_CURRENT_CONFIG"; + + std::lock_guard lock(g_rcplKeyPathsMutex); + auto it = g_rcplKeyPaths.find(hKey); + if (it != g_rcplKeyPaths.end()) return it->second; + return L""; +} + +static void RCPL_TrackKey(HKEY hKey, const std::wstring& path) { + if (!hKey || RCPL_IsPredefinedHkey(hKey)) return; + std::wstring lower = ToLower(path); + if (!RCPL_ContainsRelevantKeyword(lower)) return; + std::lock_guard lock(g_rcplKeyPathsMutex); + g_rcplKeyPaths[hKey] = path; +} + +static void RCPL_UntrackKey(HKEY hKey) { + if (!hKey || RCPL_IsPredefinedHkey(hKey)) return; + std::lock_guard lock(g_rcplKeyPathsMutex); + g_rcplKeyPaths.erase(hKey); +} + +static HKEY RCPL_CreateFakeHandle(const std::wstring& path) { + int* dummy = new int(1); + HKEY fake = (HKEY)dummy; + std::lock_guard lock(g_rcplKeyPathsMutex); + g_rcplKeyPaths[fake] = path; + g_rcplFakeHandles.insert(fake); + return fake; +} + +static void RCPL_FreeFakeHandle(HKEY hKey) { + std::lock_guard lock(g_rcplKeyPathsMutex); + if (g_rcplFakeHandles.count(hKey)) { + g_rcplFakeHandles.erase(hKey); + g_rcplKeyPaths.erase(hKey); + delete (int*)hKey; + } +} + +enum class RCPL_VNode { None, ClsidRoot, DefaultIcon, Shell, ShellOpen, OpenCommand, NameSpaceEntry, ClsidRootCategoryOnly, Suppressed }; +enum class RCPL_ItemKind { None, Personalization, CategoryOnly, Suppressed }; + +struct RCPL_ClassifyResult { + RCPL_VNode node; + RCPL_ItemKind kind; + DWORD category; +}; + +static bool RCPL_IsSuppressedNamespaceKey(const std::wstring& lower) { + if (!g_rcplSettings.suppressCompanySync.load()) return false; + return RCPL_EndsWith(lower, L"controlpanel\\namespace\\" + g_rcplSuppressedGuidLower); +} + +static bool RCPL_IsSuppressedNamespaceEntry(LPCWSTR name) { + if (!g_rcplSettings.suppressCompanySync.load() || !name) return false; + return ToLower(name) == g_rcplSuppressedGuidLower; +} + +static RCPL_ClassifyResult RCPL_ClassifyFullVirtual(const std::wstring& lower, const std::wstring& guidLower, RCPL_ItemKind kind) { + if (RCPL_EndsWith(lower, L"clsid\\" + guidLower)) return { RCPL_VNode::ClsidRoot, kind, 0 }; + if (RCPL_EndsWith(lower, L"clsid\\" + guidLower + L"\\defaulticon")) return { RCPL_VNode::DefaultIcon, kind, 0 }; + if (RCPL_EndsWith(lower, L"clsid\\" + guidLower + L"\\shell")) return { RCPL_VNode::Shell, kind, 0 }; + if (RCPL_EndsWith(lower, L"clsid\\" + guidLower + L"\\shell\\open")) return { RCPL_VNode::ShellOpen, kind, 0 }; + if (RCPL_EndsWith(lower, L"clsid\\" + guidLower + L"\\shell\\open\\command")) return { RCPL_VNode::OpenCommand, kind, 0 }; + if (RCPL_EndsWith(lower, L"controlpanel\\namespace\\" + guidLower)) return { RCPL_VNode::NameSpaceEntry, kind, 0 }; + return { RCPL_VNode::None, RCPL_ItemKind::None, 0 }; +} + +static RCPL_ClassifyResult RCPL_ClassifyPath(const std::wstring& path) { + std::wstring lower = ToLower(path); + if (!RCPL_ContainsRelevantKeyword(lower)) + return { RCPL_VNode::None, RCPL_ItemKind::None, 0 }; + + if (g_rcplSettings.suppressCompanySync.load()) { + if (RCPL_EndsWith(lower, L"clsid\\" + g_rcplSuppressedGuidLower) || + RCPL_EndsWith(lower, L"controlpanel\\namespace\\" + g_rcplSuppressedGuidLower)) + return { RCPL_VNode::Suppressed, RCPL_ItemKind::Suppressed, 0 }; + } + + if (g_rcplSettings.enableClassicCpls.load()) { + auto cr = RCPL_ClassifyFullVirtual(lower, g_rcplPersonalizationGuidLower, RCPL_ItemKind::Personalization); + if (cr.node != RCPL_VNode::None) return cr; + } + + if (g_rcplSettings.enableClassicCpls.load()) { + struct CatItem { const std::wstring* guidLower; DWORD category; } categoryItems[] = { + { &g_rcplNotificationIconsGuidLower, RCPL_kCategoryAppearance }, + { &g_rcplNetworkConnectionsGuidLower, RCPL_kCategoryNetwork }, + { &g_rcplPrintersAndFaxesGuidLower, RCPL_kCategoryHardware }, + }; + + for (auto& item : categoryItems) { + if (RCPL_EndsWith(lower, L"clsid\\" + *item.guidLower)) + return { RCPL_VNode::ClsidRootCategoryOnly, RCPL_ItemKind::CategoryOnly, item.category }; + } + } + + return { RCPL_VNode::None, RCPL_ItemKind::None, 0 }; +} + +static bool RCPL_IsTargetKey(const std::wstring& path) { + return RCPL_ClassifyPath(path).node != RCPL_VNode::None; +} + +static bool RCPL_IsNameSpaceParentKey(const std::wstring& path) { + return RCPL_EndsWith(ToLower(path), L"controlpanel\\namespace"); +} + +static LSTATUS RCPL_ProvideStringValue(LPBYTE lpData, LPDWORD lpcbData, const std::wstring& str) { + DWORD requiredSize = (DWORD)((str.length() + 1) * sizeof(wchar_t)); + if (!lpcbData) return ERROR_INVALID_PARAMETER; + if (!lpData || *lpcbData < requiredSize) { + *lpcbData = requiredSize; + return ERROR_MORE_DATA; + } + *lpcbData = requiredSize; + memcpy(lpData, str.c_str(), requiredSize); + return ERROR_SUCCESS; +} + +static LSTATUS RCPL_ProvideDwordValue(LPBYTE lpData, LPDWORD lpcbData, DWORD value) { + if (!lpcbData) return ERROR_INVALID_PARAMETER; + if (!lpData || *lpcbData < sizeof(DWORD)) { + *lpcbData = sizeof(DWORD); + return ERROR_MORE_DATA; + } + *lpcbData = sizeof(DWORD); + *(DWORD*)lpData = value; + return ERROR_SUCCESS; +} + +static bool RCPL_TryProvideValue(const std::wstring& path, const std::wstring& valueName, + LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData, LSTATUS& outStatus) { + RCPL_ClassifyResult cr = RCPL_ClassifyPath(path); + if (cr.node == RCPL_VNode::None) return false; + + if (cr.kind == RCPL_ItemKind::Suppressed) { + outStatus = ERROR_FILE_NOT_FOUND; + return true; + } + + if (cr.kind == RCPL_ItemKind::CategoryOnly) { + if (valueName == L"System.ControlPanel.Category") { + if (lpType) *lpType = REG_DWORD; + outStatus = RCPL_ProvideDwordValue(lpData, lpcbData, cr.category); + return true; + } + return false; + } + + if (cr.kind == RCPL_ItemKind::Personalization) { + if (cr.node == RCPL_VNode::NameSpaceEntry) { + if (valueName.empty()) { + if (lpType) *lpType = REG_SZ; + outStatus = RCPL_ProvideStringValue(lpData, lpcbData, g_rcplPersonalizationName); + return true; + } + } else if (cr.node == RCPL_VNode::ClsidRoot) { + if (valueName.empty()) { + if (lpType) *lpType = REG_SZ; + outStatus = RCPL_ProvideStringValue(lpData, lpcbData, g_rcplPersonalizationName); + return true; + } else if (valueName == L"InfoTip") { + if (lpType) *lpType = REG_SZ; + outStatus = RCPL_ProvideStringValue(lpData, lpcbData, L"@%SystemRoot%\\System32\\themecpl.dll,-2#immutable1"); + return true; + } else if (valueName == L"System.ApplicationName") { + if (lpType) *lpType = REG_SZ; + outStatus = RCPL_ProvideStringValue(lpData, lpcbData, L"Microsoft.Personalization"); + return true; + } else if (valueName == L"System.ControlPanel.Category") { + if (lpType) *lpType = REG_DWORD; + outStatus = RCPL_ProvideDwordValue(lpData, lpcbData, RCPL_kCategoryAppearance); + return true; + } else if (valueName == L"System.Software.TasksFileUrl") { + if (lpType) *lpType = REG_SZ; + outStatus = RCPL_ProvideStringValue(lpData, lpcbData, L"Internal"); + return true; + } + } else if (cr.node == RCPL_VNode::DefaultIcon) { + if (valueName.empty()) { + if (lpType) *lpType = REG_SZ; + outStatus = RCPL_ProvideStringValue(lpData, lpcbData, L"%SystemRoot%\\System32\\themecpl.dll,-1"); + return true; + } + } else if (cr.node == RCPL_VNode::OpenCommand) { + if (valueName.empty()) { + if (lpType) *lpType = REG_SZ; + outStatus = RCPL_ProvideStringValue(lpData, lpcbData, + L"explorer shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921}"); + return true; + } + } + } + + return false; +} + +static std::vector RCPL_GetNamespaceClsids() { + std::vector result; + if (g_rcplSettings.enableClassicCpls.load()) { + result.push_back(RCPL_kPersonalizationGuid); + result.push_back(RCPL_kNotificationIconsGuid); + result.push_back(RCPL_kNetworkConnectionsGuid); + result.push_back(RCPL_kPrintersAndFaxesGuid); + } + return result; +} + +static bool RCPL_GetVirtualSubKeyName(RCPL_VNode node, DWORD index, std::wstring& outName) { + switch (node) { + case RCPL_VNode::ClsidRoot: + if (index == 0) { outName = L"DefaultIcon"; return true; } + if (index == 1) { outName = L"Shell"; return true; } + return false; + case RCPL_VNode::Shell: + if (index == 0) { outName = L"Open"; return true; } + return false; + case RCPL_VNode::ShellOpen: + if (index == 0) { outName = L"command"; return true; } + return false; + default: + return false; + } +} + +using RCPL_RegOpenKeyExW_t = decltype(&RegOpenKeyExW); +using RCPL_RegCloseKey_t = decltype(&RegCloseKey); +using RCPL_RegQueryValueExW_t = decltype(&RegQueryValueExW); +using RCPL_RegGetValueW_t = decltype(&RegGetValueW); +using RCPL_RegEnumKeyExW_t = decltype(&RegEnumKeyExW); +using RCPL_RegEnumKeyW_t = decltype(&RegEnumKeyW); + +static RCPL_RegOpenKeyExW_t RCPL_RegOpenKeyExWOriginal = nullptr; +static RCPL_RegCloseKey_t RCPL_RegCloseKeyOriginal = nullptr; +static RCPL_RegQueryValueExW_t RCPL_RegQueryValueExWOriginal = nullptr; +static RCPL_RegGetValueW_t RCPL_RegGetValueWOriginal = nullptr; +static RCPL_RegEnumKeyExW_t RCPL_RegEnumKeyExWOriginal = nullptr; +static RCPL_RegEnumKeyW_t RCPL_RegEnumKeyWOriginal = nullptr; + +static LSTATUS WINAPI RCPL_RegOpenKeyExWHook(HKEY hKey, LPCWSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult) { + bool parentIsFake = false; + std::wstring parentPath; + { + std::lock_guard lock(g_rcplKeyPathsMutex); + if (g_rcplFakeHandles.count(hKey)) { + parentIsFake = true; + auto it = g_rcplKeyPaths.find(hKey); + if (it != g_rcplKeyPaths.end()) parentPath = it->second; + } + } + + if (parentIsFake) { + std::wstring fullPath = parentPath; + if (lpSubKey && *lpSubKey) { + if (!fullPath.empty()) fullPath += L"\\"; + fullPath += lpSubKey; + } + if (RCPL_IsTargetKey(fullPath)) { + HKEY fake = RCPL_CreateFakeHandle(fullPath); + if (phkResult) *phkResult = fake; + return ERROR_SUCCESS; + } + return ERROR_FILE_NOT_FOUND; + } + + if (g_rcplSettings.suppressCompanySync.load() && lpSubKey) { + std::wstring basePath = RCPL_GetTrackedPath(hKey); + std::wstring fullPath = basePath; + if (*lpSubKey) { + if (!fullPath.empty()) fullPath += L"\\"; + fullPath += lpSubKey; + } + if (RCPL_IsSuppressedNamespaceKey(ToLower(fullPath))) return ERROR_FILE_NOT_FOUND; + } + + LSTATUS status = RCPL_RegOpenKeyExWOriginal(hKey, lpSubKey, ulOptions, samDesired, phkResult); + if (status == ERROR_SUCCESS && phkResult && *phkResult) { + std::wstring basePath = RCPL_GetTrackedPath(hKey); + std::wstring fullPath = basePath; + if (lpSubKey && *lpSubKey) { + if (!fullPath.empty()) fullPath += L"\\"; + fullPath += lpSubKey; + } + RCPL_TrackKey(*phkResult, fullPath); + } else if (status == ERROR_FILE_NOT_FOUND && phkResult) { + std::wstring basePath = RCPL_GetTrackedPath(hKey); + std::wstring fullPath = basePath; + if (lpSubKey && *lpSubKey) { + if (!fullPath.empty()) fullPath += L"\\"; + fullPath += lpSubKey; + } + if (RCPL_IsTargetKey(fullPath)) { + HKEY fake = RCPL_CreateFakeHandle(fullPath); + *phkResult = fake; + return ERROR_SUCCESS; + } + } + return status; +} + +static LSTATUS WINAPI RCPL_RegCloseKeyHook(HKEY hKey) { + bool isFake = false; + { std::lock_guard lock(g_rcplKeyPathsMutex); isFake = g_rcplFakeHandles.count(hKey) > 0; } + if (isFake) { RCPL_FreeFakeHandle(hKey); return ERROR_SUCCESS; } + LSTATUS status = RCPL_RegCloseKeyOriginal(hKey); + RCPL_UntrackKey(hKey); + return status; +} + +static LSTATUS WINAPI RCPL_RegQueryValueExWHook(HKEY hKey, LPCWSTR lpValueName, LPDWORD lpReserved, + LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData) { + std::wstring path = RCPL_GetTrackedPath(hKey); + if (!path.empty()) { + std::wstring valueName = lpValueName ? lpValueName : L""; + LSTATUS outStatus; + if (RCPL_TryProvideValue(path, valueName, lpType, lpData, lpcbData, outStatus)) return outStatus; + } + bool isFake = false; + { std::lock_guard lock(g_rcplKeyPathsMutex); isFake = g_rcplFakeHandles.count(hKey) > 0; } + if (isFake) return ERROR_FILE_NOT_FOUND; + return RCPL_RegQueryValueExWOriginal(hKey, lpValueName, lpReserved, lpType, lpData, lpcbData); +} + +static LSTATUS WINAPI RCPL_RegGetValueWHook(HKEY hkey, LPCWSTR lpSubKey, LPCWSTR lpValue, + DWORD dwFlags, LPDWORD pdwType, PVOID pvData, LPDWORD pcbData) { + std::wstring path = RCPL_GetTrackedPath(hkey); + if (lpSubKey && *lpSubKey) { if (!path.empty()) path += L"\\"; path += lpSubKey; } + if (!path.empty()) { + std::wstring valueName = lpValue ? lpValue : L""; + LSTATUS outStatus; + if (RCPL_TryProvideValue(path, valueName, pdwType, (LPBYTE)pvData, pcbData, outStatus)) return outStatus; + } + return RCPL_RegGetValueWOriginal(hkey, lpSubKey, lpValue, dwFlags, pdwType, pvData, pcbData); +} + +static LSTATUS WINAPI RCPL_RegEnumKeyExWHook(HKEY hKey, DWORD dwIndex, LPWSTR lpName, LPDWORD lpcchName, + LPDWORD lpReserved, LPWSTR lpClass, LPDWORD lpcchClass, + PFILETIME lpftLastWriteTime) { + bool isFake = false; + { std::lock_guard lock(g_rcplKeyPathsMutex); isFake = g_rcplFakeHandles.count(hKey) > 0; } + + if (isFake) { + std::wstring path = RCPL_GetTrackedPath(hKey); + RCPL_ClassifyResult cr = RCPL_ClassifyPath(path); + std::wstring subName; + if (!RCPL_GetVirtualSubKeyName(cr.node, dwIndex, subName)) return ERROR_NO_MORE_ITEMS; + if (lpcchName && *lpcchName < subName.size() + 1) { + *lpcchName = (DWORD)(subName.size() + 1); + return ERROR_MORE_DATA; + } + if (lpName) wcscpy_s(lpName, subName.size() + 1, subName.c_str()); + if (lpcchName) *lpcchName = (DWORD)subName.size(); + if (lpftLastWriteTime) GetSystemTimeAsFileTime(lpftLastWriteTime); + return ERROR_SUCCESS; + } + + std::wstring path = RCPL_GetTrackedPath(hKey); + bool isNamespace = RCPL_IsNameSpaceParentKey(path); + + if (isNamespace && g_rcplSettings.suppressCompanySync.load()) { + DWORD targetVirtualIndex = dwIndex; + DWORD realIndex = 0; + DWORD foundCount = 0; + LSTATUS status; + wchar_t nameBuf[256]; + DWORD origCap = lpcchName ? *lpcchName : 256; + + while (true) { + LPWSTR namePtr = lpName ? lpName : nameBuf; + DWORD cch = origCap; + if (lpcchName) *lpcchName = origCap; + status = RCPL_RegEnumKeyExWOriginal(hKey, realIndex, namePtr, lpcchName ? lpcchName : &cch, + lpReserved, lpClass, lpcchClass, lpftLastWriteTime); + if (status != ERROR_SUCCESS) break; + if (!RCPL_IsSuppressedNamespaceEntry(namePtr)) { + if (foundCount == targetVirtualIndex) return ERROR_SUCCESS; + foundCount++; + } + realIndex++; + } + + if (status == ERROR_NO_MORE_ITEMS) { + std::vector clsids = RCPL_GetNamespaceClsids(); + DWORD injectedIndex = dwIndex - foundCount; + if (injectedIndex >= clsids.size()) return ERROR_NO_MORE_ITEMS; + const wchar_t* clsid = clsids[injectedIndex].c_str(); + size_t len = wcslen(clsid); + if (lpcchName && *lpcchName < len + 1) { + *lpcchName = (DWORD)(len + 1); + return ERROR_MORE_DATA; + } + if (lpName && lpcchName) wcscpy_s(lpName, *lpcchName, clsid); + if (lpcchName) *lpcchName = (DWORD)len; + if (lpftLastWriteTime) GetSystemTimeAsFileTime(lpftLastWriteTime); + return ERROR_SUCCESS; + } + return status; + } + + LSTATUS status = RCPL_RegEnumKeyExWOriginal(hKey, dwIndex, lpName, lpcchName, + lpReserved, lpClass, lpcchClass, lpftLastWriteTime); + if (isNamespace && status == ERROR_NO_MORE_ITEMS) { + std::vector clsids = RCPL_GetNamespaceClsids(); + DWORD realCount = 0; + wchar_t tmpBuf[256]; + DWORD tmpCch; + while (true) { + tmpCch = 256; + if (RCPL_RegEnumKeyExWOriginal(hKey, realCount, tmpBuf, &tmpCch, nullptr, nullptr, nullptr, nullptr) != ERROR_SUCCESS) + break; + realCount++; + } + DWORD injectedIndex = dwIndex - realCount; + if (injectedIndex >= clsids.size()) return ERROR_NO_MORE_ITEMS; + const wchar_t* clsid = clsids[injectedIndex].c_str(); + size_t len = wcslen(clsid); + if (lpcchName && *lpcchName < len + 1) { + *lpcchName = (DWORD)(len + 1); + return ERROR_MORE_DATA; + } + if (lpName && lpcchName) wcscpy_s(lpName, *lpcchName, clsid); + if (lpcchName) *lpcchName = (DWORD)len; + if (lpftLastWriteTime) GetSystemTimeAsFileTime(lpftLastWriteTime); + return ERROR_SUCCESS; + } + return status; +} + +static LSTATUS WINAPI RCPL_RegEnumKeyWHook(HKEY hKey, DWORD dwIndex, LPWSTR lpName, DWORD cchName) { + bool isFake = false; + { std::lock_guard lock(g_rcplKeyPathsMutex); isFake = g_rcplFakeHandles.count(hKey) > 0; } + + if (isFake) { + std::wstring path = RCPL_GetTrackedPath(hKey); + RCPL_ClassifyResult cr = RCPL_ClassifyPath(path); + std::wstring subName; + if (!RCPL_GetVirtualSubKeyName(cr.node, dwIndex, subName)) return ERROR_NO_MORE_ITEMS; + if (cchName <= subName.size()) return ERROR_MORE_DATA; + wcscpy_s(lpName, cchName, subName.c_str()); + return ERROR_SUCCESS; + } + + std::wstring path = RCPL_GetTrackedPath(hKey); + bool isNamespace = RCPL_IsNameSpaceParentKey(path); + + if (isNamespace && g_rcplSettings.suppressCompanySync.load()) { + DWORD targetVirtualIndex = dwIndex; + DWORD realIndex = 0; + DWORD foundCount = 0; + LSTATUS status; + wchar_t nameBuf[256]; + while (true) { + status = RCPL_RegEnumKeyWOriginal(hKey, realIndex, lpName ? lpName : nameBuf, cchName ? cchName : 256); + if (status != ERROR_SUCCESS) break; + if (!RCPL_IsSuppressedNamespaceEntry(lpName ? lpName : nameBuf)) { + if (foundCount == targetVirtualIndex) return ERROR_SUCCESS; + foundCount++; + } + realIndex++; + } + if (status == ERROR_NO_MORE_ITEMS) { + std::vector clsids = RCPL_GetNamespaceClsids(); + DWORD injectedIndex = dwIndex - foundCount; + if (injectedIndex >= clsids.size()) return ERROR_NO_MORE_ITEMS; + const wchar_t* clsid = clsids[injectedIndex].c_str(); + size_t len = wcslen(clsid); + if (cchName <= len) return ERROR_MORE_DATA; + if (lpName) wcscpy_s(lpName, cchName, clsid); + return ERROR_SUCCESS; + } + return status; + } + + LSTATUS status = RCPL_RegEnumKeyWOriginal(hKey, dwIndex, lpName, cchName); + if (isNamespace && status == ERROR_NO_MORE_ITEMS) { + std::vector clsids = RCPL_GetNamespaceClsids(); + DWORD realCount = 0; + wchar_t tmpBuf[256]; + while (RCPL_RegEnumKeyWOriginal(hKey, realCount, tmpBuf, 256) == ERROR_SUCCESS) + realCount++; + DWORD injectedIndex = dwIndex - realCount; + if (injectedIndex >= clsids.size()) return ERROR_NO_MORE_ITEMS; + const wchar_t* clsid = clsids[injectedIndex].c_str(); + size_t len = wcslen(clsid); + if (cchName <= len) return ERROR_MORE_DATA; + if (lpName) wcscpy_s(lpName, cchName, clsid); + return ERROR_SUCCESS; + } + return status; +} + +static void* RCPL_GetRegFunc(const char* name) { + HMODULE hKb = GetModuleHandleW(L"kernelbase.dll"); + if (hKb) { void* p = (void*)GetProcAddress(hKb, name); if (p) return p; } + HMODULE hAdv = GetModuleHandleW(L"advapi32.dll"); + if (!hAdv) hAdv = LoadLibraryW(L"advapi32.dll"); + if (hAdv) { void* p = (void*)GetProcAddress(hAdv, name); if (p) return p; } + return nullptr; +} + +static bool RCPL_InstallRegistryHooks() { + RCPL_LoadSettings(); + RCPL_InitDisplayNames(); + + void* pRegOpenKeyExW = RCPL_GetRegFunc("RegOpenKeyExW"); + void* pRegCloseKey = RCPL_GetRegFunc("RegCloseKey"); + void* pRegQueryValueExW = RCPL_GetRegFunc("RegQueryValueExW"); + void* pRegGetValueW = RCPL_GetRegFunc("RegGetValueW"); + void* pRegEnumKeyExW = RCPL_GetRegFunc("RegEnumKeyExW"); + void* pRegEnumKeyW = RCPL_GetRegFunc("RegEnumKeyW"); + + if (!pRegOpenKeyExW || !pRegCloseKey || !pRegQueryValueExW || !pRegGetValueW || !pRegEnumKeyExW || !pRegEnumKeyW) { + Wh_Log(L"[RCPL] Failed to get one or more registry functions"); + return false; + } + + if (!Wh_SetFunctionHook(pRegOpenKeyExW, (void*)RCPL_RegOpenKeyExWHook, (void**)&RCPL_RegOpenKeyExWOriginal)) { Wh_Log(L"[RCPL] Failed to hook RegOpenKeyExW"); return false; } + if (!Wh_SetFunctionHook(pRegCloseKey, (void*)RCPL_RegCloseKeyHook, (void**)&RCPL_RegCloseKeyOriginal)) { Wh_Log(L"[RCPL] Failed to hook RegCloseKey"); return false; } + if (!Wh_SetFunctionHook(pRegQueryValueExW, (void*)RCPL_RegQueryValueExWHook, (void**)&RCPL_RegQueryValueExWOriginal)) { Wh_Log(L"[RCPL] Failed to hook RegQueryValueExW"); return false; } + if (!Wh_SetFunctionHook(pRegGetValueW, (void*)RCPL_RegGetValueWHook, (void**)&RCPL_RegGetValueWOriginal)) { Wh_Log(L"[RCPL] Failed to hook RegGetValueW"); return false; } + if (!Wh_SetFunctionHook(pRegEnumKeyExW, (void*)RCPL_RegEnumKeyExWHook, (void**)&RCPL_RegEnumKeyExWOriginal)) { Wh_Log(L"[RCPL] Failed to hook RegEnumKeyExW"); return false; } + if (!Wh_SetFunctionHook(pRegEnumKeyW, (void*)RCPL_RegEnumKeyWHook, (void**)&RCPL_RegEnumKeyWOriginal)) { Wh_Log(L"[RCPL] Failed to hook RegEnumKeyW"); return false; } + + Wh_Log(L"[RCPL] Classic CPL registry virtualization hooks installed"); + return true; +} + +static void RCPL_Cleanup() { + std::lock_guard lock(g_rcplKeyPathsMutex); + for (HKEY fake : g_rcplFakeHandles) + delete (int*)fake; + g_rcplFakeHandles.clear(); + g_rcplKeyPaths.clear(); + Wh_Log(L"[RCPL] Cleanup completed"); +} + +// --------------------------------------------------------------------------- +// Experimental COM activation hook +// --------------------------------------------------------------------------- + struct IApplicationActivationManagerVtbl { - // IUnknown HRESULT (STDMETHODCALLTYPE *QueryInterface)(IUnknown*, REFIID, void**); ULONG (STDMETHODCALLTYPE *AddRef)(IUnknown*); ULONG (STDMETHODCALLTYPE *Release)(IUnknown*); - // IApplicationActivationManager - HRESULT (STDMETHODCALLTYPE *ActivateApplication)( - IUnknown*, - LPCWSTR appUserModelId, - LPCWSTR arguments, - DWORD options, - DWORD* processId); + HRESULT (STDMETHODCALLTYPE *ActivateApplication)(IUnknown*, LPCWSTR, LPCWSTR, DWORD, DWORD*); HRESULT (STDMETHODCALLTYPE *ActivateForFile)(IUnknown*, LPCWSTR, LPCWSTR, DWORD, DWORD*); - HRESULT (STDMETHODCALLTYPE *ActivateForProtocol)(IUnknown*, LPCWSTR, DWORD*, DWORD); + // Real ABI: HRESULT ActivateForProtocol(LPCWSTR pszProtocol, IShellItemArray* pItemArray, DWORD* pProcessId) + HRESULT (STDMETHODCALLTYPE *ActivateForProtocol)(IUnknown*, LPCWSTR, IShellItemArray*, DWORD*); }; static IApplicationActivationManagerVtbl g_origAAMVtbl = {}; static bool g_aamHookInstalled = false; static std::mutex g_aamHookMutex; -HRESULT STDMETHODCALLTYPE AAM_ActivateApplication_hook( - IUnknown* pThis, - LPCWSTR appUserModelId, - LPCWSTR arguments, - DWORD options, - DWORD* processId) -{ - Wh_Log(L"[AAM-HOOK] ActivateApplication: appId=%s, args=%s", - appUserModelId ? appUserModelId : L"(null)", - arguments ? arguments : L"(null)"); - - // Is this the Settings app being activated? - if (appUserModelId && arguments && - _wcsnicmp(appUserModelId, L"windows.immersivecontrolpanel", 29) == 0) - { +HRESULT STDMETHODCALLTYPE AAM_ActivateApplication_hook(IUnknown* pThis, LPCWSTR appUserModelId, LPCWSTR arguments, DWORD options, DWORD* processId) { + if (appUserModelId && arguments && _wcsnicmp(appUserModelId, L"windows.immersivecontrolpanel", 29) == 0) { std::wstring uri = NormalizeUri(arguments); - Wh_Log(L"[AAM-HOOK] Settings activation intercepted: %s", uri.c_str()); - auto result = ResolveUri(uri, nullptr); if (result.intercept && !result.target.empty()) { LaunchTarget(result.target); if (processId) *processId = GetCurrentProcessId(); - Wh_Log(L"[AAM-HOOK] Redirected to: %s", result.target.c_str()); return S_OK; } - Wh_Log(L"[AAM-HOOK] No mapping found, falling back to original"); } - // Not a Settings activation we can handle — call original - if (g_origAAMVtbl.ActivateApplication) { + if (g_origAAMVtbl.ActivateApplication) return g_origAAMVtbl.ActivateApplication(pThis, appUserModelId, arguments, options, processId); + return E_FAIL; +} + +// Handles "See also" style links from classic namespace pages (e.g. the +// Personalization CPL's "Schermo" / "Centro accessibilità" links, and the +// Power Options "See also" panel) that Windows 11 resolves via protocol +// activation (ms-settings:...) rather than via ActivateApplication. +HRESULT STDMETHODCALLTYPE AAM_ActivateForProtocol_hook(IUnknown* pThis, LPCWSTR pszProtocol, IShellItemArray* pItemArray, DWORD* processId) { + if (pszProtocol && IsMsSettings(pszProtocol)) { + std::wstring uri = NormalizeUri(pszProtocol); + auto result = ResolveUri(uri, nullptr); + if (result.intercept) { + if (!result.target.empty()) LaunchTarget(result.target); + if (processId) *processId = GetCurrentProcessId(); + return S_OK; + } } + + if (g_origAAMVtbl.ActivateForProtocol) + return g_origAAMVtbl.ActivateForProtocol(pThis, pszProtocol, pItemArray, processId); return E_FAIL; } @@ -1276,15 +1771,10 @@ static void InstallAAMHook() { if (g_aamHookInstalled) return; IUnknown* pAAM = nullptr; - HRESULT hr = CoCreateInstance( - CLSID_ApplicationActivationManager_STC, - nullptr, - CLSCTX_INPROC_SERVER, - IID_IApplicationActivationManager_STC, - (void**)&pAAM); - + HRESULT hr = CoCreateInstance(CLSID_ApplicationActivationManager_STC, nullptr, CLSCTX_INPROC_SERVER, + IID_IApplicationActivationManager_STC, (void**)&pAAM); if (FAILED(hr) || !pAAM) { - Wh_Log(L"[AAM-HOOK] CoCreateInstance(CLSID_ApplicationActivationManager) failed: 0x%08X", hr); + Wh_Log(L"[AAM-HOOK] CoCreateInstance failed: 0x%08X", hr); return; } @@ -1295,10 +1785,8 @@ static void InstallAAMHook() { return; } - // Save original vtable g_origAAMVtbl = *vtbl; - // Make the vtable page writable DWORD oldProtect; if (!VirtualProtect(vtbl, sizeof(*vtbl), PAGE_READWRITE, &oldProtect)) { Wh_Log(L"[AAM-HOOK] VirtualProtect failed"); @@ -1306,43 +1794,31 @@ static void InstallAAMHook() { return; } - // Patch ActivateApplication entry vtbl->ActivateApplication = AAM_ActivateApplication_hook; - + vtbl->ActivateForProtocol = AAM_ActivateForProtocol_hook; VirtualProtect(vtbl, sizeof(*vtbl), oldProtect, &oldProtect); - + g_aamHookInstalled = true; - Wh_Log(L"[AAM-HOOK] Successfully installed"); + Wh_Log(L"[AAM-HOOK] Successfully installed (ActivateApplication + ActivateForProtocol)"); pAAM->Release(); } bool (*COpenControlPanel__MapLegacyName_orig)(void*, LPCWSTR, LPWSTR, UINT, bool*); -bool COpenControlPanel__MapLegacyName_hook( - void *pThis, - LPCWSTR pszLegacyName, - LPWSTR pszNewName, - UINT uLen, - bool *nameChanged) -{ - // Always tell the caller the name was NOT changed — this forces - // Explorer to use the original legacy Control Panel path. - *nameChanged = false; - *pszNewName = L'\0'; - Wh_Log(L"[MAP-LEGACY] Suppressed mapping for: %s", - pszLegacyName ? pszLegacyName : L"(null)"); + +bool COpenControlPanel__MapLegacyName_hook(void*, LPCWSTR pszLegacyName, LPWSTR pszNewName, UINT, bool* nameChanged) { + if (nameChanged) *nameChanged = false; + if (pszNewName) *pszNewName = L'\0'; + Wh_Log(L"[MAP-LEGACY] Suppressed mapping for: %s", pszLegacyName ? pszLegacyName : L"(null)"); return false; } static bool InstallLegacyNameHook() { HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); - if (!hShell32) { - Wh_Log(L"[MAP-LEGACY] shell32.dll not loaded"); - return false; - } + if (!hShell32) return false; WindhawkUtils::SYMBOL_HOOK shell32_dll_hook = {{ L"private: bool __cdecl COpenControlPanel::_MapLegacyName" - L"(unsigned short const *,unsigned short *,unsigned int,bool *)" + L"(unsigned short const *,unsigned short* ,unsigned int,bool *)" }, (void**)&COpenControlPanel__MapLegacyName_orig, (void*)COpenControlPanel__MapLegacyName_hook, @@ -1351,17 +1827,16 @@ static bool InstallLegacyNameHook() { if (WindhawkUtils::HookSymbols(hShell32, &shell32_dll_hook, 1)) { Wh_Log(L"[MAP-LEGACY] Hook installed successfully"); return true; - } else { - Wh_Log(L"[MAP-LEGACY] Failed to install hook (symbol may differ on this build)"); - return false; } -} -static std::wstring BaseNameLower(const std::wstring& path) { - size_t pos = path.rfind(L'\\'); - return ToLower((pos != std::wstring::npos) ? path.substr(pos + 1) : path); + Wh_Log(L"[MAP-LEGACY] Failed to install hook"); + return false; } +// --------------------------------------------------------------------------- +// ShellExecute/CreateProcess hooks +// --------------------------------------------------------------------------- + static bool IsControlSystemParams(const wchar_t* file, const wchar_t* params) { if (!file || !params) return false; std::wstring exe = BaseNameLower(file); @@ -1375,10 +1850,12 @@ static bool IsControlSystemCommand(const std::wstring& cmdLine) { std::wstring current; bool inQuotes = false; for (wchar_t c : cmdLine) { - if (c == L'"') { inQuotes = !inQuotes; } + if (c == L'\"') inQuotes = !inQuotes; else if (c == L' ' && !inQuotes) { if (!current.empty()) { tokens.push_back(current); current.clear(); } - } else { current += c; } + } else { + current += c; + } } if (!current.empty()) tokens.push_back(current); if (tokens.size() != 2) return false; @@ -1387,13 +1864,14 @@ static bool IsControlSystemCommand(const std::wstring& cmdLine) { std::wstring arg = ToLower(tokens[1]); return (arg == L"system" || arg == L"microsoft.system"); } + static std::wstring ExtractExplorerLaunchUri(const std::wstring& cmdLine) { size_t i = 0, n = cmdLine.size(); while (i < n && cmdLine[i] == L' ') i++; std::wstring exeToken; - if (i < n && cmdLine[i] == L'"') { - size_t end = cmdLine.find(L'"', i + 1); + if (i < n && cmdLine[i] == L'\"') { + size_t end = cmdLine.find(L'\"', i + 1); if (end == std::wstring::npos) return L""; exeToken = cmdLine.substr(i + 1, end - i - 1); i = end + 1; @@ -1404,19 +1882,18 @@ static std::wstring ExtractExplorerLaunchUri(const std::wstring& cmdLine) { } if (BaseNameLower(exeToken) != L"explorer.exe") return L""; - while (i < n && cmdLine[i] == L' ') i++; std::wstring rest = cmdLine.substr(i); while (!rest.empty() && rest.back() == L' ') rest.pop_back(); - if (rest.size() >= 2 && rest.front() == L'"' && rest.back() == L'"') { + if (rest.size() >= 2 && rest.front() == L'\"' && rest.back() == L'\"') rest = rest.substr(1, rest.size() - 2); - } if (rest.empty()) return L""; if (IsMsSettings(rest.c_str())) return NormalizeUri(rest); if (IsShellClsid(rest.c_str())) return ToLower(rest); return L""; } + BOOL WINAPI ShellExecuteExW_hook(SHELLEXECUTEINFOW* pei) { if (IsChildProcess()) return ShellExecuteExW_orig(pei); HookGuard guard; @@ -1428,7 +1905,7 @@ BOOL WINAPI ShellExecuteExW_hook(SHELLEXECUTEINFOW* pei) { if (pei->fMask & SEE_MASK_NOCLOSEPROCESS) pei->hProcess = nullptr; return TRUE; } - + std::wstring uri; if (IsMsSettings(pei->lpFile)) uri = NormalizeUri(pei->lpFile); else if (IsMsSettings(pei->lpParameters)) uri = NormalizeUri(pei->lpParameters); @@ -1446,6 +1923,7 @@ BOOL WINAPI ShellExecuteExW_hook(SHELLEXECUTEINFOW* pei) { return TRUE; } } + return ShellExecuteExW_orig(pei); } @@ -1459,7 +1937,7 @@ HINSTANCE WINAPI ShellExecuteW_hook(HWND hwnd, LPCWSTR op, LPCWSTR file, LPCWSTR LaunchTarget(g_isWin11 ? L"sysdm.cpl" : SYSTEM_PROPS_CLSID); return SHELL_EXECUTE_SUCCESS; } - + std::wstring uri; if (IsMsSettings(file)) uri = NormalizeUri(file); else if (IsMsSettings(params)) uri = NormalizeUri(params); @@ -1476,24 +1954,30 @@ HINSTANCE WINAPI ShellExecuteW_hook(HWND hwnd, LPCWSTR op, LPCWSTR file, LPCWSTR return SHELL_EXECUTE_SUCCESS; } } + return ShellExecuteW_orig(hwnd, op, file, params, dir, show); } BOOL WINAPI CreateProcessW_hook(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, - LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, - BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, - LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, - LPPROCESS_INFORMATION lpProcessInformation) { - if (IsChildProcess()) return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, - lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, - lpStartupInfo, lpProcessInformation); + LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation) { + if (IsChildProcess()) + return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, + bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, + lpStartupInfo, lpProcessInformation); + HookGuard guard; - if (guard.IsReentrant()) return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, - lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, - lpStartupInfo, lpProcessInformation); - if (!g_settings.enableRedirects || g_settings.uiOnlyRedirects) return CreateProcessW_orig(lpApplicationName, - lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, - lpCurrentDirectory, lpStartupInfo, lpProcessInformation); + if (guard.IsReentrant()) + return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, + bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, + lpStartupInfo, lpProcessInformation); + + if (!g_settings.enableRedirects || g_settings.uiOnlyRedirects) + return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, + bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, + lpStartupInfo, lpProcessInformation); if (lpCommandLine) { std::wstring cmdLine(lpCommandLine); @@ -1504,10 +1988,6 @@ BOOL WINAPI CreateProcessW_hook(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, return TRUE; } - // Handles cases like the classic Control Panel's "See also" links, - // which Explorer launches as a new "explorer.exe ms-settings:..." - // (or "explorer.exe shell:::{...}") process via CreateProcessW - // instead of going through ShellExecuteW/ShellExecuteExW. std::wstring uri = ExtractExplorerLaunchUri(cmdLine); if (!uri.empty()) { auto result = ResolveUri(uri, nullptr); @@ -1519,19 +1999,20 @@ BOOL WINAPI CreateProcessW_hook(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, } } } - return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, - bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); + + return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, + bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, + lpStartupInfo, lpProcessInformation); } -static volatile bool g_pniduiRetryRunning = false; +// --------------------------------------------------------------------------- +// Symbol hooks and watchdog +// --------------------------------------------------------------------------- static bool TryInstallPniduiHook() { std::lock_guard lk(g_pniduiHookMutex); - - if (g_pniduiHookInstalled) { - return true; - } - + if (g_pniduiHookInstalled) return true; + HMODULE hMod = GetModuleHandleW(L"pnidui.dll"); if (!hMod) { hMod = LoadLibraryExW(L"pnidui.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); @@ -1540,8 +2021,6 @@ static bool TryInstallPniduiHook() { return false; } } - - Wh_Log(L"[PNIDUI-HOOK] pnidui.dll loaded at 0x%p", hMod); WindhawkUtils::SYMBOL_HOOK pnidui_dll_hooks[] = {{ { @@ -1552,7 +2031,7 @@ static bool TryInstallPniduiHook() { L"__stdcall" #endif L" ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu" - L"(struct HMENU__ *,struct HWND__ *)" + L"(struct HMENU__ *,struct HWND__* )" }, (void**)&g_icmhOrig_pnidui, (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, @@ -1570,49 +2049,24 @@ static bool TryInstallPniduiHook() { } static DWORD WINAPI PniduiRetryThread(LPVOID) { - if (g_pniduiRetryRunning) { - Wh_Log(L"[PNIDUI-HOOK] Retry thread already running, exiting"); - return 0; - } + if (g_pniduiRetryRunning) return 0; g_pniduiRetryRunning = true; - - Wh_Log(L"[PNIDUI-HOOK] Retry thread started - waiting for pnidui.dll to load"); - + const int MAX_WAIT_CHECKS = 60; const DWORD CHECK_INTERVAL = 500; bool dllLoaded = false; - for (int i = 0; i < MAX_WAIT_CHECKS && !g_pniduiRetryStop; i++) { - if (GetModuleHandleW(L"pnidui.dll") != nullptr) { - dllLoaded = true; - Wh_Log(L"[PNIDUI-HOOK] pnidui.dll detected after %dms", i * CHECK_INTERVAL); - break; - } + if (GetModuleHandleW(L"pnidui.dll") != nullptr) { dllLoaded = true; break; } Sleep(CHECK_INTERVAL); } - - if (!dllLoaded) { - Wh_Log(L"[PNIDUI-HOOK] pnidui.dll not loaded after timeout, giving up"); - g_pniduiRetryRunning = false; - return 0; - } - - if (TryInstallPniduiHook()) { - Wh_Log(L"[PNIDUI-HOOK] Hook installed successfully after waiting for DLL"); - } else { - Wh_Log(L"[PNIDUI-HOOK] Failed to install hook even though DLL is loaded"); - } - + + if (dllLoaded) TryInstallPniduiHook(); g_pniduiRetryRunning = false; - Wh_Log(L"[PNIDUI-HOOK] Retry thread exiting"); return 0; } static void InstallImmersiveMenuHooks() { - struct DllHook { - const wchar_t* dll; - ICMH_CAODTM_t* orig; - } targets[] = { + struct DllHook { const wchar_t* dll; ICMH_CAODTM_t* orig; } targets[] = { { L"SndVolSSO.dll", &g_icmhOrig_SndVolSSO }, }; @@ -1620,7 +2074,7 @@ static void InstallImmersiveMenuHooks() { HMODULE hMod = LoadLibraryExW(t.dll, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if (!hMod) continue; - WindhawkUtils::SYMBOL_HOOK sndVolSSO_dll_hooks[] = {{ + WindhawkUtils::SYMBOL_HOOK hooks[] = {{ { L"bool " #ifdef _WIN64 @@ -1629,27 +2083,19 @@ static void InstallImmersiveMenuHooks() { L"__stdcall" #endif L" ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu" - L"(struct HMENU__ *,struct HWND__ *)" + L"(struct HMENU__ *,struct HWND__* )" }, (void**)t.orig, (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, false }}; - - WindhawkUtils::HookSymbols(hMod, sndVolSSO_dll_hooks, 1); + WindhawkUtils::HookSymbols(hMod, hooks, 1); } if (!TryInstallPniduiHook()) { if (!g_pniduiRetryRunning && !g_pniduiRetryThread) { g_pniduiRetryStop = false; g_pniduiRetryThread = CreateThread(nullptr, 0, PniduiRetryThread, nullptr, 0, nullptr); - if (g_pniduiRetryThread) { - Wh_Log(L"[PNIDUI-HOOK] Retry thread created"); - } else { - Wh_Log(L"[PNIDUI-HOOK] Failed to create retry thread"); - } - } else { - Wh_Log(L"[PNIDUI-HOOK] Retry thread already running or hook already installed"); } } @@ -1671,7 +2117,6 @@ static void InstallImmersiveMenuHooks() { (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, false }}; - WindhawkUtils::HookSymbols(hShell32, shell32_dll_hooks, 1); } } @@ -1679,170 +2124,302 @@ static void InstallImmersiveMenuHooks() { static void InstallSyscallFallback() { HMODULE hWin32u = GetModuleHandleW(L"win32u.dll"); - if (!hWin32u) { - hWin32u = LoadLibraryW(L"win32u.dll"); - if (!hWin32u) { - Wh_Log(L"[SYSCALL-HOOK] win32u.dll not found"); - return; - } - } - + if (!hWin32u) hWin32u = LoadLibraryW(L"win32u.dll"); + if (!hWin32u) return; + FARPROC pNtUserTrackPopupMenuEx = GetProcAddress(hWin32u, "NtUserTrackPopupMenuEx"); - if (!pNtUserTrackPopupMenuEx) { - Wh_Log(L"[SYSCALL-HOOK] NtUserTrackPopupMenuEx not found by name"); - return; - } - - if (Wh_SetFunctionHook((void*)pNtUserTrackPopupMenuEx, - (void*)NtUserTrackPopupMenuEx_Hook, - (void**)&g_origNtUserTrackPopupMenuEx)) { + if (!pNtUserTrackPopupMenuEx) return; + + if (Wh_SetFunctionHook((void*)pNtUserTrackPopupMenuEx, (void*)NtUserTrackPopupMenuEx_Hook, (void**)&g_origNtUserTrackPopupMenuEx)) Wh_Log(L"[SYSCALL-HOOK] NtUserTrackPopupMenuEx hooked successfully"); - } else { - Wh_Log(L"[SYSCALL-HOOK] Failed to hook NtUserTrackPopupMenuEx"); - } } using CreateWindowExW_t = decltype(&CreateWindowExW); -CreateWindowExW_t CreateWindowExW_Original; -// --------------------------------------------------------------------------- -// Tray subclass watchdog -// --------------------------------------------------------------------------- +static CreateWindowExW_t CreateWindowExW_Original = nullptr; static bool HasTrayBeenRecreated() { HWND hTray = FindWindowW(L"Shell_TrayWnd", nullptr); - if (!hTray) { - return false; - } - + if (!hTray) return false; bool recreated = (g_lastShellTrayWnd != nullptr && hTray != g_lastShellTrayWnd); g_lastShellTrayWnd = hTray; return recreated; } static void ReinitializeTrayRedirect() { - Wh_Log(L"[TRAY-WATCHDOG] Reinitializing tray redirect state"); + Wh_Log(L"[TRAY-RESTART] Reinitializing tray redirect hooks"); RemoveTraySubclass(); - g_sndVolSSOBase = nullptr; - g_sndVolSSOEnd = nullptr; - g_pniduiBase = nullptr; - g_pniduiEnd = nullptr; - + g_sndVolSSOEnd = nullptr; + g_pniduiBase = nullptr; + g_pniduiEnd = nullptr; { std::lock_guard lk(g_pniduiHookMutex); g_pniduiHookInstalled = false; } - - if (g_settings.redirectSystemTray) { - SetupTraySubclass(); - } - + if (g_settings.redirectSystemTray) SetupTraySubclass(); if (!TryInstallPniduiHook()) { if (!g_pniduiRetryRunning && !g_pniduiRetryThread) { g_pniduiRetryStop = false; g_pniduiRetryThread = CreateThread(nullptr, 0, PniduiRetryThread, nullptr, 0, nullptr); } } - InstallImmersiveMenuHooks(); } -static DWORD WINAPI TraySubclassWatchdogThread(LPVOID) { - Wh_Log(L"[TRAY-WATCHDOG] Watchdog thread started"); +// --- NEW: hidden message-only window that listens for the "TaskbarCreated" +// broadcast message. Explorer (and any shell component that recreates the +// tray) posts this message the moment the taskbar/tray is rebuilt, which is +// far more reliable and immediate than polling for a changed Shell_TrayWnd +// handle - this is what was causing "zero logs" after a restart: the +// polling watchdog could take up to a few seconds, and in some restart +// scenarios the Shell_TrayWnd handle compare alone didn't catch that the +// child SysPager/ToolbarWindow32 had actually been rebuilt. +static LRESULT CALLBACK TrayMsgWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + if (g_uTaskbarCreatedMsg != 0 && msg == g_uTaskbarCreatedMsg) { + Wh_Log(L"[TRAY-RESTART] TaskbarCreated message received"); + g_lastShellTrayWnd = FindWindowW(L"Shell_TrayWnd", nullptr); + ReinitializeTrayRedirect(); + return 0; + } + return DefWindowProcW(hwnd, msg, wParam, lParam); +} + +static void InstallTaskbarCreatedListener() { + g_uTaskbarCreatedMsg = RegisterWindowMessageW(L"TaskbarCreated"); + if (g_uTaskbarCreatedMsg == 0) { + Wh_Log(L"[TRAY-RESTART] Failed to register TaskbarCreated message"); + return; + } + + WNDCLASSEXW wc = { sizeof(wc) }; + wc.lpfnWndProc = TrayMsgWndProc; + wc.hInstance = GetModuleHandleW(nullptr); + wc.lpszClassName = TRAY_MSG_WNDCLASS; + RegisterClassExW(&wc); // OK if this fails because it's already registered. + + g_hTrayMsgWindow = CreateWindowExW(0, TRAY_MSG_WNDCLASS, L"", 0, 0, 0, 0, 0, + HWND_MESSAGE, nullptr, wc.hInstance, nullptr); + if (!g_hTrayMsgWindow) + Wh_Log(L"[TRAY-RESTART] Failed to create message-only listener window"); + else + Wh_Log(L"[TRAY-RESTART] TaskbarCreated listener installed"); +} - const int FAST_PHASE_CHECKS = 60; - const DWORD FAST_INTERVAL_MS = 500; - const DWORD SLOW_INTERVAL_MS = 3000; +static void RemoveTaskbarCreatedListener() { + if (g_hTrayMsgWindow) { + DestroyWindow(g_hTrayMsgWindow); + g_hTrayMsgWindow = nullptr; + } + UnregisterClassW(TRAY_MSG_WNDCLASS, GetModuleHandleW(nullptr)); +} +static DWORD WINAPI TraySubclassWatchdogThread(LPVOID) { + const int FAST_PHASE_CHECKS = 60; + const DWORD FAST_INTERVAL_MS = 500; + const DWORD SLOW_INTERVAL_MS = 3000; int tick = 0; + + // Aspetta che il sistema si stabilizzi all'avvio + Sleep(2000); + while (!g_traySubclassWatchdogStop) { - Sleep(tick < FAST_PHASE_CHECKS ? FAST_INTERVAL_MS : SLOW_INTERVAL_MS); + DWORD waitTime = tick < FAST_PHASE_CHECKS ? FAST_INTERVAL_MS : SLOW_INTERVAL_MS; + + // Usa l'evento per essere interrompibile immediatamente + if (g_hWatchdogStopEvent) { + DWORD waitResult = WaitForSingleObject(g_hWatchdogStopEvent, waitTime); + if (waitResult == WAIT_OBJECT_0 || g_traySubclassWatchdogStop) break; + } else { + // Fallback se l'evento non è stato creato (polling ogni 50ms) + for (DWORD i = 0; i < waitTime / 50 && !g_traySubclassWatchdogStop; i++) { + Sleep(50); + } + if (g_traySubclassWatchdogStop) break; + } + tick++; if (g_traySubclassWatchdogStop) break; + // Verifica se siamo ancora nel processo corretto + DWORD currentProcessId = GetCurrentProcessId(); + DWORD explorerPid = 0; + + // Check se Explorer è ancora in esecuzione + HWND hTray = FindWindowW(L"Shell_TrayWnd", nullptr); + if (!hTray) { + if (g_lastShellTrayWnd != nullptr) { + Wh_Log(L"[WATCHDOG] Shell_TrayWnd disappeared - possible Explorer crash"); + g_lastShellTrayWnd = nullptr; + g_hTrayToolbar = nullptr; + std::lock_guard lk(g_pniduiHookMutex); + g_pniduiHookInstalled = false; + } + continue; + } + + // Verifica che la finestra appartenga al nostro processo + GetWindowThreadProcessId(hTray, &explorerPid); + if (explorerPid != currentProcessId) { + // Non è il nostro Explorer, resetta lo stato + if (g_lastShellTrayWnd != nullptr) { + Wh_Log(L"[WATCHDOG] Shell_TrayWnd belongs to different process"); + g_lastShellTrayWnd = nullptr; + g_hTrayToolbar = nullptr; + } + continue; + } + + // Caso 1: Shell_TrayWnd cambiata (nuova istanza Explorer) if (HasTrayBeenRecreated()) { - Wh_Log(L"[TRAY-WATCHDOG] Shell_TrayWnd recreation detected, reinitializing"); + Wh_Log(L"[WATCHDOG] Detected new Shell_TrayWnd instance - reinitializing"); ReinitializeTrayRedirect(); + tick = 0; continue; } + // Salta controlli toolbar se tray redirect è disabilitato if (!g_settings.redirectSystemTray) continue; - if (g_hTrayToolbar && !IsWindow(g_hTrayToolbar)) { - Wh_Log(L"[TRAY-WATCHDOG] Tray toolbar handle no longer valid, resetting"); - g_hTrayToolbar = nullptr; + // Caso 2: Toolbar potrebbe essere stata ricreata internamente + HWND hCurrentToolbar = FindTrayToolbar(); + + bool oldToolbarValid = (g_hTrayToolbar && IsWindow(g_hTrayToolbar)); + bool currentToolbarValid = (hCurrentToolbar && IsWindow(hCurrentToolbar)); + + if (hCurrentToolbar != g_hTrayToolbar) { + if (oldToolbarValid) { + Wh_Log(L"[WATCHDOG] Toolbar internally recreated - reinitializing"); + } else if (currentToolbarValid) { + Wh_Log(L"[WATCHDOG] Previously lost toolbar now available"); + } + ReinitializeTrayRedirect(); + tick = 0; + continue; } - if (!g_hTrayToolbar) { + // Caso 3: Toolbar subclassata non è più valida + if (g_hTrayToolbar && !IsWindow(g_hTrayToolbar)) { + Wh_Log(L"[WATCHDOG] Subclassed toolbar destroyed - reinstalling subclass"); + g_hTrayToolbar = nullptr; SetupTraySubclass(); + tick = 0; + continue; + } + + // Caso 4: Controllo periodico hook pnidui.dll + if (g_settings.redirectSystemTray && GetModuleHandleW(L"pnidui.dll")) { + std::lock_guard lk(g_pniduiHookMutex); + if (!g_pniduiHookInstalled) { + Wh_Log(L"[WATCHDOG] pnidui hook missing - attempting reinstall"); + TryInstallPniduiHook(); + } } } - Wh_Log(L"[TRAY-WATCHDOG] Watchdog thread exiting"); + Wh_Log(L"[WATCHDOG] Watchdog thread exiting"); return 0; } -HWND WINAPI CreateWindowExW_Hook( - DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, - DWORD dwStyle, int X, int Y, int nWidth, int nHeight, - HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam) -{ - HWND hwnd = CreateWindowExW_Original( - dwExStyle, lpClassName, lpWindowName, dwStyle, - X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam); - - if (g_settings.redirectSystemTray && hwnd && !g_hTrayToolbar && lpClassName && !IS_INTRESOURCE(lpClassName)) { - if (wcscmp(lpClassName, L"ToolbarWindow32") == 0) { + +HWND WINAPI CreateWindowExW_Hook(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, + DWORD dwStyle, int X, int Y, int nWidth, int nHeight, + HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam) { + HWND hwnd = CreateWindowExW_Original(dwExStyle, lpClassName, lpWindowName, dwStyle, + X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam); + + if (g_settings.redirectSystemTray && hwnd && lpClassName && !IS_INTRESOURCE(lpClassName)) { + if (wcscmp(lpClassName, L"ToolbarWindow32") == 0 && + (!g_hTrayToolbar || !IsWindow(g_hTrayToolbar))) { SetupTraySubclass(); } } - + return hwnd; } +// --------------------------------------------------------------------------- +// Windhawk entry points +// --------------------------------------------------------------------------- BOOL Wh_ModInit() { - Wh_Log(L"Redirect Settings to Control Panel v10.0.25"); - + Wh_Log(L"Redirect Settings to Control Panel + Restore Classic CPLs v10.0.26-stable"); + + // NUOVO: Verifica se siamo nel processo explorer.exe corretto (quello che possiede la Shell_TrayWnd) + // Se non lo siamo, rifiuta l'inizializzazione per evitare conflitti tra processi fantasma + HWND hTray = FindWindowW(L"Shell_TrayWnd", nullptr); + if (hTray) { + DWORD shellPid = 0; + GetWindowThreadProcessId(hTray, &shellPid); + DWORD ourPid = GetCurrentProcessId(); + + if (shellPid != ourPid) { + Wh_Log(L"[STABLE] Not the main shell process (PID %lu, shell PID %lu) - refusing to install hooks", + ourPid, shellPid); + return FALSE; + } + } + // Se Shell_TrayWnd non esiste affatto, procediamo comunque (potrebbe non essere ancora creata) + + std::lock_guard lock(g_initMutex); + + // Inizializzazione di base DetectWindowsVersion(); LoadSettings(); BuildChildEnvironment(); InitMappings(); - + + // Shell hooks (fondamentali, vanno installati una volta sola) HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); if (!hShell32) hShell32 = LoadLibraryW(L"shell32.dll"); if (!hShell32) return FALSE; - + FARPROC pExW = GetProcAddress(hShell32, "ShellExecuteExW"); FARPROC pW = GetProcAddress(hShell32, "ShellExecuteW"); if (!pExW || !pW) return FALSE; - - Wh_SetFunctionHook((void*)pExW, (void*)ShellExecuteExW_hook, (void**)&ShellExecuteExW_orig); - Wh_SetFunctionHook((void*)pW, (void*)ShellExecuteW_hook, (void**)&ShellExecuteW_orig); - + + if (!g_shellExecHooksInstalled) { + Wh_SetFunctionHook((void*)pExW, (void*)ShellExecuteExW_hook, (void**)&ShellExecuteExW_orig); + Wh_SetFunctionHook((void*)pW, (void*)ShellExecuteW_hook, (void**)&ShellExecuteW_orig); + g_shellExecHooksInstalled = true; + Wh_Log(L"[STABLE] Shell execution hooks installed"); + } + + // CreateProcess hook HMODULE hKernel32 = GetModuleHandleW(L"kernel32.dll"); if (!hKernel32) hKernel32 = LoadLibraryW(L"kernel32.dll"); - if (hKernel32) { + if (hKernel32 && !g_createProcessHookInstalled) { FARPROC pCPW = GetProcAddress(hKernel32, "CreateProcessW"); - if (pCPW) Wh_SetFunctionHook((void*)pCPW, (void*)CreateProcessW_hook, (void**)&CreateProcessW_orig); + if (pCPW) { + Wh_SetFunctionHook((void*)pCPW, (void*)CreateProcessW_hook, (void**)&CreateProcessW_orig); + g_createProcessHookInstalled = true; + Wh_Log(L"[STABLE] CreateProcess hook installed"); + } } - + + // CreateWindowEx hook per tray Wh_SetFunctionHook((void*)CreateWindowExW, (void*)CreateWindowExW_Hook, (void**)&CreateWindowExW_Original); + + // RCPL hooks + if (!g_rcplHooksInstalled) { + g_rcplHooksInstalled = RCPL_InstallRegistryHooks(); + } + + // Tray subsystem if (g_settings.redirectSystemTray) { SetupTraySubclass(); } - - InstallImmersiveMenuHooks(); + InstallImmersiveMenuHooks(); InstallSyscallFallback(); - + if (g_settings.comActivationRedirect && g_isWin11) { InstallAAMHook(); } - + if (g_settings.legacyNameMappingFix) { InstallLegacyNameHook(); } - + + // TrackPopupMenu hooks HMODULE hUser32 = GetModuleHandleW(L"user32.dll"); if (!hUser32) hUser32 = LoadLibraryW(L"user32.dll"); if (hUser32) { @@ -1851,37 +2428,66 @@ BOOL Wh_ModInit() { Wh_SetFunctionHook(pTrackPopupMenuEx, (void*)TrackPopupMenuEx_Hook, (void**)&g_origTrackPopupMenuEx); } } - // Initialize the Shell_TrayWnd reference for the watchdog + + // TaskbarCreated listener g_lastShellTrayWnd = FindWindowW(L"Shell_TrayWnd", nullptr); - + if (!g_taskbarListenerInstalled) { + InstallTaskbarCreatedListener(); + g_taskbarListenerInstalled = true; + } + + if (g_hWatchdogStopEvent) { + CloseHandle(g_hWatchdogStopEvent); + } + g_hWatchdogStopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); g_traySubclassWatchdogStop = false; g_traySubclassWatchdogThread = CreateThread(nullptr, 0, TraySubclassWatchdogThread, nullptr, 0, nullptr); - if (g_traySubclassWatchdogThread) { - Wh_Log(L"[TRAY-WATCHDOG] Watchdog thread created"); - } else { - Wh_Log(L"[TRAY-WATCHDOG] Failed to create watchdog thread"); - } + + g_modActive.store(true); + Wh_Log(L"[STABLE] Initialization completed"); + return TRUE; } - void Wh_ModUninit() { + Wh_Log(L"[STABLE] Uninit started"); + + // 1. Disabilita subito il mod per bloccare nuove chiamate + g_modActive.store(false); + + // 2. Ferma il reinit thread se in esecuzione + if (g_reinitThread) { + g_reinitPending.store(false); // Cancella eventuali reinit pendenti + if (WaitForSingleObject(g_reinitThread, 500) == WAIT_TIMEOUT) { + Wh_Log(L"[STABLE] Reinit thread timeout, forcing termination"); + TerminateThread(g_reinitThread, 0); + } + CloseHandle(g_reinitThread); + g_reinitThread = nullptr; + } + + // 3. Ferma il watchdog immediatamente con l'evento + g_traySubclassWatchdogStop = true; + if (g_hWatchdogStopEvent) { + SetEvent(g_hWatchdogStopEvent); // Sveglia il watchdog immediatamente + } if (g_traySubclassWatchdogThread) { - g_traySubclassWatchdogStop = true; - Wh_Log(L"[TRAY-WATCHDOG] Stopping watchdog thread..."); - DWORD waitResult = WaitForSingleObject(g_traySubclassWatchdogThread, 3000); - if (waitResult == WAIT_TIMEOUT) { - Wh_Log(L"[TRAY-WATCHDOG] Timeout, terminating thread"); + if (WaitForSingleObject(g_traySubclassWatchdogThread, 500) == WAIT_TIMEOUT) { + Wh_Log(L"[STABLE] Watchdog thread timeout, forcing termination"); TerminateThread(g_traySubclassWatchdogThread, 0); } CloseHandle(g_traySubclassWatchdogThread); g_traySubclassWatchdogThread = nullptr; } + if (g_hWatchdogStopEvent) { + CloseHandle(g_hWatchdogStopEvent); + g_hWatchdogStopEvent = nullptr; + } + + // 4. Ferma il retry thread di pnidui if (g_pniduiRetryThread) { g_pniduiRetryStop = true; - Wh_Log(L"[PNIDUI-HOOK] Stopping retry thread..."); - DWORD waitResult = WaitForSingleObject(g_pniduiRetryThread, 3000); - if (waitResult == WAIT_TIMEOUT) { - Wh_Log(L"[PNIDUI-HOOK] Retry thread timeout, terminating"); + if (WaitForSingleObject(g_pniduiRetryThread, 1000) == WAIT_TIMEOUT) { + Wh_Log(L"[STABLE] Pnidui retry thread timeout, forcing termination"); TerminateThread(g_pniduiRetryThread, 0); } CloseHandle(g_pniduiRetryThread); @@ -1889,34 +2495,111 @@ void Wh_ModUninit() { g_pniduiRetryRunning = false; } + // 5. Ripristina hook COM + { + std::lock_guard lk(g_aamHookMutex); + if (g_aamHookInstalled) { + IUnknown* pAAM = nullptr; + if (SUCCEEDED(CoCreateInstance(CLSID_ApplicationActivationManager_STC, nullptr, + CLSCTX_INPROC_SERVER, IID_IApplicationActivationManager_STC, (void**)&pAAM)) && pAAM) { + auto vtbl = *(IApplicationActivationManagerVtbl**)pAAM; + DWORD oldProtect; + if (VirtualProtect(vtbl, sizeof(*vtbl), PAGE_READWRITE, &oldProtect)) { + vtbl->ActivateApplication = g_origAAMVtbl.ActivateApplication; + vtbl->ActivateForProtocol = g_origAAMVtbl.ActivateForProtocol; + VirtualProtect(vtbl, sizeof(*vtbl), oldProtect, &oldProtect); + } + pAAM->Release(); + } + g_aamHookInstalled = false; + } + } + + // 6. Rimuovi listener TaskbarCreated + RemoveTaskbarCreatedListener(); + g_taskbarListenerInstalled = false; + + // 7. Rimuovi subclass della tray RemoveTraySubclass(); + + // 8. Pulisci RCPL + RCPL_Cleanup(); + g_rcplHooksInstalled = false; + + Wh_Log(L"[STABLE] Uninit completed"); } - -void Wh_ModSettingsChanged() { +static void SafeReinitializeAll() { + std::lock_guard lock(g_initMutex); + + Wh_Log(L"[STABLE] Starting safe reinitialization..."); + + // Spegni il mod attivo durante la transizione + g_modActive.store(false); + + // Piccola pausa per permettere alle chiamate in corso di completare + Sleep(100); + + // Pulisci stato tray senza rimuovere gli hook di sistema RemoveTraySubclass(); g_sndVolSSOBase = nullptr; g_sndVolSSOEnd = nullptr; g_pniduiBase = nullptr; g_pniduiEnd = nullptr; - { std::lock_guard lk(g_pniduiHookMutex); g_pniduiHookInstalled = false; } + // Ricarica tutto lo stato LoadSettings(); + RCPL_LoadSettings(); + BuildChildEnvironment(); InitMappings(); + + // Reinstalla solo i sottosistemi che dipendono dalle impostazioni if (g_settings.redirectSystemTray) { SetupTraySubclass(); InstallImmersiveMenuHooks(); } - - // Re-install experimental hooks if settings changed + if (g_settings.comActivationRedirect && g_isWin11) { InstallAAMHook(); } - + if (g_settings.legacyNameMappingFix) { InstallLegacyNameHook(); } + + // Aggiorna riferimento tray + g_lastShellTrayWnd = FindWindowW(L"Shell_TrayWnd", nullptr); + + // Riattiva il mod + g_modActive.store(true); + + Wh_Log(L"[STABLE] Safe reinitialization completed"); +} +static DWORD WINAPI DeferredReinitThreadProc(LPVOID) { + Wh_Log(L"[STABLE] Deferred reinit thread started"); + + // Aspetta 500ms per sicurezza (chiamate pendenti) + Sleep(500); + + // Reinizializza + SafeReinitializeAll(); + + g_reinitPending.store(false); + Wh_Log(L"[STABLE] Deferred reinit thread completed"); + + return 0; +} + +void Wh_ModSettingsChanged() { + Wh_Log(L"[STABLE] Settings changed - scheduling safe reinit"); + + if (!g_reinitPending.exchange(true)) { + if (g_reinitThread) CloseHandle(g_reinitThread); + g_reinitThread = CreateThread(nullptr, 0, DeferredReinitThreadProc, nullptr, 0, nullptr); +} else { + Wh_Log(L"[STABLE] Reinit already pending, skipping duplicate"); + } } From c1f3332eb848fbf962a0042292664f9154dd01e4 Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Sat, 11 Jul 2026 09:23:07 +0200 Subject: [PATCH 12/25] Remove control panel applets feature and focus on fixing the Windows 11 23H2 network tray problem --- mods/settings-to-control-panel.wh.cpp | 837 +++++--------------------- 1 file changed, 139 insertions(+), 698 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index c36f506fb1..5c7f2b619c 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -2,7 +2,7 @@ // @id settings-to-control-panel // @name Redirect Settings to Control Panel // @description Forces classic Control Panel to open instead of Windows Settings and restores some classic CPL entries. -// @version 10.0.26 +// @version 10.0.27 // @author babamohammed // @github https://github.com/babamohammed2022 // @include explorer.exe @@ -32,7 +32,6 @@ The mod has been tested on Windows 10 21H2, Windows 10 1809, Windows 11 23H2 and - Anti-loop protection (stops windows from reopening endlessly) - Configurable fallback behavior for unmapped links - Tray menu detection (experimental) -- Restore Classic Control Panel Entries (credits to Anixx for the original implementation) --- ## Limitations @@ -44,7 +43,7 @@ The mod has been tested on Windows 10 21H2, Windows 10 1809, Windows 11 23H2 and ## Credits - m417z – Code reviews and feedback -- Anixx – Testing on Windows 11 23H2, the original toolbar subclassing approach and restoring control panel applets +- Anixx – Testing on Windows 11 23H2, the original toolbar subclassing approach - sebastian08dm08-cpu - Testing on Windows 10 1809 - dbilanoski – CLSID documentation */ @@ -87,14 +86,6 @@ The mod has been tested on Windows 10 21H2, Windows 10 1809, Windows 11 23H2 and - LegacyNameMappingFix: true $name: Legacy Name Mapping Fix (EXPERIMENTAL) $description: "Prevents Explorer from remapping classic Control Panel names to the modern Settings app." - -- enableClassicCpls: true - $name: Restore Classic Control Panel Entries - $description: "Restores the classic Personalization, Notification Area Icons, Network Connections, and Printers & Faxes sections to the Control Panel. Implementation by Anixx." - -- suppressCompanySync: true - $name: Hide Company Sync Entry - $description: "Suppresses the Company Sync namespace entry from the Control Panel. Implementation by Anixx." */ // ==/WindhawkModSettings== @@ -167,10 +158,13 @@ static bool g_taskbarListenerInstalled = false; static std::atomic g_modActive{false}; static std::mutex g_initMutex; static std::atomic g_reinitPending{false}; +static std::atomic g_unloading{false}; static constexpr DWORD TRAY_CONTEXT_MAX_AGE_MS = 1500; static bool g_pniduiHookInstalled = false; static std::mutex g_pniduiHookMutex; +static bool g_sndVolSSOHookInstalled = false; +static bool g_shell32DevicesHookInstalled = false; static HANDLE g_pniduiRetryThread = nullptr; static volatile bool g_pniduiRetryStop = false; static volatile bool g_pniduiRetryRunning = false; @@ -1091,627 +1085,6 @@ static ResolveResult ResolveUri(const std::wstring& uri, HWND hwnd) { return { L"", false }; } -// --------------------------------------------------------------------------- -// Integrated Restore Classic CPLs by Anixx, prefixed with RCPL_ -// --------------------------------------------------------------------------- - -struct RCPL_Settings { - // Consolidated single toggle. Previously this mod exposed 4 separate - // switches (Personalization / Notification Icons / Network Connections / - // Printers and Faxes). They all did the same kind of thing (restore a - // classic CPL entry), so they're now one simple on/off toggle, enabled - // by default. - std::atomic enableClassicCpls = true; - std::atomic suppressCompanySync = true; -} g_rcplSettings; - -static std::wstring g_rcplPersonalizationName; -static std::unordered_map g_rcplKeyPaths; -static std::unordered_set g_rcplFakeHandles; -static std::mutex g_rcplKeyPathsMutex; - -static std::wstring g_rcplPersonalizationGuidLower; -static std::wstring g_rcplNotificationIconsGuidLower; -static std::wstring g_rcplNetworkConnectionsGuidLower; -static std::wstring g_rcplPrintersAndFaxesGuidLower; -static std::wstring g_rcplSuppressedGuidLower; - -static const std::wstring RCPL_kPersonalizationGuid = L"{580722ff-16a7-44c1-bf74-7e1acd00f4f9}"; -static const std::wstring RCPL_kNotificationIconsGuid = L"{05d7b0f4-2121-4eff-bf6b-ed3f69b894d9}"; -static const std::wstring RCPL_kNetworkConnectionsGuid = L"{7007acc7-3202-11d1-aad2-00805fc1270e}"; -static const std::wstring RCPL_kPrintersAndFaxesGuid = L"{2227a280-3aea-1069-a2de-08002b30309d}"; -static const std::wstring RCPL_kSuppressedGuid = L"{98f2ab62-0e29-4e4c-8ee7-b542e66740b1}"; - -static const DWORD RCPL_kCategoryAppearance = 1; -static const DWORD RCPL_kCategoryHardware = 2; -static const DWORD RCPL_kCategoryNetwork = 3; - -static bool RCPL_IsPredefinedHkey(HKEY hKey) { - return hKey == HKEY_CLASSES_ROOT || hKey == HKEY_CURRENT_USER || - hKey == HKEY_LOCAL_MACHINE || hKey == HKEY_USERS || - hKey == HKEY_CURRENT_CONFIG; -} - -static bool RCPL_EndsWith(const std::wstring& str, const std::wstring& suffix) { - if (str.size() < suffix.size()) return false; - return str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; -} - -static bool RCPL_ContainsRelevantKeyword(const std::wstring& lowerPath) { - return lowerPath.find(L"clsid") != std::wstring::npos || - lowerPath.find(L"controlpanel") != std::wstring::npos; -} - -static void RCPL_LoadSettings() { - g_rcplSettings.enableClassicCpls.store(Wh_GetIntSetting(L"enableClassicCpls") != 0); - g_rcplSettings.suppressCompanySync.store(Wh_GetIntSetting(L"suppressCompanySync") != 0); -} - -static void RCPL_InitDisplayNames() { - wchar_t buffer[256] = { 0 }; - HMODULE hTheme = LoadLibraryExW(L"themecpl.dll", nullptr, - LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_SEARCH_SYSTEM32); - if (hTheme) { - if (LoadStringW(hTheme, 1, buffer, 256) && buffer[0]) - g_rcplPersonalizationName = buffer; - else - g_rcplPersonalizationName = L"Personalization"; - FreeLibrary(hTheme); - } else { - g_rcplPersonalizationName = L"Personalization"; - } - - g_rcplPersonalizationGuidLower = ToLower(RCPL_kPersonalizationGuid); - g_rcplNotificationIconsGuidLower = ToLower(RCPL_kNotificationIconsGuid); - g_rcplNetworkConnectionsGuidLower = ToLower(RCPL_kNetworkConnectionsGuid); - g_rcplPrintersAndFaxesGuidLower = ToLower(RCPL_kPrintersAndFaxesGuid); - g_rcplSuppressedGuidLower = ToLower(RCPL_kSuppressedGuid); -} - -static std::wstring RCPL_GetTrackedPath(HKEY hKey) { - if (!hKey) return L""; - if (hKey == HKEY_CLASSES_ROOT) return L"HKEY_CLASSES_ROOT"; - if (hKey == HKEY_CURRENT_USER) return L"HKEY_CURRENT_USER"; - if (hKey == HKEY_LOCAL_MACHINE) return L"HKEY_LOCAL_MACHINE"; - if (hKey == HKEY_USERS) return L"HKEY_USERS"; - if (hKey == HKEY_CURRENT_CONFIG) return L"HKEY_CURRENT_CONFIG"; - - std::lock_guard lock(g_rcplKeyPathsMutex); - auto it = g_rcplKeyPaths.find(hKey); - if (it != g_rcplKeyPaths.end()) return it->second; - return L""; -} - -static void RCPL_TrackKey(HKEY hKey, const std::wstring& path) { - if (!hKey || RCPL_IsPredefinedHkey(hKey)) return; - std::wstring lower = ToLower(path); - if (!RCPL_ContainsRelevantKeyword(lower)) return; - std::lock_guard lock(g_rcplKeyPathsMutex); - g_rcplKeyPaths[hKey] = path; -} - -static void RCPL_UntrackKey(HKEY hKey) { - if (!hKey || RCPL_IsPredefinedHkey(hKey)) return; - std::lock_guard lock(g_rcplKeyPathsMutex); - g_rcplKeyPaths.erase(hKey); -} - -static HKEY RCPL_CreateFakeHandle(const std::wstring& path) { - int* dummy = new int(1); - HKEY fake = (HKEY)dummy; - std::lock_guard lock(g_rcplKeyPathsMutex); - g_rcplKeyPaths[fake] = path; - g_rcplFakeHandles.insert(fake); - return fake; -} - -static void RCPL_FreeFakeHandle(HKEY hKey) { - std::lock_guard lock(g_rcplKeyPathsMutex); - if (g_rcplFakeHandles.count(hKey)) { - g_rcplFakeHandles.erase(hKey); - g_rcplKeyPaths.erase(hKey); - delete (int*)hKey; - } -} - -enum class RCPL_VNode { None, ClsidRoot, DefaultIcon, Shell, ShellOpen, OpenCommand, NameSpaceEntry, ClsidRootCategoryOnly, Suppressed }; -enum class RCPL_ItemKind { None, Personalization, CategoryOnly, Suppressed }; - -struct RCPL_ClassifyResult { - RCPL_VNode node; - RCPL_ItemKind kind; - DWORD category; -}; - -static bool RCPL_IsSuppressedNamespaceKey(const std::wstring& lower) { - if (!g_rcplSettings.suppressCompanySync.load()) return false; - return RCPL_EndsWith(lower, L"controlpanel\\namespace\\" + g_rcplSuppressedGuidLower); -} - -static bool RCPL_IsSuppressedNamespaceEntry(LPCWSTR name) { - if (!g_rcplSettings.suppressCompanySync.load() || !name) return false; - return ToLower(name) == g_rcplSuppressedGuidLower; -} - -static RCPL_ClassifyResult RCPL_ClassifyFullVirtual(const std::wstring& lower, const std::wstring& guidLower, RCPL_ItemKind kind) { - if (RCPL_EndsWith(lower, L"clsid\\" + guidLower)) return { RCPL_VNode::ClsidRoot, kind, 0 }; - if (RCPL_EndsWith(lower, L"clsid\\" + guidLower + L"\\defaulticon")) return { RCPL_VNode::DefaultIcon, kind, 0 }; - if (RCPL_EndsWith(lower, L"clsid\\" + guidLower + L"\\shell")) return { RCPL_VNode::Shell, kind, 0 }; - if (RCPL_EndsWith(lower, L"clsid\\" + guidLower + L"\\shell\\open")) return { RCPL_VNode::ShellOpen, kind, 0 }; - if (RCPL_EndsWith(lower, L"clsid\\" + guidLower + L"\\shell\\open\\command")) return { RCPL_VNode::OpenCommand, kind, 0 }; - if (RCPL_EndsWith(lower, L"controlpanel\\namespace\\" + guidLower)) return { RCPL_VNode::NameSpaceEntry, kind, 0 }; - return { RCPL_VNode::None, RCPL_ItemKind::None, 0 }; -} - -static RCPL_ClassifyResult RCPL_ClassifyPath(const std::wstring& path) { - std::wstring lower = ToLower(path); - if (!RCPL_ContainsRelevantKeyword(lower)) - return { RCPL_VNode::None, RCPL_ItemKind::None, 0 }; - - if (g_rcplSettings.suppressCompanySync.load()) { - if (RCPL_EndsWith(lower, L"clsid\\" + g_rcplSuppressedGuidLower) || - RCPL_EndsWith(lower, L"controlpanel\\namespace\\" + g_rcplSuppressedGuidLower)) - return { RCPL_VNode::Suppressed, RCPL_ItemKind::Suppressed, 0 }; - } - - if (g_rcplSettings.enableClassicCpls.load()) { - auto cr = RCPL_ClassifyFullVirtual(lower, g_rcplPersonalizationGuidLower, RCPL_ItemKind::Personalization); - if (cr.node != RCPL_VNode::None) return cr; - } - - if (g_rcplSettings.enableClassicCpls.load()) { - struct CatItem { const std::wstring* guidLower; DWORD category; } categoryItems[] = { - { &g_rcplNotificationIconsGuidLower, RCPL_kCategoryAppearance }, - { &g_rcplNetworkConnectionsGuidLower, RCPL_kCategoryNetwork }, - { &g_rcplPrintersAndFaxesGuidLower, RCPL_kCategoryHardware }, - }; - - for (auto& item : categoryItems) { - if (RCPL_EndsWith(lower, L"clsid\\" + *item.guidLower)) - return { RCPL_VNode::ClsidRootCategoryOnly, RCPL_ItemKind::CategoryOnly, item.category }; - } - } - - return { RCPL_VNode::None, RCPL_ItemKind::None, 0 }; -} - -static bool RCPL_IsTargetKey(const std::wstring& path) { - return RCPL_ClassifyPath(path).node != RCPL_VNode::None; -} - -static bool RCPL_IsNameSpaceParentKey(const std::wstring& path) { - return RCPL_EndsWith(ToLower(path), L"controlpanel\\namespace"); -} - -static LSTATUS RCPL_ProvideStringValue(LPBYTE lpData, LPDWORD lpcbData, const std::wstring& str) { - DWORD requiredSize = (DWORD)((str.length() + 1) * sizeof(wchar_t)); - if (!lpcbData) return ERROR_INVALID_PARAMETER; - if (!lpData || *lpcbData < requiredSize) { - *lpcbData = requiredSize; - return ERROR_MORE_DATA; - } - *lpcbData = requiredSize; - memcpy(lpData, str.c_str(), requiredSize); - return ERROR_SUCCESS; -} - -static LSTATUS RCPL_ProvideDwordValue(LPBYTE lpData, LPDWORD lpcbData, DWORD value) { - if (!lpcbData) return ERROR_INVALID_PARAMETER; - if (!lpData || *lpcbData < sizeof(DWORD)) { - *lpcbData = sizeof(DWORD); - return ERROR_MORE_DATA; - } - *lpcbData = sizeof(DWORD); - *(DWORD*)lpData = value; - return ERROR_SUCCESS; -} - -static bool RCPL_TryProvideValue(const std::wstring& path, const std::wstring& valueName, - LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData, LSTATUS& outStatus) { - RCPL_ClassifyResult cr = RCPL_ClassifyPath(path); - if (cr.node == RCPL_VNode::None) return false; - - if (cr.kind == RCPL_ItemKind::Suppressed) { - outStatus = ERROR_FILE_NOT_FOUND; - return true; - } - - if (cr.kind == RCPL_ItemKind::CategoryOnly) { - if (valueName == L"System.ControlPanel.Category") { - if (lpType) *lpType = REG_DWORD; - outStatus = RCPL_ProvideDwordValue(lpData, lpcbData, cr.category); - return true; - } - return false; - } - - if (cr.kind == RCPL_ItemKind::Personalization) { - if (cr.node == RCPL_VNode::NameSpaceEntry) { - if (valueName.empty()) { - if (lpType) *lpType = REG_SZ; - outStatus = RCPL_ProvideStringValue(lpData, lpcbData, g_rcplPersonalizationName); - return true; - } - } else if (cr.node == RCPL_VNode::ClsidRoot) { - if (valueName.empty()) { - if (lpType) *lpType = REG_SZ; - outStatus = RCPL_ProvideStringValue(lpData, lpcbData, g_rcplPersonalizationName); - return true; - } else if (valueName == L"InfoTip") { - if (lpType) *lpType = REG_SZ; - outStatus = RCPL_ProvideStringValue(lpData, lpcbData, L"@%SystemRoot%\\System32\\themecpl.dll,-2#immutable1"); - return true; - } else if (valueName == L"System.ApplicationName") { - if (lpType) *lpType = REG_SZ; - outStatus = RCPL_ProvideStringValue(lpData, lpcbData, L"Microsoft.Personalization"); - return true; - } else if (valueName == L"System.ControlPanel.Category") { - if (lpType) *lpType = REG_DWORD; - outStatus = RCPL_ProvideDwordValue(lpData, lpcbData, RCPL_kCategoryAppearance); - return true; - } else if (valueName == L"System.Software.TasksFileUrl") { - if (lpType) *lpType = REG_SZ; - outStatus = RCPL_ProvideStringValue(lpData, lpcbData, L"Internal"); - return true; - } - } else if (cr.node == RCPL_VNode::DefaultIcon) { - if (valueName.empty()) { - if (lpType) *lpType = REG_SZ; - outStatus = RCPL_ProvideStringValue(lpData, lpcbData, L"%SystemRoot%\\System32\\themecpl.dll,-1"); - return true; - } - } else if (cr.node == RCPL_VNode::OpenCommand) { - if (valueName.empty()) { - if (lpType) *lpType = REG_SZ; - outStatus = RCPL_ProvideStringValue(lpData, lpcbData, - L"explorer shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921}"); - return true; - } - } - } - - return false; -} - -static std::vector RCPL_GetNamespaceClsids() { - std::vector result; - if (g_rcplSettings.enableClassicCpls.load()) { - result.push_back(RCPL_kPersonalizationGuid); - result.push_back(RCPL_kNotificationIconsGuid); - result.push_back(RCPL_kNetworkConnectionsGuid); - result.push_back(RCPL_kPrintersAndFaxesGuid); - } - return result; -} - -static bool RCPL_GetVirtualSubKeyName(RCPL_VNode node, DWORD index, std::wstring& outName) { - switch (node) { - case RCPL_VNode::ClsidRoot: - if (index == 0) { outName = L"DefaultIcon"; return true; } - if (index == 1) { outName = L"Shell"; return true; } - return false; - case RCPL_VNode::Shell: - if (index == 0) { outName = L"Open"; return true; } - return false; - case RCPL_VNode::ShellOpen: - if (index == 0) { outName = L"command"; return true; } - return false; - default: - return false; - } -} - -using RCPL_RegOpenKeyExW_t = decltype(&RegOpenKeyExW); -using RCPL_RegCloseKey_t = decltype(&RegCloseKey); -using RCPL_RegQueryValueExW_t = decltype(&RegQueryValueExW); -using RCPL_RegGetValueW_t = decltype(&RegGetValueW); -using RCPL_RegEnumKeyExW_t = decltype(&RegEnumKeyExW); -using RCPL_RegEnumKeyW_t = decltype(&RegEnumKeyW); - -static RCPL_RegOpenKeyExW_t RCPL_RegOpenKeyExWOriginal = nullptr; -static RCPL_RegCloseKey_t RCPL_RegCloseKeyOriginal = nullptr; -static RCPL_RegQueryValueExW_t RCPL_RegQueryValueExWOriginal = nullptr; -static RCPL_RegGetValueW_t RCPL_RegGetValueWOriginal = nullptr; -static RCPL_RegEnumKeyExW_t RCPL_RegEnumKeyExWOriginal = nullptr; -static RCPL_RegEnumKeyW_t RCPL_RegEnumKeyWOriginal = nullptr; - -static LSTATUS WINAPI RCPL_RegOpenKeyExWHook(HKEY hKey, LPCWSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult) { - bool parentIsFake = false; - std::wstring parentPath; - { - std::lock_guard lock(g_rcplKeyPathsMutex); - if (g_rcplFakeHandles.count(hKey)) { - parentIsFake = true; - auto it = g_rcplKeyPaths.find(hKey); - if (it != g_rcplKeyPaths.end()) parentPath = it->second; - } - } - - if (parentIsFake) { - std::wstring fullPath = parentPath; - if (lpSubKey && *lpSubKey) { - if (!fullPath.empty()) fullPath += L"\\"; - fullPath += lpSubKey; - } - if (RCPL_IsTargetKey(fullPath)) { - HKEY fake = RCPL_CreateFakeHandle(fullPath); - if (phkResult) *phkResult = fake; - return ERROR_SUCCESS; - } - return ERROR_FILE_NOT_FOUND; - } - - if (g_rcplSettings.suppressCompanySync.load() && lpSubKey) { - std::wstring basePath = RCPL_GetTrackedPath(hKey); - std::wstring fullPath = basePath; - if (*lpSubKey) { - if (!fullPath.empty()) fullPath += L"\\"; - fullPath += lpSubKey; - } - if (RCPL_IsSuppressedNamespaceKey(ToLower(fullPath))) return ERROR_FILE_NOT_FOUND; - } - - LSTATUS status = RCPL_RegOpenKeyExWOriginal(hKey, lpSubKey, ulOptions, samDesired, phkResult); - if (status == ERROR_SUCCESS && phkResult && *phkResult) { - std::wstring basePath = RCPL_GetTrackedPath(hKey); - std::wstring fullPath = basePath; - if (lpSubKey && *lpSubKey) { - if (!fullPath.empty()) fullPath += L"\\"; - fullPath += lpSubKey; - } - RCPL_TrackKey(*phkResult, fullPath); - } else if (status == ERROR_FILE_NOT_FOUND && phkResult) { - std::wstring basePath = RCPL_GetTrackedPath(hKey); - std::wstring fullPath = basePath; - if (lpSubKey && *lpSubKey) { - if (!fullPath.empty()) fullPath += L"\\"; - fullPath += lpSubKey; - } - if (RCPL_IsTargetKey(fullPath)) { - HKEY fake = RCPL_CreateFakeHandle(fullPath); - *phkResult = fake; - return ERROR_SUCCESS; - } - } - return status; -} - -static LSTATUS WINAPI RCPL_RegCloseKeyHook(HKEY hKey) { - bool isFake = false; - { std::lock_guard lock(g_rcplKeyPathsMutex); isFake = g_rcplFakeHandles.count(hKey) > 0; } - if (isFake) { RCPL_FreeFakeHandle(hKey); return ERROR_SUCCESS; } - LSTATUS status = RCPL_RegCloseKeyOriginal(hKey); - RCPL_UntrackKey(hKey); - return status; -} - -static LSTATUS WINAPI RCPL_RegQueryValueExWHook(HKEY hKey, LPCWSTR lpValueName, LPDWORD lpReserved, - LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData) { - std::wstring path = RCPL_GetTrackedPath(hKey); - if (!path.empty()) { - std::wstring valueName = lpValueName ? lpValueName : L""; - LSTATUS outStatus; - if (RCPL_TryProvideValue(path, valueName, lpType, lpData, lpcbData, outStatus)) return outStatus; - } - bool isFake = false; - { std::lock_guard lock(g_rcplKeyPathsMutex); isFake = g_rcplFakeHandles.count(hKey) > 0; } - if (isFake) return ERROR_FILE_NOT_FOUND; - return RCPL_RegQueryValueExWOriginal(hKey, lpValueName, lpReserved, lpType, lpData, lpcbData); -} - -static LSTATUS WINAPI RCPL_RegGetValueWHook(HKEY hkey, LPCWSTR lpSubKey, LPCWSTR lpValue, - DWORD dwFlags, LPDWORD pdwType, PVOID pvData, LPDWORD pcbData) { - std::wstring path = RCPL_GetTrackedPath(hkey); - if (lpSubKey && *lpSubKey) { if (!path.empty()) path += L"\\"; path += lpSubKey; } - if (!path.empty()) { - std::wstring valueName = lpValue ? lpValue : L""; - LSTATUS outStatus; - if (RCPL_TryProvideValue(path, valueName, pdwType, (LPBYTE)pvData, pcbData, outStatus)) return outStatus; - } - return RCPL_RegGetValueWOriginal(hkey, lpSubKey, lpValue, dwFlags, pdwType, pvData, pcbData); -} - -static LSTATUS WINAPI RCPL_RegEnumKeyExWHook(HKEY hKey, DWORD dwIndex, LPWSTR lpName, LPDWORD lpcchName, - LPDWORD lpReserved, LPWSTR lpClass, LPDWORD lpcchClass, - PFILETIME lpftLastWriteTime) { - bool isFake = false; - { std::lock_guard lock(g_rcplKeyPathsMutex); isFake = g_rcplFakeHandles.count(hKey) > 0; } - - if (isFake) { - std::wstring path = RCPL_GetTrackedPath(hKey); - RCPL_ClassifyResult cr = RCPL_ClassifyPath(path); - std::wstring subName; - if (!RCPL_GetVirtualSubKeyName(cr.node, dwIndex, subName)) return ERROR_NO_MORE_ITEMS; - if (lpcchName && *lpcchName < subName.size() + 1) { - *lpcchName = (DWORD)(subName.size() + 1); - return ERROR_MORE_DATA; - } - if (lpName) wcscpy_s(lpName, subName.size() + 1, subName.c_str()); - if (lpcchName) *lpcchName = (DWORD)subName.size(); - if (lpftLastWriteTime) GetSystemTimeAsFileTime(lpftLastWriteTime); - return ERROR_SUCCESS; - } - - std::wstring path = RCPL_GetTrackedPath(hKey); - bool isNamespace = RCPL_IsNameSpaceParentKey(path); - - if (isNamespace && g_rcplSettings.suppressCompanySync.load()) { - DWORD targetVirtualIndex = dwIndex; - DWORD realIndex = 0; - DWORD foundCount = 0; - LSTATUS status; - wchar_t nameBuf[256]; - DWORD origCap = lpcchName ? *lpcchName : 256; - - while (true) { - LPWSTR namePtr = lpName ? lpName : nameBuf; - DWORD cch = origCap; - if (lpcchName) *lpcchName = origCap; - status = RCPL_RegEnumKeyExWOriginal(hKey, realIndex, namePtr, lpcchName ? lpcchName : &cch, - lpReserved, lpClass, lpcchClass, lpftLastWriteTime); - if (status != ERROR_SUCCESS) break; - if (!RCPL_IsSuppressedNamespaceEntry(namePtr)) { - if (foundCount == targetVirtualIndex) return ERROR_SUCCESS; - foundCount++; - } - realIndex++; - } - - if (status == ERROR_NO_MORE_ITEMS) { - std::vector clsids = RCPL_GetNamespaceClsids(); - DWORD injectedIndex = dwIndex - foundCount; - if (injectedIndex >= clsids.size()) return ERROR_NO_MORE_ITEMS; - const wchar_t* clsid = clsids[injectedIndex].c_str(); - size_t len = wcslen(clsid); - if (lpcchName && *lpcchName < len + 1) { - *lpcchName = (DWORD)(len + 1); - return ERROR_MORE_DATA; - } - if (lpName && lpcchName) wcscpy_s(lpName, *lpcchName, clsid); - if (lpcchName) *lpcchName = (DWORD)len; - if (lpftLastWriteTime) GetSystemTimeAsFileTime(lpftLastWriteTime); - return ERROR_SUCCESS; - } - return status; - } - - LSTATUS status = RCPL_RegEnumKeyExWOriginal(hKey, dwIndex, lpName, lpcchName, - lpReserved, lpClass, lpcchClass, lpftLastWriteTime); - if (isNamespace && status == ERROR_NO_MORE_ITEMS) { - std::vector clsids = RCPL_GetNamespaceClsids(); - DWORD realCount = 0; - wchar_t tmpBuf[256]; - DWORD tmpCch; - while (true) { - tmpCch = 256; - if (RCPL_RegEnumKeyExWOriginal(hKey, realCount, tmpBuf, &tmpCch, nullptr, nullptr, nullptr, nullptr) != ERROR_SUCCESS) - break; - realCount++; - } - DWORD injectedIndex = dwIndex - realCount; - if (injectedIndex >= clsids.size()) return ERROR_NO_MORE_ITEMS; - const wchar_t* clsid = clsids[injectedIndex].c_str(); - size_t len = wcslen(clsid); - if (lpcchName && *lpcchName < len + 1) { - *lpcchName = (DWORD)(len + 1); - return ERROR_MORE_DATA; - } - if (lpName && lpcchName) wcscpy_s(lpName, *lpcchName, clsid); - if (lpcchName) *lpcchName = (DWORD)len; - if (lpftLastWriteTime) GetSystemTimeAsFileTime(lpftLastWriteTime); - return ERROR_SUCCESS; - } - return status; -} - -static LSTATUS WINAPI RCPL_RegEnumKeyWHook(HKEY hKey, DWORD dwIndex, LPWSTR lpName, DWORD cchName) { - bool isFake = false; - { std::lock_guard lock(g_rcplKeyPathsMutex); isFake = g_rcplFakeHandles.count(hKey) > 0; } - - if (isFake) { - std::wstring path = RCPL_GetTrackedPath(hKey); - RCPL_ClassifyResult cr = RCPL_ClassifyPath(path); - std::wstring subName; - if (!RCPL_GetVirtualSubKeyName(cr.node, dwIndex, subName)) return ERROR_NO_MORE_ITEMS; - if (cchName <= subName.size()) return ERROR_MORE_DATA; - wcscpy_s(lpName, cchName, subName.c_str()); - return ERROR_SUCCESS; - } - - std::wstring path = RCPL_GetTrackedPath(hKey); - bool isNamespace = RCPL_IsNameSpaceParentKey(path); - - if (isNamespace && g_rcplSettings.suppressCompanySync.load()) { - DWORD targetVirtualIndex = dwIndex; - DWORD realIndex = 0; - DWORD foundCount = 0; - LSTATUS status; - wchar_t nameBuf[256]; - while (true) { - status = RCPL_RegEnumKeyWOriginal(hKey, realIndex, lpName ? lpName : nameBuf, cchName ? cchName : 256); - if (status != ERROR_SUCCESS) break; - if (!RCPL_IsSuppressedNamespaceEntry(lpName ? lpName : nameBuf)) { - if (foundCount == targetVirtualIndex) return ERROR_SUCCESS; - foundCount++; - } - realIndex++; - } - if (status == ERROR_NO_MORE_ITEMS) { - std::vector clsids = RCPL_GetNamespaceClsids(); - DWORD injectedIndex = dwIndex - foundCount; - if (injectedIndex >= clsids.size()) return ERROR_NO_MORE_ITEMS; - const wchar_t* clsid = clsids[injectedIndex].c_str(); - size_t len = wcslen(clsid); - if (cchName <= len) return ERROR_MORE_DATA; - if (lpName) wcscpy_s(lpName, cchName, clsid); - return ERROR_SUCCESS; - } - return status; - } - - LSTATUS status = RCPL_RegEnumKeyWOriginal(hKey, dwIndex, lpName, cchName); - if (isNamespace && status == ERROR_NO_MORE_ITEMS) { - std::vector clsids = RCPL_GetNamespaceClsids(); - DWORD realCount = 0; - wchar_t tmpBuf[256]; - while (RCPL_RegEnumKeyWOriginal(hKey, realCount, tmpBuf, 256) == ERROR_SUCCESS) - realCount++; - DWORD injectedIndex = dwIndex - realCount; - if (injectedIndex >= clsids.size()) return ERROR_NO_MORE_ITEMS; - const wchar_t* clsid = clsids[injectedIndex].c_str(); - size_t len = wcslen(clsid); - if (cchName <= len) return ERROR_MORE_DATA; - if (lpName) wcscpy_s(lpName, cchName, clsid); - return ERROR_SUCCESS; - } - return status; -} - -static void* RCPL_GetRegFunc(const char* name) { - HMODULE hKb = GetModuleHandleW(L"kernelbase.dll"); - if (hKb) { void* p = (void*)GetProcAddress(hKb, name); if (p) return p; } - HMODULE hAdv = GetModuleHandleW(L"advapi32.dll"); - if (!hAdv) hAdv = LoadLibraryW(L"advapi32.dll"); - if (hAdv) { void* p = (void*)GetProcAddress(hAdv, name); if (p) return p; } - return nullptr; -} - -static bool RCPL_InstallRegistryHooks() { - RCPL_LoadSettings(); - RCPL_InitDisplayNames(); - - void* pRegOpenKeyExW = RCPL_GetRegFunc("RegOpenKeyExW"); - void* pRegCloseKey = RCPL_GetRegFunc("RegCloseKey"); - void* pRegQueryValueExW = RCPL_GetRegFunc("RegQueryValueExW"); - void* pRegGetValueW = RCPL_GetRegFunc("RegGetValueW"); - void* pRegEnumKeyExW = RCPL_GetRegFunc("RegEnumKeyExW"); - void* pRegEnumKeyW = RCPL_GetRegFunc("RegEnumKeyW"); - - if (!pRegOpenKeyExW || !pRegCloseKey || !pRegQueryValueExW || !pRegGetValueW || !pRegEnumKeyExW || !pRegEnumKeyW) { - Wh_Log(L"[RCPL] Failed to get one or more registry functions"); - return false; - } - - if (!Wh_SetFunctionHook(pRegOpenKeyExW, (void*)RCPL_RegOpenKeyExWHook, (void**)&RCPL_RegOpenKeyExWOriginal)) { Wh_Log(L"[RCPL] Failed to hook RegOpenKeyExW"); return false; } - if (!Wh_SetFunctionHook(pRegCloseKey, (void*)RCPL_RegCloseKeyHook, (void**)&RCPL_RegCloseKeyOriginal)) { Wh_Log(L"[RCPL] Failed to hook RegCloseKey"); return false; } - if (!Wh_SetFunctionHook(pRegQueryValueExW, (void*)RCPL_RegQueryValueExWHook, (void**)&RCPL_RegQueryValueExWOriginal)) { Wh_Log(L"[RCPL] Failed to hook RegQueryValueExW"); return false; } - if (!Wh_SetFunctionHook(pRegGetValueW, (void*)RCPL_RegGetValueWHook, (void**)&RCPL_RegGetValueWOriginal)) { Wh_Log(L"[RCPL] Failed to hook RegGetValueW"); return false; } - if (!Wh_SetFunctionHook(pRegEnumKeyExW, (void*)RCPL_RegEnumKeyExWHook, (void**)&RCPL_RegEnumKeyExWOriginal)) { Wh_Log(L"[RCPL] Failed to hook RegEnumKeyExW"); return false; } - if (!Wh_SetFunctionHook(pRegEnumKeyW, (void*)RCPL_RegEnumKeyWHook, (void**)&RCPL_RegEnumKeyWOriginal)) { Wh_Log(L"[RCPL] Failed to hook RegEnumKeyW"); return false; } - - Wh_Log(L"[RCPL] Classic CPL registry virtualization hooks installed"); - return true; -} - -static void RCPL_Cleanup() { - std::lock_guard lock(g_rcplKeyPathsMutex); - for (HKEY fake : g_rcplFakeHandles) - delete (int*)fake; - g_rcplFakeHandles.clear(); - g_rcplKeyPaths.clear(); - Wh_Log(L"[RCPL] Cleanup completed"); -} - // --------------------------------------------------------------------------- // Experimental COM activation hook // --------------------------------------------------------------------------- @@ -2022,6 +1395,7 @@ static bool TryInstallPniduiHook() { } } + // pnidui.dll WindhawkUtils::SYMBOL_HOOK pnidui_dll_hooks[] = {{ { L"bool " @@ -2052,44 +1426,52 @@ static DWORD WINAPI PniduiRetryThread(LPVOID) { if (g_pniduiRetryRunning) return 0; g_pniduiRetryRunning = true; - const int MAX_WAIT_CHECKS = 60; - const DWORD CHECK_INTERVAL = 500; + // Attesa in step da 50ms invece di 500ms: rende il thread reattivo + // allo stop quasi istantaneamente, evitando che Wh_ModUninit debba + // ricorrere a TerminateThread() (causa nota di instabilità di explorer.exe). + const int MAX_WAIT_MS = 30000; + const DWORD STEP_MS = 50; bool dllLoaded = false; - for (int i = 0; i < MAX_WAIT_CHECKS && !g_pniduiRetryStop; i++) { + for (int waited = 0; waited < MAX_WAIT_MS && !g_pniduiRetryStop && !g_unloading.load(); waited += STEP_MS) { if (GetModuleHandleW(L"pnidui.dll") != nullptr) { dllLoaded = true; break; } - Sleep(CHECK_INTERVAL); + Sleep(STEP_MS); } - if (dllLoaded) TryInstallPniduiHook(); + if (dllLoaded && !g_pniduiRetryStop && !g_unloading.load()) TryInstallPniduiHook(); g_pniduiRetryRunning = false; return 0; } static void InstallImmersiveMenuHooks() { - struct DllHook { const wchar_t* dll; ICMH_CAODTM_t* orig; } targets[] = { - { L"SndVolSSO.dll", &g_icmhOrig_SndVolSSO }, - }; - - for (auto& t : targets) { - HMODULE hMod = LoadLibraryExW(t.dll, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); - if (!hMod) continue; - - WindhawkUtils::SYMBOL_HOOK hooks[] = {{ - { - L"bool " + // NOTA IMPORTANTE: WindhawkUtils::HookSymbols() non va richiamato una seconda + // volta sullo stesso modulo già hookato: pnidui.dll/SndVolSSO.dll/shell32.dll + // restano residenti per tutta la vita del processo explorer.exe anche quando + // il tray/toolbar viene ricreato (solo le finestre vengono ricreate, non i + // moduli). Ri-tentare l'hook in quei casi non solo è inutile, ma può + // fallire o corrompere il puntatore alla funzione originale già salvato, + // rompendo un redirect che fino a quel momento funzionava. Per questo ogni + // hook qui è protetto da un proprio flag "già installato". + if (!g_sndVolSSOHookInstalled) { + HMODULE hMod = LoadLibraryExW(L"SndVolSSO.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (hMod) { + WindhawkUtils::SYMBOL_HOOK hooks[] = {{ + { + L"bool " #ifdef _WIN64 - L"__cdecl" + L"__cdecl" #else - L"__stdcall" + L"__stdcall" #endif - L" ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu" - L"(struct HMENU__ *,struct HWND__* )" - }, - (void**)t.orig, - (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, - false - }}; - WindhawkUtils::HookSymbols(hMod, hooks, 1); + L" ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu" + L"(struct HMENU__ *,struct HWND__* )" + }, + (void**)&g_icmhOrig_SndVolSSO, + (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, + false + }}; + if (WindhawkUtils::HookSymbols(hMod, hooks, 1)) + g_sndVolSSOHookInstalled = true; + } } if (!TryInstallPniduiHook()) { @@ -2099,7 +1481,7 @@ static void InstallImmersiveMenuHooks() { } } - if (g_isWin11) { + if (g_isWin11 && !g_shell32DevicesHookInstalled) { HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); if (hShell32) { WindhawkUtils::SYMBOL_HOOK shell32_dll_hooks[] = {{ @@ -2117,7 +1499,8 @@ static void InstallImmersiveMenuHooks() { (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, false }}; - WindhawkUtils::HookSymbols(hShell32, shell32_dll_hooks, 1); + if (WindhawkUtils::HookSymbols(hShell32, shell32_dll_hooks, 1)) + g_shell32DevicesHookInstalled = true; } } } @@ -2149,21 +1532,50 @@ static void ReinitializeTrayRedirect() { Wh_Log(L"[TRAY-RESTART] Reinitializing tray redirect hooks"); RemoveTraySubclass(); - g_sndVolSSOBase = nullptr; - g_sndVolSSOEnd = nullptr; - g_pniduiBase = nullptr; - g_pniduiEnd = nullptr; - { - std::lock_guard lk(g_pniduiHookMutex); - g_pniduiHookInstalled = false; + + // pnidui.dll/SndVolSSO.dll/shell32.dll restano residenti per tutta la vita + // del processo explorer.exe: un riavvio del tray/toolbar ricrea solo le + // finestre (Shell_TrayWnd/SysPager/ToolbarWindow32), non le DLL. Prima si + // azzerava incondizionatamente lo stato dell'hook pnidui a ogni riavvio, + // forzando un secondo WindhawkUtils::HookSymbols() sullo stesso modulo + // già hookato — cosa che Windhawk non supporta e che rompeva il redirect + // che fino a quel momento funzionava (da qui la dipendenza "goffa" dal + // momento di attivazione del mod). Ora l'hook viene reinstallato solo se + // il modulo è sparito o è stato ricaricato a un indirizzo diverso. + bool pniduiModuleChanged = false; + HMODULE hPniduiNow = GetModuleHandleW(L"pnidui.dll"); + if (hPniduiNow) { + MODULEINFO mi{}; + if (GetModuleInformation(GetCurrentProcess(), hPniduiNow, &mi, sizeof(mi))) { + if ((BYTE*)mi.lpBaseOfDll != g_pniduiBase) pniduiModuleChanged = true; + } + } else if (g_pniduiBase != nullptr) { + pniduiModuleChanged = true; + } + + if (pniduiModuleChanged) { + g_pniduiBase = nullptr; + g_pniduiEnd = nullptr; + { + std::lock_guard lk(g_pniduiHookMutex); + g_pniduiHookInstalled = false; + } } + if (g_settings.redirectSystemTray) SetupTraySubclass(); - if (!TryInstallPniduiHook()) { - if (!g_pniduiRetryRunning && !g_pniduiRetryThread) { - g_pniduiRetryStop = false; - g_pniduiRetryThread = CreateThread(nullptr, 0, PniduiRetryThread, nullptr, 0, nullptr); + + if (!g_pniduiHookInstalled) { + if (!TryInstallPniduiHook()) { + if (!g_pniduiRetryRunning && !g_pniduiRetryThread) { + g_pniduiRetryStop = false; + g_pniduiRetryThread = CreateThread(nullptr, 0, PniduiRetryThread, nullptr, 0, nullptr); + } } } + + // SndVolSSO/shell32 sono già protetti dai rispettivi flag "già installato" + // dentro InstallImmersiveMenuHooks(), quindi è sicuro richiamarla sempre: + // farà solo lavoro utile se manca ancora qualcosa da hookare. InstallImmersiveMenuHooks(); } @@ -2219,9 +1631,16 @@ static DWORD WINAPI TraySubclassWatchdogThread(LPVOID) { const DWORD FAST_INTERVAL_MS = 500; const DWORD SLOW_INTERVAL_MS = 3000; int tick = 0; - - // Aspetta che il sistema si stabilizzi all'avvio - Sleep(2000); + + // Aspetta che il sistema si stabilizzi all'avvio, ma in modo interrompibile: + // uno Sleep() bloccante qui impedirebbe a Wh_ModUninit di terminare questo + // thread in tempo, forzando una TerminateThread() che può destabilizzare + // explorer.exe (causa del riavvio della shell durante la ricompilazione). + if (g_hWatchdogStopEvent) { + WaitForSingleObject(g_hWatchdogStopEvent, 2000); + } else { + for (int i = 0; i < 40 && !g_traySubclassWatchdogStop; i++) Sleep(50); + } while (!g_traySubclassWatchdogStop) { DWORD waitTime = tick < FAST_PHASE_CHECKS ? FAST_INTERVAL_MS : SLOW_INTERVAL_MS; @@ -2342,7 +1761,7 @@ HWND WINAPI CreateWindowExW_Hook(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR l // --------------------------------------------------------------------------- BOOL Wh_ModInit() { - Wh_Log(L"Redirect Settings to Control Panel + Restore Classic CPLs v10.0.26-stable"); + Wh_Log(L"Redirect Settings to Control Panel initialized"); // NUOVO: Verifica se siamo nel processo explorer.exe corretto (quello che possiede la Shell_TrayWnd) // Se non lo siamo, rifiuta l'inizializzazione per evitare conflitti tra processi fantasma @@ -2361,7 +1780,9 @@ BOOL Wh_ModInit() { // Se Shell_TrayWnd non esiste affatto, procediamo comunque (potrebbe non essere ancora creata) std::lock_guard lock(g_initMutex); - + + g_unloading.store(false); + // Inizializzazione di base DetectWindowsVersion(); LoadSettings(); @@ -2399,10 +1820,7 @@ BOOL Wh_ModInit() { // CreateWindowEx hook per tray Wh_SetFunctionHook((void*)CreateWindowExW, (void*)CreateWindowExW_Hook, (void**)&CreateWindowExW_Original); - // RCPL hooks - if (!g_rcplHooksInstalled) { - g_rcplHooksInstalled = RCPL_InstallRegistryHooks(); - } + // Tray subsystem if (g_settings.redirectSystemTray) { @@ -2450,7 +1868,12 @@ BOOL Wh_ModInit() { } void Wh_ModUninit() { Wh_Log(L"[STABLE] Uninit started"); - + + // 0. Segnala a tutti i thread in background che il mod sta per scaricarsi, + // così le loro attese cooperative terminano subito invece di forzare + // TerminateThread() più avanti (causa nota di instabilità di explorer.exe). + g_unloading.store(true); + // 1. Disabilita subito il mod per bloccare nuove chiamate g_modActive.store(false); @@ -2522,8 +1945,7 @@ void Wh_ModUninit() { // 7. Rimuovi subclass della tray RemoveTraySubclass(); - // 8. Pulisci RCPL - RCPL_Cleanup(); + g_rcplHooksInstalled = false; Wh_Log(L"[STABLE] Uninit completed"); @@ -2541,18 +1963,34 @@ static void SafeReinitializeAll() { // Pulisci stato tray senza rimuovere gli hook di sistema RemoveTraySubclass(); - g_sndVolSSOBase = nullptr; - g_sndVolSSOEnd = nullptr; - g_pniduiBase = nullptr; - g_pniduiEnd = nullptr; - { - std::lock_guard lk(g_pniduiHookMutex); - g_pniduiHookInstalled = false; + + // Vedi il commento in ReinitializeTrayRedirect(): pnidui.dll resta + // residente tra un cambio di impostazioni e l'altro, quindi non va + // ri-hookato incondizionatamente (Windhawk non supporta un secondo + // HookSymbols sullo stesso modulo). Invalidiamo solo se è realmente + // sparito o è stato ricaricato a un indirizzo diverso. + bool pniduiModuleChanged = false; + HMODULE hPniduiNow = GetModuleHandleW(L"pnidui.dll"); + if (hPniduiNow) { + MODULEINFO mi{}; + if (GetModuleInformation(GetCurrentProcess(), hPniduiNow, &mi, sizeof(mi))) { + if ((BYTE*)mi.lpBaseOfDll != g_pniduiBase) pniduiModuleChanged = true; + } + } else if (g_pniduiBase != nullptr) { + pniduiModuleChanged = true; + } + + if (pniduiModuleChanged) { + g_pniduiBase = nullptr; + g_pniduiEnd = nullptr; + { + std::lock_guard lk(g_pniduiHookMutex); + g_pniduiHookInstalled = false; + } } // Ricarica tutto lo stato LoadSettings(); - RCPL_LoadSettings(); BuildChildEnvironment(); InitMappings(); @@ -2580,10 +2018,13 @@ static void SafeReinitializeAll() { } static DWORD WINAPI DeferredReinitThreadProc(LPVOID) { Wh_Log(L"[STABLE] Deferred reinit thread started"); - - // Aspetta 500ms per sicurezza (chiamate pendenti) - Sleep(500); - + for (int i = 0; i < 10 && !g_unloading.load(); i++) Sleep(50); + + if (g_unloading.load()) { + g_reinitPending.store(false); + return 0; + } + // Reinizializza SafeReinitializeAll(); @@ -2597,9 +2038,9 @@ void Wh_ModSettingsChanged() { Wh_Log(L"[STABLE] Settings changed - scheduling safe reinit"); if (!g_reinitPending.exchange(true)) { - if (g_reinitThread) CloseHandle(g_reinitThread); - g_reinitThread = CreateThread(nullptr, 0, DeferredReinitThreadProc, nullptr, 0, nullptr); -} else { + if (g_reinitThread) CloseHandle(g_reinitThread); + g_reinitThread = CreateThread(nullptr, 0, DeferredReinitThreadProc, nullptr, 0, nullptr); + } else { Wh_Log(L"[STABLE] Reinit already pending, skipping duplicate"); } } From 1a0a9c4edd3f6fcd10e4ef297d5f532020c3a881 Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Sat, 11 Jul 2026 09:25:43 +0200 Subject: [PATCH 13/25] Fix validation issue --- mods/settings-to-control-panel.wh.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index 5c7f2b619c..8f90d39908 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -1395,7 +1395,6 @@ static bool TryInstallPniduiHook() { } } - // pnidui.dll WindhawkUtils::SYMBOL_HOOK pnidui_dll_hooks[] = {{ { L"bool " @@ -1454,7 +1453,7 @@ static void InstallImmersiveMenuHooks() { if (!g_sndVolSSOHookInstalled) { HMODULE hMod = LoadLibraryExW(L"SndVolSSO.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if (hMod) { - WindhawkUtils::SYMBOL_HOOK hooks[] = {{ + WindhawkUtils::SYMBOL_HOOK sndVolSSODllHooks[] = {{ { L"bool " #ifdef _WIN64 @@ -1469,7 +1468,7 @@ static void InstallImmersiveMenuHooks() { (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, false }}; - if (WindhawkUtils::HookSymbols(hMod, hooks, 1)) + if (WindhawkUtils::HookSymbols(hMod, sndVolSSODllHooks, 1)) g_sndVolSSOHookInstalled = true; } } From dfc8ba772c7b51988899a216b4fa8e23d5aa3236 Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Sat, 11 Jul 2026 13:58:32 +0200 Subject: [PATCH 14/25] Update the README about the Windhawk "system instability" message and revert the changes - Removed unnecessary features --- mods/settings-to-control-panel.wh.cpp | 1577 ++++++++----------------- 1 file changed, 486 insertions(+), 1091 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index 8f90d39908..e594a2fcf2 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -1,17 +1,16 @@ // ==WindhawkMod== // @id settings-to-control-panel // @name Redirect Settings to Control Panel -// @description Forces classic Control Panel to open instead of Windows Settings and restores some classic CPL entries. -// @version 10.0.27 +// @description Forces classic Control Panel to open instead of Windows 10/11 Settings app using native components. Primarily designed for Windows 10; Windows 11 support is limited due to Microsoft's shell architecture changes. +// @version 10.0.28 // @author babamohammed // @github https://github.com/babamohammed2022 // @include explorer.exe // @compilerOptions -lcomctl32 -lpsapi -lole32 // ==/WindhawkMod== - // ==WindhawkModReadme== /* -# Redirect Settings → Control Panel +# Redirect Settings → Control Panel This mod intercepts modern `ms-settings:` links (the ones that open the Settings app) and redirects them to their corresponding classic Control @@ -23,76 +22,62 @@ Panel pages, using only native Windows components. - **Windows 10** – Mostly complete support - **Windows 11** – Partial support -The mod has been tested on Windows 10 21H2, Windows 10 1809, Windows 11 23H2 and Windows 11 24H2. + --- ## Features -- Redirects many `ms-settings:` links to the classic Control Panel where possible +- Redirects many `ms-settings:` links to the classic Control Panel - Anti-loop protection (stops windows from reopening endlessly) - Configurable fallback behavior for unmapped links - Tray menu detection (experimental) ---- ## Limitations - The system tray context menu redirect only supports the Win32 taskbar (the one from Windows 10 and previous versions). If using Windows 11, it might function decently but it is still an experimental feature. - The device & printers system tray redirect may not work on some Windows 11 configurations, as Microsoft hardcoded the redirect to the Settings app in certain shell code paths. This could change in future if correct documentation is found. -- The redirect for the network connection menu in the system tray might not work on very specific taskbar configurations. +- On some systems, Windhawk's "system instability" warning may briefly appear on the + first tray redirect after an Explorer restart. This does not affect functionality + and the redirect completes normally; it appears to be related to the burst of + window creation during Explorer's startup rather than an actual crash or hang. --- ## Credits - m417z – Code reviews and feedback -- Anixx – Testing on Windows 11 23H2, the original toolbar subclassing approach -- sebastian08dm08-cpu - Testing on Windows 10 1809 +- Anixx – Testing on Windows 11 23H2 and the original toolbar subclassing approach - dbilanoski – CLSID documentation */ // ==/WindhawkModReadme== - // ==WindhawkModSettings== /* - EnableRedirects: true $name: Enable Redirects - $description: "Turns the mod on or off. When disabled, Settings opens normally." - + $description: "Turns the mod on or off. When disabled, Settings opens normally as usual." - RedirectSystemTray: false - $name: Redirect System Tray (EXPERIMENTAL) - $description: "If enabled, right-clicking the Audio, Network, or Devices & Printers tray icons and selecting their Settings item opens the classic Control Panel instead." - + $name: Redirect System Tray Audio/Network/Device & Printers (EXPERIMENTAL) + $description: "If enabled, right-clicking the Audio or Network or Device & Printers icon near the clock and choosing 'Open Sound settings' or 'Open Network settings' or 'Open devices and printers' will open the classic panel instead of the Settings app." - UIOnlyRedirects: false $name: Non-Invasive Mode - $description: "Only redirects clicks made in the user interface. Programs that open Settings programmatically are left untouched." - + $description: "Only redirects clicks made in the UI. Doesn't touch programs that open Settings in other ways." - FallbackMode: "2" - $name: Fallback for Unmapped Links - $description: "What to do when a Settings page has no classic Control Panel equivalent." + $name: Behavior for Unmapped Links + $description: "What to do when a Settings page has no classic equivalent." $options: - - "0": Ignore (do nothing) - - "1": Open Control Panel - - "2": Pass through to the modern Settings app - + - "0": Ignore (silent fail) + - "1": Open the Control Panel (control.exe) + - "2": Pass through to the modern Settings application (ms-settings.exe)" - Win11CompatibilityMode: false $name: Windows 11 Compatibility Mode - $description: "On Windows 11, only uses proven redirects. Everything else opens the Control Panel as a fallback." - + $description: "Safer mode for Windows 11. When enabled, only uses proven redirects while everything else opens the standard Control Panel page as a fallback to avoid loops or other issues." - MaxLaunchesPerUri: 3 - $name: Anti-Loop Limit - $description: "Maximum number of times the same target can be launched within 5 seconds. Set to 0 to disable this safety measure." - -- ComActivationRedirect: true - $name: COM Activation Redirect (EXPERIMENTAL) - $description: "Intercepts IApplicationActivationManager activations of the Settings app on Windows 11." - -- LegacyNameMappingFix: true - $name: Legacy Name Mapping Fix (EXPERIMENTAL) - $description: "Prevents Explorer from remapping classic Control Panel names to the modern Settings app." + $name: Anti-Loop Limit (per window, every 5 seconds) + $description: "Safety measure: if the same window gets opened too many times within a few seconds, the mod stops reopening it. Set to 0 to disable this limit." */ // ==/WindhawkModSettings== #include #include #include -#include #include #include #include @@ -101,92 +86,47 @@ The mod has been tested on Windows 10 21H2, Windows 10 1809, Windows 11 23H2 and #include #include #include -#include - -// --------------------------------------------------------------------------- -// GUIDs and constants -// --------------------------------------------------------------------------- - -static const CLSID CLSID_ApplicationActivationManager_STC = - { 0x45ba127d, 0x10a8, 0x46ea, { 0x8a, 0xb7, 0x56, 0xea, 0x90, 0x78, 0x94, 0x3c } }; - -static const IID IID_IApplicationActivationManager_STC = - { 0x2e941141, 0x7f97, 0x4756, { 0xba, 0x1d, 0x9d, 0xec, 0xde, 0x89, 0x4a, 0x3d } }; +// Custom IDs for tray menu redirection (TrackPopupMenuEx method) #define TRAY_CUSTOM_ID_AUDIO 65001 #define TRAY_CUSTOM_ID_NETWORK 65002 #define TRAY_CUSTOM_ID_DEVICES 65003 -static const HINSTANCE SHELL_EXECUTE_SUCCESS = (HINSTANCE)33; +// TrackPopupMenuEx hook (DLL-based fallback method) +using TrackPopupMenuEx_t = BOOL(WINAPI*)(HMENU, UINT, int, int, HWND, const TPMPARAMS*); +static TrackPopupMenuEx_t g_origTrackPopupMenuEx = nullptr; + +// Set on WM_RBUTTONUP by the subclass proc so TrackPopupMenuEx knows the icon type. +static int g_trayContextType = 0; +static DWORD g_trayContextTick = 0; +static std::mutex g_trayContextMutex; +static constexpr DWORD TRAY_CONTEXT_MAX_AGE_MS = 1500; + +using ICMH_CAODTM_t = bool(__fastcall*)(HMENU, HWND); +static ICMH_CAODTM_t g_icmhOrig_SndVolSSO = nullptr; +static ICMH_CAODTM_t g_icmhOrig_pnidui = nullptr; +static ICMH_CAODTM_t g_icmhOrig_Shell32Devices = nullptr; +static bool __fastcall ICMH_CAODTM_hook(HMENU, HWND); + +// Constants +static const HINSTANCE SHELL_EXECUTE_SUCCESS = (HINSTANCE)33; #define PERS_ROOT L"explorer shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921}" #define PERS_WALLPAPER L"explorer shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921} -Microsoft.Personalization\\pageWallpaper" #define PERS_COLORS L"explorer shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921} -Microsoft.Personalization\\pageColorization" + #define SYSTEM_PROPS_CLSID L"shell:::{BB06C0E4-D293-4f75-8A90-CB05B6477EEE}" #define NOTIF_AREA_CLSID L"shell:::{05d7b0f4-2121-4eff-bf6b-ed3f69b894d9}" #define WIN11_PASSTHROUGH L"__PASSTHROUGH__" #define EASE_OF_ACCESS L"explorer shell:::{D555645E-D4F8-4c29-A827-D93C859C4F2A}" -// --------------------------------------------------------------------------- -// Typedefs and globals -// --------------------------------------------------------------------------- - using CreateProcessW_t = BOOL(WINAPI*)(LPCWSTR, LPWSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION); +static CreateProcessW_t CreateProcessW_orig = nullptr; + using ShellExecuteW_t = HINSTANCE(WINAPI*)(HWND, LPCWSTR, LPCWSTR, LPCWSTR, LPCWSTR, INT); using ShellExecuteExW_t = BOOL(WINAPI*)(SHELLEXECUTEINFOW*); -using TrackPopupMenuEx_t = BOOL(WINAPI*)(HMENU, UINT, int, int, HWND, const TPMPARAMS*); -using NtUserTrackPopupMenuEx_t = BOOL(WINAPI*)(HMENU, UINT, int, int, HWND, const TPMPARAMS*); -using ICMH_CAODTM_t = bool(__fastcall*)(HMENU, HWND); - -static CreateProcessW_t CreateProcessW_orig = nullptr; static ShellExecuteExW_t ShellExecuteExW_orig = nullptr; static ShellExecuteW_t ShellExecuteW_orig = nullptr; -static TrackPopupMenuEx_t g_origTrackPopupMenuEx = nullptr; -static NtUserTrackPopupMenuEx_t g_origNtUserTrackPopupMenuEx = nullptr; - -static ICMH_CAODTM_t g_icmhOrig_SndVolSSO = nullptr; -static ICMH_CAODTM_t g_icmhOrig_pnidui = nullptr; -static ICMH_CAODTM_t g_icmhOrig_Shell32Devices = nullptr; - -static int g_trayContextType = 0; -static DWORD g_trayContextTick = 0; -static std::mutex g_trayContextMutex; -static bool g_shellExecHooksInstalled = false; -static bool g_createProcessHookInstalled = false; -static bool g_rcplHooksInstalled = false; -static bool g_taskbarListenerInstalled = false; -static std::atomic g_modActive{false}; -static std::mutex g_initMutex; -static std::atomic g_reinitPending{false}; -static std::atomic g_unloading{false}; -static constexpr DWORD TRAY_CONTEXT_MAX_AGE_MS = 1500; - -static bool g_pniduiHookInstalled = false; -static std::mutex g_pniduiHookMutex; -static bool g_sndVolSSOHookInstalled = false; -static bool g_shell32DevicesHookInstalled = false; -static HANDLE g_pniduiRetryThread = nullptr; -static volatile bool g_pniduiRetryStop = false; -static volatile bool g_pniduiRetryRunning = false; -static HANDLE g_reinitThread = nullptr; -static HANDLE g_traySubclassWatchdogThread = nullptr; -static volatile bool g_traySubclassWatchdogStop = false; -static HANDLE g_hWatchdogStopEvent = nullptr; -static HWND g_lastShellTrayWnd = nullptr; -static HWND g_hTrayToolbar = nullptr; - -static BYTE* g_sndVolSSOBase = nullptr; -static BYTE* g_sndVolSSOEnd = nullptr; -static BYTE* g_pniduiBase = nullptr; -static BYTE* g_pniduiEnd = nullptr; - -// --- NEW: TaskbarCreated message-only window, used to detect Explorer/tray -// restarts *immediately* instead of relying only on the polling watchdog. -// This is the standard, Microsoft-documented mechanism apps use to know when -// the shell has recreated the taskbar (see RegisterWindowMessage("TaskbarCreated")). -static UINT g_uTaskbarCreatedMsg = 0; -static HWND g_hTrayMsgWindow = nullptr; -static const wchar_t* TRAY_MSG_WNDCLASS = L"STC_TrayRestartListener"; struct ResolveResult { std::wstring target; @@ -203,44 +143,15 @@ struct HookGuard { static std::wstring g_childEnvBlock; -struct ModSettings { - bool enableRedirects = true; - bool redirectSystemTray = false; - bool uiOnlyRedirects = false; - int fallbackMode = 2; - bool win11CompatibilityMode = false; - int maxLaunchesPerUri = 3; - bool comActivationRedirect = true; - bool legacyNameMappingFix = true; -}; - -static ModSettings g_settings; -static bool g_isWin11 = false; -static std::unordered_map g_mappings; - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - -static std::wstring ToLower(std::wstring s) { - std::transform(s.begin(), s.end(), s.begin(), ::towlower); - return s; -} - -static std::wstring BaseNameLower(const std::wstring& path) { - size_t pos = path.rfind(L'\\'); - return ToLower((pos != std::wstring::npos) ? path.substr(pos + 1) : path); -} - static void BuildChildEnvironment() { - g_childEnvBlock.clear(); LPWCH curEnv = GetEnvironmentStringsW(); if (curEnv) { LPWCH p = curEnv; while (*p) { std::wstring entry(p); - if (entry.find(L"WH_STC_NOREDIRECT=") != 0) + if (entry.find(L"WH_STC_NOREDIRECT=") != 0) { g_childEnvBlock += entry + L'\0'; + } p += entry.length() + 1; } FreeEnvironmentStringsW(curEnv); @@ -252,16 +163,52 @@ static bool IsChildProcess() { return GetEnvironmentVariableW(L"WH_STC_NOREDIRECT", nullptr, 0) > 0; } -static void DetectWindowsVersion() { - OSVERSIONINFOEXW osvi = {}; - osvi.dwOSVersionInfoSize = sizeof(osvi); - using RtlGetVersion_t = NTSTATUS(WINAPI*)(OSVERSIONINFOEXW*); - HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll"); - if (hNtdll) { - auto fn = (RtlGetVersion_t)GetProcAddress(hNtdll, "RtlGetVersion"); - if (fn) fn(&osvi); +static bool IsMemoryReadable(const void* ptr, size_t size) { + if (!ptr) return false; + + MEMORY_BASIC_INFORMATION mbi{}; + if (!VirtualQuery(ptr, &mbi, sizeof(mbi))) return false; + + if (mbi.State != MEM_COMMIT) return false; + + if (mbi.Protect & PAGE_NOACCESS) return false; + if (mbi.Protect & PAGE_GUARD) return false; + + // Deve avere almeno un permesso di lettura + DWORD readableMask = PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY | + PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY; + if (!(mbi.Protect & readableMask)) return false; + + // Verifica che l'intero blocco richiesto rientri nella regione committata + uintptr_t regionEnd = (uintptr_t)mbi.BaseAddress + mbi.RegionSize; + uintptr_t readEnd = (uintptr_t)ptr + size; + if (readEnd > regionEnd) return false; + + return true; +} + +static bool SafeReadIconHwnd(DWORD_PTR dwData, HWND* outHwnd) { + if (!IsMemoryReadable((const void*)dwData, sizeof(HWND))) { + return false; } - g_isWin11 = (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber >= 22000); + *outHwnd = *(HWND*)dwData; + return true; +} + +struct ModSettings { + bool enableRedirects = true; + bool redirectSystemTray = false; + bool uiOnlyRedirects = false; + int fallbackMode = 2; + bool win11CompatibilityMode = false; + int maxLaunchesPerUri = 3; +}; + +static ModSettings g_settings; + +static bool __fastcall ICMH_CAODTM_hook(HMENU, HWND) { + if (!g_settings.redirectSystemTray) return true; + return false; } static void LoadSettings() { @@ -271,7 +218,7 @@ static void LoadSettings() { WindhawkUtils::StringSetting fallbackSetting(Wh_GetStringSetting(L"FallbackMode")); PCWSTR fallbackStr = fallbackSetting; - if (fallbackStr && fallbackStr[0] != L'\0') { + if (fallbackStr[0] != L'\0') { int mode = _wtoi(fallbackStr); g_settings.fallbackMode = (mode >= 0 && mode <= 2) ? mode : 2; } else { @@ -279,25 +226,32 @@ static void LoadSettings() { } g_settings.win11CompatibilityMode = Wh_GetIntSetting(L"Win11CompatibilityMode") != 0; + int ml = Wh_GetIntSetting(L"MaxLaunchesPerUri"); g_settings.maxLaunchesPerUri = (ml >= 0 && ml <= 20) ? ml : 3; - g_settings.comActivationRedirect = Wh_GetIntSetting(L"ComActivationRedirect") != 0; - g_settings.legacyNameMappingFix = Wh_GetIntSetting(L"LegacyNameMappingFix") != 0; } -// === NUOVO: Funzione di reinit iperstabile === -static bool __fastcall ICMH_CAODTM_hook(HMENU, HWND) { - if (!g_settings.redirectSystemTray) return true; - return false; +static bool g_isWin11 = false; + +static void DetectWindowsVersion() { + OSVERSIONINFOEXW osvi = {}; + osvi.dwOSVersionInfoSize = sizeof(osvi); + using RtlGetVersion_t = NTSTATUS(WINAPI*)(OSVERSIONINFOEXW*); + HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll"); + if (hNtdll) { + auto fn = (RtlGetVersion_t)GetProcAddress(hNtdll, "RtlGetVersion"); + if (fn) fn(&osvi); + } + g_isWin11 = (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber >= 22000); } -// --------------------------------------------------------------------------- -// Loop/bounce guards -// --------------------------------------------------------------------------- +struct BounceRecord { + DWORD lastRedirectTick = 0; +}; -struct BounceRecord { DWORD lastRedirectTick = 0; }; static std::mutex g_bounceGuardMtx; static std::unordered_map g_bounceGuard; + static constexpr DWORD BOUNCE_WINDOW_MS = 3000; static void BounceGuardRecord(const std::wstring& uri) { @@ -317,32 +271,37 @@ static bool BounceGuardIsBounce(const std::wstring& uri) { return false; } -struct LaunchRecord { int count = 0; DWORD firstTick = 0; }; +struct LaunchRecord { + int count = 0; + DWORD firstTick = 0; +}; + static std::mutex g_loopGuardMtx; static std::unordered_map g_loopGuard; + static constexpr DWORD LOOP_WINDOW_MS = 5000; static bool LoopGuardAllow(const std::wstring& target) { if (g_settings.maxLaunchesPerUri <= 0) return true; + std::lock_guard lk(g_loopGuardMtx); DWORD now = GetTickCount(); auto& rec = g_loopGuard[target]; + if (rec.count == 0 || (now - rec.firstTick) >= LOOP_WINDOW_MS) { rec.count = 1; rec.firstTick = now; return true; } + if (rec.count < g_settings.maxLaunchesPerUri) { rec.count++; return true; } + return false; } -// --------------------------------------------------------------------------- -// Windows 11 CLSID filters -// --------------------------------------------------------------------------- - static const std::unordered_set g_win11SafeClsids = { L"shell:::{025a5937-a6be-4686-a844-36fe4bec8b6d}", L"shell:::{05d7b0f4-2121-4eff-bf6b-ed3f69b894d9}", @@ -393,28 +352,39 @@ static bool IsClsidLoopOnWin11(const std::wstring& lowerTarget) { return g_win11LoopClsids.count(base) > 0; } -// --------------------------------------------------------------------------- -// Tray helpers -// --------------------------------------------------------------------------- +static std::wstring ToLower(std::wstring s) { + std::transform(s.begin(), s.end(), s.begin(), ::towlower); + return s; +} + +static BYTE* g_sndVolSSOBase = nullptr; +static BYTE* g_sndVolSSOEnd = nullptr; +static BYTE* g_pniduiBase = nullptr; +static BYTE* g_pniduiEnd = nullptr; + +static std::mutex g_toolbarsMutex; +static std::unordered_set g_subclassedToolbars; static bool InitTrayDllInfo() { - if (g_sndVolSSOBase && g_pniduiBase) return true; - - HMODULE hSndVol = GetModuleHandleW(L"SndVolSSO.dll"); - if (hSndVol) { - MODULEINFO mi{}; - if (GetModuleInformation(GetCurrentProcess(), hSndVol, &mi, sizeof(mi))) { - g_sndVolSSOBase = (BYTE*)mi.lpBaseOfDll; - g_sndVolSSOEnd = g_sndVolSSOBase + mi.SizeOfImage; + if (!g_sndVolSSOBase) { + HMODULE hSndVol = GetModuleHandleW(L"SndVolSSO.dll"); + if (hSndVol) { + MODULEINFO mi{}; + if (GetModuleInformation(GetCurrentProcess(), hSndVol, &mi, sizeof(mi))) { + g_sndVolSSOBase = (BYTE*)mi.lpBaseOfDll; + g_sndVolSSOEnd = g_sndVolSSOBase + mi.SizeOfImage; + } } } - HMODULE hPniDui = GetModuleHandleW(L"pnidui.dll"); - if (hPniDui) { - MODULEINFO mi{}; - if (GetModuleInformation(GetCurrentProcess(), hPniDui, &mi, sizeof(mi))) { - g_pniduiBase = (BYTE*)mi.lpBaseOfDll; - g_pniduiEnd = g_pniduiBase + mi.SizeOfImage; + if (!g_pniduiBase) { + HMODULE hPniDui = GetModuleHandleW(L"pnidui.dll"); + if (hPniDui) { + MODULEINFO mi{}; + if (GetModuleInformation(GetCurrentProcess(), hPniDui, &mi, sizeof(mi))) { + g_pniduiBase = (BYTE*)mi.lpBaseOfDll; + g_pniduiEnd = g_pniduiBase + mi.SizeOfImage; + } } } @@ -422,22 +392,32 @@ static bool InitTrayDllInfo() { } static int GetTrayButtonType(HWND hToolbar, int buttonIndex) { - if (buttonIndex < 0) return 0; InitTrayDllInfo(); + if (buttonIndex < 0) return 0; + TBBUTTON tb{}; if (!SendMessageW(hToolbar, TB_GETBUTTON, buttonIndex, (LPARAM)&tb)) return 0; if (!tb.dwData) return 0; - HWND hIconWnd = *(HWND*)tb.dwData; + HWND hIconWnd = nullptr; + if (!SafeReadIconHwnd(tb.dwData, &hIconWnd)) { + Wh_Log(L"[TRAY-HOOK] Skipped unreadable icon pointer (tray not fully initialized yet)"); + return 0; + } + if (!hIconWnd || !IsWindow(hIconWnd)) return 0; wchar_t className[256]{}; if (!GetClassNameW(hIconWnd, className, 256)) return 0; - if (wcsncmp(className, L"ATL:", 4) != 0) return 0; + + if (wcsncmp(className, L"ATL:", 4) != 0) { + return 0; + } const wchar_t* hexPart = className + 4; ULONG_PTR addr = 0; + while (*hexPart) { wchar_t c = *hexPart; int digit = 0; @@ -450,9 +430,9 @@ static int GetTrayButtonType(HWND hToolbar, int buttonIndex) { } if (g_sndVolSSOBase && addr >= (ULONG_PTR)g_sndVolSSOBase && addr < (ULONG_PTR)g_sndVolSSOEnd) - return 1; + return 1; // Audio if (g_pniduiBase && addr >= (ULONG_PTR)g_pniduiBase && addr < (ULONG_PTR)g_pniduiEnd) - return 2; + return 2; // Network return 0; } @@ -490,70 +470,116 @@ static void OpenClassicDevicesAndPrinters() { ShellExecuteExW_orig(&sei); } -static LRESULT CALLBACK TrayToolbarSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, DWORD_PTR) { +static LRESULT CALLBACK TrayToolbarSubclassProc( + HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, DWORD_PTR dwRefData) +{ + // FIX: автоматически очищаем сет, если окно трея уничтожено (например, Explorer перезагружает UI) + if (msg == WM_NCDESTROY) { + std::lock_guard lk(g_toolbarsMutex); + g_subclassedToolbars.erase(hwnd); + return DefSubclassProc(hwnd, msg, wParam, lParam); + } + if (msg == WM_RBUTTONUP) { POINT pt; pt.x = (int)(short)LOWORD(lParam); pt.y = (int)(short)HIWORD(lParam); int hitIndex = (int)SendMessageW(hwnd, TB_HITTEST, 0, (LPARAM)&pt); + if (hitIndex >= 0) { int buttonType = GetTrayButtonType(hwnd, hitIndex); - if (buttonType == 1 || buttonType == 2) { + if (buttonType == 1) { + std::lock_guard lk(g_trayContextMutex); + g_trayContextType = 1; + g_trayContextTick = GetTickCount(); + } + else if (buttonType == 2) { std::lock_guard lk(g_trayContextMutex); - g_trayContextType = buttonType; + g_trayContextType = 2; g_trayContextTick = GetTickCount(); } } } - return DefSubclassProc(hwnd, msg, wParam, lParam); } -static HWND FindTrayToolbar() { - HWND hTray = FindWindowW(L"Shell_TrayWnd", nullptr); - if (!hTray) return nullptr; - - DWORD pid = 0; - GetWindowThreadProcessId(hTray, &pid); - if (pid != GetCurrentProcessId()) return nullptr; - - HWND hNotify = FindWindowExW(hTray, nullptr, L"TrayNotifyWnd", nullptr); - if (!hNotify) return nullptr; +static void SubclassToolbar(HWND hwnd) { + if (!hwnd) return; + std::lock_guard lk(g_toolbarsMutex); + if (g_subclassedToolbars.count(hwnd) == 0) { + if (WindhawkUtils::SetWindowSubclassFromAnyThread(hwnd, TrayToolbarSubclassProc, 0)) { + g_subclassedToolbars.insert(hwnd); + } + } +} - HWND hSysPager = FindWindowExW(hNotify, nullptr, L"SysPager", nullptr); - if (hSysPager) { - HWND hToolbar = FindWindowExW(hSysPager, nullptr, L"ToolbarWindow32", nullptr); - if (hToolbar) return hToolbar; +// FIX: non sabclassare MAI in modo sincrono dentro CreateWindowExW_Hook. +// Subito dopo il riavvio di Explorer questo hook scatta mentre siamo ancora +// nel bel mezzo dello stack annidato di WM_CREATE (Shell_TrayWnd -> TrayNotifyWnd +// -> ToolbarWindow32). Fare subito il subclass li' dentro puo' far scattare il +// watchdog di hang-detection di Windhawk (da cui il popup "instabilita'"), anche +// se poi tutto si sblocca da solo. Rimandiamo il lavoro vero a un thread separato +// che aspetta che lo stack di creazione si sia srotolato prima di procedere. +static DWORD WINAPI SubclassToolbarDeferredProc(LPVOID lpParam) { + HWND hwnd = (HWND)lpParam; + Sleep(50); + if (IsWindow(hwnd)) { + SubclassToolbar(hwnd); } + return 0; +} - return FindWindowExW(hNotify, nullptr, L"ToolbarWindow32", nullptr); +static void SubclassToolbarDeferred(HWND hwnd) { + HANDLE hThread = CreateThread(nullptr, 0, SubclassToolbarDeferredProc, (LPVOID)hwnd, 0, nullptr); + if (hThread) CloseHandle(hThread); } -static void SetupTraySubclass() { - if (g_hTrayToolbar) { - // Guard against a stale handle that's no longer the actual current - // toolbar (e.g. after a tray restart the old handle can, in rare - // cases, still test as IsWindow==TRUE for a brief moment while a - // brand new toolbar has already been created alongside it). - HWND hCurrent = FindTrayToolbar(); - if (hCurrent == g_hTrayToolbar) return; - if (IsWindow(g_hTrayToolbar)) - WindhawkUtils::RemoveWindowSubclassFromAnyThread(g_hTrayToolbar, TrayToolbarSubclassProc); - g_hTrayToolbar = nullptr; +static void UnsubclassAllToolbars() { + std::lock_guard lk(g_toolbarsMutex); + for (HWND hwnd : g_subclassedToolbars) { + if (IsWindow(hwnd)) { + WindhawkUtils::RemoveWindowSubclassFromAnyThread(hwnd, TrayToolbarSubclassProc); + } } - if (!InitTrayDllInfo()) return; - - HWND hToolbar = FindTrayToolbar(); - if (!hToolbar) return; + g_subclassedToolbars.clear(); +} - if (WindhawkUtils::SetWindowSubclassFromAnyThread(hToolbar, TrayToolbarSubclassProc, 0)) - g_hTrayToolbar = hToolbar; +static std::vector FindTrayToolbars() { + std::vector toolbars; + HWND hTray = FindWindowW(L"Shell_TrayWnd", nullptr); + if (hTray) { + DWORD pid = 0; + GetWindowThreadProcessId(hTray, &pid); + if (pid == GetCurrentProcessId()) { + HWND hNotify = FindWindowExW(hTray, nullptr, L"TrayNotifyWnd", nullptr); + if (hNotify) { + HWND hSysPager = FindWindowExW(hNotify, nullptr, L"SysPager", nullptr); + if (hSysPager) { + HWND hToolbar = FindWindowExW(hSysPager, nullptr, L"ToolbarWindow32", nullptr); + if (hToolbar) toolbars.push_back(hToolbar); + } else { + HWND hToolbar = FindWindowExW(hNotify, nullptr, L"ToolbarWindow32", nullptr); + if (hToolbar) toolbars.push_back(hToolbar); + } + } + } + } + HWND hOverflow = FindWindowW(L"NotifyIconOverflowWindow", nullptr); + if (hOverflow) { + DWORD pid = 0; + GetWindowThreadProcessId(hOverflow, &pid); + if (pid == GetCurrentProcessId()) { + HWND hToolbar = FindWindowExW(hOverflow, nullptr, L"ToolbarWindow32", nullptr); + if (hToolbar) toolbars.push_back(hToolbar); + } + } + return toolbars; } -static void RemoveTraySubclass() { - if (g_hTrayToolbar) { - WindhawkUtils::RemoveWindowSubclassFromAnyThread(g_hTrayToolbar, TrayToolbarSubclassProc); - g_hTrayToolbar = nullptr; +static void SetupTraySubclass() { + InitTrayDllInfo(); + for (HWND hToolbar : FindTrayToolbars()) { + SubclassToolbar(hToolbar); } } @@ -574,15 +600,13 @@ static bool IsAddressInModule(void* address, const wchar_t* moduleName) { return false; } -static BOOL HandleTrayPopupMenu(HMENU hMenu, UINT uFlags, int x, int y, HWND hWnd, const TPMPARAMS* lptpm, bool syscallHook) { - auto orig = syscallHook ? g_origNtUserTrackPopupMenuEx : g_origTrackPopupMenuEx; - +BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND hWnd, const TPMPARAMS* lptpm) { if (!g_settings.redirectSystemTray || !g_settings.enableRedirects) - return orig(hMenu, uFlags, x, y, hWnd, lptpm); + return g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); HookGuard guard; if (guard.IsReentrant()) - return orig(hMenu, uFlags, x, y, hWnd, lptpm); + return g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); int contextType; { @@ -591,13 +615,14 @@ static BOOL HandleTrayPopupMenu(HMENU hMenu, UINT uFlags, int x, int y, HWND hWn DWORD tick = g_trayContextTick; g_trayContextType = 0; g_trayContextTick = 0; - if (contextType != 0 && GetTickCount() - tick > TRAY_CONTEXT_MAX_AGE_MS) + if (contextType != 0 && GetTickCount() - tick > TRAY_CONTEXT_MAX_AGE_MS) { contextType = 0; + } } - bool isAudioMenu = (contextType == 1); + bool isAudioMenu = (contextType == 1); bool isNetworkMenu = (contextType == 2); - bool isDeviceMenu = false; + bool isDeviceMenu = false; if (!isAudioMenu && !isNetworkMenu) { void* retAddr = GetReturnAddress(); @@ -605,44 +630,61 @@ static BOOL HandleTrayPopupMenu(HMENU hMenu, UINT uFlags, int x, int y, HWND hWn if (itemCount > 0) { if (IsAddressInModule(retAddr, L"SndVolSSO.dll")) { if (itemCount <= 6) isAudioMenu = true; - } else if (IsAddressInModule(retAddr, L"pnidui.dll")) { + } + else if (IsAddressInModule(retAddr, L"pnidui.dll")) { if (itemCount <= 6) isNetworkMenu = (itemCount >= 2 && itemCount <= 5); - } else if (IsAddressInModule(retAddr, L"dxgi.dll")) { - if (itemCount == 2 && GetMenuItemID(hMenu, 0) == 3107 && GetMenuItemID(hMenu, 1) == 3109) + } + else if (IsAddressInModule(retAddr, L"dxgi.dll")) { + if (itemCount == 2 && GetMenuItemID(hMenu, 0) == 3107 && GetMenuItemID(hMenu, 1) == 3109) { isNetworkMenu = true; - else if (GetMenuItemID(hMenu, 0) == 215) + } + else if (GetMenuItemID(hMenu, 0) == 215) { isDeviceMenu = true; - } else if (IsAddressInModule(retAddr, L"shell32.dll")) { + Wh_Log(L"[TRAY-HOOK] Device menu detected via dxgi.dll + ID 215"); + } + } + else if (IsAddressInModule(retAddr, L"shell32.dll")) { for (int i = 0; i < itemCount; i++) { - if (GetMenuItemID(hMenu, i) == 215) { + UINT itemId = GetMenuItemID(hMenu, i); + if (itemId == 215) { isDeviceMenu = true; + Wh_Log(L"[TRAY-HOOK] Device menu detected via shell32.dll + ID 215 at index %d", i); break; } } - } else if (IsAddressInModule(retAddr, L"hotplug.dll")) { + } + else if (IsAddressInModule(retAddr, L"hotplug.dll")) { isDeviceMenu = true; + Wh_Log(L"[TRAY-HOOK] Device menu detected via hotplug.dll"); } } } if (!isAudioMenu && !isNetworkMenu && !isDeviceMenu) - return orig(hMenu, uFlags, x, y, hWnd, lptpm); + return g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); int itemCount = GetMenuItemCount(hMenu); - int targetIndex = -1; + const wchar_t* menuKind = isAudioMenu ? L"AUDIO" : (isNetworkMenu ? L"NETWORK" : L"DEVICE"); + Wh_Log(L"[TRAY-HOOK] %s menu, %d items", menuKind, itemCount); + int targetIndex = -1; + if (isAudioMenu) { targetIndex = 0; - } else if (isNetworkMenu) { + } + else if (isNetworkMenu) { for (int i = itemCount - 1; i >= 0; i--) { MENUITEMINFOW miiCheck = { sizeof(MENUITEMINFOW) }; miiCheck.fMask = MIIM_FTYPE; - if (GetMenuItemInfoW(hMenu, i, TRUE, &miiCheck) && !(miiCheck.fType & MFT_SEPARATOR)) { - targetIndex = i; - break; + if (GetMenuItemInfoW(hMenu, i, TRUE, &miiCheck)) { + if (!(miiCheck.fType & MFT_SEPARATOR)) { + targetIndex = i; + break; + } } } - } else if (isDeviceMenu) { + } + else if (isDeviceMenu) { for (int i = 0; i < itemCount; i++) { if (GetMenuItemID(hMenu, i) == 215) { targetIndex = i; @@ -653,30 +695,49 @@ static BOOL HandleTrayPopupMenu(HMENU hMenu, UINT uFlags, int x, int y, HWND hWn for (int i = 0; i < itemCount; i++) { MENUITEMINFOW miiCheck = { sizeof(MENUITEMINFOW) }; miiCheck.fMask = MIIM_FTYPE; - if (GetMenuItemInfoW(hMenu, i, TRUE, &miiCheck) && !(miiCheck.fType & MFT_SEPARATOR)) { - targetIndex = i; - break; + if (GetMenuItemInfoW(hMenu, i, TRUE, &miiCheck)) { + if (!(miiCheck.fType & MFT_SEPARATOR)) { + targetIndex = i; + break; + } } } } } + + if (targetIndex == -1) { + Wh_Log(L"[TRAY-HOOK] No valid menu item found for %s menu", menuKind); + return g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + } - if (targetIndex == -1) - return orig(hMenu, uFlags, x, y, hWnd, lptpm); + Wh_Log(L"[TRAY-HOOK] %s menu target index=%d, ID=%u", menuKind, targetIndex, GetMenuItemID(hMenu, targetIndex)); + UINT customId = isAudioMenu ? TRAY_CUSTOM_ID_AUDIO + : isNetworkMenu ? TRAY_CUSTOM_ID_NETWORK + : TRAY_CUSTOM_ID_DEVICES; UINT originalId = GetMenuItemID(hMenu, targetIndex); + + MENUITEMINFOW mii = { sizeof(MENUITEMINFOW) }; + mii.fMask = MIIM_ID; + mii.wID = customId; + SetMenuItemInfoW(hMenu, targetIndex, TRUE, &mii); + bool callerWantedReturnCmd = (uFlags & TPM_RETURNCMD) != 0; uFlags |= TPM_RETURNCMD; + BOOL result = g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + int selectedId = (int)result; + + mii.wID = originalId; + SetMenuItemInfoW(hMenu, targetIndex, TRUE, &mii); - BOOL result = orig(hMenu, uFlags, x, y, hWnd, lptpm); - int selectedId = (int)result; - if (selectedId == (int)originalId) { - Wh_Log(L"%s", syscallHook ? L"[SYSCALL-HOOK] User selected target item, redirecting" : L"[TRAY-HOOK] User selected target item, redirecting"); - if (isAudioMenu) OpenClassicSoundPanel(); + if (selectedId == (int)customId) { + Wh_Log(L"[TRAY-HOOK] User selected target item, redirecting"); + if (isAudioMenu) OpenClassicSoundPanel(); else if (isNetworkMenu) OpenClassicNetworkConnections(); - else OpenClassicDevicesAndPrinters(); + else OpenClassicDevicesAndPrinters(); return 0; } + if (selectedId != 0 && !callerWantedReturnCmd) { PostMessageW(hWnd, WM_COMMAND, MAKEWPARAM((WORD)selectedId, 0), 0); return TRUE; @@ -685,20 +746,11 @@ static BOOL HandleTrayPopupMenu(HMENU hMenu, UINT uFlags, int x, int y, HWND hWn return result; } -BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND hWnd, const TPMPARAMS* lptpm) { - return HandleTrayPopupMenu(hMenu, uFlags, x, y, hWnd, lptpm, false); -} - -BOOL WINAPI NtUserTrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND hWnd, const TPMPARAMS* lptpm) { - return HandleTrayPopupMenu(hMenu, uFlags, x, y, hWnd, lptpm, true); -} - -// --------------------------------------------------------------------------- -// Mappings and URI resolving -// --------------------------------------------------------------------------- +static std::unordered_map g_mappings; static void InitMappings() { const bool w11 = g_isWin11; + g_mappings = { {L"ms-settings:personalization", PERS_ROOT}, {L"ms-settings:personalization-colors", PERS_COLORS}, @@ -815,7 +867,7 @@ static void InitMappings() { {L"ms-settings:easeofaccess-mouse", EASE_OF_ACCESS}, {L"ms-settings:easeofaccess-keyboard", EASE_OF_ACCESS}, {L"ms-settings:recovery", w11 ? L"control.exe" : L"shell:::{9FE63AFD-59CF-4419-9775-ABCC3849F861}"}, - {L"ms-settings:troubleshoot", w11 ? L"msdt.exe -id DeviceDiagnostic" : L"shell:::{C58C4893-3BE0-4B45-ABB5-A63E4B8C8651}"}, + {L"ms-settings:troubleshoot", w11 ? L"msdt.exe -id DeviceDiagnostic" : L"shell:::{C58C4893-3BE0-4B45-ABB5-A63E4B8C8651}"}, {L"ms-settings:deviceencryption", L"shell:::{D9EF8727-CAC2-4e60-809E-86F80A666C91}"}, {L"ms-settings:gaming-gamebar", L"joy.cpl"}, {L"ms-settings:folders", L"shell:::{6DFD7C5C-2451-11d3-A299-00C04F8EF6AF}"}, @@ -845,6 +897,7 @@ static void InitMappings() { g_mappings[L"ms-settings:backup"] = L"control.exe /name Microsoft.BackupAndRestore"; g_mappings[L"ms-settings:network-advancedsettings"] = L"control.exe /name Microsoft.NetworkAndSharingCenter"; + if (g_isWin11) { g_mappings[L"ms-settings:recovery"] = L"shell:::{26EE0668-A00A-44D7-9371-BEB064C98683}\\0\\::{9FE63AFD-59CF-4419-9775-ABCC3849F861}"; } @@ -854,13 +907,16 @@ static std::wstring NormalizeUri(const std::wstring& uri) { std::wstring result = ToLower(uri); const std::wstring PROTOCOL = L"ms-settings://"; size_t pos = result.find(PROTOCOL); - if (pos != std::wstring::npos) + if (pos != std::wstring::npos) { result = L"ms-settings:" + result.substr(pos + PROTOCOL.length()); + } pos = result.find(L'?'); - if (pos != std::wstring::npos) + if (pos != std::wstring::npos) { result = result.substr(0, pos); - while (!result.empty() && result.back() == L'/') + } + while (!result.empty() && result.back() == L'/') { result.pop_back(); + } return result; } @@ -878,10 +934,10 @@ static std::wstring ApplyWin11Filter(const std::wstring& target) { if (!g_isWin11) return target; std::wstring lower = ToLower(target); if (lower.find(L"shell:::") != 0 && lower.find(L"explorer shell:::") != 0) return target; - + std::wstring clsPart = lower; if (lower.find(L"explorer ") == 0) clsPart = lower.substr(9); - + if (IsClsidLoopOnWin11(clsPart)) { if (lower.find(L"ed834ed6") != std::wstring::npos) { if (lower.find(L"pagewallpaper") != std::wstring::npos) return PERS_WALLPAPER; @@ -891,19 +947,16 @@ static std::wstring ApplyWin11Filter(const std::wstring& target) { if (lower.find(L"bb06c0e4") != std::wstring::npos) return L"sysdm.cpl"; return L"control.exe"; } - - if (g_settings.win11CompatibilityMode && !IsClsidSafeOnWin11(clsPart)) + if (g_settings.win11CompatibilityMode && !IsClsidSafeOnWin11(clsPart)) { return L"control.exe"; - + } return target; } -static bool HandleFallback(const std::wstring&) { +static bool HandleFallback(const std::wstring& uri) { switch (g_settings.fallbackMode) { - case 0: - return true; + case 0: return true; case 1: { - if (!CreateProcessW_orig) return true; std::wstring cmd = L"control.exe"; STARTUPINFOW si = {}; si.cb = sizeof(si); @@ -916,15 +969,15 @@ static bool HandleFallback(const std::wstring&) { } return true; } - default: - return false; + default: return false; } } static void LaunchTarget(const std::wstring& command) { if (!LoopGuardAllow(command)) return; - std::wstring lower = ToLower(command); + std::wstring lower = ToLower(command); + if (lower.find(L"explorer shell:::") != std::wstring::npos) { SHELLEXECUTEINFOW sei = {}; sei.cbSize = sizeof(sei); @@ -936,14 +989,14 @@ static void LaunchTarget(const std::wstring& command) { ShellExecuteExW_orig(&sei); return; } - + if (lower.find(L"rundll32.exe ") == 0) { wchar_t rundll32Path[MAX_PATH]; - if (GetSystemDirectoryW(rundll32Path, MAX_PATH)) + if (GetSystemDirectoryW(rundll32Path, MAX_PATH)) { wcscat_s(rundll32Path, MAX_PATH, L"\\rundll32.exe"); - else + } else { wcscpy_s(rundll32Path, MAX_PATH, L"rundll32.exe"); - + } SHELLEXECUTEINFOW sei = {}; sei.cbSize = sizeof(sei); sei.fMask = SEE_MASK_FLAG_NO_UI; @@ -954,23 +1007,20 @@ static void LaunchTarget(const std::wstring& command) { ShellExecuteExW_orig(&sei); return; } - + bool isFullCmdLine = (lower.find(L"explorer.exe ") != std::wstring::npos) || - (lower.find(L"control.exe /") != std::wstring::npos) || - (lower.find(L"control.exe ") == 0 && lower.find(L".cpl") != std::wstring::npos) || - (lower.find(L"msdt.exe ") == 0) || - (lower.find(L"sndvol.exe ") == 0); - + (lower.find(L"control.exe /") != std::wstring::npos); if (isFullCmdLine) { STARTUPINFOW si = {}; si.cb = sizeof(si); si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_SHOWNORMAL; PROCESS_INFORMATION pi = {}; - std::wstring mutableCmd = command; - if (CreateProcessW_orig && CreateProcessW_orig(nullptr, mutableCmd.data(), nullptr, nullptr, + std::wstring mutable_cmd = command; + if (!CreateProcessW_orig(nullptr, mutable_cmd.data(), nullptr, nullptr, FALSE, CREATE_UNICODE_ENVIRONMENT, (LPVOID)g_childEnvBlock.c_str(), nullptr, &si, &pi)) { + } else { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } @@ -978,9 +1028,7 @@ static void LaunchTarget(const std::wstring& command) { } if (command == L"devmgmt.msc" || command == L"compmgmt.msc" || - command == L"slui.exe" || command == L"OptionalFeatures.exe" || - command == L"msconfig.exe" || command == L"netplwiz" || - command == L"colorcpl.exe") { + command == L"slui.exe" || command == L"OptionalFeatures.exe") { ShellExecuteW_orig(nullptr, L"open", command.c_str(), nullptr, nullptr, SW_SHOWNORMAL); return; } @@ -1015,7 +1063,7 @@ static void LaunchTarget(const std::wstring& command) { cmdLine = L"control.exe " + command; } - if (!cmdLine.empty() && CreateProcessW_orig) { + if (!cmdLine.empty()) { std::wstring mutableCmd = cmdLine; if (!CreateProcessW_orig(nullptr, mutableCmd.data(), nullptr, nullptr, FALSE, CREATE_UNICODE_ENVIRONMENT, @@ -1055,161 +1103,38 @@ static bool ShouldApplyBounceGuard(const std::wstring& uri) { static ResolveResult ResolveUri(const std::wstring& uri, HWND hwnd) { if (uri == L"ms-settings:personalization-background") { - if (BounceGuardIsBounce(uri)) return { L"", true }; + if (BounceGuardIsBounce(uri)) return {L"", true}; std::wstring t = ApplyWin11Filter(ResolvePersonalizationBackground(hwnd)); BounceGuardRecord(uri); - return { t, true }; + return {t, true}; } - auto it = g_mappings.find(uri); if (it != g_mappings.end()) { bool useBounceGuard = ShouldApplyBounceGuard(uri); if (useBounceGuard && BounceGuardIsBounce(uri)) { bool handled = HandleFallback(uri); - return { L"", handled }; + return {L"", handled}; } std::wstring t = ApplyWin11Filter(it->second); if (t == WIN11_PASSTHROUGH) { bool handled = HandleFallback(uri); - return { L"", handled }; + return {L"", handled}; } if (useBounceGuard) BounceGuardRecord(uri); - return { t, true }; + return {t, true}; } - if (uri.find(L"ms-settings:") == 0) { bool handled = HandleFallback(uri); - return { L"", handled }; - } - - return { L"", false }; -} - -// --------------------------------------------------------------------------- -// Experimental COM activation hook -// --------------------------------------------------------------------------- - -struct IApplicationActivationManagerVtbl { - HRESULT (STDMETHODCALLTYPE *QueryInterface)(IUnknown*, REFIID, void**); - ULONG (STDMETHODCALLTYPE *AddRef)(IUnknown*); - ULONG (STDMETHODCALLTYPE *Release)(IUnknown*); - HRESULT (STDMETHODCALLTYPE *ActivateApplication)(IUnknown*, LPCWSTR, LPCWSTR, DWORD, DWORD*); - HRESULT (STDMETHODCALLTYPE *ActivateForFile)(IUnknown*, LPCWSTR, LPCWSTR, DWORD, DWORD*); - // Real ABI: HRESULT ActivateForProtocol(LPCWSTR pszProtocol, IShellItemArray* pItemArray, DWORD* pProcessId) - HRESULT (STDMETHODCALLTYPE *ActivateForProtocol)(IUnknown*, LPCWSTR, IShellItemArray*, DWORD*); -}; - -static IApplicationActivationManagerVtbl g_origAAMVtbl = {}; -static bool g_aamHookInstalled = false; -static std::mutex g_aamHookMutex; - -HRESULT STDMETHODCALLTYPE AAM_ActivateApplication_hook(IUnknown* pThis, LPCWSTR appUserModelId, LPCWSTR arguments, DWORD options, DWORD* processId) { - if (appUserModelId && arguments && _wcsnicmp(appUserModelId, L"windows.immersivecontrolpanel", 29) == 0) { - std::wstring uri = NormalizeUri(arguments); - auto result = ResolveUri(uri, nullptr); - if (result.intercept && !result.target.empty()) { - LaunchTarget(result.target); - if (processId) *processId = GetCurrentProcessId(); - return S_OK; - } - } - - if (g_origAAMVtbl.ActivateApplication) - return g_origAAMVtbl.ActivateApplication(pThis, appUserModelId, arguments, options, processId); - return E_FAIL; -} - -// Handles "See also" style links from classic namespace pages (e.g. the -// Personalization CPL's "Schermo" / "Centro accessibilità" links, and the -// Power Options "See also" panel) that Windows 11 resolves via protocol -// activation (ms-settings:...) rather than via ActivateApplication. -HRESULT STDMETHODCALLTYPE AAM_ActivateForProtocol_hook(IUnknown* pThis, LPCWSTR pszProtocol, IShellItemArray* pItemArray, DWORD* processId) { - if (pszProtocol && IsMsSettings(pszProtocol)) { - std::wstring uri = NormalizeUri(pszProtocol); - auto result = ResolveUri(uri, nullptr); - if (result.intercept) { - if (!result.target.empty()) LaunchTarget(result.target); - if (processId) *processId = GetCurrentProcessId(); - return S_OK; - } - } - - if (g_origAAMVtbl.ActivateForProtocol) - return g_origAAMVtbl.ActivateForProtocol(pThis, pszProtocol, pItemArray, processId); - return E_FAIL; -} - -static void InstallAAMHook() { - std::lock_guard lk(g_aamHookMutex); - if (g_aamHookInstalled) return; - - IUnknown* pAAM = nullptr; - HRESULT hr = CoCreateInstance(CLSID_ApplicationActivationManager_STC, nullptr, CLSCTX_INPROC_SERVER, - IID_IApplicationActivationManager_STC, (void**)&pAAM); - if (FAILED(hr) || !pAAM) { - Wh_Log(L"[AAM-HOOK] CoCreateInstance failed: 0x%08X", hr); - return; - } - - IApplicationActivationManagerVtbl* vtbl = *(IApplicationActivationManagerVtbl**)pAAM; - if (!vtbl || IsBadReadPtr(vtbl, sizeof(*vtbl))) { - Wh_Log(L"[AAM-HOOK] Invalid vtable pointer"); - pAAM->Release(); - return; - } - - g_origAAMVtbl = *vtbl; - - DWORD oldProtect; - if (!VirtualProtect(vtbl, sizeof(*vtbl), PAGE_READWRITE, &oldProtect)) { - Wh_Log(L"[AAM-HOOK] VirtualProtect failed"); - pAAM->Release(); - return; + return {L"", handled}; } - - vtbl->ActivateApplication = AAM_ActivateApplication_hook; - vtbl->ActivateForProtocol = AAM_ActivateForProtocol_hook; - VirtualProtect(vtbl, sizeof(*vtbl), oldProtect, &oldProtect); - - g_aamHookInstalled = true; - Wh_Log(L"[AAM-HOOK] Successfully installed (ActivateApplication + ActivateForProtocol)"); - pAAM->Release(); -} - -bool (*COpenControlPanel__MapLegacyName_orig)(void*, LPCWSTR, LPWSTR, UINT, bool*); - -bool COpenControlPanel__MapLegacyName_hook(void*, LPCWSTR pszLegacyName, LPWSTR pszNewName, UINT, bool* nameChanged) { - if (nameChanged) *nameChanged = false; - if (pszNewName) *pszNewName = L'\0'; - Wh_Log(L"[MAP-LEGACY] Suppressed mapping for: %s", pszLegacyName ? pszLegacyName : L"(null)"); - return false; + return {L"", false}; } -static bool InstallLegacyNameHook() { - HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); - if (!hShell32) return false; - - WindhawkUtils::SYMBOL_HOOK shell32_dll_hook = {{ - L"private: bool __cdecl COpenControlPanel::_MapLegacyName" - L"(unsigned short const *,unsigned short* ,unsigned int,bool *)" - }, - (void**)&COpenControlPanel__MapLegacyName_orig, - (void*)COpenControlPanel__MapLegacyName_hook, - false}; - - if (WindhawkUtils::HookSymbols(hShell32, &shell32_dll_hook, 1)) { - Wh_Log(L"[MAP-LEGACY] Hook installed successfully"); - return true; - } - - Wh_Log(L"[MAP-LEGACY] Failed to install hook"); - return false; +static std::wstring BaseNameLower(const std::wstring& path) { + size_t pos = path.rfind(L'\\'); + return ToLower((pos != std::wstring::npos) ? path.substr(pos + 1) : path); } -// --------------------------------------------------------------------------- -// ShellExecute/CreateProcess hooks -// --------------------------------------------------------------------------- - static bool IsControlSystemParams(const wchar_t* file, const wchar_t* params) { if (!file || !params) return false; std::wstring exe = BaseNameLower(file); @@ -1223,12 +1148,10 @@ static bool IsControlSystemCommand(const std::wstring& cmdLine) { std::wstring current; bool inQuotes = false; for (wchar_t c : cmdLine) { - if (c == L'\"') inQuotes = !inQuotes; + if (c == L'"') { inQuotes = !inQuotes; } else if (c == L' ' && !inQuotes) { if (!current.empty()) { tokens.push_back(current); current.clear(); } - } else { - current += c; - } + } else { current += c; } } if (!current.empty()) tokens.push_back(current); if (tokens.size() != 2) return false; @@ -1238,35 +1161,6 @@ static bool IsControlSystemCommand(const std::wstring& cmdLine) { return (arg == L"system" || arg == L"microsoft.system"); } -static std::wstring ExtractExplorerLaunchUri(const std::wstring& cmdLine) { - size_t i = 0, n = cmdLine.size(); - while (i < n && cmdLine[i] == L' ') i++; - - std::wstring exeToken; - if (i < n && cmdLine[i] == L'\"') { - size_t end = cmdLine.find(L'\"', i + 1); - if (end == std::wstring::npos) return L""; - exeToken = cmdLine.substr(i + 1, end - i - 1); - i = end + 1; - } else { - size_t start = i; - while (i < n && cmdLine[i] != L' ') i++; - exeToken = cmdLine.substr(start, i - start); - } - - if (BaseNameLower(exeToken) != L"explorer.exe") return L""; - while (i < n && cmdLine[i] == L' ') i++; - std::wstring rest = cmdLine.substr(i); - while (!rest.empty() && rest.back() == L' ') rest.pop_back(); - if (rest.size() >= 2 && rest.front() == L'\"' && rest.back() == L'\"') - rest = rest.substr(1, rest.size() - 2); - if (rest.empty()) return L""; - - if (IsMsSettings(rest.c_str())) return NormalizeUri(rest); - if (IsShellClsid(rest.c_str())) return ToLower(rest); - return L""; -} - BOOL WINAPI ShellExecuteExW_hook(SHELLEXECUTEINFOW* pei) { if (IsChildProcess()) return ShellExecuteExW_orig(pei); HookGuard guard; @@ -1278,7 +1172,7 @@ BOOL WINAPI ShellExecuteExW_hook(SHELLEXECUTEINFOW* pei) { if (pei->fMask & SEE_MASK_NOCLOSEPROCESS) pei->hProcess = nullptr; return TRUE; } - + std::wstring uri; if (IsMsSettings(pei->lpFile)) uri = NormalizeUri(pei->lpFile); else if (IsMsSettings(pei->lpParameters)) uri = NormalizeUri(pei->lpParameters); @@ -1296,7 +1190,6 @@ BOOL WINAPI ShellExecuteExW_hook(SHELLEXECUTEINFOW* pei) { return TRUE; } } - return ShellExecuteExW_orig(pei); } @@ -1310,7 +1203,7 @@ HINSTANCE WINAPI ShellExecuteW_hook(HWND hwnd, LPCWSTR op, LPCWSTR file, LPCWSTR LaunchTarget(g_isWin11 ? L"sysdm.cpl" : SYSTEM_PROPS_CLSID); return SHELL_EXECUTE_SUCCESS; } - + std::wstring uri; if (IsMsSettings(file)) uri = NormalizeUri(file); else if (IsMsSettings(params)) uri = NormalizeUri(params); @@ -1327,30 +1220,24 @@ HINSTANCE WINAPI ShellExecuteW_hook(HWND hwnd, LPCWSTR op, LPCWSTR file, LPCWSTR return SHELL_EXECUTE_SUCCESS; } } - return ShellExecuteW_orig(hwnd, op, file, params, dir, show); } BOOL WINAPI CreateProcessW_hook(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, - LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, - BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, - LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, - LPPROCESS_INFORMATION lpProcessInformation) { - if (IsChildProcess()) - return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, - bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, - lpStartupInfo, lpProcessInformation); - + LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation) { + if (IsChildProcess()) return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, + lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, + lpStartupInfo, lpProcessInformation); HookGuard guard; - if (guard.IsReentrant()) - return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, - bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, - lpStartupInfo, lpProcessInformation); - - if (!g_settings.enableRedirects || g_settings.uiOnlyRedirects) - return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, - bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, - lpStartupInfo, lpProcessInformation); + if (guard.IsReentrant()) return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, + lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, + lpStartupInfo, lpProcessInformation); + if (!g_settings.enableRedirects || g_settings.uiOnlyRedirects) return CreateProcessW_orig(lpApplicationName, + lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, + lpCurrentDirectory, lpStartupInfo, lpProcessInformation); if (lpCommandLine) { std::wstring cmdLine(lpCommandLine); @@ -1360,131 +1247,47 @@ BOOL WINAPI CreateProcessW_hook(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, SetLastError(ERROR_SUCCESS); return TRUE; } - - std::wstring uri = ExtractExplorerLaunchUri(cmdLine); - if (!uri.empty()) { - auto result = ResolveUri(uri, nullptr); - if (result.intercept) { - if (!result.target.empty()) LaunchTarget(result.target); - if (lpProcessInformation) ZeroMemory(lpProcessInformation, sizeof(PROCESS_INFORMATION)); - SetLastError(ERROR_SUCCESS); - return TRUE; - } - } - } - - return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, - bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, - lpStartupInfo, lpProcessInformation); -} - -// --------------------------------------------------------------------------- -// Symbol hooks and watchdog -// --------------------------------------------------------------------------- - -static bool TryInstallPniduiHook() { - std::lock_guard lk(g_pniduiHookMutex); - if (g_pniduiHookInstalled) return true; - - HMODULE hMod = GetModuleHandleW(L"pnidui.dll"); - if (!hMod) { - hMod = LoadLibraryExW(L"pnidui.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); - if (!hMod) { - Wh_Log(L"[PNIDUI-HOOK] pnidui.dll not loaded yet"); - return false; - } - } - - WindhawkUtils::SYMBOL_HOOK pnidui_dll_hooks[] = {{ - { - L"bool " -#ifdef _WIN64 - L"__cdecl" -#else - L"__stdcall" -#endif - L" ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu" - L"(struct HMENU__ *,struct HWND__* )" - }, - (void**)&g_icmhOrig_pnidui, - (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, - false - }}; - - bool result = WindhawkUtils::HookSymbols(hMod, pnidui_dll_hooks, 1); - if (result) { - Wh_Log(L"[PNIDUI-HOOK] Successfully installed pnidui.dll hook"); - g_pniduiHookInstalled = true; - } else { - Wh_Log(L"[PNIDUI-HOOK] Failed to install pnidui.dll hook"); } - return result; + return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, + bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); } -static DWORD WINAPI PniduiRetryThread(LPVOID) { - if (g_pniduiRetryRunning) return 0; - g_pniduiRetryRunning = true; - - // Attesa in step da 50ms invece di 500ms: rende il thread reattivo - // allo stop quasi istantaneamente, evitando che Wh_ModUninit debba - // ricorrere a TerminateThread() (causa nota di instabilità di explorer.exe). - const int MAX_WAIT_MS = 30000; - const DWORD STEP_MS = 50; - bool dllLoaded = false; - for (int waited = 0; waited < MAX_WAIT_MS && !g_pniduiRetryStop && !g_unloading.load(); waited += STEP_MS) { - if (GetModuleHandleW(L"pnidui.dll") != nullptr) { dllLoaded = true; break; } - Sleep(STEP_MS); - } +static void InstallImmersiveMenuHooks() { + struct DllHook { + const wchar_t* dll; + ICMH_CAODTM_t* orig; + } targets[] = { + { L"SndVolSSO.dll", &g_icmhOrig_SndVolSSO }, + { L"pnidui.dll", &g_icmhOrig_pnidui }, + }; - if (dllLoaded && !g_pniduiRetryStop && !g_unloading.load()) TryInstallPniduiHook(); - g_pniduiRetryRunning = false; - return 0; -} + for (auto& t : targets) { + HMODULE hMod = LoadLibraryExW(t.dll, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (!hMod) continue; -static void InstallImmersiveMenuHooks() { - // NOTA IMPORTANTE: WindhawkUtils::HookSymbols() non va richiamato una seconda - // volta sullo stesso modulo già hookato: pnidui.dll/SndVolSSO.dll/shell32.dll - // restano residenti per tutta la vita del processo explorer.exe anche quando - // il tray/toolbar viene ricreato (solo le finestre vengono ricreate, non i - // moduli). Ri-tentare l'hook in quei casi non solo è inutile, ma può - // fallire o corrompere il puntatore alla funzione originale già salvato, - // rompendo un redirect che fino a quel momento funzionava. Per questo ogni - // hook qui è protetto da un proprio flag "già installato". - if (!g_sndVolSSOHookInstalled) { - HMODULE hMod = LoadLibraryExW(L"SndVolSSO.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); - if (hMod) { - WindhawkUtils::SYMBOL_HOOK sndVolSSODllHooks[] = {{ - { - L"bool " + WindhawkUtils::SYMBOL_HOOK sndVolSSO_pnidui_hooks[] = { + {{ + L"bool " #ifdef _WIN64 - L"__cdecl" + L"__cdecl" #else - L"__stdcall" + L"__stdcall" #endif - L" ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu" - L"(struct HMENU__ *,struct HWND__* )" - }, - (void**)&g_icmhOrig_SndVolSSO, - (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, - false - }}; - if (WindhawkUtils::HookSymbols(hMod, sndVolSSODllHooks, 1)) - g_sndVolSSOHookInstalled = true; - } - } + L" ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu" + L"(struct HMENU__ *,struct HWND__ *)" + }, + (void**)t.orig, + (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook} + }; - if (!TryInstallPniduiHook()) { - if (!g_pniduiRetryRunning && !g_pniduiRetryThread) { - g_pniduiRetryStop = false; - g_pniduiRetryThread = CreateThread(nullptr, 0, PniduiRetryThread, nullptr, 0, nullptr); - } + WindhawkUtils::HookSymbols(hMod, sndVolSSO_pnidui_hooks, 1); } - if (g_isWin11 && !g_shell32DevicesHookInstalled) { + if (g_isWin11) { HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); if (hShell32) { - WindhawkUtils::SYMBOL_HOOK shell32_dll_hooks[] = {{ - { + WindhawkUtils::SYMBOL_HOOK shell32_hooks[] = { + {{ L"bool " #ifdef _WIN64 L"__cdecl" @@ -1495,348 +1298,118 @@ static void InstallImmersiveMenuHooks() { L"(struct HMENU__ *,unsigned int)" }, (void**)&g_icmhOrig_Shell32Devices, - (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, - false - }}; - if (WindhawkUtils::HookSymbols(hShell32, shell32_dll_hooks, 1)) - g_shell32DevicesHookInstalled = true; + (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook} + }; + + WindhawkUtils::HookSymbols(hShell32, shell32_hooks, 1); } } } - -static void InstallSyscallFallback() { - HMODULE hWin32u = GetModuleHandleW(L"win32u.dll"); - if (!hWin32u) hWin32u = LoadLibraryW(L"win32u.dll"); - if (!hWin32u) return; - - FARPROC pNtUserTrackPopupMenuEx = GetProcAddress(hWin32u, "NtUserTrackPopupMenuEx"); - if (!pNtUserTrackPopupMenuEx) return; - - if (Wh_SetFunctionHook((void*)pNtUserTrackPopupMenuEx, (void*)NtUserTrackPopupMenuEx_Hook, (void**)&g_origNtUserTrackPopupMenuEx)) - Wh_Log(L"[SYSCALL-HOOK] NtUserTrackPopupMenuEx hooked successfully"); -} - using CreateWindowExW_t = decltype(&CreateWindowExW); -static CreateWindowExW_t CreateWindowExW_Original = nullptr; - -static bool HasTrayBeenRecreated() { - HWND hTray = FindWindowW(L"Shell_TrayWnd", nullptr); - if (!hTray) return false; - bool recreated = (g_lastShellTrayWnd != nullptr && hTray != g_lastShellTrayWnd); - g_lastShellTrayWnd = hTray; - return recreated; -} - -static void ReinitializeTrayRedirect() { - Wh_Log(L"[TRAY-RESTART] Reinitializing tray redirect hooks"); - - RemoveTraySubclass(); - - // pnidui.dll/SndVolSSO.dll/shell32.dll restano residenti per tutta la vita - // del processo explorer.exe: un riavvio del tray/toolbar ricrea solo le - // finestre (Shell_TrayWnd/SysPager/ToolbarWindow32), non le DLL. Prima si - // azzerava incondizionatamente lo stato dell'hook pnidui a ogni riavvio, - // forzando un secondo WindhawkUtils::HookSymbols() sullo stesso modulo - // già hookato — cosa che Windhawk non supporta e che rompeva il redirect - // che fino a quel momento funzionava (da qui la dipendenza "goffa" dal - // momento di attivazione del mod). Ora l'hook viene reinstallato solo se - // il modulo è sparito o è stato ricaricato a un indirizzo diverso. - bool pniduiModuleChanged = false; - HMODULE hPniduiNow = GetModuleHandleW(L"pnidui.dll"); - if (hPniduiNow) { - MODULEINFO mi{}; - if (GetModuleInformation(GetCurrentProcess(), hPniduiNow, &mi, sizeof(mi))) { - if ((BYTE*)mi.lpBaseOfDll != g_pniduiBase) pniduiModuleChanged = true; - } - } else if (g_pniduiBase != nullptr) { - pniduiModuleChanged = true; - } - - if (pniduiModuleChanged) { - g_pniduiBase = nullptr; - g_pniduiEnd = nullptr; - { - std::lock_guard lk(g_pniduiHookMutex); - g_pniduiHookInstalled = false; - } - } - - if (g_settings.redirectSystemTray) SetupTraySubclass(); - - if (!g_pniduiHookInstalled) { - if (!TryInstallPniduiHook()) { - if (!g_pniduiRetryRunning && !g_pniduiRetryThread) { - g_pniduiRetryStop = false; - g_pniduiRetryThread = CreateThread(nullptr, 0, PniduiRetryThread, nullptr, 0, nullptr); - } - } - } - - // SndVolSSO/shell32 sono già protetti dai rispettivi flag "già installato" - // dentro InstallImmersiveMenuHooks(), quindi è sicuro richiamarla sempre: - // farà solo lavoro utile se manca ancora qualcosa da hookare. - InstallImmersiveMenuHooks(); -} - -// --- NEW: hidden message-only window that listens for the "TaskbarCreated" -// broadcast message. Explorer (and any shell component that recreates the -// tray) posts this message the moment the taskbar/tray is rebuilt, which is -// far more reliable and immediate than polling for a changed Shell_TrayWnd -// handle - this is what was causing "zero logs" after a restart: the -// polling watchdog could take up to a few seconds, and in some restart -// scenarios the Shell_TrayWnd handle compare alone didn't catch that the -// child SysPager/ToolbarWindow32 had actually been rebuilt. -static LRESULT CALLBACK TrayMsgWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { - if (g_uTaskbarCreatedMsg != 0 && msg == g_uTaskbarCreatedMsg) { - Wh_Log(L"[TRAY-RESTART] TaskbarCreated message received"); - g_lastShellTrayWnd = FindWindowW(L"Shell_TrayWnd", nullptr); - ReinitializeTrayRedirect(); - return 0; - } - return DefWindowProcW(hwnd, msg, wParam, lParam); -} - -static void InstallTaskbarCreatedListener() { - g_uTaskbarCreatedMsg = RegisterWindowMessageW(L"TaskbarCreated"); - if (g_uTaskbarCreatedMsg == 0) { - Wh_Log(L"[TRAY-RESTART] Failed to register TaskbarCreated message"); - return; - } - - WNDCLASSEXW wc = { sizeof(wc) }; - wc.lpfnWndProc = TrayMsgWndProc; - wc.hInstance = GetModuleHandleW(nullptr); - wc.lpszClassName = TRAY_MSG_WNDCLASS; - RegisterClassExW(&wc); // OK if this fails because it's already registered. - - g_hTrayMsgWindow = CreateWindowExW(0, TRAY_MSG_WNDCLASS, L"", 0, 0, 0, 0, 0, - HWND_MESSAGE, nullptr, wc.hInstance, nullptr); - if (!g_hTrayMsgWindow) - Wh_Log(L"[TRAY-RESTART] Failed to create message-only listener window"); - else - Wh_Log(L"[TRAY-RESTART] TaskbarCreated listener installed"); -} - -static void RemoveTaskbarCreatedListener() { - if (g_hTrayMsgWindow) { - DestroyWindow(g_hTrayMsgWindow); - g_hTrayMsgWindow = nullptr; - } - UnregisterClassW(TRAY_MSG_WNDCLASS, GetModuleHandleW(nullptr)); -} - -static DWORD WINAPI TraySubclassWatchdogThread(LPVOID) { - const int FAST_PHASE_CHECKS = 60; - const DWORD FAST_INTERVAL_MS = 500; - const DWORD SLOW_INTERVAL_MS = 3000; - int tick = 0; - - // Aspetta che il sistema si stabilizzi all'avvio, ma in modo interrompibile: - // uno Sleep() bloccante qui impedirebbe a Wh_ModUninit di terminare questo - // thread in tempo, forzando una TerminateThread() che può destabilizzare - // explorer.exe (causa del riavvio della shell durante la ricompilazione). - if (g_hWatchdogStopEvent) { - WaitForSingleObject(g_hWatchdogStopEvent, 2000); - } else { - for (int i = 0; i < 40 && !g_traySubclassWatchdogStop; i++) Sleep(50); - } - - while (!g_traySubclassWatchdogStop) { - DWORD waitTime = tick < FAST_PHASE_CHECKS ? FAST_INTERVAL_MS : SLOW_INTERVAL_MS; - - // Usa l'evento per essere interrompibile immediatamente - if (g_hWatchdogStopEvent) { - DWORD waitResult = WaitForSingleObject(g_hWatchdogStopEvent, waitTime); - if (waitResult == WAIT_OBJECT_0 || g_traySubclassWatchdogStop) break; - } else { - // Fallback se l'evento non è stato creato (polling ogni 50ms) - for (DWORD i = 0; i < waitTime / 50 && !g_traySubclassWatchdogStop; i++) { - Sleep(50); - } - if (g_traySubclassWatchdogStop) break; - } - - tick++; - if (g_traySubclassWatchdogStop) break; - - // Verifica se siamo ancora nel processo corretto - DWORD currentProcessId = GetCurrentProcessId(); - DWORD explorerPid = 0; - - // Check se Explorer è ancora in esecuzione - HWND hTray = FindWindowW(L"Shell_TrayWnd", nullptr); - if (!hTray) { - if (g_lastShellTrayWnd != nullptr) { - Wh_Log(L"[WATCHDOG] Shell_TrayWnd disappeared - possible Explorer crash"); - g_lastShellTrayWnd = nullptr; - g_hTrayToolbar = nullptr; - std::lock_guard lk(g_pniduiHookMutex); - g_pniduiHookInstalled = false; - } - continue; - } - - // Verifica che la finestra appartenga al nostro processo - GetWindowThreadProcessId(hTray, &explorerPid); - if (explorerPid != currentProcessId) { - // Non è il nostro Explorer, resetta lo stato - if (g_lastShellTrayWnd != nullptr) { - Wh_Log(L"[WATCHDOG] Shell_TrayWnd belongs to different process"); - g_lastShellTrayWnd = nullptr; - g_hTrayToolbar = nullptr; - } - continue; - } - - // Caso 1: Shell_TrayWnd cambiata (nuova istanza Explorer) - if (HasTrayBeenRecreated()) { - Wh_Log(L"[WATCHDOG] Detected new Shell_TrayWnd instance - reinitializing"); - ReinitializeTrayRedirect(); - tick = 0; - continue; - } - - // Salta controlli toolbar se tray redirect è disabilitato - if (!g_settings.redirectSystemTray) continue; - - // Caso 2: Toolbar potrebbe essere stata ricreata internamente - HWND hCurrentToolbar = FindTrayToolbar(); - - bool oldToolbarValid = (g_hTrayToolbar && IsWindow(g_hTrayToolbar)); - bool currentToolbarValid = (hCurrentToolbar && IsWindow(hCurrentToolbar)); - - if (hCurrentToolbar != g_hTrayToolbar) { - if (oldToolbarValid) { - Wh_Log(L"[WATCHDOG] Toolbar internally recreated - reinitializing"); - } else if (currentToolbarValid) { - Wh_Log(L"[WATCHDOG] Previously lost toolbar now available"); +CreateWindowExW_t CreateWindowExW_Original; + +HWND WINAPI CreateWindowExW_Hook( + DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, + DWORD dwStyle, int X, int Y, int nWidth, int nHeight, + HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam) +{ + HWND hwnd = CreateWindowExW_Original( + dwExStyle, lpClassName, lpWindowName, dwStyle, + X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam); + + if (g_settings.redirectSystemTray && hwnd && lpClassName && !IS_INTRESOURCE(lpClassName)) { + if (wcscmp(lpClassName, L"ToolbarWindow32") == 0) { + // FIX: Ищем нужный тулбар напрямую по родительской цепи + // Это решает проблему, когда FindWindowW возвращает NULL во время процесса создания окон. + bool isTrayToolbar = false; + HWND hParent = hWndParent; + int depth = 0; + while (hParent && IsWindow(hParent) && depth < 10) { + wchar_t className[256] = {0}; + if (GetClassNameW(hParent, className, 256)) { + if (wcscmp(className, L"TrayNotifyWnd") == 0 || wcscmp(className, L"NotifyIconOverflowWindow") == 0) { + isTrayToolbar = true; + break; + } + } + hParent = GetParent(hParent); + depth++; } - ReinitializeTrayRedirect(); - tick = 0; - continue; - } - - // Caso 3: Toolbar subclassata non è più valida - if (g_hTrayToolbar && !IsWindow(g_hTrayToolbar)) { - Wh_Log(L"[WATCHDOG] Subclassed toolbar destroyed - reinstalling subclass"); - g_hTrayToolbar = nullptr; - SetupTraySubclass(); - tick = 0; - continue; - } - - // Caso 4: Controllo periodico hook pnidui.dll - if (g_settings.redirectSystemTray && GetModuleHandleW(L"pnidui.dll")) { - std::lock_guard lk(g_pniduiHookMutex); - if (!g_pniduiHookInstalled) { - Wh_Log(L"[WATCHDOG] pnidui hook missing - attempting reinstall"); - TryInstallPniduiHook(); + + if (isTrayToolbar) { + InitTrayDllInfo(); + // FIX: sabclassing rimandato fuori dallo stack annidato di WM_CREATE + // per evitare il falso positivo di "instabilita'" di Windhawk al + // primo riavvio di Explorer (vedi commento su SubclassToolbarDeferred). + SubclassToolbarDeferred(hwnd); } } } - - Wh_Log(L"[WATCHDOG] Watchdog thread exiting"); - return 0; -} - -HWND WINAPI CreateWindowExW_Hook(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, - DWORD dwStyle, int X, int Y, int nWidth, int nHeight, - HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam) { - HWND hwnd = CreateWindowExW_Original(dwExStyle, lpClassName, lpWindowName, dwStyle, - X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam); - - if (g_settings.redirectSystemTray && hwnd && lpClassName && !IS_INTRESOURCE(lpClassName)) { - if (wcscmp(lpClassName, L"ToolbarWindow32") == 0 && - (!g_hTrayToolbar || !IsWindow(g_hTrayToolbar))) { - SetupTraySubclass(); - } - } - + return hwnd; } -// --------------------------------------------------------------------------- -// Windhawk entry points -// --------------------------------------------------------------------------- +static DWORD WINAPI DelayedCreateWindowExWHookProc(LPVOID) { + Sleep(300); + Wh_SetFunctionHook((void*)CreateWindowExW, (void*)CreateWindowExW_Hook, (void**)&CreateWindowExW_Original); + return 0; +} BOOL Wh_ModInit() { - Wh_Log(L"Redirect Settings to Control Panel initialized"); - - // NUOVO: Verifica se siamo nel processo explorer.exe corretto (quello che possiede la Shell_TrayWnd) - // Se non lo siamo, rifiuta l'inizializzazione per evitare conflitti tra processi fantasma - HWND hTray = FindWindowW(L"Shell_TrayWnd", nullptr); - if (hTray) { - DWORD shellPid = 0; - GetWindowThreadProcessId(hTray, &shellPid); - DWORD ourPid = GetCurrentProcessId(); - - if (shellPid != ourPid) { - Wh_Log(L"[STABLE] Not the main shell process (PID %lu, shell PID %lu) - refusing to install hooks", - ourPid, shellPid); + // FIX: non inizializzare il mod in processi explorer.exe ausiliari + // (viste di cartella tipo "shell:::{CLSID}" o COM factory "-Embedding"/"/factory"). + // Questi processi non sono la shell vera, terminano quasi subito, e inizializzare + // qui gli hook del tray/menu causa il falso positivo di "instabilita'" di Windhawk. + { + const wchar_t* cmdLine = GetCommandLineW(); + if (cmdLine && (wcsstr(cmdLine, L"-Embedding") || + wcsstr(cmdLine, L"/factory") || + wcsstr(cmdLine, L"shell:::"))) { + Wh_Log(L"Skipping init: auxiliary explorer.exe process (%s)", cmdLine); return FALSE; } } - // Se Shell_TrayWnd non esiste affatto, procediamo comunque (potrebbe non essere ancora creata) - - std::lock_guard lock(g_initMutex); - g_unloading.store(false); + Wh_Log(L"Initializing the Redirect Settings to Control Panel mod..."); - // Inizializzazione di base DetectWindowsVersion(); LoadSettings(); BuildChildEnvironment(); InitMappings(); - - // Shell hooks (fondamentali, vanno installati una volta sola) + HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); if (!hShell32) hShell32 = LoadLibraryW(L"shell32.dll"); if (!hShell32) return FALSE; - + FARPROC pExW = GetProcAddress(hShell32, "ShellExecuteExW"); FARPROC pW = GetProcAddress(hShell32, "ShellExecuteW"); if (!pExW || !pW) return FALSE; - - if (!g_shellExecHooksInstalled) { - Wh_SetFunctionHook((void*)pExW, (void*)ShellExecuteExW_hook, (void**)&ShellExecuteExW_orig); - Wh_SetFunctionHook((void*)pW, (void*)ShellExecuteW_hook, (void**)&ShellExecuteW_orig); - g_shellExecHooksInstalled = true; - Wh_Log(L"[STABLE] Shell execution hooks installed"); - } - - // CreateProcess hook + + Wh_SetFunctionHook((void*)pExW, (void*)ShellExecuteExW_hook, (void**)&ShellExecuteExW_orig); + Wh_SetFunctionHook((void*)pW, (void*)ShellExecuteW_hook, (void**)&ShellExecuteW_orig); + HMODULE hKernel32 = GetModuleHandleW(L"kernel32.dll"); if (!hKernel32) hKernel32 = LoadLibraryW(L"kernel32.dll"); - if (hKernel32 && !g_createProcessHookInstalled) { + if (hKernel32) { FARPROC pCPW = GetProcAddress(hKernel32, "CreateProcessW"); - if (pCPW) { - Wh_SetFunctionHook((void*)pCPW, (void*)CreateProcessW_hook, (void**)&CreateProcessW_orig); - g_createProcessHookInstalled = true; - Wh_Log(L"[STABLE] CreateProcess hook installed"); - } + if (pCPW) Wh_SetFunctionHook((void*)pCPW, (void*)CreateProcessW_hook, (void**)&CreateProcessW_orig); } - - // CreateWindowEx hook per tray - Wh_SetFunctionHook((void*)CreateWindowExW, (void*)CreateWindowExW_Hook, (void**)&CreateWindowExW_Original); - - - // Tray subsystem - if (g_settings.redirectSystemTray) { - SetupTraySubclass(); + // FIX: ritardiamo di poco l'installazione dell'hook su CreateWindowExW per + // evitare di intercettarlo durante la raffica iniziale di creazione finestre + // di Explorer all'avvio (sospettata causa del falso positivo di "instabilita'" + // di Windhawk). Tutto il resto rimane invariato. + { + HANDLE hDelayThread = CreateThread(nullptr, 0, DelayedCreateWindowExWHookProc, nullptr, 0, nullptr); + if (hDelayThread) CloseHandle(hDelayThread); } + InstallImmersiveMenuHooks(); - InstallSyscallFallback(); - - if (g_settings.comActivationRedirect && g_isWin11) { - InstallAAMHook(); - } - - if (g_settings.legacyNameMappingFix) { - InstallLegacyNameHook(); + + if (g_settings.redirectSystemTray) { + SetupTraySubclass(); } - - // TrackPopupMenu hooks + HMODULE hUser32 = GetModuleHandleW(L"user32.dll"); if (!hUser32) hUser32 = LoadLibraryW(L"user32.dll"); if (hUser32) { @@ -1845,201 +1418,23 @@ BOOL Wh_ModInit() { Wh_SetFunctionHook(pTrackPopupMenuEx, (void*)TrackPopupMenuEx_Hook, (void**)&g_origTrackPopupMenuEx); } } - - // TaskbarCreated listener - g_lastShellTrayWnd = FindWindowW(L"Shell_TrayWnd", nullptr); - if (!g_taskbarListenerInstalled) { - InstallTaskbarCreatedListener(); - g_taskbarListenerInstalled = true; - } - - if (g_hWatchdogStopEvent) { - CloseHandle(g_hWatchdogStopEvent); - } - g_hWatchdogStopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); - g_traySubclassWatchdogStop = false; - g_traySubclassWatchdogThread = CreateThread(nullptr, 0, TraySubclassWatchdogThread, nullptr, 0, nullptr); - - g_modActive.store(true); - Wh_Log(L"[STABLE] Initialization completed"); - + return TRUE; } -void Wh_ModUninit() { - Wh_Log(L"[STABLE] Uninit started"); - - // 0. Segnala a tutti i thread in background che il mod sta per scaricarsi, - // così le loro attese cooperative terminano subito invece di forzare - // TerminateThread() più avanti (causa nota di instabilità di explorer.exe). - g_unloading.store(true); - - // 1. Disabilita subito il mod per bloccare nuove chiamate - g_modActive.store(false); - - // 2. Ferma il reinit thread se in esecuzione - if (g_reinitThread) { - g_reinitPending.store(false); // Cancella eventuali reinit pendenti - if (WaitForSingleObject(g_reinitThread, 500) == WAIT_TIMEOUT) { - Wh_Log(L"[STABLE] Reinit thread timeout, forcing termination"); - TerminateThread(g_reinitThread, 0); - } - CloseHandle(g_reinitThread); - g_reinitThread = nullptr; - } - - // 3. Ferma il watchdog immediatamente con l'evento - g_traySubclassWatchdogStop = true; - if (g_hWatchdogStopEvent) { - SetEvent(g_hWatchdogStopEvent); // Sveglia il watchdog immediatamente - } - if (g_traySubclassWatchdogThread) { - if (WaitForSingleObject(g_traySubclassWatchdogThread, 500) == WAIT_TIMEOUT) { - Wh_Log(L"[STABLE] Watchdog thread timeout, forcing termination"); - TerminateThread(g_traySubclassWatchdogThread, 0); - } - CloseHandle(g_traySubclassWatchdogThread); - g_traySubclassWatchdogThread = nullptr; - } - if (g_hWatchdogStopEvent) { - CloseHandle(g_hWatchdogStopEvent); - g_hWatchdogStopEvent = nullptr; - } - - // 4. Ferma il retry thread di pnidui - if (g_pniduiRetryThread) { - g_pniduiRetryStop = true; - if (WaitForSingleObject(g_pniduiRetryThread, 1000) == WAIT_TIMEOUT) { - Wh_Log(L"[STABLE] Pnidui retry thread timeout, forcing termination"); - TerminateThread(g_pniduiRetryThread, 0); - } - CloseHandle(g_pniduiRetryThread); - g_pniduiRetryThread = nullptr; - g_pniduiRetryRunning = false; - } - - // 5. Ripristina hook COM - { - std::lock_guard lk(g_aamHookMutex); - if (g_aamHookInstalled) { - IUnknown* pAAM = nullptr; - if (SUCCEEDED(CoCreateInstance(CLSID_ApplicationActivationManager_STC, nullptr, - CLSCTX_INPROC_SERVER, IID_IApplicationActivationManager_STC, (void**)&pAAM)) && pAAM) { - auto vtbl = *(IApplicationActivationManagerVtbl**)pAAM; - DWORD oldProtect; - if (VirtualProtect(vtbl, sizeof(*vtbl), PAGE_READWRITE, &oldProtect)) { - vtbl->ActivateApplication = g_origAAMVtbl.ActivateApplication; - vtbl->ActivateForProtocol = g_origAAMVtbl.ActivateForProtocol; - VirtualProtect(vtbl, sizeof(*vtbl), oldProtect, &oldProtect); - } - pAAM->Release(); - } - g_aamHookInstalled = false; - } - } - - // 6. Rimuovi listener TaskbarCreated - RemoveTaskbarCreatedListener(); - g_taskbarListenerInstalled = false; - - // 7. Rimuovi subclass della tray - RemoveTraySubclass(); - - g_rcplHooksInstalled = false; - - Wh_Log(L"[STABLE] Uninit completed"); +void Wh_ModUninit() { + UnsubclassAllToolbars(); } -static void SafeReinitializeAll() { - std::lock_guard lock(g_initMutex); - - Wh_Log(L"[STABLE] Starting safe reinitialization..."); - - // Spegni il mod attivo durante la transizione - g_modActive.store(false); - - // Piccola pausa per permettere alle chiamate in corso di completare - Sleep(100); - - // Pulisci stato tray senza rimuovere gli hook di sistema - RemoveTraySubclass(); - - // Vedi il commento in ReinitializeTrayRedirect(): pnidui.dll resta - // residente tra un cambio di impostazioni e l'altro, quindi non va - // ri-hookato incondizionatamente (Windhawk non supporta un secondo - // HookSymbols sullo stesso modulo). Invalidiamo solo se è realmente - // sparito o è stato ricaricato a un indirizzo diverso. - bool pniduiModuleChanged = false; - HMODULE hPniduiNow = GetModuleHandleW(L"pnidui.dll"); - if (hPniduiNow) { - MODULEINFO mi{}; - if (GetModuleInformation(GetCurrentProcess(), hPniduiNow, &mi, sizeof(mi))) { - if ((BYTE*)mi.lpBaseOfDll != g_pniduiBase) pniduiModuleChanged = true; - } - } else if (g_pniduiBase != nullptr) { - pniduiModuleChanged = true; - } - if (pniduiModuleChanged) { - g_pniduiBase = nullptr; - g_pniduiEnd = nullptr; - { - std::lock_guard lk(g_pniduiHookMutex); - g_pniduiHookInstalled = false; - } - } - - // Ricarica tutto lo stato +void Wh_ModSettingsChanged() { + UnsubclassAllToolbars(); + g_sndVolSSOBase = nullptr; + g_sndVolSSOEnd = nullptr; + g_pniduiBase = nullptr; + g_pniduiEnd = nullptr; LoadSettings(); - BuildChildEnvironment(); InitMappings(); - - // Reinstalla solo i sottosistemi che dipendono dalle impostazioni if (g_settings.redirectSystemTray) { SetupTraySubclass(); - InstallImmersiveMenuHooks(); - } - - if (g_settings.comActivationRedirect && g_isWin11) { - InstallAAMHook(); - } - - if (g_settings.legacyNameMappingFix) { - InstallLegacyNameHook(); - } - - // Aggiorna riferimento tray - g_lastShellTrayWnd = FindWindowW(L"Shell_TrayWnd", nullptr); - - // Riattiva il mod - g_modActive.store(true); - - Wh_Log(L"[STABLE] Safe reinitialization completed"); -} -static DWORD WINAPI DeferredReinitThreadProc(LPVOID) { - Wh_Log(L"[STABLE] Deferred reinit thread started"); - for (int i = 0; i < 10 && !g_unloading.load(); i++) Sleep(50); - - if (g_unloading.load()) { - g_reinitPending.store(false); - return 0; - } - - // Reinizializza - SafeReinitializeAll(); - - g_reinitPending.store(false); - Wh_Log(L"[STABLE] Deferred reinit thread completed"); - - return 0; -} - -void Wh_ModSettingsChanged() { - Wh_Log(L"[STABLE] Settings changed - scheduling safe reinit"); - - if (!g_reinitPending.exchange(true)) { - if (g_reinitThread) CloseHandle(g_reinitThread); - g_reinitThread = CreateThread(nullptr, 0, DeferredReinitThreadProc, nullptr, 0, nullptr); - } else { - Wh_Log(L"[STABLE] Reinit already pending, skipping duplicate"); } } From ffc74c56b112bd353bb1b9c73f0009e9696f4675 Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Sat, 11 Jul 2026 14:01:43 +0200 Subject: [PATCH 15/25] Fix validator issue --- mods/settings-to-control-panel.wh.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index e594a2fcf2..c498ff74ea 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -35,10 +35,8 @@ Panel pages, using only native Windows components. - The system tray context menu redirect only supports the Win32 taskbar (the one from Windows 10 and previous versions). If using Windows 11, it might function decently but it is still an experimental feature. - The device & printers system tray redirect may not work on some Windows 11 configurations, as Microsoft hardcoded the redirect to the Settings app in certain shell code paths. This could change in future if correct documentation is found. -- On some systems, Windhawk's "system instability" warning may briefly appear on the - first tray redirect after an Explorer restart. This does not affect functionality - and the redirect completes normally; it appears to be related to the burst of - window creation during Explorer's startup rather than an actual crash or hang. +- The two new experimental features above (`ComActivationRedirect` and `LegacyNameMappingFix`) are based on undocumented/internal Windows behavior and reverse engineering assumptions. They are disabled by default, are not guaranteed to work on every Windows build, and could stop working (harmlessly) after a Windows update. They are written to always fall back to the original, unmodified behavior whenever something doesn't match what's expected, so enabling them should never break normal functionality — worst case, they simply do nothing. + --- ## Credits @@ -1265,7 +1263,8 @@ static void InstallImmersiveMenuHooks() { HMODULE hMod = LoadLibraryExW(t.dll, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if (!hMod) continue; - WindhawkUtils::SYMBOL_HOOK sndVolSSO_pnidui_hooks[] = { + // SndVolSSO.dll, pnidui.dll + WindhawkUtils::SYMBOL_HOOK immersiveMenuHooks[] = { {{ L"bool " #ifdef _WIN64 @@ -1280,13 +1279,13 @@ static void InstallImmersiveMenuHooks() { (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook} }; - WindhawkUtils::HookSymbols(hMod, sndVolSSO_pnidui_hooks, 1); + WindhawkUtils::HookSymbols(hMod, immersiveMenuHooks, 1); } if (g_isWin11) { HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); if (hShell32) { - WindhawkUtils::SYMBOL_HOOK shell32_hooks[] = { + WindhawkUtils::SYMBOL_HOOK shell32dll_hooks[] = { {{ L"bool " #ifdef _WIN64 @@ -1301,7 +1300,7 @@ static void InstallImmersiveMenuHooks() { (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook} }; - WindhawkUtils::HookSymbols(hShell32, shell32_hooks, 1); + WindhawkUtils::HookSymbols(hShell32, shell32dll_hooks, 1); } } } From fb0b3abddab7675021509fc718479c72dc99208c Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Sat, 11 Jul 2026 17:19:32 +0200 Subject: [PATCH 16/25] Added watchdog, minor context menu fix and reverted some changes --- mods/settings-to-control-panel.wh.cpp | 930 ++++++++++++++++++++------ 1 file changed, 708 insertions(+), 222 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index c498ff74ea..6cc03b576f 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -2,7 +2,7 @@ // @id settings-to-control-panel // @name Redirect Settings to Control Panel // @description Forces classic Control Panel to open instead of Windows 10/11 Settings app using native components. Primarily designed for Windows 10; Windows 11 support is limited due to Microsoft's shell architecture changes. -// @version 10.0.28 +// @version 10.0.30 // @author babamohammed // @github https://github.com/babamohammed2022 // @include explorer.exe @@ -11,7 +11,10 @@ // ==WindhawkModReadme== /* # Redirect Settings → Control Panel - +## Screenshot +![Image](https://raw.githubusercontent.com/babamohammed2022/babamohammed2022/main/Senza%20nome.png) +--- +## About This mod intercepts modern `ms-settings:` links (the ones that open the Settings app) and redirects them to their corresponding classic Control Panel pages, using only native Windows components. @@ -76,6 +79,7 @@ Panel pages, using only native Windows components. #include #include #include +#include #include #include #include @@ -85,6 +89,14 @@ Panel pages, using only native Windows components. #include #include +// Manually defined GUIDs to avoid requiring -luuid / static ole32 linkage. +// {45BA127D-10A8-46EA-8AB7-56EA9078943C} = CLSID_ApplicationActivationManager +static const CLSID CLSID_ApplicationActivationManager_STC = + { 0x45ba127d, 0x10a8, 0x46ea, { 0x8a, 0xb7, 0x56, 0xea, 0x90, 0x78, 0x94, 0x3c } }; +// {2E941141-7F97-4756-BA1D-9DECDE894A3D} = IID_IApplicationActivationManager +static const IID IID_IApplicationActivationManager_STC = + { 0x2e941141, 0x7f97, 0x4756, { 0xba, 0x1d, 0x9d, 0xec, 0xde, 0x89, 0x4a, 0x3d } }; + // Custom IDs for tray menu redirection (TrackPopupMenuEx method) #define TRAY_CUSTOM_ID_AUDIO 65001 #define TRAY_CUSTOM_ID_NETWORK 65002 @@ -104,6 +116,13 @@ using ICMH_CAODTM_t = bool(__fastcall*)(HMENU, HWND); static ICMH_CAODTM_t g_icmhOrig_SndVolSSO = nullptr; static ICMH_CAODTM_t g_icmhOrig_pnidui = nullptr; static ICMH_CAODTM_t g_icmhOrig_Shell32Devices = nullptr; +static bool g_pniduiHookInstalled = false; +static std::mutex g_pniduiHookMutex; +static HANDLE g_pniduiRetryThread = nullptr; +static volatile bool g_pniduiRetryStop = false; +static HANDLE g_traySubclassWatchdogThread = nullptr; +static volatile bool g_traySubclassWatchdogStop = false; +static HWND g_lastShellTrayWnd = nullptr; static bool __fastcall ICMH_CAODTM_hook(HMENU, HWND); @@ -125,6 +144,9 @@ using ShellExecuteW_t = HINSTANCE(WINAPI*)(HWND, LPCWSTR, LPCWSTR, LPCWSTR, LPCW using ShellExecuteExW_t = BOOL(WINAPI*)(SHELLEXECUTEINFOW*); static ShellExecuteExW_t ShellExecuteExW_orig = nullptr; static ShellExecuteW_t ShellExecuteW_orig = nullptr; +// NtUserTrackPopupMenuEx fallback (syscall level) +using NtUserTrackPopupMenuEx_t = BOOL(WINAPI*)(HMENU, UINT, int, int, HWND, const TPMPARAMS*); +static NtUserTrackPopupMenuEx_t g_origNtUserTrackPopupMenuEx = nullptr; struct ResolveResult { std::wstring target; @@ -161,38 +183,6 @@ static bool IsChildProcess() { return GetEnvironmentVariableW(L"WH_STC_NOREDIRECT", nullptr, 0) > 0; } -static bool IsMemoryReadable(const void* ptr, size_t size) { - if (!ptr) return false; - - MEMORY_BASIC_INFORMATION mbi{}; - if (!VirtualQuery(ptr, &mbi, sizeof(mbi))) return false; - - if (mbi.State != MEM_COMMIT) return false; - - if (mbi.Protect & PAGE_NOACCESS) return false; - if (mbi.Protect & PAGE_GUARD) return false; - - // Deve avere almeno un permesso di lettura - DWORD readableMask = PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY | - PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY; - if (!(mbi.Protect & readableMask)) return false; - - // Verifica che l'intero blocco richiesto rientri nella regione committata - uintptr_t regionEnd = (uintptr_t)mbi.BaseAddress + mbi.RegionSize; - uintptr_t readEnd = (uintptr_t)ptr + size; - if (readEnd > regionEnd) return false; - - return true; -} - -static bool SafeReadIconHwnd(DWORD_PTR dwData, HWND* outHwnd) { - if (!IsMemoryReadable((const void*)dwData, sizeof(HWND))) { - return false; - } - *outHwnd = *(HWND*)dwData; - return true; -} - struct ModSettings { bool enableRedirects = true; bool redirectSystemTray = false; @@ -200,6 +190,8 @@ struct ModSettings { int fallbackMode = 2; bool win11CompatibilityMode = false; int maxLaunchesPerUri = 3; + bool comActivationRedirect = true; + bool legacyNameMappingFix = true; }; static ModSettings g_settings; @@ -227,6 +219,9 @@ static void LoadSettings() { int ml = Wh_GetIntSetting(L"MaxLaunchesPerUri"); g_settings.maxLaunchesPerUri = (ml >= 0 && ml <= 20) ? ml : 3; + + g_settings.comActivationRedirect = Wh_GetIntSetting(L"ComActivationRedirect") != 0; + g_settings.legacyNameMappingFix = Wh_GetIntSetting(L"LegacyNameMappingFix") != 0; } static bool g_isWin11 = false; @@ -355,34 +350,30 @@ static std::wstring ToLower(std::wstring s) { return s; } +static HWND g_hTrayToolbar = nullptr; static BYTE* g_sndVolSSOBase = nullptr; static BYTE* g_sndVolSSOEnd = nullptr; static BYTE* g_pniduiBase = nullptr; static BYTE* g_pniduiEnd = nullptr; -static std::mutex g_toolbarsMutex; -static std::unordered_set g_subclassedToolbars; - static bool InitTrayDllInfo() { - if (!g_sndVolSSOBase) { - HMODULE hSndVol = GetModuleHandleW(L"SndVolSSO.dll"); - if (hSndVol) { - MODULEINFO mi{}; - if (GetModuleInformation(GetCurrentProcess(), hSndVol, &mi, sizeof(mi))) { - g_sndVolSSOBase = (BYTE*)mi.lpBaseOfDll; - g_sndVolSSOEnd = g_sndVolSSOBase + mi.SizeOfImage; - } + if (g_sndVolSSOBase && g_pniduiBase) return true; + + HMODULE hSndVol = GetModuleHandleW(L"SndVolSSO.dll"); + if (hSndVol) { + MODULEINFO mi{}; + if (GetModuleInformation(GetCurrentProcess(), hSndVol, &mi, sizeof(mi))) { + g_sndVolSSOBase = (BYTE*)mi.lpBaseOfDll; + g_sndVolSSOEnd = g_sndVolSSOBase + mi.SizeOfImage; } } - if (!g_pniduiBase) { - HMODULE hPniDui = GetModuleHandleW(L"pnidui.dll"); - if (hPniDui) { - MODULEINFO mi{}; - if (GetModuleInformation(GetCurrentProcess(), hPniDui, &mi, sizeof(mi))) { - g_pniduiBase = (BYTE*)mi.lpBaseOfDll; - g_pniduiEnd = g_pniduiBase + mi.SizeOfImage; - } + HMODULE hPniDui = GetModuleHandleW(L"pnidui.dll"); + if (hPniDui) { + MODULEINFO mi{}; + if (GetModuleInformation(GetCurrentProcess(), hPniDui, &mi, sizeof(mi))) { + g_pniduiBase = (BYTE*)mi.lpBaseOfDll; + g_pniduiEnd = g_pniduiBase + mi.SizeOfImage; } } @@ -390,25 +381,18 @@ static bool InitTrayDllInfo() { } static int GetTrayButtonType(HWND hToolbar, int buttonIndex) { - InitTrayDllInfo(); - if (buttonIndex < 0) return 0; + InitTrayDllInfo(); TBBUTTON tb{}; if (!SendMessageW(hToolbar, TB_GETBUTTON, buttonIndex, (LPARAM)&tb)) return 0; if (!tb.dwData) return 0; - HWND hIconWnd = nullptr; - if (!SafeReadIconHwnd(tb.dwData, &hIconWnd)) { - Wh_Log(L"[TRAY-HOOK] Skipped unreadable icon pointer (tray not fully initialized yet)"); - return 0; - } - + HWND hIconWnd = *(HWND*)tb.dwData; if (!hIconWnd || !IsWindow(hIconWnd)) return 0; wchar_t className[256]{}; if (!GetClassNameW(hIconWnd, className, 256)) return 0; - if (wcsncmp(className, L"ATL:", 4) != 0) { return 0; } @@ -471,13 +455,6 @@ static void OpenClassicDevicesAndPrinters() { static LRESULT CALLBACK TrayToolbarSubclassProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, DWORD_PTR dwRefData) { - // FIX: автоматически очищаем сет, если окно трея уничтожено (например, Explorer перезагружает UI) - if (msg == WM_NCDESTROY) { - std::lock_guard lk(g_toolbarsMutex); - g_subclassedToolbars.erase(hwnd); - return DefSubclassProc(hwnd, msg, wParam, lParam); - } - if (msg == WM_RBUTTONUP) { POINT pt; pt.x = (int)(short)LOWORD(lParam); @@ -501,83 +478,36 @@ static LRESULT CALLBACK TrayToolbarSubclassProc( return DefSubclassProc(hwnd, msg, wParam, lParam); } -static void SubclassToolbar(HWND hwnd) { - if (!hwnd) return; - std::lock_guard lk(g_toolbarsMutex); - if (g_subclassedToolbars.count(hwnd) == 0) { - if (WindhawkUtils::SetWindowSubclassFromAnyThread(hwnd, TrayToolbarSubclassProc, 0)) { - g_subclassedToolbars.insert(hwnd); - } - } -} - -// FIX: non sabclassare MAI in modo sincrono dentro CreateWindowExW_Hook. -// Subito dopo il riavvio di Explorer questo hook scatta mentre siamo ancora -// nel bel mezzo dello stack annidato di WM_CREATE (Shell_TrayWnd -> TrayNotifyWnd -// -> ToolbarWindow32). Fare subito il subclass li' dentro puo' far scattare il -// watchdog di hang-detection di Windhawk (da cui il popup "instabilita'"), anche -// se poi tutto si sblocca da solo. Rimandiamo il lavoro vero a un thread separato -// che aspetta che lo stack di creazione si sia srotolato prima di procedere. -static DWORD WINAPI SubclassToolbarDeferredProc(LPVOID lpParam) { - HWND hwnd = (HWND)lpParam; - Sleep(50); - if (IsWindow(hwnd)) { - SubclassToolbar(hwnd); - } - return 0; -} - -static void SubclassToolbarDeferred(HWND hwnd) { - HANDLE hThread = CreateThread(nullptr, 0, SubclassToolbarDeferredProc, (LPVOID)hwnd, 0, nullptr); - if (hThread) CloseHandle(hThread); -} - -static void UnsubclassAllToolbars() { - std::lock_guard lk(g_toolbarsMutex); - for (HWND hwnd : g_subclassedToolbars) { - if (IsWindow(hwnd)) { - WindhawkUtils::RemoveWindowSubclassFromAnyThread(hwnd, TrayToolbarSubclassProc); - } +static HWND FindTrayToolbar() { + HWND hTray = FindWindowW(L"Shell_TrayWnd", nullptr); + if (!hTray) return nullptr; + DWORD pid = 0; + GetWindowThreadProcessId(hTray, &pid); + if (pid != GetCurrentProcessId()) return nullptr; + HWND hNotify = FindWindowExW(hTray, nullptr, L"TrayNotifyWnd", nullptr); + if (!hNotify) return nullptr; + HWND hSysPager = FindWindowExW(hNotify, nullptr, L"SysPager", nullptr); + if (hSysPager) { + HWND hToolbar = FindWindowExW(hSysPager, nullptr, L"ToolbarWindow32", nullptr); + if (hToolbar) return hToolbar; } - g_subclassedToolbars.clear(); + return FindWindowExW(hNotify, nullptr, L"ToolbarWindow32", nullptr); } -static std::vector FindTrayToolbars() { - std::vector toolbars; - HWND hTray = FindWindowW(L"Shell_TrayWnd", nullptr); - if (hTray) { - DWORD pid = 0; - GetWindowThreadProcessId(hTray, &pid); - if (pid == GetCurrentProcessId()) { - HWND hNotify = FindWindowExW(hTray, nullptr, L"TrayNotifyWnd", nullptr); - if (hNotify) { - HWND hSysPager = FindWindowExW(hNotify, nullptr, L"SysPager", nullptr); - if (hSysPager) { - HWND hToolbar = FindWindowExW(hSysPager, nullptr, L"ToolbarWindow32", nullptr); - if (hToolbar) toolbars.push_back(hToolbar); - } else { - HWND hToolbar = FindWindowExW(hNotify, nullptr, L"ToolbarWindow32", nullptr); - if (hToolbar) toolbars.push_back(hToolbar); - } - } - } - } - HWND hOverflow = FindWindowW(L"NotifyIconOverflowWindow", nullptr); - if (hOverflow) { - DWORD pid = 0; - GetWindowThreadProcessId(hOverflow, &pid); - if (pid == GetCurrentProcessId()) { - HWND hToolbar = FindWindowExW(hOverflow, nullptr, L"ToolbarWindow32", nullptr); - if (hToolbar) toolbars.push_back(hToolbar); - } +static void SetupTraySubclass() { + if (g_hTrayToolbar) return; + if (!InitTrayDllInfo()) return; + HWND hToolbar = FindTrayToolbar(); + if (!hToolbar) return; + if (WindhawkUtils::SetWindowSubclassFromAnyThread(hToolbar, TrayToolbarSubclassProc, 0)) { + g_hTrayToolbar = hToolbar; } - return toolbars; } -static void SetupTraySubclass() { - InitTrayDllInfo(); - for (HWND hToolbar : FindTrayToolbars()) { - SubclassToolbar(hToolbar); +static void RemoveTraySubclass() { + if (g_hTrayToolbar) { + WindhawkUtils::RemoveWindowSubclassFromAnyThread(g_hTrayToolbar, TrayToolbarSubclassProc); + g_hTrayToolbar = nullptr; } } @@ -597,7 +527,6 @@ static bool IsAddressInModule(void* address, const wchar_t* moduleName) { } return false; } - BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND hWnd, const TPMPARAMS* lptpm) { if (!g_settings.redirectSystemTray || !g_settings.enableRedirects) return g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); @@ -606,6 +535,7 @@ BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND h if (guard.IsReentrant()) return g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + // --- Primary: subclass flag set on WM_RBUTTONUP (language-independent) --- int contextType; { std::lock_guard lk(g_trayContextMutex); @@ -622,6 +552,7 @@ BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND h bool isNetworkMenu = (contextType == 2); bool isDeviceMenu = false; + // --- Fallback: DLL return-address detection (language-independent) --- if (!isAudioMenu && !isNetworkMenu) { void* retAddr = GetReturnAddress(); int itemCount = GetMenuItemCount(hMenu); @@ -633,15 +564,21 @@ BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND h if (itemCount <= 6) isNetworkMenu = (itemCount >= 2 && itemCount <= 5); } else if (IsAddressInModule(retAddr, L"dxgi.dll")) { + // dxgi.dll handles both Network flyout (IDs 3107/3109) and + // Device/Safely Remove Hardware flyout (ID 215 = "Open Devices and Printers") if (itemCount == 2 && GetMenuItemID(hMenu, 0) == 3107 && GetMenuItemID(hMenu, 1) == 3109) { isNetworkMenu = true; } + // Check for device menu: item ID 215 is the language-independent + // identifier for "Open Devices and Printers" else if (GetMenuItemID(hMenu, 0) == 215) { isDeviceMenu = true; Wh_Log(L"[TRAY-HOOK] Device menu detected via dxgi.dll + ID 215"); } } else if (IsAddressInModule(retAddr, L"shell32.dll")) { + // NUOVO: Rilevamento shell32.dll per Win11 23H2 + // Cerca l'ID 215 in qualsiasi posizione del menu for (int i = 0; i < itemCount; i++) { UINT itemId = GetMenuItemID(hMenu, i); if (itemId == 215) { @@ -652,6 +589,7 @@ BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND h } } else if (IsAddressInModule(retAddr, L"hotplug.dll")) { + // Fallback for older Windows versions where hotplug.dll handles the menu isDeviceMenu = true; Wh_Log(L"[TRAY-HOOK] Device menu detected via hotplug.dll"); } @@ -665,6 +603,10 @@ BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND h const wchar_t* menuKind = isAudioMenu ? L"AUDIO" : (isNetworkMenu ? L"NETWORK" : L"DEVICE"); Wh_Log(L"[TRAY-HOOK] %s menu, %d items", menuKind, itemCount); + // Find target item without any text matching. + // Audio: first item. + // Network: last non-separator item. + // Device: item with ID 215 ("Open Devices and Printers") — language-independent. int targetIndex = -1; if (isAudioMenu) { @@ -683,12 +625,14 @@ BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND h } } else if (isDeviceMenu) { + // Find item with ID 215 — the language-independent ID for "Open Devices and Printers" for (int i = 0; i < itemCount; i++) { if (GetMenuItemID(hMenu, i) == 215) { targetIndex = i; break; } } + // Fallback: if ID 215 not found (unlikely), use first non-separator item if (targetIndex == -1) { for (int i = 0; i < itemCount; i++) { MENUITEMINFOW miiCheck = { sizeof(MENUITEMINFOW) }; @@ -710,25 +654,21 @@ BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND h Wh_Log(L"[TRAY-HOOK] %s menu target index=%d, ID=%u", menuKind, targetIndex, GetMenuItemID(hMenu, targetIndex)); - UINT customId = isAudioMenu ? TRAY_CUSTOM_ID_AUDIO - : isNetworkMenu ? TRAY_CUSTOM_ID_NETWORK - : TRAY_CUSTOM_ID_DEVICES; UINT originalId = GetMenuItemID(hMenu, targetIndex); - MENUITEMINFOW mii = { sizeof(MENUITEMINFOW) }; - mii.fMask = MIIM_ID; - mii.wID = customId; - SetMenuItemInfoW(hMenu, targetIndex, TRUE, &mii); - + // NOTE: we intentionally do NOT swap the item's ID (via SetMenuItemInfoW) + // anymore. This menu item still gets its text/icon painted by pnidui.dll + // through WM_DRAWITEM, which is keyed off the item's real ID — replacing + // it with our own custom ID left pnidui.dll unable to look up what to + // draw for it, so the item rendered with no text (it still worked, + // since selection is ID-driven, but visually looked broken). Comparing + // directly against the original ID avoids touching the item at all. bool callerWantedReturnCmd = (uFlags & TPM_RETURNCMD) != 0; uFlags |= TPM_RETURNCMD; BOOL result = g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); int selectedId = (int)result; - mii.wID = originalId; - SetMenuItemInfoW(hMenu, targetIndex, TRUE, &mii); - - if (selectedId == (int)customId) { + if (selectedId == (int)originalId) { Wh_Log(L"[TRAY-HOOK] User selected target item, redirecting"); if (isAudioMenu) OpenClassicSoundPanel(); else if (isNetworkMenu) OpenClassicNetworkConnections(); @@ -743,7 +683,138 @@ BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND h return result; } - +// Hook a livello syscall per catturare menu che bypassano TrackPopupMenuEx +BOOL WINAPI NtUserTrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND hWnd, const TPMPARAMS* lptpm) { + // Se non è un menu della system tray, passa all'originale + if (!g_settings.redirectSystemTray || !g_settings.enableRedirects) { + return g_origNtUserTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + } + + HookGuard guard; + if (guard.IsReentrant()) { + return g_origNtUserTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + } + + // --- Rilevamento menu usando la stessa logica di TrackPopupMenuEx_Hook --- + int contextType; + { + std::lock_guard lk(g_trayContextMutex); + contextType = g_trayContextType; + DWORD tick = g_trayContextTick; + g_trayContextType = 0; + g_trayContextTick = 0; + if (contextType != 0 && GetTickCount() - tick > TRAY_CONTEXT_MAX_AGE_MS) { + contextType = 0; + } + } + + bool isAudioMenu = (contextType == 1); + bool isNetworkMenu = (contextType == 2); + bool isDeviceMenu = false; + + // Fallback: DLL return-address detection + if (!isAudioMenu && !isNetworkMenu) { + void* retAddr = GetReturnAddress(); + int itemCount = GetMenuItemCount(hMenu); + if (itemCount > 0) { + if (IsAddressInModule(retAddr, L"SndVolSSO.dll")) { + if (itemCount <= 6) isAudioMenu = true; + } + else if (IsAddressInModule(retAddr, L"pnidui.dll")) { + if (itemCount <= 6) isNetworkMenu = (itemCount >= 2 && itemCount <= 5); + } + else if (IsAddressInModule(retAddr, L"dxgi.dll")) { + if (itemCount == 2 && GetMenuItemID(hMenu, 0) == 3107 && GetMenuItemID(hMenu, 1) == 3109) { + isNetworkMenu = true; + } + else if (GetMenuItemID(hMenu, 0) == 215) { + isDeviceMenu = true; + } + } + else if (IsAddressInModule(retAddr, L"shell32.dll")) { + for (int i = 0; i < itemCount; i++) { + if (GetMenuItemID(hMenu, i) == 215) { + isDeviceMenu = true; + break; + } + } + } + else if (IsAddressInModule(retAddr, L"hotplug.dll")) { + isDeviceMenu = true; + } + } + } + + if (!isAudioMenu && !isNetworkMenu && !isDeviceMenu) { + return g_origNtUserTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + } + + // Trova l'item target + int itemCount = GetMenuItemCount(hMenu); + int targetIndex = -1; + + if (isAudioMenu) { + targetIndex = 0; + } + else if (isNetworkMenu) { + for (int i = itemCount - 1; i >= 0; i--) { + MENUITEMINFOW miiCheck = { sizeof(MENUITEMINFOW) }; + miiCheck.fMask = MIIM_FTYPE; + if (GetMenuItemInfoW(hMenu, i, TRUE, &miiCheck)) { + if (!(miiCheck.fType & MFT_SEPARATOR)) { + targetIndex = i; + break; + } + } + } + } + else if (isDeviceMenu) { + for (int i = 0; i < itemCount; i++) { + if (GetMenuItemID(hMenu, i) == 215) { + targetIndex = i; + break; + } + } + if (targetIndex == -1) { + for (int i = 0; i < itemCount; i++) { + MENUITEMINFOW miiCheck = { sizeof(MENUITEMINFOW) }; + miiCheck.fMask = MIIM_FTYPE; + if (GetMenuItemInfoW(hMenu, i, TRUE, &miiCheck)) { + if (!(miiCheck.fType & MFT_SEPARATOR)) { + targetIndex = i; + break; + } + } + } + } + } + + if (targetIndex == -1) { + return g_origNtUserTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + } + + UINT originalId = GetMenuItemID(hMenu, targetIndex); + bool callerWantedReturnCmd = (uFlags & TPM_RETURNCMD) != 0; + uFlags |= TPM_RETURNCMD; + + BOOL result = g_origNtUserTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + int selectedId = (int)result; + + if (selectedId == (int)originalId) { + Wh_Log(L"[SYSCALL-HOOK] User selected target item, redirecting"); + if (isAudioMenu) OpenClassicSoundPanel(); + else if (isNetworkMenu) OpenClassicNetworkConnections(); + else OpenClassicDevicesAndPrinters(); + return 0; + } + + if (selectedId != 0 && !callerWantedReturnCmd) { + PostMessageW(hWnd, WM_COMMAND, MAKEWPARAM((WORD)selectedId, 0), 0); + return TRUE; + } + + return result; +} static std::unordered_map g_mappings; static void InitMappings() { @@ -1127,6 +1198,167 @@ static ResolveResult ResolveUri(const std::wstring& uri, HWND hwnd) { } return {L"", false}; } +// =========================================================================== +// EXPERIMENTAL: IApplicationActivationManager COM interception +// +// Some Windows 11 shell components (notably the system tray flyouts for +// "Open Devices and Printers") may bypass ShellExecute/CreateProcess entirely +// and instead activate the Settings app through the low-level COM interface +// IApplicationActivationManager::ActivateApplication(). +// +// We install a tiny vtable-style hook on the COM object returned by +// CoCreateInstance(CLSID_ApplicationActivationManager) to inspect every +// ActivateApplication call. When the appUserModelId matches +// "windows.immersivecontrolpanel..." (the Settings app), we: +// 1) map the ms-settings: URI embedded in the arguments to a classic CPL +// 2) launch that CPL ourselves +// 3) return S_OK to the caller (making it believe Settings was launched) +// +// This is entirely best-effort and based on reverse engineering assumptions. +// If anything unexpected happens we fall back to the original vtable entry. +// =========================================================================== + +// Minimal vtable layout for IApplicationActivationManager (3 methods) +struct IApplicationActivationManagerVtbl { + // IUnknown + HRESULT (STDMETHODCALLTYPE *QueryInterface)(IUnknown*, REFIID, void**); + ULONG (STDMETHODCALLTYPE *AddRef)(IUnknown*); + ULONG (STDMETHODCALLTYPE *Release)(IUnknown*); + // IApplicationActivationManager + HRESULT (STDMETHODCALLTYPE *ActivateApplication)( + IUnknown*, + LPCWSTR appUserModelId, + LPCWSTR arguments, + DWORD options, + DWORD* processId); + HRESULT (STDMETHODCALLTYPE *ActivateForFile)(IUnknown*, LPCWSTR, LPCWSTR, DWORD, DWORD*); + HRESULT (STDMETHODCALLTYPE *ActivateForProtocol)(IUnknown*, LPCWSTR, DWORD*, DWORD); +}; + +static IApplicationActivationManagerVtbl g_origAAMVtbl = {}; +static bool g_aamHookInstalled = false; +static std::mutex g_aamHookMutex; + +HRESULT STDMETHODCALLTYPE AAM_ActivateApplication_hook( + IUnknown* pThis, + LPCWSTR appUserModelId, + LPCWSTR arguments, + DWORD options, + DWORD* processId) +{ + Wh_Log(L"[AAM-HOOK] ActivateApplication: appId=%s, args=%s", + appUserModelId ? appUserModelId : L"(null)", + arguments ? arguments : L"(null)"); + + // Is this the Settings app being activated? + if (appUserModelId && arguments && + _wcsnicmp(appUserModelId, L"windows.immersivecontrolpanel", 29) == 0) + { + std::wstring uri = NormalizeUri(arguments); + Wh_Log(L"[AAM-HOOK] Settings activation intercepted: %s", uri.c_str()); + + auto result = ResolveUri(uri, nullptr); + if (result.intercept && !result.target.empty()) { + LaunchTarget(result.target); + if (processId) *processId = GetCurrentProcessId(); + Wh_Log(L"[AAM-HOOK] Redirected to: %s", result.target.c_str()); + return S_OK; + } + Wh_Log(L"[AAM-HOOK] No mapping found, falling back to original"); + } + + // Not a Settings activation we can handle — call original + if (g_origAAMVtbl.ActivateApplication) { + return g_origAAMVtbl.ActivateApplication(pThis, appUserModelId, arguments, options, processId); + } + return E_FAIL; +} + +static void InstallAAMHook() { + std::lock_guard lk(g_aamHookMutex); + if (g_aamHookInstalled) return; + + IUnknown* pAAM = nullptr; + HRESULT hr = CoCreateInstance( + CLSID_ApplicationActivationManager_STC, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IApplicationActivationManager_STC, + (void**)&pAAM); + + if (FAILED(hr) || !pAAM) { + Wh_Log(L"[AAM-HOOK] CoCreateInstance(CLSID_ApplicationActivationManager) failed: 0x%08X", hr); + return; + } + + IApplicationActivationManagerVtbl* vtbl = *(IApplicationActivationManagerVtbl**)pAAM; + if (!vtbl || IsBadReadPtr(vtbl, sizeof(*vtbl))) { + Wh_Log(L"[AAM-HOOK] Invalid vtable pointer"); + pAAM->Release(); + return; + } + + // Save original vtable + g_origAAMVtbl = *vtbl; + + // Make the vtable page writable + DWORD oldProtect; + if (!VirtualProtect(vtbl, sizeof(*vtbl), PAGE_READWRITE, &oldProtect)) { + Wh_Log(L"[AAM-HOOK] VirtualProtect failed"); + pAAM->Release(); + return; + } + + // Patch ActivateApplication entry + vtbl->ActivateApplication = AAM_ActivateApplication_hook; + + VirtualProtect(vtbl, sizeof(*vtbl), oldProtect, &oldProtect); + + g_aamHookInstalled = true; + Wh_Log(L"[AAM-HOOK] Successfully installed"); + pAAM->Release(); +} + +bool (*COpenControlPanel__MapLegacyName_orig)(void*, LPCWSTR, LPWSTR, UINT, bool*); +bool COpenControlPanel__MapLegacyName_hook( + void *pThis, + LPCWSTR pszLegacyName, + LPWSTR pszNewName, + UINT uLen, + bool *nameChanged) +{ + // Always tell the caller the name was NOT changed — this forces + // Explorer to use the original legacy Control Panel path. + *nameChanged = false; + *pszNewName = L'\0'; + Wh_Log(L"[MAP-LEGACY] Suppressed mapping for: %s", + pszLegacyName ? pszLegacyName : L"(null)"); + return false; +} + +static bool InstallLegacyNameHook() { + HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); + if (!hShell32) { + Wh_Log(L"[MAP-LEGACY] shell32.dll not loaded"); + return false; + } + + WindhawkUtils::SYMBOL_HOOK shell32_dll_hook = {{ + L"private: bool __cdecl COpenControlPanel::_MapLegacyName" + L"(unsigned short const *,unsigned short *,unsigned int,bool *)" + }, + (void**)&COpenControlPanel__MapLegacyName_orig, + (void*)COpenControlPanel__MapLegacyName_hook, + false}; + + if (WindhawkUtils::HookSymbols(hShell32, &shell32_dll_hook, 1)) { + Wh_Log(L"[MAP-LEGACY] Hook installed successfully"); + return true; + } else { + Wh_Log(L"[MAP-LEGACY] Failed to install hook (symbol may differ on this build)"); + return false; + } +} static std::wstring BaseNameLower(const std::wstring& path) { size_t pos = path.rfind(L'\\'); @@ -1158,7 +1390,36 @@ static bool IsControlSystemCommand(const std::wstring& cmdLine) { std::wstring arg = ToLower(tokens[1]); return (arg == L"system" || arg == L"microsoft.system"); } +static std::wstring ExtractExplorerLaunchUri(const std::wstring& cmdLine) { + size_t i = 0, n = cmdLine.size(); + while (i < n && cmdLine[i] == L' ') i++; + + std::wstring exeToken; + if (i < n && cmdLine[i] == L'"') { + size_t end = cmdLine.find(L'"', i + 1); + if (end == std::wstring::npos) return L""; + exeToken = cmdLine.substr(i + 1, end - i - 1); + i = end + 1; + } else { + size_t start = i; + while (i < n && cmdLine[i] != L' ') i++; + exeToken = cmdLine.substr(start, i - start); + } + + if (BaseNameLower(exeToken) != L"explorer.exe") return L""; + + while (i < n && cmdLine[i] == L' ') i++; + std::wstring rest = cmdLine.substr(i); + while (!rest.empty() && rest.back() == L' ') rest.pop_back(); + if (rest.size() >= 2 && rest.front() == L'"' && rest.back() == L'"') { + rest = rest.substr(1, rest.size() - 2); + } + if (rest.empty()) return L""; + if (IsMsSettings(rest.c_str())) return NormalizeUri(rest); + if (IsShellClsid(rest.c_str())) return ToLower(rest); + return L""; +} BOOL WINAPI ShellExecuteExW_hook(SHELLEXECUTEINFOW* pei) { if (IsChildProcess()) return ShellExecuteExW_orig(pei); HookGuard guard; @@ -1245,10 +1506,110 @@ BOOL WINAPI CreateProcessW_hook(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, SetLastError(ERROR_SUCCESS); return TRUE; } + + // Handles cases like the classic Control Panel's "See also" links, + // which Explorer launches as a new "explorer.exe ms-settings:..." + // (or "explorer.exe shell:::{...}") process via CreateProcessW + // instead of going through ShellExecuteW/ShellExecuteExW. + std::wstring uri = ExtractExplorerLaunchUri(cmdLine); + if (!uri.empty()) { + auto result = ResolveUri(uri, nullptr); + if (result.intercept) { + if (!result.target.empty()) LaunchTarget(result.target); + if (lpProcessInformation) ZeroMemory(lpProcessInformation, sizeof(PROCESS_INFORMATION)); + SetLastError(ERROR_SUCCESS); + return TRUE; + } + } } return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); } +static volatile bool g_pniduiRetryRunning = false; + + +static bool TryInstallPniduiHook() { + std::lock_guard lk(g_pniduiHookMutex); + + if (g_pniduiHookInstalled) { + return true; + } + + HMODULE hMod = GetModuleHandleW(L"pnidui.dll"); + if (!hMod) { + hMod = LoadLibraryExW(L"pnidui.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (!hMod) { + Wh_Log(L"[PNIDUI-HOOK] pnidui.dll not loaded yet"); + return false; + } + } + + Wh_Log(L"[PNIDUI-HOOK] pnidui.dll loaded at 0x%p", hMod); + + WindhawkUtils::SYMBOL_HOOK pnidui_dll_hooks[] = {{ + { + L"bool " +#ifdef _WIN64 + L"__cdecl" +#else + L"__stdcall" +#endif + L" ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu" + L"(struct HMENU__ *,struct HWND__ *)" + }, + (void**)&g_icmhOrig_pnidui, + (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, + false + }}; + + bool result = WindhawkUtils::HookSymbols(hMod, pnidui_dll_hooks, 1); + if (result) { + Wh_Log(L"[PNIDUI-HOOK] Successfully installed pnidui.dll hook"); + g_pniduiHookInstalled = true; + } else { + Wh_Log(L"[PNIDUI-HOOK] Failed to install pnidui.dll hook"); + } + return result; +} + +static DWORD WINAPI PniduiRetryThread(LPVOID) { + if (g_pniduiRetryRunning) { + Wh_Log(L"[PNIDUI-HOOK] Retry thread already running, exiting"); + return 0; + } + g_pniduiRetryRunning = true; + + Wh_Log(L"[PNIDUI-HOOK] Retry thread started - waiting for pnidui.dll to load"); + + const int MAX_WAIT_CHECKS = 60; + const DWORD CHECK_INTERVAL = 500; + bool dllLoaded = false; + + for (int i = 0; i < MAX_WAIT_CHECKS && !g_pniduiRetryStop; i++) { + if (GetModuleHandleW(L"pnidui.dll") != nullptr) { + dllLoaded = true; + Wh_Log(L"[PNIDUI-HOOK] pnidui.dll detected after %dms", i * CHECK_INTERVAL); + break; + } + Sleep(CHECK_INTERVAL); + } + + if (!dllLoaded) { + Wh_Log(L"[PNIDUI-HOOK] pnidui.dll not loaded after timeout, giving up"); + g_pniduiRetryRunning = false; + return 0; + } + + if (TryInstallPniduiHook()) { + Wh_Log(L"[PNIDUI-HOOK] Hook installed successfully after waiting for DLL"); + } else { + Wh_Log(L"[PNIDUI-HOOK] Failed to install hook even though DLL is loaded"); + } + + g_pniduiRetryRunning = false; + Wh_Log(L"[PNIDUI-HOOK] Retry thread exiting"); + return 0; +} static void InstallImmersiveMenuHooks() { struct DllHook { @@ -1256,16 +1617,14 @@ static void InstallImmersiveMenuHooks() { ICMH_CAODTM_t* orig; } targets[] = { { L"SndVolSSO.dll", &g_icmhOrig_SndVolSSO }, - { L"pnidui.dll", &g_icmhOrig_pnidui }, }; for (auto& t : targets) { HMODULE hMod = LoadLibraryExW(t.dll, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if (!hMod) continue; - // SndVolSSO.dll, pnidui.dll - WindhawkUtils::SYMBOL_HOOK immersiveMenuHooks[] = { - {{ + WindhawkUtils::SYMBOL_HOOK sndVolSSO_dll_hooks[] = {{ + { L"bool " #ifdef _WIN64 L"__cdecl" @@ -1276,17 +1635,32 @@ static void InstallImmersiveMenuHooks() { L"(struct HMENU__ *,struct HWND__ *)" }, (void**)t.orig, - (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook} - }; + (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, + false + }}; - WindhawkUtils::HookSymbols(hMod, immersiveMenuHooks, 1); + WindhawkUtils::HookSymbols(hMod, sndVolSSO_dll_hooks, 1); + } + + if (!TryInstallPniduiHook()) { + if (!g_pniduiRetryRunning && !g_pniduiRetryThread) { + g_pniduiRetryStop = false; + g_pniduiRetryThread = CreateThread(nullptr, 0, PniduiRetryThread, nullptr, 0, nullptr); + if (g_pniduiRetryThread) { + Wh_Log(L"[PNIDUI-HOOK] Retry thread created"); + } else { + Wh_Log(L"[PNIDUI-HOOK] Failed to create retry thread"); + } + } else { + Wh_Log(L"[PNIDUI-HOOK] Retry thread already running or hook already installed"); + } } if (g_isWin11) { HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); if (hShell32) { - WindhawkUtils::SYMBOL_HOOK shell32dll_hooks[] = { - {{ + WindhawkUtils::SYMBOL_HOOK shell32_dll_hooks[] = {{ + { L"bool " #ifdef _WIN64 L"__cdecl" @@ -1297,16 +1671,120 @@ static void InstallImmersiveMenuHooks() { L"(struct HMENU__ *,unsigned int)" }, (void**)&g_icmhOrig_Shell32Devices, - (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook} - }; + (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, + false + }}; - WindhawkUtils::HookSymbols(hShell32, shell32dll_hooks, 1); + WindhawkUtils::HookSymbols(hShell32, shell32_dll_hooks, 1); } } } + +static void InstallSyscallFallback() { + HMODULE hWin32u = GetModuleHandleW(L"win32u.dll"); + if (!hWin32u) { + hWin32u = LoadLibraryW(L"win32u.dll"); + if (!hWin32u) { + Wh_Log(L"[SYSCALL-HOOK] win32u.dll not found"); + return; + } + } + + FARPROC pNtUserTrackPopupMenuEx = GetProcAddress(hWin32u, "NtUserTrackPopupMenuEx"); + if (!pNtUserTrackPopupMenuEx) { + Wh_Log(L"[SYSCALL-HOOK] NtUserTrackPopupMenuEx not found by name"); + return; + } + + if (Wh_SetFunctionHook((void*)pNtUserTrackPopupMenuEx, + (void*)NtUserTrackPopupMenuEx_Hook, + (void**)&g_origNtUserTrackPopupMenuEx)) { + Wh_Log(L"[SYSCALL-HOOK] NtUserTrackPopupMenuEx hooked successfully"); + } else { + Wh_Log(L"[SYSCALL-HOOK] Failed to hook NtUserTrackPopupMenuEx"); + } +} + using CreateWindowExW_t = decltype(&CreateWindowExW); CreateWindowExW_t CreateWindowExW_Original; +// --------------------------------------------------------------------------- +// Tray subclass watchdog +// --------------------------------------------------------------------------- + +static bool HasTrayBeenRecreated() { + HWND hTray = FindWindowW(L"Shell_TrayWnd", nullptr); + if (!hTray) { + return false; + } + + bool recreated = (g_lastShellTrayWnd != nullptr && hTray != g_lastShellTrayWnd); + g_lastShellTrayWnd = hTray; + return recreated; +} + +static void ReinitializeTrayRedirect() { + Wh_Log(L"[TRAY-WATCHDOG] Reinitializing tray redirect state"); + + RemoveTraySubclass(); + + g_sndVolSSOBase = nullptr; + g_sndVolSSOEnd = nullptr; + g_pniduiBase = nullptr; + g_pniduiEnd = nullptr; + + { + std::lock_guard lk(g_pniduiHookMutex); + g_pniduiHookInstalled = false; + } + + if (g_settings.redirectSystemTray) { + SetupTraySubclass(); + } + + if (!TryInstallPniduiHook()) { + if (!g_pniduiRetryRunning && !g_pniduiRetryThread) { + g_pniduiRetryStop = false; + g_pniduiRetryThread = CreateThread(nullptr, 0, PniduiRetryThread, nullptr, 0, nullptr); + } + } + + InstallImmersiveMenuHooks(); +} + +static DWORD WINAPI TraySubclassWatchdogThread(LPVOID) { + Wh_Log(L"[TRAY-WATCHDOG] Watchdog thread started"); + + const int FAST_PHASE_CHECKS = 60; + const DWORD FAST_INTERVAL_MS = 500; + const DWORD SLOW_INTERVAL_MS = 3000; + + int tick = 0; + while (!g_traySubclassWatchdogStop) { + Sleep(tick < FAST_PHASE_CHECKS ? FAST_INTERVAL_MS : SLOW_INTERVAL_MS); + tick++; + if (g_traySubclassWatchdogStop) break; + + if (HasTrayBeenRecreated()) { + Wh_Log(L"[TRAY-WATCHDOG] Shell_TrayWnd recreation detected, reinitializing"); + ReinitializeTrayRedirect(); + continue; + } + + if (!g_settings.redirectSystemTray) continue; + + if (g_hTrayToolbar && !IsWindow(g_hTrayToolbar)) { + Wh_Log(L"[TRAY-WATCHDOG] Tray toolbar handle no longer valid, resetting"); + g_hTrayToolbar = nullptr; + } + if (!g_hTrayToolbar) { + SetupTraySubclass(); + } + } + + Wh_Log(L"[TRAY-WATCHDOG] Watchdog thread exiting"); + return 0; +} HWND WINAPI CreateWindowExW_Hook( DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, @@ -1316,59 +1794,17 @@ HWND WINAPI CreateWindowExW_Hook( dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam); - if (g_settings.redirectSystemTray && hwnd && lpClassName && !IS_INTRESOURCE(lpClassName)) { + if (g_settings.redirectSystemTray && hwnd && !g_hTrayToolbar && lpClassName && !IS_INTRESOURCE(lpClassName)) { if (wcscmp(lpClassName, L"ToolbarWindow32") == 0) { - // FIX: Ищем нужный тулбар напрямую по родительской цепи - // Это решает проблему, когда FindWindowW возвращает NULL во время процесса создания окон. - bool isTrayToolbar = false; - HWND hParent = hWndParent; - int depth = 0; - while (hParent && IsWindow(hParent) && depth < 10) { - wchar_t className[256] = {0}; - if (GetClassNameW(hParent, className, 256)) { - if (wcscmp(className, L"TrayNotifyWnd") == 0 || wcscmp(className, L"NotifyIconOverflowWindow") == 0) { - isTrayToolbar = true; - break; - } - } - hParent = GetParent(hParent); - depth++; - } - - if (isTrayToolbar) { - InitTrayDllInfo(); - // FIX: sabclassing rimandato fuori dallo stack annidato di WM_CREATE - // per evitare il falso positivo di "instabilita'" di Windhawk al - // primo riavvio di Explorer (vedi commento su SubclassToolbarDeferred). - SubclassToolbarDeferred(hwnd); - } + SetupTraySubclass(); } } return hwnd; } -static DWORD WINAPI DelayedCreateWindowExWHookProc(LPVOID) { - Sleep(300); - Wh_SetFunctionHook((void*)CreateWindowExW, (void*)CreateWindowExW_Hook, (void**)&CreateWindowExW_Original); - return 0; -} BOOL Wh_ModInit() { - // FIX: non inizializzare il mod in processi explorer.exe ausiliari - // (viste di cartella tipo "shell:::{CLSID}" o COM factory "-Embedding"/"/factory"). - // Questi processi non sono la shell vera, terminano quasi subito, e inizializzare - // qui gli hook del tray/menu causa il falso positivo di "instabilita'" di Windhawk. - { - const wchar_t* cmdLine = GetCommandLineW(); - if (cmdLine && (wcsstr(cmdLine, L"-Embedding") || - wcsstr(cmdLine, L"/factory") || - wcsstr(cmdLine, L"shell:::"))) { - Wh_Log(L"Skipping init: auxiliary explorer.exe process (%s)", cmdLine); - return FALSE; - } - } - Wh_Log(L"Initializing the Redirect Settings to Control Panel mod..."); DetectWindowsVersion(); @@ -1394,19 +1830,20 @@ BOOL Wh_ModInit() { if (pCPW) Wh_SetFunctionHook((void*)pCPW, (void*)CreateProcessW_hook, (void**)&CreateProcessW_orig); } - // FIX: ritardiamo di poco l'installazione dell'hook su CreateWindowExW per - // evitare di intercettarlo durante la raffica iniziale di creazione finestre - // di Explorer all'avvio (sospettata causa del falso positivo di "instabilita'" - // di Windhawk). Tutto il resto rimane invariato. - { - HANDLE hDelayThread = CreateThread(nullptr, 0, DelayedCreateWindowExWHookProc, nullptr, 0, nullptr); - if (hDelayThread) CloseHandle(hDelayThread); + Wh_SetFunctionHook((void*)CreateWindowExW, (void*)CreateWindowExW_Hook, (void**)&CreateWindowExW_Original); + if (g_settings.redirectSystemTray) { + SetupTraySubclass(); } - InstallImmersiveMenuHooks(); + InstallImmersiveMenuHooks(); + InstallSyscallFallback(); - if (g_settings.redirectSystemTray) { - SetupTraySubclass(); + if (g_settings.comActivationRedirect && g_isWin11) { + InstallAAMHook(); + } + + if (g_settings.legacyNameMappingFix) { + InstallLegacyNameHook(); } HMODULE hUser32 = GetModuleHandleW(L"user32.dll"); @@ -1417,23 +1854,72 @@ BOOL Wh_ModInit() { Wh_SetFunctionHook(pTrackPopupMenuEx, (void*)TrackPopupMenuEx_Hook, (void**)&g_origTrackPopupMenuEx); } } + // Initialize the Shell_TrayWnd reference for the watchdog + g_lastShellTrayWnd = FindWindowW(L"Shell_TrayWnd", nullptr); + g_traySubclassWatchdogStop = false; + g_traySubclassWatchdogThread = CreateThread(nullptr, 0, TraySubclassWatchdogThread, nullptr, 0, nullptr); + if (g_traySubclassWatchdogThread) { + Wh_Log(L"[TRAY-WATCHDOG] Watchdog thread created"); + } else { + Wh_Log(L"[TRAY-WATCHDOG] Failed to create watchdog thread"); + } return TRUE; } void Wh_ModUninit() { - UnsubclassAllToolbars(); + if (g_traySubclassWatchdogThread) { + g_traySubclassWatchdogStop = true; + Wh_Log(L"[TRAY-WATCHDOG] Stopping watchdog thread..."); + DWORD waitResult = WaitForSingleObject(g_traySubclassWatchdogThread, 3000); + if (waitResult == WAIT_TIMEOUT) { + Wh_Log(L"[TRAY-WATCHDOG] Timeout, terminating thread"); + TerminateThread(g_traySubclassWatchdogThread, 0); + } + CloseHandle(g_traySubclassWatchdogThread); + g_traySubclassWatchdogThread = nullptr; + } + if (g_pniduiRetryThread) { + g_pniduiRetryStop = true; + Wh_Log(L"[PNIDUI-HOOK] Stopping retry thread..."); + DWORD waitResult = WaitForSingleObject(g_pniduiRetryThread, 3000); + if (waitResult == WAIT_TIMEOUT) { + Wh_Log(L"[PNIDUI-HOOK] Retry thread timeout, terminating"); + TerminateThread(g_pniduiRetryThread, 0); + } + CloseHandle(g_pniduiRetryThread); + g_pniduiRetryThread = nullptr; + g_pniduiRetryRunning = false; + } + + RemoveTraySubclass(); } void Wh_ModSettingsChanged() { - UnsubclassAllToolbars(); + RemoveTraySubclass(); g_sndVolSSOBase = nullptr; g_sndVolSSOEnd = nullptr; g_pniduiBase = nullptr; g_pniduiEnd = nullptr; + + { + std::lock_guard lk(g_pniduiHookMutex); + g_pniduiHookInstalled = false; + } + LoadSettings(); InitMappings(); if (g_settings.redirectSystemTray) { SetupTraySubclass(); + InstallImmersiveMenuHooks(); + } + + // Re-install experimental hooks if settings changed + if (g_settings.comActivationRedirect && g_isWin11) { + InstallAAMHook(); + } + + if (g_settings.legacyNameMappingFix) { + InstallLegacyNameHook(); } } From 5edd221d71e09e1d92728305e7cb4384ae853483 Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Sat, 11 Jul 2026 17:28:59 +0200 Subject: [PATCH 17/25] Revise mod documentation for Windows support and features Updated documentation regarding Windows compatibility and limitations of the mod, including testing results and feature descriptions. --- mods/settings-to-control-panel.wh.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index 6cc03b576f..117f9e4945 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -26,6 +26,8 @@ Panel pages, using only native Windows components. - **Windows 10** – Mostly complete support - **Windows 11** – Partial support +**Note**: The mod has been tested on Windows 10 1809, Windows 10 21H2, Windows 11 23H2 and Windows 11 24H2 and the tests confirm that the mod is more functional on Windows 10 but both should not cause issues. + --- ## Features @@ -34,18 +36,19 @@ Panel pages, using only native Windows components. - Anti-loop protection (stops windows from reopening endlessly) - Configurable fallback behavior for unmapped links - Tray menu detection (experimental) +--- + ## Limitations -- The system tray context menu redirect only supports the Win32 taskbar (the one from Windows 10 and previous versions). If using Windows 11, it might function decently but it is still an experimental feature. +- The system tray context menu redirect only supports the Win32 taskbar (the one from Windows 10). However, in some configurations if explorer is restarted the redirects might not work (especially on Windows 11). - The device & printers system tray redirect may not work on some Windows 11 configurations, as Microsoft hardcoded the redirect to the Settings app in certain shell code paths. This could change in future if correct documentation is found. -- The two new experimental features above (`ComActivationRedirect` and `LegacyNameMappingFix`) are based on undocumented/internal Windows behavior and reverse engineering assumptions. They are disabled by default, are not guaranteed to work on every Windows build, and could stop working (harmlessly) after a Windows update. They are written to always fall back to the original, unmodified behavior whenever something doesn't match what's expected, so enabling them should never break normal functionality — worst case, they simply do nothing. - --- ## Credits - m417z – Code reviews and feedback - Anixx – Testing on Windows 11 23H2 and the original toolbar subclassing approach +- sebastian08dm08-cpu - Testing on Windows 10 1809 - dbilanoski – CLSID documentation */ // ==/WindhawkModReadme== From c62ef2f36daa669a81759314bff1840108ee804d Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Sat, 11 Jul 2026 21:34:15 +0200 Subject: [PATCH 18/25] Refine mod description and limitations for clarity Updated the mod description for clarity and improved the limitations section regarding Windows 11 configurations. --- mods/settings-to-control-panel.wh.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index 117f9e4945..e3c1d20a2f 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -1,7 +1,7 @@ // ==WindhawkMod== // @id settings-to-control-panel // @name Redirect Settings to Control Panel -// @description Forces classic Control Panel to open instead of Windows 10/11 Settings app using native components. Primarily designed for Windows 10; Windows 11 support is limited due to Microsoft's shell architecture changes. +// @description This mod forces the classic Control Panel to open instead of Windows 10/11 Settings app using native components. // @version 10.0.30 // @author babamohammed // @github https://github.com/babamohammed2022 @@ -40,8 +40,11 @@ Panel pages, using only native Windows components. ## Limitations -- The system tray context menu redirect only supports the Win32 taskbar (the one from Windows 10). However, in some configurations if explorer is restarted the redirects might not work (especially on Windows 11). +- The system tray context menu redirect only supports the Win32 taskbar (the one from Windows 10). However, in some Windows 11 configurations if explorer is restarted the network system tray redirect might not work. - The device & printers system tray redirect may not work on some Windows 11 configurations, as Microsoft hardcoded the redirect to the Settings app in certain shell code paths. This could change in future if correct documentation is found. + +**Note**: For a better experience on Windows 11, it is recommended to pair this mod with Anixx's **[Restore the classic Personalization and other CPLs](https://windhawk.net/mods/restore-classic-cpls)** that re-enables some of the classic applets from older Windows versions. + --- ## Credits From b9593cc6edb9a8d67b3b19515816b1154d355edf Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Wed, 15 Jul 2026 00:41:40 +0200 Subject: [PATCH 19/25] Address the reported issues --- mods/settings-to-control-panel.wh.cpp | 734 +++++++++----------------- 1 file changed, 257 insertions(+), 477 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index e3c1d20a2f..71f861908d 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -2,7 +2,7 @@ // @id settings-to-control-panel // @name Redirect Settings to Control Panel // @description This mod forces the classic Control Panel to open instead of Windows 10/11 Settings app using native components. -// @version 10.0.30 +// @version 10.0.31 // @author babamohammed // @github https://github.com/babamohammed2022 // @include explorer.exe @@ -36,6 +36,9 @@ Panel pages, using only native Windows components. - Anti-loop protection (stops windows from reopening endlessly) - Configurable fallback behavior for unmapped links - Tray menu detection (experimental) + +**Note**: This mod is a best-effort implementation. It aims to intercept and redirect as many `ms-settings:` links as possible, but due to differences between Windows 10 and Windows 11, as well as changes introduced by Microsoft in each build, some redirects may not work perfectly in all environments. + --- ## Limitations @@ -62,7 +65,7 @@ Panel pages, using only native Windows components. $description: "Turns the mod on or off. When disabled, Settings opens normally as usual." - RedirectSystemTray: false $name: Redirect System Tray Audio/Network/Device & Printers (EXPERIMENTAL) - $description: "If enabled, right-clicking the Audio or Network or Device & Printers icon near the clock and choosing 'Open Sound settings' or 'Open Network settings' or 'Open devices and printers' will open the classic panel instead of the Settings app." + $description: "If enabled, right-clicking the Audio or Network or Device & Printers icon near the clock and choosing 'Open Sound settings' or 'Open Network settings' or 'Open devices and printers' should open the classic panel instead of the Settings app. Note: In some builds after explorer's restart the network redirect might not work." - UIOnlyRedirects: false $name: Non-Invasive Mode $description: "Only redirects clicks made in the UI. Doesn't touch programs that open Settings in other ways." @@ -79,6 +82,13 @@ Panel pages, using only native Windows components. - MaxLaunchesPerUri: 3 $name: Anti-Loop Limit (per window, every 5 seconds) $description: "Safety measure: if the same window gets opened too many times within a few seconds, the mod stops reopening it. Set to 0 to disable this limit." +- ComActivationRedirect: true + $name: COM-activation Redirect (EXPERIMENTAL) + $description: "This setting redirects modern Settings calls made through the COM interface to ensure they open correctly. This addresses compatibility issues with certain Windows 11 builds where Settings may fail to launch." + +- LegacyNameMappingFix: true + $name: Fix Legacy Name Mapping + $description: "This setting attempts to correct a mapping error that causes legacy Control Panel items to display as blank pages or automatically redirect to the modern Settings app. This should ensure that the classic system tools open and function as intended." */ // ==/WindhawkModSettings== @@ -129,11 +139,11 @@ static volatile bool g_pniduiRetryStop = false; static HANDLE g_traySubclassWatchdogThread = nullptr; static volatile bool g_traySubclassWatchdogStop = false; static HWND g_lastShellTrayWnd = nullptr; +static HANDLE g_stopEvent = nullptr; static bool __fastcall ICMH_CAODTM_hook(HMENU, HWND); // Constants -static const HINSTANCE SHELL_EXECUTE_SUCCESS = (HINSTANCE)33; #define PERS_ROOT L"explorer shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921}" #define PERS_WALLPAPER L"explorer shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921} -Microsoft.Personalization\\pageWallpaper" #define PERS_COLORS L"explorer shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921} -Microsoft.Personalization\\pageColorization" @@ -150,9 +160,6 @@ using ShellExecuteW_t = HINSTANCE(WINAPI*)(HWND, LPCWSTR, LPCWSTR, LPCWSTR, LPCW using ShellExecuteExW_t = BOOL(WINAPI*)(SHELLEXECUTEINFOW*); static ShellExecuteExW_t ShellExecuteExW_orig = nullptr; static ShellExecuteW_t ShellExecuteW_orig = nullptr; -// NtUserTrackPopupMenuEx fallback (syscall level) -using NtUserTrackPopupMenuEx_t = BOOL(WINAPI*)(HMENU, UINT, int, int, HWND, const TPMPARAMS*); -static NtUserTrackPopupMenuEx_t g_origNtUserTrackPopupMenuEx = nullptr; struct ResolveResult { std::wstring target; @@ -167,6 +174,34 @@ struct HookGuard { bool IsReentrant() const { return g_hookDepth > 1; } }; +static std::wstring ToLower(std::wstring s) { + std::transform(s.begin(), s.end(), s.begin(), ::towlower); + return s; +} + +static bool IsShellProcess() { + static int isShell = -1; + if (isShell == -1) { + HWND hShellWnd = GetShellWindow(); + if (hShellWnd) { + DWORD shellPid = 0; + GetWindowThreadProcessId(hShellWnd, &shellPid); + isShell = (shellPid == GetCurrentProcessId()) ? 1 : 0; + } else { + // Early startup: check command line for factory/worker flags + std::wstring cmd = ToLower(GetCommandLineW()); + if (cmd.find(L" /factory") != std::wstring::npos || + cmd.find(L" /separate") != std::wstring::npos || + cmd.find(L" /nodeuse") != std::wstring::npos) { + isShell = 0; + } else { + isShell = 1; + } + } + } + return isShell == 1; +} + static std::wstring g_childEnvBlock; static void BuildChildEnvironment() { @@ -186,7 +221,11 @@ static void BuildChildEnvironment() { } static bool IsChildProcess() { - return GetEnvironmentVariableW(L"WH_STC_NOREDIRECT", nullptr, 0) > 0; + static int isChild = -1; + if (isChild == -1) { + isChild = (GetEnvironmentVariableW(L"WH_STC_NOREDIRECT", nullptr, 0) > 0) ? 1 : 0; + } + return isChild == 1; } struct ModSettings { @@ -335,7 +374,7 @@ static const std::unordered_set g_win11LoopClsids = { L"shell:::{17cd9488-1228-4b2f-88ce-4298e93e0966}", L"shell:::{80f3f1d5-feca-45f3-bc32-752c152e456e}", L"shell:::{9fe63afd-59cf-4419-9775-abcc3849f861}", - L"shell:::{bb06c0e4-d293-4f75-8a90-cb05b6477eee}", + L"shell:::{bb06c0e4-d293-4f75-8a90-cb05B6477EEE}", L"shell:::{ed834ed6-4b5a-4bfe-8f11-a626dcb6a921}", }; @@ -351,11 +390,6 @@ static bool IsClsidLoopOnWin11(const std::wstring& lowerTarget) { return g_win11LoopClsids.count(base) > 0; } -static std::wstring ToLower(std::wstring s) { - std::transform(s.begin(), s.end(), s.begin(), ::towlower); - return s; -} - static HWND g_hTrayToolbar = nullptr; static BYTE* g_sndVolSSOBase = nullptr; static BYTE* g_sndVolSSOEnd = nullptr; @@ -363,23 +397,25 @@ static BYTE* g_pniduiBase = nullptr; static BYTE* g_pniduiEnd = nullptr; static bool InitTrayDllInfo() { - if (g_sndVolSSOBase && g_pniduiBase) return true; - - HMODULE hSndVol = GetModuleHandleW(L"SndVolSSO.dll"); - if (hSndVol) { - MODULEINFO mi{}; - if (GetModuleInformation(GetCurrentProcess(), hSndVol, &mi, sizeof(mi))) { - g_sndVolSSOBase = (BYTE*)mi.lpBaseOfDll; - g_sndVolSSOEnd = g_sndVolSSOBase + mi.SizeOfImage; + if (!g_sndVolSSOBase) { + HMODULE hSndVol = GetModuleHandleW(L"SndVolSSO.dll"); + if (hSndVol) { + MODULEINFO mi{}; + if (GetModuleInformation(GetCurrentProcess(), hSndVol, &mi, sizeof(mi))) { + g_sndVolSSOBase = (BYTE*)mi.lpBaseOfDll; + g_sndVolSSOEnd = g_sndVolSSOBase + mi.SizeOfImage; + } } } - HMODULE hPniDui = GetModuleHandleW(L"pnidui.dll"); - if (hPniDui) { - MODULEINFO mi{}; - if (GetModuleInformation(GetCurrentProcess(), hPniDui, &mi, sizeof(mi))) { - g_pniduiBase = (BYTE*)mi.lpBaseOfDll; - g_pniduiEnd = g_pniduiBase + mi.SizeOfImage; + if (!g_pniduiBase) { + HMODULE hPniDui = GetModuleHandleW(L"pnidui.dll"); + if (hPniDui) { + MODULEINFO mi{}; + if (GetModuleInformation(GetCurrentProcess(), hPniDui, &mi, sizeof(mi))) { + g_pniduiBase = (BYTE*)mi.lpBaseOfDll; + g_pniduiEnd = g_pniduiBase + mi.SizeOfImage; + } } } @@ -502,9 +538,9 @@ static HWND FindTrayToolbar() { static void SetupTraySubclass() { if (g_hTrayToolbar) return; - if (!InitTrayDllInfo()) return; HWND hToolbar = FindTrayToolbar(); if (!hToolbar) return; + if (!InitTrayDllInfo()) return; if (WindhawkUtils::SetWindowSubclassFromAnyThread(hToolbar, TrayToolbarSubclassProc, 0)) { g_hTrayToolbar = hToolbar; } @@ -526,93 +562,76 @@ static void* GetReturnAddress() { static bool IsAddressInModule(void* address, const wchar_t* moduleName) { HMODULE hModule = nullptr; - if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)address, &hModule)) { - wchar_t path[MAX_PATH]; - if (GetModuleFileNameW(hModule, path, MAX_PATH)) - return (wcsstr(path, moduleName) != nullptr); + if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCWSTR)address, &hModule)) { + HMODULE hTarget = GetModuleHandleW(moduleName); + return (hModule != nullptr && hModule == hTarget); } return false; } -BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND hWnd, const TPMPARAMS* lptpm) { + +static BOOL WINAPI CommonTrackPopupMenuEx_Hook( + HMENU hMenu, UINT uFlags, int x, int y, HWND hWnd, const TPMPARAMS* lptpm, + BOOL (WINAPI* pOrig)(HMENU, UINT, int, int, HWND, const TPMPARAMS*), + const wchar_t* logPrefix) +{ + if (!pOrig) return FALSE; + if (!g_settings.redirectSystemTray || !g_settings.enableRedirects) - return g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + return pOrig(hMenu, uFlags, x, y, hWnd, lptpm); HookGuard guard; if (guard.IsReentrant()) - return g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + return pOrig(hMenu, uFlags, x, y, hWnd, lptpm); - // --- Primary: subclass flag set on WM_RBUTTONUP (language-independent) --- - int contextType; + // --- Primary: subclass flag set on WM_RBUTTONUP --- + int contextType = 0; { std::lock_guard lk(g_trayContextMutex); - contextType = g_trayContextType; - DWORD tick = g_trayContextTick; + if (g_trayContextType != 0 && (GetTickCount() - g_trayContextTick <= TRAY_CONTEXT_MAX_AGE_MS)) { + contextType = g_trayContextType; + } g_trayContextType = 0; g_trayContextTick = 0; - if (contextType != 0 && GetTickCount() - tick > TRAY_CONTEXT_MAX_AGE_MS) { - contextType = 0; - } } bool isAudioMenu = (contextType == 1); bool isNetworkMenu = (contextType == 2); bool isDeviceMenu = false; - // --- Fallback: DLL return-address detection (language-independent) --- + // --- Fallback: DLL return-address detection --- if (!isAudioMenu && !isNetworkMenu) { void* retAddr = GetReturnAddress(); int itemCount = GetMenuItemCount(hMenu); if (itemCount > 0) { if (IsAddressInModule(retAddr, L"SndVolSSO.dll")) { - if (itemCount <= 6) isAudioMenu = true; + isAudioMenu = (itemCount <= 10); } else if (IsAddressInModule(retAddr, L"pnidui.dll")) { - if (itemCount <= 6) isNetworkMenu = (itemCount >= 2 && itemCount <= 5); + isNetworkMenu = (itemCount >= 1 && itemCount <= 20); } else if (IsAddressInModule(retAddr, L"dxgi.dll")) { - // dxgi.dll handles both Network flyout (IDs 3107/3109) and - // Device/Safely Remove Hardware flyout (ID 215 = "Open Devices and Printers") if (itemCount == 2 && GetMenuItemID(hMenu, 0) == 3107 && GetMenuItemID(hMenu, 1) == 3109) { isNetworkMenu = true; } - // Check for device menu: item ID 215 is the language-independent - // identifier for "Open Devices and Printers" else if (GetMenuItemID(hMenu, 0) == 215) { isDeviceMenu = true; - Wh_Log(L"[TRAY-HOOK] Device menu detected via dxgi.dll + ID 215"); } } else if (IsAddressInModule(retAddr, L"shell32.dll")) { - // NUOVO: Rilevamento shell32.dll per Win11 23H2 - // Cerca l'ID 215 in qualsiasi posizione del menu for (int i = 0; i < itemCount; i++) { - UINT itemId = GetMenuItemID(hMenu, i); - if (itemId == 215) { + if (GetMenuItemID(hMenu, i) == 215) { isDeviceMenu = true; - Wh_Log(L"[TRAY-HOOK] Device menu detected via shell32.dll + ID 215 at index %d", i); break; } } } - else if (IsAddressInModule(retAddr, L"hotplug.dll")) { - // Fallback for older Windows versions where hotplug.dll handles the menu - isDeviceMenu = true; - Wh_Log(L"[TRAY-HOOK] Device menu detected via hotplug.dll"); - } } } if (!isAudioMenu && !isNetworkMenu && !isDeviceMenu) - return g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + return pOrig(hMenu, uFlags, x, y, hWnd, lptpm); int itemCount = GetMenuItemCount(hMenu); - const wchar_t* menuKind = isAudioMenu ? L"AUDIO" : (isNetworkMenu ? L"NETWORK" : L"DEVICE"); - Wh_Log(L"[TRAY-HOOK] %s menu, %d items", menuKind, itemCount); - - // Find target item without any text matching. - // Audio: first item. - // Network: last non-separator item. - // Device: item with ID 215 ("Open Devices and Printers") — language-independent. int targetIndex = -1; if (isAudioMenu) { @@ -631,51 +650,27 @@ BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND h } } else if (isDeviceMenu) { - // Find item with ID 215 — the language-independent ID for "Open Devices and Printers" for (int i = 0; i < itemCount; i++) { if (GetMenuItemID(hMenu, i) == 215) { targetIndex = i; break; } } - // Fallback: if ID 215 not found (unlikely), use first non-separator item - if (targetIndex == -1) { - for (int i = 0; i < itemCount; i++) { - MENUITEMINFOW miiCheck = { sizeof(MENUITEMINFOW) }; - miiCheck.fMask = MIIM_FTYPE; - if (GetMenuItemInfoW(hMenu, i, TRUE, &miiCheck)) { - if (!(miiCheck.fType & MFT_SEPARATOR)) { - targetIndex = i; - break; - } - } - } - } } if (targetIndex == -1) { - Wh_Log(L"[TRAY-HOOK] No valid menu item found for %s menu", menuKind); - return g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + return pOrig(hMenu, uFlags, x, y, hWnd, lptpm); } - Wh_Log(L"[TRAY-HOOK] %s menu target index=%d, ID=%u", menuKind, targetIndex, GetMenuItemID(hMenu, targetIndex)); - UINT originalId = GetMenuItemID(hMenu, targetIndex); - - // NOTE: we intentionally do NOT swap the item's ID (via SetMenuItemInfoW) - // anymore. This menu item still gets its text/icon painted by pnidui.dll - // through WM_DRAWITEM, which is keyed off the item's real ID — replacing - // it with our own custom ID left pnidui.dll unable to look up what to - // draw for it, so the item rendered with no text (it still worked, - // since selection is ID-driven, but visually looked broken). Comparing - // directly against the original ID avoids touching the item at all. bool callerWantedReturnCmd = (uFlags & TPM_RETURNCMD) != 0; uFlags |= TPM_RETURNCMD; - BOOL result = g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + + BOOL result = pOrig(hMenu, uFlags, x, y, hWnd, lptpm); int selectedId = (int)result; if (selectedId == (int)originalId) { - Wh_Log(L"[TRAY-HOOK] User selected target item, redirecting"); + Wh_Log(L"[%s] Redirecting selection", logPrefix); if (isAudioMenu) OpenClassicSoundPanel(); else if (isNetworkMenu) OpenClassicNetworkConnections(); else OpenClassicDevicesAndPrinters(); @@ -689,138 +684,11 @@ BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND h return result; } -// Hook a livello syscall per catturare menu che bypassano TrackPopupMenuEx -BOOL WINAPI NtUserTrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND hWnd, const TPMPARAMS* lptpm) { - // Se non è un menu della system tray, passa all'originale - if (!g_settings.redirectSystemTray || !g_settings.enableRedirects) { - return g_origNtUserTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); - } - - HookGuard guard; - if (guard.IsReentrant()) { - return g_origNtUserTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); - } - - // --- Rilevamento menu usando la stessa logica di TrackPopupMenuEx_Hook --- - int contextType; - { - std::lock_guard lk(g_trayContextMutex); - contextType = g_trayContextType; - DWORD tick = g_trayContextTick; - g_trayContextType = 0; - g_trayContextTick = 0; - if (contextType != 0 && GetTickCount() - tick > TRAY_CONTEXT_MAX_AGE_MS) { - contextType = 0; - } - } - - bool isAudioMenu = (contextType == 1); - bool isNetworkMenu = (contextType == 2); - bool isDeviceMenu = false; - - // Fallback: DLL return-address detection - if (!isAudioMenu && !isNetworkMenu) { - void* retAddr = GetReturnAddress(); - int itemCount = GetMenuItemCount(hMenu); - if (itemCount > 0) { - if (IsAddressInModule(retAddr, L"SndVolSSO.dll")) { - if (itemCount <= 6) isAudioMenu = true; - } - else if (IsAddressInModule(retAddr, L"pnidui.dll")) { - if (itemCount <= 6) isNetworkMenu = (itemCount >= 2 && itemCount <= 5); - } - else if (IsAddressInModule(retAddr, L"dxgi.dll")) { - if (itemCount == 2 && GetMenuItemID(hMenu, 0) == 3107 && GetMenuItemID(hMenu, 1) == 3109) { - isNetworkMenu = true; - } - else if (GetMenuItemID(hMenu, 0) == 215) { - isDeviceMenu = true; - } - } - else if (IsAddressInModule(retAddr, L"shell32.dll")) { - for (int i = 0; i < itemCount; i++) { - if (GetMenuItemID(hMenu, i) == 215) { - isDeviceMenu = true; - break; - } - } - } - else if (IsAddressInModule(retAddr, L"hotplug.dll")) { - isDeviceMenu = true; - } - } - } - - if (!isAudioMenu && !isNetworkMenu && !isDeviceMenu) { - return g_origNtUserTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); - } - - // Trova l'item target - int itemCount = GetMenuItemCount(hMenu); - int targetIndex = -1; - - if (isAudioMenu) { - targetIndex = 0; - } - else if (isNetworkMenu) { - for (int i = itemCount - 1; i >= 0; i--) { - MENUITEMINFOW miiCheck = { sizeof(MENUITEMINFOW) }; - miiCheck.fMask = MIIM_FTYPE; - if (GetMenuItemInfoW(hMenu, i, TRUE, &miiCheck)) { - if (!(miiCheck.fType & MFT_SEPARATOR)) { - targetIndex = i; - break; - } - } - } - } - else if (isDeviceMenu) { - for (int i = 0; i < itemCount; i++) { - if (GetMenuItemID(hMenu, i) == 215) { - targetIndex = i; - break; - } - } - if (targetIndex == -1) { - for (int i = 0; i < itemCount; i++) { - MENUITEMINFOW miiCheck = { sizeof(MENUITEMINFOW) }; - miiCheck.fMask = MIIM_FTYPE; - if (GetMenuItemInfoW(hMenu, i, TRUE, &miiCheck)) { - if (!(miiCheck.fType & MFT_SEPARATOR)) { - targetIndex = i; - break; - } - } - } - } - } - - if (targetIndex == -1) { - return g_origNtUserTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); - } - - UINT originalId = GetMenuItemID(hMenu, targetIndex); - bool callerWantedReturnCmd = (uFlags & TPM_RETURNCMD) != 0; - uFlags |= TPM_RETURNCMD; - - BOOL result = g_origNtUserTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); - int selectedId = (int)result; - - if (selectedId == (int)originalId) { - Wh_Log(L"[SYSCALL-HOOK] User selected target item, redirecting"); - if (isAudioMenu) OpenClassicSoundPanel(); - else if (isNetworkMenu) OpenClassicNetworkConnections(); - else OpenClassicDevicesAndPrinters(); - return 0; - } - - if (selectedId != 0 && !callerWantedReturnCmd) { - PostMessageW(hWnd, WM_COMMAND, MAKEWPARAM((WORD)selectedId, 0), 0); - return TRUE; - } - - return result; + +BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND hWnd, const TPMPARAMS* lptpm) { + return CommonTrackPopupMenuEx_Hook(hMenu, uFlags, x, y, hWnd, lptpm, g_origTrackPopupMenuEx, L"TRAY-HOOK"); } + static std::unordered_map g_mappings; static void InitMappings() { @@ -995,15 +863,6 @@ static std::wstring NormalizeUri(const std::wstring& uri) { return result; } -static bool IsMsSettings(const wchar_t* s) { - if (!s) return false; - return ToLower(s).find(L"ms-settings:") != std::wstring::npos; -} - -static bool IsShellClsid(const wchar_t* s) { - if (!s) return false; - return ToLower(s).find(L"shell:::") != std::wstring::npos; -} static std::wstring ApplyWin11Filter(const std::wstring& target) { if (!g_isWin11) return target; @@ -1241,7 +1100,14 @@ struct IApplicationActivationManagerVtbl { HRESULT (STDMETHODCALLTYPE *ActivateForProtocol)(IUnknown*, LPCWSTR, DWORD*, DWORD); }; -static IApplicationActivationManagerVtbl g_origAAMVtbl = {}; +using ActivateApplication_t = HRESULT (STDMETHODCALLTYPE *)( + IUnknown*, + LPCWSTR appUserModelId, + LPCWSTR arguments, + DWORD options, + DWORD* processId); + +static ActivateApplication_t g_origActivateApplication = nullptr; static bool g_aamHookInstalled = false; static std::mutex g_aamHookMutex; @@ -1274,8 +1140,8 @@ HRESULT STDMETHODCALLTYPE AAM_ActivateApplication_hook( } // Not a Settings activation we can handle — call original - if (g_origAAMVtbl.ActivateApplication) { - return g_origAAMVtbl.ActivateApplication(pThis, appUserModelId, arguments, options, processId); + if (g_origActivateApplication) { + return g_origActivateApplication(pThis, appUserModelId, arguments, options, processId); } return E_FAIL; } @@ -1284,6 +1150,8 @@ static void InstallAAMHook() { std::lock_guard lk(g_aamHookMutex); if (g_aamHookInstalled) return; + HRESULT hrCo = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + IUnknown* pAAM = nullptr; HRESULT hr = CoCreateInstance( CLSID_ApplicationActivationManager_STC, @@ -1292,37 +1160,20 @@ static void InstallAAMHook() { IID_IApplicationActivationManager_STC, (void**)&pAAM); - if (FAILED(hr) || !pAAM) { - Wh_Log(L"[AAM-HOOK] CoCreateInstance(CLSID_ApplicationActivationManager) failed: 0x%08X", hr); - return; - } - - IApplicationActivationManagerVtbl* vtbl = *(IApplicationActivationManagerVtbl**)pAAM; - if (!vtbl || IsBadReadPtr(vtbl, sizeof(*vtbl))) { - Wh_Log(L"[AAM-HOOK] Invalid vtable pointer"); - pAAM->Release(); - return; - } - - // Save original vtable - g_origAAMVtbl = *vtbl; - - // Make the vtable page writable - DWORD oldProtect; - if (!VirtualProtect(vtbl, sizeof(*vtbl), PAGE_READWRITE, &oldProtect)) { - Wh_Log(L"[AAM-HOOK] VirtualProtect failed"); + if (SUCCEEDED(hr) && pAAM) { + IApplicationActivationManagerVtbl* vtbl = *(IApplicationActivationManagerVtbl**)pAAM; + if (vtbl) { + if (WindhawkUtils::SetFunctionHook((ActivateApplication_t)vtbl->ActivateApplication, AAM_ActivateApplication_hook, &g_origActivateApplication)) { + g_aamHookInstalled = true; + Wh_Log(L"[AAM-HOOK] Successfully installed"); + } + } pAAM->Release(); - return; + } else { + Wh_Log(L"[AAM-HOOK] CoCreateInstance failed: 0x%08X", hr); } - - // Patch ActivateApplication entry - vtbl->ActivateApplication = AAM_ActivateApplication_hook; - - VirtualProtect(vtbl, sizeof(*vtbl), oldProtect, &oldProtect); - g_aamHookInstalled = true; - Wh_Log(L"[AAM-HOOK] Successfully installed"); - pAAM->Release(); + if (SUCCEEDED(hrCo)) CoUninitialize(); } bool (*COpenControlPanel__MapLegacyName_orig)(void*, LPCWSTR, LPWSTR, UINT, bool*); @@ -1396,6 +1247,7 @@ static bool IsControlSystemCommand(const std::wstring& cmdLine) { std::wstring arg = ToLower(tokens[1]); return (arg == L"system" || arg == L"microsoft.system"); } + static std::wstring ExtractExplorerLaunchUri(const std::wstring& cmdLine) { size_t i = 0, n = cmdLine.size(); while (i < n && cmdLine[i] == L' ') i++; @@ -1422,10 +1274,12 @@ static std::wstring ExtractExplorerLaunchUri(const std::wstring& cmdLine) { } if (rest.empty()) return L""; - if (IsMsSettings(rest.c_str())) return NormalizeUri(rest); - if (IsShellClsid(rest.c_str())) return ToLower(rest); + const wchar_t* restC = rest.c_str(); + if (ToLower(restC).find(L"ms-settings:") != std::wstring::npos) return NormalizeUri(rest); + if (ToLower(restC).find(L"shell:::") != std::wstring::npos) return ToLower(rest); return L""; } + BOOL WINAPI ShellExecuteExW_hook(SHELLEXECUTEINFOW* pei) { if (IsChildProcess()) return ShellExecuteExW_orig(pei); HookGuard guard; @@ -1439,10 +1293,13 @@ BOOL WINAPI ShellExecuteExW_hook(SHELLEXECUTEINFOW* pei) { } std::wstring uri; - if (IsMsSettings(pei->lpFile)) uri = NormalizeUri(pei->lpFile); - else if (IsMsSettings(pei->lpParameters)) uri = NormalizeUri(pei->lpParameters); - else if (IsShellClsid(pei->lpFile)) uri = ToLower(pei->lpFile); - else if (IsShellClsid(pei->lpParameters)) uri = ToLower(pei->lpParameters); + const wchar_t* f = pei->lpFile; + const wchar_t* p = pei->lpParameters; + + if (f && ToLower(f).find(L"ms-settings:") != std::wstring::npos) uri = NormalizeUri(f); + else if (p && ToLower(p).find(L"ms-settings:") != std::wstring::npos) uri = NormalizeUri(p); + else if (f && ToLower(f).find(L"shell:::") != std::wstring::npos) uri = ToLower(f); + else if (p && ToLower(p).find(L"shell:::") != std::wstring::npos) uri = ToLower(p); if (uri == L"ms-settings:taskbar") return ShellExecuteExW_orig(pei); @@ -1466,14 +1323,14 @@ HINSTANCE WINAPI ShellExecuteW_hook(HWND hwnd, LPCWSTR op, LPCWSTR file, LPCWSTR if (IsControlSystemParams(file, params)) { LaunchTarget(g_isWin11 ? L"sysdm.cpl" : SYSTEM_PROPS_CLSID); - return SHELL_EXECUTE_SUCCESS; + return (HINSTANCE)33; } std::wstring uri; - if (IsMsSettings(file)) uri = NormalizeUri(file); - else if (IsMsSettings(params)) uri = NormalizeUri(params); - else if (IsShellClsid(file)) uri = ToLower(file); - else if (IsShellClsid(params)) uri = ToLower(params); + if (file && ToLower(file).find(L"ms-settings:") != std::wstring::npos) uri = NormalizeUri(file); + else if (params && ToLower(params).find(L"ms-settings:") != std::wstring::npos) uri = NormalizeUri(params); + else if (file && ToLower(file).find(L"shell:::") != std::wstring::npos) uri = ToLower(file); + else if (params && ToLower(params).find(L"shell:::") != std::wstring::npos) uri = ToLower(params); if (uri == L"ms-settings:taskbar") return ShellExecuteW_orig(hwnd, op, file, params, dir, show); @@ -1482,7 +1339,7 @@ HINSTANCE WINAPI ShellExecuteW_hook(HWND hwnd, LPCWSTR op, LPCWSTR file, LPCWSTR auto result = ResolveUri(uri, hwnd); if (result.intercept) { if (!result.target.empty()) LaunchTarget(result.target); - return SHELL_EXECUTE_SUCCESS; + return (HINSTANCE)33; } } return ShellExecuteW_orig(hwnd, op, file, params, dir, show); @@ -1513,10 +1370,6 @@ BOOL WINAPI CreateProcessW_hook(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, return TRUE; } - // Handles cases like the classic Control Panel's "See also" links, - // which Explorer launches as a new "explorer.exe ms-settings:..." - // (or "explorer.exe shell:::{...}") process via CreateProcessW - // instead of going through ShellExecuteW/ShellExecuteExW. std::wstring uri = ExtractExplorerLaunchUri(cmdLine); if (!uri.empty()) { auto result = ResolveUri(uri, nullptr); @@ -1531,8 +1384,8 @@ BOOL WINAPI CreateProcessW_hook(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); } -static volatile bool g_pniduiRetryRunning = false; +static volatile bool g_pniduiRetryRunning = false; static bool TryInstallPniduiHook() { std::lock_guard lk(g_pniduiHookMutex); @@ -1545,13 +1398,10 @@ static bool TryInstallPniduiHook() { if (!hMod) { hMod = LoadLibraryExW(L"pnidui.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if (!hMod) { - Wh_Log(L"[PNIDUI-HOOK] pnidui.dll not loaded yet"); return false; } } - Wh_Log(L"[PNIDUI-HOOK] pnidui.dll loaded at 0x%p", hMod); - WindhawkUtils::SYMBOL_HOOK pnidui_dll_hooks[] = {{ { L"bool " @@ -1570,99 +1420,80 @@ static bool TryInstallPniduiHook() { bool result = WindhawkUtils::HookSymbols(hMod, pnidui_dll_hooks, 1); if (result) { - Wh_Log(L"[PNIDUI-HOOK] Successfully installed pnidui.dll hook"); g_pniduiHookInstalled = true; - } else { - Wh_Log(L"[PNIDUI-HOOK] Failed to install pnidui.dll hook"); } return result; } static DWORD WINAPI PniduiRetryThread(LPVOID) { if (g_pniduiRetryRunning) { - Wh_Log(L"[PNIDUI-HOOK] Retry thread already running, exiting"); return 0; } g_pniduiRetryRunning = true; - Wh_Log(L"[PNIDUI-HOOK] Retry thread started - waiting for pnidui.dll to load"); - const int MAX_WAIT_CHECKS = 60; const DWORD CHECK_INTERVAL = 500; bool dllLoaded = false; - for (int i = 0; i < MAX_WAIT_CHECKS && !g_pniduiRetryStop; i++) { + for (int i = 0; i < MAX_WAIT_CHECKS; i++) { + if (WaitForSingleObject(g_stopEvent, CHECK_INTERVAL) == WAIT_OBJECT_0) break; if (GetModuleHandleW(L"pnidui.dll") != nullptr) { dllLoaded = true; - Wh_Log(L"[PNIDUI-HOOK] pnidui.dll detected after %dms", i * CHECK_INTERVAL); break; } - Sleep(CHECK_INTERVAL); } - if (!dllLoaded) { - Wh_Log(L"[PNIDUI-HOOK] pnidui.dll not loaded after timeout, giving up"); - g_pniduiRetryRunning = false; - return 0; - } - - if (TryInstallPniduiHook()) { - Wh_Log(L"[PNIDUI-HOOK] Hook installed successfully after waiting for DLL"); - } else { - Wh_Log(L"[PNIDUI-HOOK] Failed to install hook even though DLL is loaded"); + if (dllLoaded) { + if (TryInstallPniduiHook()) { + g_pniduiHookInstalled = true; + } } g_pniduiRetryRunning = false; - Wh_Log(L"[PNIDUI-HOOK] Retry thread exiting"); return 0; } -static void InstallImmersiveMenuHooks() { - struct DllHook { - const wchar_t* dll; - ICMH_CAODTM_t* orig; - } targets[] = { - { L"SndVolSSO.dll", &g_icmhOrig_SndVolSSO }, - }; +static bool g_sndVolSSOHookInstalled = false; +static bool g_shell32DevicesHookInstalled = false; - for (auto& t : targets) { - HMODULE hMod = LoadLibraryExW(t.dll, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); - if (!hMod) continue; - - WindhawkUtils::SYMBOL_HOOK sndVolSSO_dll_hooks[] = {{ - { - L"bool " +static void InstallImmersiveMenuHooks() { + if (!g_sndVolSSOHookInstalled) { + HMODULE hMod = GetModuleHandleW(L"SndVolSSO.dll"); + if (!hMod) hMod = LoadLibraryExW(L"SndVolSSO.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + + if (hMod) { + WindhawkUtils::SYMBOL_HOOK sndVolSSO_dll_hooks[] = {{ + { + L"bool " #ifdef _WIN64 - L"__cdecl" + L"__cdecl" #else - L"__stdcall" + L"__stdcall" #endif - L" ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu" - L"(struct HMENU__ *,struct HWND__ *)" - }, - (void**)t.orig, - (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, - false - }}; - - WindhawkUtils::HookSymbols(hMod, sndVolSSO_dll_hooks, 1); - } - - if (!TryInstallPniduiHook()) { - if (!g_pniduiRetryRunning && !g_pniduiRetryThread) { - g_pniduiRetryStop = false; - g_pniduiRetryThread = CreateThread(nullptr, 0, PniduiRetryThread, nullptr, 0, nullptr); - if (g_pniduiRetryThread) { - Wh_Log(L"[PNIDUI-HOOK] Retry thread created"); - } else { - Wh_Log(L"[PNIDUI-HOOK] Failed to create retry thread"); + L" ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu" + L"(struct HMENU__ *,struct HWND__ *)" + }, + (void**)&g_icmhOrig_SndVolSSO, + (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, + false + }}; + + if (WindhawkUtils::HookSymbols(hMod, sndVolSSO_dll_hooks, 1)) { + g_sndVolSSOHookInstalled = true; } - } else { - Wh_Log(L"[PNIDUI-HOOK] Retry thread already running or hook already installed"); } } - if (g_isWin11) { + if (!g_pniduiHookInstalled) { + if (!TryInstallPniduiHook()) { + if (!g_pniduiRetryRunning && !g_pniduiRetryThread) { + g_pniduiRetryStop = false; + g_pniduiRetryThread = CreateThread(nullptr, 0, PniduiRetryThread, nullptr, 0, nullptr); + } + } + } + + if (g_isWin11 && !g_shell32DevicesHookInstalled) { HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); if (hShell32) { WindhawkUtils::SYMBOL_HOOK shell32_dll_hooks[] = {{ @@ -1681,56 +1512,34 @@ static void InstallImmersiveMenuHooks() { false }}; - WindhawkUtils::HookSymbols(hShell32, shell32_dll_hooks, 1); - } - } -} - -static void InstallSyscallFallback() { - HMODULE hWin32u = GetModuleHandleW(L"win32u.dll"); - if (!hWin32u) { - hWin32u = LoadLibraryW(L"win32u.dll"); - if (!hWin32u) { - Wh_Log(L"[SYSCALL-HOOK] win32u.dll not found"); - return; + if (WindhawkUtils::HookSymbols(hShell32, shell32_dll_hooks, 1)) { + g_shell32DevicesHookInstalled = true; + } } } - - FARPROC pNtUserTrackPopupMenuEx = GetProcAddress(hWin32u, "NtUserTrackPopupMenuEx"); - if (!pNtUserTrackPopupMenuEx) { - Wh_Log(L"[SYSCALL-HOOK] NtUserTrackPopupMenuEx not found by name"); - return; - } - - if (Wh_SetFunctionHook((void*)pNtUserTrackPopupMenuEx, - (void*)NtUserTrackPopupMenuEx_Hook, - (void**)&g_origNtUserTrackPopupMenuEx)) { - Wh_Log(L"[SYSCALL-HOOK] NtUserTrackPopupMenuEx hooked successfully"); - } else { - Wh_Log(L"[SYSCALL-HOOK] Failed to hook NtUserTrackPopupMenuEx"); - } } -using CreateWindowExW_t = decltype(&CreateWindowExW); -CreateWindowExW_t CreateWindowExW_Original; -// --------------------------------------------------------------------------- -// Tray subclass watchdog -// --------------------------------------------------------------------------- - static bool HasTrayBeenRecreated() { HWND hTray = FindWindowW(L"Shell_TrayWnd", nullptr); if (!hTray) { + g_lastShellTrayWnd = nullptr; return false; } - bool recreated = (g_lastShellTrayWnd != nullptr && hTray != g_lastShellTrayWnd); - g_lastShellTrayWnd = hTray; - return recreated; + if (g_lastShellTrayWnd == nullptr) { + g_lastShellTrayWnd = hTray; + return true; + } + + if (hTray != g_lastShellTrayWnd) { + g_lastShellTrayWnd = hTray; + return true; + } + + return false; } static void ReinitializeTrayRedirect() { - Wh_Log(L"[TRAY-WATCHDOG] Reinitializing tray redirect state"); - RemoveTraySubclass(); g_sndVolSSOBase = nullptr; @@ -1738,40 +1547,42 @@ static void ReinitializeTrayRedirect() { g_pniduiBase = nullptr; g_pniduiEnd = nullptr; - { - std::lock_guard lk(g_pniduiHookMutex); - g_pniduiHookInstalled = false; - } - if (g_settings.redirectSystemTray) { SetupTraySubclass(); } - if (!TryInstallPniduiHook()) { - if (!g_pniduiRetryRunning && !g_pniduiRetryThread) { - g_pniduiRetryStop = false; - g_pniduiRetryThread = CreateThread(nullptr, 0, PniduiRetryThread, nullptr, 0, nullptr); - } + InstallImmersiveMenuHooks(); +} + +static void PerformBackgroundInit() { + Sleep(200); + + InstallImmersiveMenuHooks(); + + if (g_settings.comActivationRedirect && g_isWin11) { + InstallAAMHook(); } - InstallImmersiveMenuHooks(); + if (g_settings.legacyNameMappingFix) { + InstallLegacyNameHook(); + } } static DWORD WINAPI TraySubclassWatchdogThread(LPVOID) { - Wh_Log(L"[TRAY-WATCHDOG] Watchdog thread started"); + PerformBackgroundInit(); const int FAST_PHASE_CHECKS = 60; const DWORD FAST_INTERVAL_MS = 500; const DWORD SLOW_INTERVAL_MS = 3000; int tick = 0; - while (!g_traySubclassWatchdogStop) { - Sleep(tick < FAST_PHASE_CHECKS ? FAST_INTERVAL_MS : SLOW_INTERVAL_MS); + while (true) { + DWORD interval = tick < FAST_PHASE_CHECKS ? FAST_INTERVAL_MS : SLOW_INTERVAL_MS; + if (WaitForSingleObject(g_stopEvent, interval) == WAIT_OBJECT_0) break; + tick++; - if (g_traySubclassWatchdogStop) break; if (HasTrayBeenRecreated()) { - Wh_Log(L"[TRAY-WATCHDOG] Shell_TrayWnd recreation detected, reinitializing"); ReinitializeTrayRedirect(); continue; } @@ -1779,7 +1590,6 @@ static DWORD WINAPI TraySubclassWatchdogThread(LPVOID) { if (!g_settings.redirectSystemTray) continue; if (g_hTrayToolbar && !IsWindow(g_hTrayToolbar)) { - Wh_Log(L"[TRAY-WATCHDOG] Tray toolbar handle no longer valid, resetting"); g_hTrayToolbar = nullptr; } @@ -1787,10 +1597,12 @@ static DWORD WINAPI TraySubclassWatchdogThread(LPVOID) { SetupTraySubclass(); } } - - Wh_Log(L"[TRAY-WATCHDOG] Watchdog thread exiting"); return 0; } + +using CreateWindowExW_t = HWND(WINAPI*)(DWORD, LPCWSTR, LPCWSTR, DWORD, int, int, int, int, HWND, HMENU, HINSTANCE, LPVOID); +static CreateWindowExW_t CreateWindowExW_Original = nullptr; + HWND WINAPI CreateWindowExW_Hook( DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, @@ -1800,7 +1612,10 @@ HWND WINAPI CreateWindowExW_Hook( dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam); - if (g_settings.redirectSystemTray && hwnd && !g_hTrayToolbar && lpClassName && !IS_INTRESOURCE(lpClassName)) { + if (g_settings.redirectSystemTray && hwnd && !g_hTrayToolbar && + lpClassName && !IS_INTRESOURCE(lpClassName) && + lpClassName[0] == L'T') + { if (wcscmp(lpClassName, L"ToolbarWindow32") == 0) { SetupTraySubclass(); } @@ -1809,123 +1624,88 @@ HWND WINAPI CreateWindowExW_Hook( return hwnd; } - BOOL Wh_ModInit() { - Wh_Log(L"Initializing the Redirect Settings to Control Panel mod..."); - DetectWindowsVersion(); LoadSettings(); BuildChildEnvironment(); InitMappings(); + g_stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); - if (!hShell32) hShell32 = LoadLibraryW(L"shell32.dll"); + if (!hShell32) hShell32 = LoadLibraryExW(L"shell32.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if (!hShell32) return FALSE; FARPROC pExW = GetProcAddress(hShell32, "ShellExecuteExW"); FARPROC pW = GetProcAddress(hShell32, "ShellExecuteW"); if (!pExW || !pW) return FALSE; - Wh_SetFunctionHook((void*)pExW, (void*)ShellExecuteExW_hook, (void**)&ShellExecuteExW_orig); - Wh_SetFunctionHook((void*)pW, (void*)ShellExecuteW_hook, (void**)&ShellExecuteW_orig); + WindhawkUtils::SetFunctionHook((ShellExecuteExW_t)pExW, ShellExecuteExW_hook, &ShellExecuteExW_orig); + WindhawkUtils::SetFunctionHook((ShellExecuteW_t)pW, ShellExecuteW_hook, &ShellExecuteW_orig); HMODULE hKernel32 = GetModuleHandleW(L"kernel32.dll"); - if (!hKernel32) hKernel32 = LoadLibraryW(L"kernel32.dll"); + if (!hKernel32) hKernel32 = LoadLibraryExW(L"kernel32.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if (hKernel32) { - FARPROC pCPW = GetProcAddress(hKernel32, "CreateProcessW"); - if (pCPW) Wh_SetFunctionHook((void*)pCPW, (void*)CreateProcessW_hook, (void**)&CreateProcessW_orig); + void* pCPW = (void*)GetProcAddress(hKernel32, "CreateProcessW"); + if (pCPW) WindhawkUtils::SetFunctionHook((CreateProcessW_t)pCPW, CreateProcessW_hook, &CreateProcessW_orig); } - Wh_SetFunctionHook((void*)CreateWindowExW, (void*)CreateWindowExW_Hook, (void**)&CreateWindowExW_Original); - if (g_settings.redirectSystemTray) { - SetupTraySubclass(); - } - - InstallImmersiveMenuHooks(); - InstallSyscallFallback(); - - if (g_settings.comActivationRedirect && g_isWin11) { - InstallAAMHook(); - } - - if (g_settings.legacyNameMappingFix) { - InstallLegacyNameHook(); - } + if (IsShellProcess()) { + WindhawkUtils::SetFunctionHook(CreateWindowExW, CreateWindowExW_Hook, &CreateWindowExW_Original); + if (g_settings.redirectSystemTray) { + SetupTraySubclass(); + } - HMODULE hUser32 = GetModuleHandleW(L"user32.dll"); - if (!hUser32) hUser32 = LoadLibraryW(L"user32.dll"); - if (hUser32) { - void* pTrackPopupMenuEx = (void*)GetProcAddress(hUser32, "TrackPopupMenuEx"); - if (pTrackPopupMenuEx) { - Wh_SetFunctionHook(pTrackPopupMenuEx, (void*)TrackPopupMenuEx_Hook, (void**)&g_origTrackPopupMenuEx); + HMODULE hUser32 = GetModuleHandleW(L"user32.dll"); + if (!hUser32) hUser32 = LoadLibraryExW(L"user32.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (hUser32) { + FARPROC pTrackPopupMenuEx = GetProcAddress(hUser32, "TrackPopupMenuEx"); + if (pTrackPopupMenuEx) { + WindhawkUtils::SetFunctionHook((TrackPopupMenuEx_t)pTrackPopupMenuEx, TrackPopupMenuEx_Hook, &g_origTrackPopupMenuEx); + } } + + g_lastShellTrayWnd = nullptr; + g_traySubclassWatchdogStop = false; + g_traySubclassWatchdogThread = CreateThread(nullptr, 0, TraySubclassWatchdogThread, nullptr, 0, nullptr); } - // Initialize the Shell_TrayWnd reference for the watchdog - g_lastShellTrayWnd = FindWindowW(L"Shell_TrayWnd", nullptr); - g_traySubclassWatchdogStop = false; - g_traySubclassWatchdogThread = CreateThread(nullptr, 0, TraySubclassWatchdogThread, nullptr, 0, nullptr); - if (g_traySubclassWatchdogThread) { - Wh_Log(L"[TRAY-WATCHDOG] Watchdog thread created"); - } else { - Wh_Log(L"[TRAY-WATCHDOG] Failed to create watchdog thread"); - } return TRUE; } void Wh_ModUninit() { + if (g_stopEvent) { + SetEvent(g_stopEvent); + } + if (g_traySubclassWatchdogThread) { - g_traySubclassWatchdogStop = true; - Wh_Log(L"[TRAY-WATCHDOG] Stopping watchdog thread..."); - DWORD waitResult = WaitForSingleObject(g_traySubclassWatchdogThread, 3000); - if (waitResult == WAIT_TIMEOUT) { - Wh_Log(L"[TRAY-WATCHDOG] Timeout, terminating thread"); - TerminateThread(g_traySubclassWatchdogThread, 0); - } + WaitForSingleObject(g_traySubclassWatchdogThread, 3000); CloseHandle(g_traySubclassWatchdogThread); g_traySubclassWatchdogThread = nullptr; } if (g_pniduiRetryThread) { - g_pniduiRetryStop = true; - Wh_Log(L"[PNIDUI-HOOK] Stopping retry thread..."); - DWORD waitResult = WaitForSingleObject(g_pniduiRetryThread, 3000); - if (waitResult == WAIT_TIMEOUT) { - Wh_Log(L"[PNIDUI-HOOK] Retry thread timeout, terminating"); - TerminateThread(g_pniduiRetryThread, 0); - } + WaitForSingleObject(g_pniduiRetryThread, 3000); CloseHandle(g_pniduiRetryThread); g_pniduiRetryThread = nullptr; g_pniduiRetryRunning = false; } + if (g_stopEvent) { + CloseHandle(g_stopEvent); + g_stopEvent = nullptr; + } + RemoveTraySubclass(); } void Wh_ModSettingsChanged() { - RemoveTraySubclass(); - g_sndVolSSOBase = nullptr; - g_sndVolSSOEnd = nullptr; - g_pniduiBase = nullptr; - g_pniduiEnd = nullptr; - - { - std::lock_guard lk(g_pniduiHookMutex); - g_pniduiHookInstalled = false; - } - LoadSettings(); InitMappings(); - if (g_settings.redirectSystemTray) { - SetupTraySubclass(); - InstallImmersiveMenuHooks(); - } - // Re-install experimental hooks if settings changed - if (g_settings.comActivationRedirect && g_isWin11) { - InstallAAMHook(); - } - - if (g_settings.legacyNameMappingFix) { - InstallLegacyNameHook(); + if (IsShellProcess()) { + if (g_settings.redirectSystemTray) { + SetupTraySubclass(); + } + PerformBackgroundInit(); } } From e6a823274283612c2a949b2ce00620880ac440d9 Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Wed, 15 Jul 2026 14:44:29 +0200 Subject: [PATCH 20/25] Fix --- mods/settings-to-control-panel.wh.cpp | 126 +++++++++++++------------- 1 file changed, 61 insertions(+), 65 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index 71f861908d..e99e6945d5 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -46,7 +46,7 @@ Panel pages, using only native Windows components. - The system tray context menu redirect only supports the Win32 taskbar (the one from Windows 10). However, in some Windows 11 configurations if explorer is restarted the network system tray redirect might not work. - The device & printers system tray redirect may not work on some Windows 11 configurations, as Microsoft hardcoded the redirect to the Settings app in certain shell code paths. This could change in future if correct documentation is found. -**Note**: For a better experience on Windows 11, it is recommended to pair this mod with Anixx's **[Restore the classic Personalization and other CPLs](https://windhawk.net/mods/restore-classic-cpls)** that re-enables some of the classic applets from older Windows versions. +**Recommendation**: For a better experience on Windows 11, it is recommended to pair this mod with Anixx's **[Restore the classic Personalization and other CPLs](https://windhawk.net/mods/restore-classic-cpls)** that re-enables some of the classic applets from older Windows versions. --- @@ -78,14 +78,13 @@ Panel pages, using only native Windows components. - "2": Pass through to the modern Settings application (ms-settings.exe)" - Win11CompatibilityMode: false $name: Windows 11 Compatibility Mode - $description: "Safer mode for Windows 11. When enabled, only uses proven redirects while everything else opens the standard Control Panel page as a fallback to avoid loops or other issues." + $description: "This is a safer mode for Windows 11. When enabled, only uses proven redirects while everything else opens the standard Control Panel page as a fallback to avoid loops or other issues." - MaxLaunchesPerUri: 3 $name: Anti-Loop Limit (per window, every 5 seconds) $description: "Safety measure: if the same window gets opened too many times within a few seconds, the mod stops reopening it. Set to 0 to disable this limit." - ComActivationRedirect: true $name: COM-activation Redirect (EXPERIMENTAL) $description: "This setting redirects modern Settings calls made through the COM interface to ensure they open correctly. This addresses compatibility issues with certain Windows 11 builds where Settings may fail to launch." - - LegacyNameMappingFix: true $name: Fix Legacy Name Mapping $description: "This setting attempts to correct a mapping error that causes legacy Control Panel items to display as blank pages or automatically redirect to the modern Settings app. This should ensure that the classic system tools open and function as intended." @@ -113,11 +112,6 @@ static const CLSID CLSID_ApplicationActivationManager_STC = static const IID IID_IApplicationActivationManager_STC = { 0x2e941141, 0x7f97, 0x4756, { 0xba, 0x1d, 0x9d, 0xec, 0xde, 0x89, 0x4a, 0x3d } }; -// Custom IDs for tray menu redirection (TrackPopupMenuEx method) -#define TRAY_CUSTOM_ID_AUDIO 65001 -#define TRAY_CUSTOM_ID_NETWORK 65002 -#define TRAY_CUSTOM_ID_DEVICES 65003 - // TrackPopupMenuEx hook (DLL-based fallback method) using TrackPopupMenuEx_t = BOOL(WINAPI*)(HMENU, UINT, int, int, HWND, const TPMPARAMS*); static TrackPopupMenuEx_t g_origTrackPopupMenuEx = nullptr; @@ -133,11 +127,10 @@ static ICMH_CAODTM_t g_icmhOrig_SndVolSSO = nullptr; static ICMH_CAODTM_t g_icmhOrig_pnidui = nullptr; static ICMH_CAODTM_t g_icmhOrig_Shell32Devices = nullptr; static bool g_pniduiHookInstalled = false; +static bool g_legacyNameHookInstalled = false; static std::mutex g_pniduiHookMutex; static HANDLE g_pniduiRetryThread = nullptr; -static volatile bool g_pniduiRetryStop = false; static HANDLE g_traySubclassWatchdogThread = nullptr; -static volatile bool g_traySubclassWatchdogStop = false; static HWND g_lastShellTrayWnd = nullptr; static HANDLE g_stopEvent = nullptr; @@ -374,7 +367,7 @@ static const std::unordered_set g_win11LoopClsids = { L"shell:::{17cd9488-1228-4b2f-88ce-4298e93e0966}", L"shell:::{80f3f1d5-feca-45f3-bc32-752c152e456e}", L"shell:::{9fe63afd-59cf-4419-9775-abcc3849f861}", - L"shell:::{bb06c0e4-d293-4f75-8a90-cb05B6477EEE}", + L"shell:::{bb06c0e4-d293-4f75-8a90-cb05b6477eee}", L"shell:::{ed834ed6-4b5a-4bfe-8f11-a626dcb6a921}", }; @@ -669,7 +662,7 @@ static BOOL WINAPI CommonTrackPopupMenuEx_Hook( BOOL result = pOrig(hMenu, uFlags, x, y, hWnd, lptpm); int selectedId = (int)result; - if (selectedId == (int)originalId) { + if (originalId != 0 && selectedId == (int)originalId) { Wh_Log(L"[%s] Redirecting selection", logPrefix); if (isAudioMenu) OpenClassicSoundPanel(); else if (isNetworkMenu) OpenClassicNetworkConnections(); @@ -1193,30 +1186,6 @@ bool COpenControlPanel__MapLegacyName_hook( return false; } -static bool InstallLegacyNameHook() { - HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); - if (!hShell32) { - Wh_Log(L"[MAP-LEGACY] shell32.dll not loaded"); - return false; - } - - WindhawkUtils::SYMBOL_HOOK shell32_dll_hook = {{ - L"private: bool __cdecl COpenControlPanel::_MapLegacyName" - L"(unsigned short const *,unsigned short *,unsigned int,bool *)" - }, - (void**)&COpenControlPanel__MapLegacyName_orig, - (void*)COpenControlPanel__MapLegacyName_hook, - false}; - - if (WindhawkUtils::HookSymbols(hShell32, &shell32_dll_hook, 1)) { - Wh_Log(L"[MAP-LEGACY] Hook installed successfully"); - return true; - } else { - Wh_Log(L"[MAP-LEGACY] Failed to install hook (symbol may differ on this build)"); - return false; - } -} - static std::wstring BaseNameLower(const std::wstring& path) { size_t pos = path.rfind(L'\\'); return ToLower((pos != std::wstring::npos) ? path.substr(pos + 1) : path); @@ -1276,7 +1245,6 @@ static std::wstring ExtractExplorerLaunchUri(const std::wstring& cmdLine) { const wchar_t* restC = rest.c_str(); if (ToLower(restC).find(L"ms-settings:") != std::wstring::npos) return NormalizeUri(rest); - if (ToLower(restC).find(L"shell:::") != std::wstring::npos) return ToLower(rest); return L""; } @@ -1446,6 +1414,7 @@ static DWORD WINAPI PniduiRetryThread(LPVOID) { if (dllLoaded) { if (TryInstallPniduiHook()) { g_pniduiHookInstalled = true; + Wh_ApplyHookOperations(); } } @@ -1487,34 +1456,59 @@ static void InstallImmersiveMenuHooks() { if (!g_pniduiHookInstalled) { if (!TryInstallPniduiHook()) { if (!g_pniduiRetryRunning && !g_pniduiRetryThread) { - g_pniduiRetryStop = false; g_pniduiRetryThread = CreateThread(nullptr, 0, PniduiRetryThread, nullptr, 0, nullptr); } } } +} - if (g_isWin11 && !g_shell32DevicesHookInstalled) { - HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); - if (hShell32) { - WindhawkUtils::SYMBOL_HOOK shell32_dll_hooks[] = {{ - { - L"bool " +// Combines all shell32.dll hooks into a single HookSymbols call per the +// Windhawk API best practice. Called when either the Win11 devices hook +// or the legacy-name-mapping hook is needed. +static void InstallShell32Hooks() { + bool needDevices = g_isWin11 && !g_shell32DevicesHookInstalled; + bool needLegacy = g_settings.legacyNameMappingFix && !g_legacyNameHookInstalled; + if (!needDevices && !needLegacy) return; + + HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); + if (!hShell32) return; + + WindhawkUtils::SYMBOL_HOOK hooks[2]; + int hookCount = 0; + + if (needDevices) { + hooks[hookCount] = {{ + L"bool " #ifdef _WIN64 - L"__cdecl" + L"__cdecl" #else - L"__stdcall" + L"__stdcall" #endif - L" CDevicesAndPrintersFolder::_HandleContextMenu" - L"(struct HMENU__ *,unsigned int)" - }, - (void**)&g_icmhOrig_Shell32Devices, - (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, - false - }}; - - if (WindhawkUtils::HookSymbols(hShell32, shell32_dll_hooks, 1)) { - g_shell32DevicesHookInstalled = true; - } + L" CDevicesAndPrintersFolder::_HandleContextMenu" + L"(struct HMENU__ *,unsigned int)" + }, + (void**)&g_icmhOrig_Shell32Devices, + (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, + false}; + hookCount++; + } + + if (needLegacy) { + hooks[hookCount] = {{ + L"private: bool __cdecl COpenControlPanel::_MapLegacyName" + L"(unsigned short const *,unsigned short *,unsigned int,bool *)" + }, + (void**)&COpenControlPanel__MapLegacyName_orig, + (void*)COpenControlPanel__MapLegacyName_hook, + false}; + hookCount++; + } + + if (hookCount > 0) { + if (WindhawkUtils::HookSymbols(hShell32, hooks, hookCount)) { + if (needDevices) g_shell32DevicesHookInstalled = true; + if (needLegacy) g_legacyNameHookInstalled = true; + Wh_Log(L"[SHELL32-HOOKS] Installed %d hook(s)", hookCount); } } } @@ -1552,20 +1546,23 @@ static void ReinitializeTrayRedirect() { } InstallImmersiveMenuHooks(); + InstallShell32Hooks(); + Wh_ApplyHookOperations(); } -static void PerformBackgroundInit() { - Sleep(200); +static void PerformBackgroundInit(bool skipSleep = false) { + if (!skipSleep) { + Sleep(200); + } - InstallImmersiveMenuHooks(); + InstallImmersiveMenuHooks(); + InstallShell32Hooks(); if (g_settings.comActivationRedirect && g_isWin11) { InstallAAMHook(); } - if (g_settings.legacyNameMappingFix) { - InstallLegacyNameHook(); - } + Wh_ApplyHookOperations(); } static DWORD WINAPI TraySubclassWatchdogThread(LPVOID) { @@ -1666,7 +1663,6 @@ BOOL Wh_ModInit() { } g_lastShellTrayWnd = nullptr; - g_traySubclassWatchdogStop = false; g_traySubclassWatchdogThread = CreateThread(nullptr, 0, TraySubclassWatchdogThread, nullptr, 0, nullptr); } @@ -1706,6 +1702,6 @@ void Wh_ModSettingsChanged() { if (g_settings.redirectSystemTray) { SetupTraySubclass(); } - PerformBackgroundInit(); + PerformBackgroundInit(true); } } From 3913d123f9b90aeb1df4eee5f7c224cbf440eace Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Wed, 15 Jul 2026 14:47:21 +0200 Subject: [PATCH 21/25] Fix validation issue --- mods/settings-to-control-panel.wh.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index e99e6945d5..c185fd5efb 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -2,7 +2,7 @@ // @id settings-to-control-panel // @name Redirect Settings to Control Panel // @description This mod forces the classic Control Panel to open instead of Windows 10/11 Settings app using native components. -// @version 10.0.31 +// @version 10.0.32 // @author babamohammed // @github https://github.com/babamohammed2022 // @include explorer.exe @@ -1473,11 +1473,11 @@ static void InstallShell32Hooks() { HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); if (!hShell32) return; - WindhawkUtils::SYMBOL_HOOK hooks[2]; + WindhawkUtils::SYMBOL_HOOK shell32_dll_hooks[2]; int hookCount = 0; if (needDevices) { - hooks[hookCount] = {{ + shell32_dll_hooks[hookCount] = {{ L"bool " #ifdef _WIN64 L"__cdecl" @@ -1494,7 +1494,7 @@ static void InstallShell32Hooks() { } if (needLegacy) { - hooks[hookCount] = {{ + shell32_dll_hooks[hookCount] = {{ L"private: bool __cdecl COpenControlPanel::_MapLegacyName" L"(unsigned short const *,unsigned short *,unsigned int,bool *)" }, @@ -1505,7 +1505,7 @@ static void InstallShell32Hooks() { } if (hookCount > 0) { - if (WindhawkUtils::HookSymbols(hShell32, hooks, hookCount)) { + if (WindhawkUtils::HookSymbols(hShell32, shell32_dll_hooks, hookCount)) { if (needDevices) g_shell32DevicesHookInstalled = true; if (needLegacy) g_legacyNameHookInstalled = true; Wh_Log(L"[SHELL32-HOOKS] Installed %d hook(s)", hookCount); From 91017e0751ebb4dc482569db61263390b4249d99 Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Wed, 15 Jul 2026 20:02:49 +0200 Subject: [PATCH 22/25] Address the issues --- mods/settings-to-control-panel.wh.cpp | 275 ++++++++++++++++++-------- 1 file changed, 191 insertions(+), 84 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index c185fd5efb..8a3f5d77c3 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -2,7 +2,7 @@ // @id settings-to-control-panel // @name Redirect Settings to Control Panel // @description This mod forces the classic Control Panel to open instead of Windows 10/11 Settings app using native components. -// @version 10.0.32 +// @version 10.0.33 // @author babamohammed // @github https://github.com/babamohammed2022 // @include explorer.exe @@ -46,7 +46,16 @@ Panel pages, using only native Windows components. - The system tray context menu redirect only supports the Win32 taskbar (the one from Windows 10). However, in some Windows 11 configurations if explorer is restarted the network system tray redirect might not work. - The device & printers system tray redirect may not work on some Windows 11 configurations, as Microsoft hardcoded the redirect to the Settings app in certain shell code paths. This could change in future if correct documentation is found. -**Recommendation**: For a better experience on Windows 11, it is recommended to pair this mod with Anixx's **[Restore the classic Personalization and other CPLs](https://windhawk.net/mods/restore-classic-cpls)** that re-enables some of the classic applets from older Windows versions. + +--- + +**Recommendation**: For a better experience on Windows 11 (and Windows 10 if necessary), it is recommended to pair this mod with Anixx's **[Restore the classic Personalization and other CPLs](https://windhawk.net/mods/restore-classic-cpls)** that re-enables some of the classic applets from older Windows versions. Some other suggested mods are: + +- **[Windows 7/8.1 Action Center Recreation](https://windhawk.net/mods/win7-action-center-recreation)** – recreates the classic Windows 7/8.1 Action Center tray icon and flyout with real-time security status monitoring. +- **[Classic Taskbar and Start Menu Properties](https://windhawk.net/mods/classic-taskbar-properties)** – recreates the classic Windows 7 "Taskbar and Start Menu Properties" dialog for Windows 10 and 11. +- **[Windows 7 Network Flyout Recreation](https://windhawk.net/mods/win7-network-flyout-recreation)** – recreates the classic Windows 7 network flyout with Wi-Fi list, signal strength, and connection support. + +All of these mods are **reversible** and help make Windows 10 and 11 look more like Windows 7 and classic versions of Windows, without replacing system files. --- @@ -103,6 +112,9 @@ Panel pages, using only native Windows components. #include #include #include +#ifdef _MSC_VER +#include +#endif // Manually defined GUIDs to avoid requiring -luuid / static ole32 linkage. // {45BA127D-10A8-46EA-8AB7-56EA9078943C} = CLSID_ApplicationActivationManager @@ -122,19 +134,24 @@ static DWORD g_trayContextTick = 0; static std::mutex g_trayContextMutex; static constexpr DWORD TRAY_CONTEXT_MAX_AGE_MS = 1500; -using ICMH_CAODTM_t = bool(__fastcall*)(HMENU, HWND); +#ifdef _WIN64 +#define ICMH_CALL __cdecl +#else +#define ICMH_CALL __stdcall +#endif + +using ICMH_CAODTM_t = bool(ICMH_CALL*)(HMENU, HWND); static ICMH_CAODTM_t g_icmhOrig_SndVolSSO = nullptr; static ICMH_CAODTM_t g_icmhOrig_pnidui = nullptr; static ICMH_CAODTM_t g_icmhOrig_Shell32Devices = nullptr; static bool g_pniduiHookInstalled = false; -static bool g_legacyNameHookInstalled = false; static std::mutex g_pniduiHookMutex; static HANDLE g_pniduiRetryThread = nullptr; static HANDLE g_traySubclassWatchdogThread = nullptr; static HWND g_lastShellTrayWnd = nullptr; static HANDLE g_stopEvent = nullptr; -static bool __fastcall ICMH_CAODTM_hook(HMENU, HWND); +static bool ICMH_CALL ICMH_CAODTM_hook(HMENU, HWND); // Constants #define PERS_ROOT L"explorer shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921}" @@ -234,8 +251,8 @@ struct ModSettings { static ModSettings g_settings; -static bool __fastcall ICMH_CAODTM_hook(HMENU, HWND) { - if (!g_settings.redirectSystemTray) return true; +static bool ICMH_CALL ICMH_CAODTM_hook(HMENU, HWND) { + if (!g_settings.enableRedirects || !g_settings.redirectSystemTray) return true; return false; } @@ -384,12 +401,17 @@ static bool IsClsidLoopOnWin11(const std::wstring& lowerTarget) { } static HWND g_hTrayToolbar = nullptr; +static std::mutex g_traySubclassMutex; +static std::mutex g_trayDllInfoMutex; +static std::mutex g_shellTrayWndMutex; static BYTE* g_sndVolSSOBase = nullptr; static BYTE* g_sndVolSSOEnd = nullptr; static BYTE* g_pniduiBase = nullptr; static BYTE* g_pniduiEnd = nullptr; static bool InitTrayDllInfo() { + std::lock_guard lk(g_trayDllInfoMutex); + if (!g_sndVolSSOBase) { HMODULE hSndVol = GetModuleHandleW(L"SndVolSSO.dll"); if (hSndVol) { @@ -414,7 +436,6 @@ static bool InitTrayDllInfo() { return (g_sndVolSSOBase != nullptr || g_pniduiBase != nullptr); } - static int GetTrayButtonType(HWND hToolbar, int buttonIndex) { if (buttonIndex < 0) return 0; InitTrayDllInfo(); @@ -446,14 +467,25 @@ static int GetTrayButtonType(HWND hToolbar, int buttonIndex) { hexPart++; } - if (g_sndVolSSOBase && addr >= (ULONG_PTR)g_sndVolSSOBase && addr < (ULONG_PTR)g_sndVolSSOEnd) + BYTE* sndVolBase = nullptr; + BYTE* sndVolEnd = nullptr; + BYTE* pniduiBase = nullptr; + BYTE* pniduiEnd = nullptr; + { + std::lock_guard lk(g_trayDllInfoMutex); + sndVolBase = g_sndVolSSOBase; + sndVolEnd = g_sndVolSSOEnd; + pniduiBase = g_pniduiBase; + pniduiEnd = g_pniduiEnd; + } + + if (sndVolBase && addr >= (ULONG_PTR)sndVolBase && addr < (ULONG_PTR)sndVolEnd) return 1; // Audio - if (g_pniduiBase && addr >= (ULONG_PTR)g_pniduiBase && addr < (ULONG_PTR)g_pniduiEnd) + if (pniduiBase && addr >= (ULONG_PTR)pniduiBase && addr < (ULONG_PTR)pniduiEnd) return 2; // Network return 0; } - static void OpenClassicSoundPanel() { SHELLEXECUTEINFOW sei = {}; sei.cbSize = sizeof(sei); @@ -530,7 +562,11 @@ static HWND FindTrayToolbar() { } static void SetupTraySubclass() { - if (g_hTrayToolbar) return; + std::lock_guard lk(g_traySubclassMutex); + + if (g_hTrayToolbar && IsWindow(g_hTrayToolbar)) return; + g_hTrayToolbar = nullptr; + HWND hToolbar = FindTrayToolbar(); if (!hToolbar) return; if (!InitTrayDllInfo()) return; @@ -540,19 +576,13 @@ static void SetupTraySubclass() { } static void RemoveTraySubclass() { + std::lock_guard lk(g_traySubclassMutex); + if (g_hTrayToolbar) { WindhawkUtils::RemoveWindowSubclassFromAnyThread(g_hTrayToolbar, TrayToolbarSubclassProc); g_hTrayToolbar = nullptr; } } - -static void* GetReturnAddress() { - void* stackTrace[3]; - WORD frames = CaptureStackBackTrace(0, 3, stackTrace, NULL); - if (frames >= 3) return stackTrace[2]; - return nullptr; -} - static bool IsAddressInModule(void* address, const wchar_t* moduleName) { HMODULE hModule = nullptr; if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCWSTR)address, &hModule)) { @@ -564,6 +594,7 @@ static bool IsAddressInModule(void* address, const wchar_t* moduleName) { static BOOL WINAPI CommonTrackPopupMenuEx_Hook( HMENU hMenu, UINT uFlags, int x, int y, HWND hWnd, const TPMPARAMS* lptpm, + void* callerRetAddr, BOOL (WINAPI* pOrig)(HMENU, UINT, int, int, HWND, const TPMPARAMS*), const wchar_t* logPrefix) { @@ -593,7 +624,7 @@ static BOOL WINAPI CommonTrackPopupMenuEx_Hook( // --- Fallback: DLL return-address detection --- if (!isAudioMenu && !isNetworkMenu) { - void* retAddr = GetReturnAddress(); + void* retAddr = callerRetAddr; int itemCount = GetMenuItemCount(hMenu); if (itemCount > 0) { if (IsAddressInModule(retAddr, L"SndVolSSO.dll")) { @@ -679,7 +710,15 @@ static BOOL WINAPI CommonTrackPopupMenuEx_Hook( } BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND hWnd, const TPMPARAMS* lptpm) { - return CommonTrackPopupMenuEx_Hook(hMenu, uFlags, x, y, hWnd, lptpm, g_origTrackPopupMenuEx, L"TRAY-HOOK"); + // Capture the real caller before entering the shared implementation. + // Using _ReturnAddress here avoids depending on stack depth after this + // wrapper was introduced, which is especially important on 32-bit builds. +#ifdef _MSC_VER + void* callerRetAddr = _ReturnAddress(); +#else + void* callerRetAddr = __builtin_return_address(0); +#endif + return CommonTrackPopupMenuEx_Hook(hMenu, uFlags, x, y, hWnd, lptpm, callerRetAddr, g_origTrackPopupMenuEx, L"TRAY-HOOK"); } static std::unordered_map g_mappings; @@ -1111,6 +1150,13 @@ HRESULT STDMETHODCALLTYPE AAM_ActivateApplication_hook( DWORD options, DWORD* processId) { + if (!g_settings.enableRedirects || !g_settings.comActivationRedirect) { + if (g_origActivateApplication) { + return g_origActivateApplication(pThis, appUserModelId, arguments, options, processId); + } + return E_FAIL; + } + Wh_Log(L"[AAM-HOOK] ActivateApplication: appId=%s, args=%s", appUserModelId ? appUserModelId : L"(null)", arguments ? arguments : L"(null)"); @@ -1123,10 +1169,14 @@ HRESULT STDMETHODCALLTYPE AAM_ActivateApplication_hook( Wh_Log(L"[AAM-HOOK] Settings activation intercepted: %s", uri.c_str()); auto result = ResolveUri(uri, nullptr); - if (result.intercept && !result.target.empty()) { - LaunchTarget(result.target); + if (result.intercept) { + if (!result.target.empty()) { + LaunchTarget(result.target); + Wh_Log(L"[AAM-HOOK] Redirected to: %s", result.target.c_str()); + } else { + Wh_Log(L"[AAM-HOOK] Activation handled by fallback mode"); + } if (processId) *processId = GetCurrentProcessId(); - Wh_Log(L"[AAM-HOOK] Redirected to: %s", result.target.c_str()); return S_OK; } Wh_Log(L"[AAM-HOOK] No mapping found, falling back to original"); @@ -1170,6 +1220,33 @@ static void InstallAAMHook() { } bool (*COpenControlPanel__MapLegacyName_orig)(void*, LPCWSTR, LPWSTR, UINT, bool*); + +static bool ShouldSuppressLegacyNameMapping(LPCWSTR pszLegacyName) { + if (!pszLegacyName || !*pszLegacyName) return false; + + std::wstring name = ToLower(pszLegacyName); + + // Keep the fix narrowly scoped. _MapLegacyName is a process-wide shell32 + // internal used by Control Panel name resolution, so suppressing every + // mapping can affect unrelated Control Panel navigation. Only suppress + // the legacy names that this mod can launch directly and that are known + // to be susceptible to Settings remapping/blank-page behavior. + static const std::unordered_set kNames = { + L"system", + L"microsoft.system", + L"sound", + L"microsoft.sound", + L"backupandrestore", + L"microsoft.backupandrestore", + L"networkandsharingcenter", + L"microsoft.networkandsharingcenter", + L"personalization", + L"microsoft.personalization", + }; + + return kNames.count(name) != 0; +} + bool COpenControlPanel__MapLegacyName_hook( void *pThis, LPCWSTR pszLegacyName, @@ -1177,15 +1254,25 @@ bool COpenControlPanel__MapLegacyName_hook( UINT uLen, bool *nameChanged) { - // Always tell the caller the name was NOT changed — this forces - // Explorer to use the original legacy Control Panel path. - *nameChanged = false; - *pszNewName = L'\0'; + if (!g_settings.legacyNameMappingFix || + !ShouldSuppressLegacyNameMapping(pszLegacyName)) + { + if (COpenControlPanel__MapLegacyName_orig) { + return COpenControlPanel__MapLegacyName_orig( + pThis, pszLegacyName, pszNewName, uLen, nameChanged); + } + return false; + } + + // Tell the caller the name was NOT changed — this forces Explorer to use + // the original legacy Control Panel path, but only for the whitelisted + // legacy names above. + if (nameChanged) *nameChanged = false; + if (pszNewName && uLen > 0) *pszNewName = L'\0'; Wh_Log(L"[MAP-LEGACY] Suppressed mapping for: %s", pszLegacyName ? pszLegacyName : L"(null)"); return false; } - static std::wstring BaseNameLower(const std::wstring& path) { size_t pos = path.rfind(L'\\'); return ToLower((pos != std::wstring::npos) ? path.substr(pos + 1) : path); @@ -1414,7 +1501,6 @@ static DWORD WINAPI PniduiRetryThread(LPVOID) { if (dllLoaded) { if (TryInstallPniduiHook()) { g_pniduiHookInstalled = true; - Wh_ApplyHookOperations(); } } @@ -1423,7 +1509,8 @@ static DWORD WINAPI PniduiRetryThread(LPVOID) { } static bool g_sndVolSSOHookInstalled = false; -static bool g_shell32DevicesHookInstalled = false; +static bool g_shell32HooksInstalled = false; +static std::mutex g_shell32HookMutex; static void InstallImmersiveMenuHooks() { if (!g_sndVolSSOHookInstalled) { @@ -1462,59 +1549,60 @@ static void InstallImmersiveMenuHooks() { } } -// Combines all shell32.dll hooks into a single HookSymbols call per the -// Windhawk API best practice. Called when either the Win11 devices hook -// or the legacy-name-mapping hook is needed. +// Combines all shell32.dll hooks into a single stable HookSymbols call per the +// Windhawk API best practice. The hook list is intentionally not conditional +// on settings or OS version, to keep Windhawk symbol caching valid. static void InstallShell32Hooks() { - bool needDevices = g_isWin11 && !g_shell32DevicesHookInstalled; - bool needLegacy = g_settings.legacyNameMappingFix && !g_legacyNameHookInstalled; - if (!needDevices && !needLegacy) return; + std::lock_guard lk(g_shell32HookMutex); + if (g_shell32HooksInstalled) return; HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); if (!hShell32) return; - WindhawkUtils::SYMBOL_HOOK shell32_dll_hooks[2]; - int hookCount = 0; - - if (needDevices) { - shell32_dll_hooks[hookCount] = {{ - L"bool " + // Register every shell32 symbol hook that this mod might ever need in one + // stable HookSymbols call. Don't build the array conditionally based on + // settings/OS version: changing the symbol list breaks Windhawk's symbol + // cache. Runtime decisions are made inside the hook bodies instead. + WindhawkUtils::SYMBOL_HOOK shell32_dll_hooks[] = { + { + { + L"bool " #ifdef _WIN64 - L"__cdecl" + L"__cdecl" #else - L"__stdcall" + L"__stdcall" #endif - L" CDevicesAndPrintersFolder::_HandleContextMenu" - L"(struct HMENU__ *,unsigned int)" + L" CDevicesAndPrintersFolder::_HandleContextMenu" + L"(struct HMENU__ *,unsigned int)" + }, + (void**)&g_icmhOrig_Shell32Devices, + (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, + true }, - (void**)&g_icmhOrig_Shell32Devices, - (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, - false}; - hookCount++; - } - - if (needLegacy) { - shell32_dll_hooks[hookCount] = {{ - L"private: bool __cdecl COpenControlPanel::_MapLegacyName" - L"(unsigned short const *,unsigned short *,unsigned int,bool *)" + { + { + L"private: bool __cdecl COpenControlPanel::_MapLegacyName" + L"(unsigned short const *,unsigned short *,unsigned int,bool *)" + }, + (void**)&COpenControlPanel__MapLegacyName_orig, + (void*)COpenControlPanel__MapLegacyName_hook, + true }, - (void**)&COpenControlPanel__MapLegacyName_orig, - (void*)COpenControlPanel__MapLegacyName_hook, - false}; - hookCount++; - } + }; - if (hookCount > 0) { - if (WindhawkUtils::HookSymbols(hShell32, shell32_dll_hooks, hookCount)) { - if (needDevices) g_shell32DevicesHookInstalled = true; - if (needLegacy) g_legacyNameHookInstalled = true; - Wh_Log(L"[SHELL32-HOOKS] Installed %d hook(s)", hookCount); - } + if (WindhawkUtils::HookSymbols( + hShell32, + shell32_dll_hooks, + ARRAYSIZE(shell32_dll_hooks))) + { + g_shell32HooksInstalled = true; + Wh_Log(L"[SHELL32-HOOKS] Installed shell32 hook set"); } } - static bool HasTrayBeenRecreated() { HWND hTray = FindWindowW(L"Shell_TrayWnd", nullptr); + std::lock_guard lk(g_shellTrayWndMutex); + if (!hTray) { g_lastShellTrayWnd = nullptr; return false; @@ -1522,7 +1610,7 @@ static bool HasTrayBeenRecreated() { if (g_lastShellTrayWnd == nullptr) { g_lastShellTrayWnd = hTray; - return true; + return true; } if (hTray != g_lastShellTrayWnd) { @@ -1532,14 +1620,16 @@ static bool HasTrayBeenRecreated() { return false; } - static void ReinitializeTrayRedirect() { RemoveTraySubclass(); - g_sndVolSSOBase = nullptr; - g_sndVolSSOEnd = nullptr; - g_pniduiBase = nullptr; - g_pniduiEnd = nullptr; + { + std::lock_guard lk(g_trayDllInfoMutex); + g_sndVolSSOBase = nullptr; + g_sndVolSSOEnd = nullptr; + g_pniduiBase = nullptr; + g_pniduiEnd = nullptr; + } if (g_settings.redirectSystemTray) { SetupTraySubclass(); @@ -1547,7 +1637,6 @@ static void ReinitializeTrayRedirect() { InstallImmersiveMenuHooks(); InstallShell32Hooks(); - Wh_ApplyHookOperations(); } static void PerformBackgroundInit(bool skipSleep = false) { @@ -1558,11 +1647,10 @@ static void PerformBackgroundInit(bool skipSleep = false) { InstallImmersiveMenuHooks(); InstallShell32Hooks(); - if (g_settings.comActivationRedirect && g_isWin11) { + if (g_isWin11) { InstallAAMHook(); } - Wh_ApplyHookOperations(); } static DWORD WINAPI TraySubclassWatchdogThread(LPVOID) { @@ -1586,11 +1674,16 @@ static DWORD WINAPI TraySubclassWatchdogThread(LPVOID) { if (!g_settings.redirectSystemTray) continue; - if (g_hTrayToolbar && !IsWindow(g_hTrayToolbar)) { - g_hTrayToolbar = nullptr; + bool needSetup = false; + { + std::lock_guard lk(g_traySubclassMutex); + if (g_hTrayToolbar && !IsWindow(g_hTrayToolbar)) { + g_hTrayToolbar = nullptr; + } + needSetup = (g_hTrayToolbar == nullptr); } - if (!g_hTrayToolbar) { + if (needSetup) { SetupTraySubclass(); } } @@ -1609,7 +1702,13 @@ HWND WINAPI CreateWindowExW_Hook( dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam); - if (g_settings.redirectSystemTray && hwnd && !g_hTrayToolbar && + bool trayToolbarMissing = false; + { + std::lock_guard lk(g_traySubclassMutex); + trayToolbarMissing = (g_hTrayToolbar == nullptr); + } + + if (g_settings.redirectSystemTray && hwnd && trayToolbarMissing && lpClassName && !IS_INTRESOURCE(lpClassName) && lpClassName[0] == L'T') { @@ -1662,7 +1761,15 @@ BOOL Wh_ModInit() { } } - g_lastShellTrayWnd = nullptr; + // Queue all symbol/vtable hooks that might be needed before Wh_ModInit + // returns, so Windhawk can apply them as part of normal initialization + // without explicit Wh_ApplyHookOperations calls later. + PerformBackgroundInit(true); + + { + std::lock_guard lk(g_shellTrayWndMutex); + g_lastShellTrayWnd = nullptr; + } g_traySubclassWatchdogThread = CreateThread(nullptr, 0, TraySubclassWatchdogThread, nullptr, 0, nullptr); } From 583a7ccb6e64098bd4b2a3a3fdb9434008b6b795 Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Thu, 16 Jul 2026 08:49:31 +0200 Subject: [PATCH 23/25] Fix the issues according to the suggestions --- mods/settings-to-control-panel.wh.cpp | 132 +++++++++++--------------- 1 file changed, 57 insertions(+), 75 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index 8a3f5d77c3..127660744c 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -2,7 +2,7 @@ // @id settings-to-control-panel // @name Redirect Settings to Control Panel // @description This mod forces the classic Control Panel to open instead of Windows 10/11 Settings app using native components. -// @version 10.0.33 +// @version 10.0.35 // @author babamohammed // @github https://github.com/babamohammed2022 // @include explorer.exe @@ -51,11 +51,11 @@ Panel pages, using only native Windows components. **Recommendation**: For a better experience on Windows 11 (and Windows 10 if necessary), it is recommended to pair this mod with Anixx's **[Restore the classic Personalization and other CPLs](https://windhawk.net/mods/restore-classic-cpls)** that re-enables some of the classic applets from older Windows versions. Some other suggested mods are: -- **[Windows 7/8.1 Action Center Recreation](https://windhawk.net/mods/win7-action-center-recreation)** – recreates the classic Windows 7/8.1 Action Center tray icon and flyout with real-time security status monitoring. +- **[Windows 7/8.1 Action Center Recreation](https://windhawk.net/mods/win7-action-center-recreation)** – recreates the classic Windows 7/8.1 Action Center tray icon and flyout with real-time security status monitoring along with a partial restore of a link inside the Action Center Control Panel page. - **[Classic Taskbar and Start Menu Properties](https://windhawk.net/mods/classic-taskbar-properties)** – recreates the classic Windows 7 "Taskbar and Start Menu Properties" dialog for Windows 10 and 11. -- **[Windows 7 Network Flyout Recreation](https://windhawk.net/mods/win7-network-flyout-recreation)** – recreates the classic Windows 7 network flyout with Wi-Fi list, signal strength, and connection support. +- **[Windows 7 Network Flyout Recreation](https://windhawk.net/mods/win7-network-flyout-recreation)** – recreates the classic Windows 7 network flyout with Wi-Fi list, signal strength, and connection support and, if enabled, partial restore of some links inside the classic "Network and Sharing Center" Contro Panel page. -All of these mods are **reversible** and help make Windows 10 and 11 look more like Windows 7 and classic versions of Windows, without replacing system files. +All of these mods are **reversible** and help make Windows 10 and 11 look more like Windows 7 and classic versions of Windows without replacing system files. --- @@ -71,32 +71,32 @@ All of these mods are **reversible** and help make Windows 10 and 11 look more l /* - EnableRedirects: true $name: Enable Redirects - $description: "Turns the mod on or off. When disabled, Settings opens normally as usual." + $description: "This setting turns the mod on or off. When disabled, Settings opens normally as usual." - RedirectSystemTray: false $name: Redirect System Tray Audio/Network/Device & Printers (EXPERIMENTAL) - $description: "If enabled, right-clicking the Audio or Network or Device & Printers icon near the clock and choosing 'Open Sound settings' or 'Open Network settings' or 'Open devices and printers' should open the classic panel instead of the Settings app. Note: In some builds after explorer's restart the network redirect might not work." + $description: "If this setting is enabled, right-clicking the Audio, Network, or Devices & Printers icon near the clock and choosing 'Open Sound settings', 'Open Network settings', or 'Open devices and printers' will open the classic Control Panel instead of the Settings app. It is primarly recommended on Windows 10. Note: the network redirect may stop working after Explorer restarts on certain builds." - UIOnlyRedirects: false $name: Non-Invasive Mode - $description: "Only redirects clicks made in the UI. Doesn't touch programs that open Settings in other ways." + $description: "This setting changes the behavior of the mod by only redirecting Settings links clicked in the UI. Programs and background processes that open Settings directly are not affected. It is recommended on Windows 11 for safety. On Windows 10, leaving this off gives better coverage as it has more parts of the Control Panel compared to the successor." - FallbackMode: "2" $name: Behavior for Unmapped Links - $description: "What to do when a Settings page has no classic equivalent." + $description: "This setting changes the fallback method (what to do when a Settings page has no classic Control Panel equivalent). It is recommended to put 'Pass through' on both Windows 10 and 11, so unmapped pages still open normally instead of silently failing." $options: - "0": Ignore (silent fail) - "1": Open the Control Panel (control.exe) - - "2": Pass through to the modern Settings application (ms-settings.exe)" + - "2": Pass through to the modern Settings application (ms-settings.exe) - Win11CompatibilityMode: false $name: Windows 11 Compatibility Mode - $description: "This is a safer mode for Windows 11. When enabled, only uses proven redirects while everything else opens the standard Control Panel page as a fallback to avoid loops or other issues." + $description: "This is a safer mode for Windows 11. When enabled, only redirects pages that are known to work correctly, and opens the standard Control Panel as a fallback for everything else. Helps avoid redirect loops and blank pages. Recommended on Windows 11. Not needed on Windows 10." - MaxLaunchesPerUri: 3 $name: Anti-Loop Limit (per window, every 5 seconds) - $description: "Safety measure: if the same window gets opened too many times within a few seconds, the mod stops reopening it. Set to 0 to disable this limit." + $description: "This is a safety measure: if the same window gets opened too many times within a few seconds, the mod stops reopening it. Do not set this to 0 — without this limit, a redirect loop can open windows endlessly and freeze Explorer." - ComActivationRedirect: true $name: COM-activation Redirect (EXPERIMENTAL) - $description: "This setting redirects modern Settings calls made through the COM interface to ensure they open correctly. This addresses compatibility issues with certain Windows 11 builds where Settings may fail to launch." + $description: "This setting intercepts Settings launches that happen through the COM interface rather than the normal shell. Recommended on Windows 10. On Windows 11, this affects all app launches process-wide, so only enable it if you have a specific issue it fixes (such as tray icons opening Settings instead of Control Panel on certain builds)." - LegacyNameMappingFix: true $name: Fix Legacy Name Mapping - $description: "This setting attempts to correct a mapping error that causes legacy Control Panel items to display as blank pages or automatically redirect to the modern Settings app. This should ensure that the classic system tools open and function as intended." + $description: "This option fixes a shell issue where certain classic Control Panel pages show up blank or silently redirect to the modern Settings app. Recommended on both Windows 10 and 11." */ // ==/WindhawkModSettings== @@ -141,17 +141,21 @@ static constexpr DWORD TRAY_CONTEXT_MAX_AGE_MS = 1500; #endif using ICMH_CAODTM_t = bool(ICMH_CALL*)(HMENU, HWND); +// CDevicesAndPrintersFolder::_HandleContextMenu has a different second parameter +// (unsigned int, not HWND), so it gets its own correctly-typed function pointer type. +using ICMH_HCM_t = bool(ICMH_CALL*)(HMENU, UINT); static ICMH_CAODTM_t g_icmhOrig_SndVolSSO = nullptr; static ICMH_CAODTM_t g_icmhOrig_pnidui = nullptr; -static ICMH_CAODTM_t g_icmhOrig_Shell32Devices = nullptr; +static ICMH_HCM_t g_icmhOrig_Shell32Devices = nullptr; static bool g_pniduiHookInstalled = false; static std::mutex g_pniduiHookMutex; -static HANDLE g_pniduiRetryThread = nullptr; static HANDLE g_traySubclassWatchdogThread = nullptr; static HWND g_lastShellTrayWnd = nullptr; static HANDLE g_stopEvent = nullptr; -static bool ICMH_CALL ICMH_CAODTM_hook(HMENU, HWND); +static bool ICMH_CALL ICMH_hook_SndVolSSO(HMENU m, HWND w); +static bool ICMH_CALL ICMH_hook_pnidui(HMENU m, HWND w); +static bool ICMH_CALL ICMH_hook_Shell32Devices(HMENU m, UINT u); // Constants #define PERS_ROOT L"explorer shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921}" @@ -251,8 +255,21 @@ struct ModSettings { static ModSettings g_settings; -static bool ICMH_CALL ICMH_CAODTM_hook(HMENU, HWND) { - if (!g_settings.enableRedirects || !g_settings.redirectSystemTray) return true; +static bool ICMH_CALL ICMH_hook_SndVolSSO(HMENU m, HWND w) { + if (!g_settings.enableRedirects || !g_settings.redirectSystemTray) + return g_icmhOrig_SndVolSSO ? g_icmhOrig_SndVolSSO(m, w) : true; + return false; +} + +static bool ICMH_CALL ICMH_hook_pnidui(HMENU m, HWND w) { + if (!g_settings.enableRedirects || !g_settings.redirectSystemTray) + return g_icmhOrig_pnidui ? g_icmhOrig_pnidui(m, w) : true; + return false; +} + +static bool ICMH_CALL ICMH_hook_Shell32Devices(HMENU m, UINT u) { + if (!g_settings.enableRedirects || !g_settings.redirectSystemTray) + return g_icmhOrig_Shell32Devices ? g_icmhOrig_Shell32Devices(m, u) : true; return false; } @@ -562,26 +579,29 @@ static HWND FindTrayToolbar() { } static void SetupTraySubclass() { - std::lock_guard lk(g_traySubclassMutex); - - if (g_hTrayToolbar && IsWindow(g_hTrayToolbar)) return; - g_hTrayToolbar = nullptr; - - HWND hToolbar = FindTrayToolbar(); - if (!hToolbar) return; - if (!InitTrayDllInfo()) return; - if (WindhawkUtils::SetWindowSubclassFromAnyThread(hToolbar, TrayToolbarSubclassProc, 0)) { + HWND hToolbar; + { + std::lock_guard lk(g_traySubclassMutex); + if (g_hTrayToolbar && IsWindow(g_hTrayToolbar)) return; + g_hTrayToolbar = nullptr; + hToolbar = FindTrayToolbar(); + } + if (!hToolbar || !InitTrayDllInfo()) return; + BOOL ok = WindhawkUtils::SetWindowSubclassFromAnyThread(hToolbar, TrayToolbarSubclassProc, 0); + if (ok) { + std::lock_guard lk(g_traySubclassMutex); g_hTrayToolbar = hToolbar; } } static void RemoveTraySubclass() { - std::lock_guard lk(g_traySubclassMutex); - - if (g_hTrayToolbar) { - WindhawkUtils::RemoveWindowSubclassFromAnyThread(g_hTrayToolbar, TrayToolbarSubclassProc); + HWND h; + { + std::lock_guard lk(g_traySubclassMutex); + h = g_hTrayToolbar; g_hTrayToolbar = nullptr; } + if (h) WindhawkUtils::RemoveWindowSubclassFromAnyThread(h, TrayToolbarSubclassProc); } static bool IsAddressInModule(void* address, const wchar_t* moduleName) { HMODULE hModule = nullptr; @@ -1440,8 +1460,6 @@ BOOL WINAPI CreateProcessW_hook(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); } -static volatile bool g_pniduiRetryRunning = false; - static bool TryInstallPniduiHook() { std::lock_guard lk(g_pniduiHookMutex); @@ -1469,7 +1487,7 @@ static bool TryInstallPniduiHook() { L"(struct HMENU__ *,struct HWND__ *)" }, (void**)&g_icmhOrig_pnidui, - (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, + (void*)(ICMH_CAODTM_t)ICMH_hook_pnidui, false }}; @@ -1480,34 +1498,6 @@ static bool TryInstallPniduiHook() { return result; } -static DWORD WINAPI PniduiRetryThread(LPVOID) { - if (g_pniduiRetryRunning) { - return 0; - } - g_pniduiRetryRunning = true; - - const int MAX_WAIT_CHECKS = 60; - const DWORD CHECK_INTERVAL = 500; - bool dllLoaded = false; - - for (int i = 0; i < MAX_WAIT_CHECKS; i++) { - if (WaitForSingleObject(g_stopEvent, CHECK_INTERVAL) == WAIT_OBJECT_0) break; - if (GetModuleHandleW(L"pnidui.dll") != nullptr) { - dllLoaded = true; - break; - } - } - - if (dllLoaded) { - if (TryInstallPniduiHook()) { - g_pniduiHookInstalled = true; - } - } - - g_pniduiRetryRunning = false; - return 0; -} - static bool g_sndVolSSOHookInstalled = false; static bool g_shell32HooksInstalled = false; static std::mutex g_shell32HookMutex; @@ -1530,7 +1520,7 @@ static void InstallImmersiveMenuHooks() { L"(struct HMENU__ *,struct HWND__ *)" }, (void**)&g_icmhOrig_SndVolSSO, - (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, + (void*)(ICMH_CAODTM_t)ICMH_hook_SndVolSSO, false }}; @@ -1541,11 +1531,7 @@ static void InstallImmersiveMenuHooks() { } if (!g_pniduiHookInstalled) { - if (!TryInstallPniduiHook()) { - if (!g_pniduiRetryRunning && !g_pniduiRetryThread) { - g_pniduiRetryThread = CreateThread(nullptr, 0, PniduiRetryThread, nullptr, 0, nullptr); - } - } + TryInstallPniduiHook(); } } @@ -1576,7 +1562,7 @@ static void InstallShell32Hooks() { L"(struct HMENU__ *,unsigned int)" }, (void**)&g_icmhOrig_Shell32Devices, - (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook, + (void*)(ICMH_HCM_t)ICMH_hook_Shell32Devices, true }, { @@ -1786,12 +1772,6 @@ void Wh_ModUninit() { CloseHandle(g_traySubclassWatchdogThread); g_traySubclassWatchdogThread = nullptr; } - if (g_pniduiRetryThread) { - WaitForSingleObject(g_pniduiRetryThread, 3000); - CloseHandle(g_pniduiRetryThread); - g_pniduiRetryThread = nullptr; - g_pniduiRetryRunning = false; - } if (g_stopEvent) { CloseHandle(g_stopEvent); @@ -1808,6 +1788,8 @@ void Wh_ModSettingsChanged() { if (IsShellProcess()) { if (g_settings.redirectSystemTray) { SetupTraySubclass(); + } else { + RemoveTraySubclass(); } PerformBackgroundInit(true); } From 7c358a1b252d416db6567c3a93952ade803755f9 Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Thu, 16 Jul 2026 22:41:02 +0200 Subject: [PATCH 24/25] Address the issues and remove support for 32 bit operating systems as they are rarely used --- mods/settings-to-control-panel.wh.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index 127660744c..e9a6cfbd34 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -6,6 +6,7 @@ // @author babamohammed // @github https://github.com/babamohammed2022 // @include explorer.exe +// @architecture x86-64 // @compilerOptions -lcomctl32 -lpsapi -lole32 // ==/WindhawkMod== // ==WindhawkModReadme== @@ -45,7 +46,7 @@ Panel pages, using only native Windows components. - The system tray context menu redirect only supports the Win32 taskbar (the one from Windows 10). However, in some Windows 11 configurations if explorer is restarted the network system tray redirect might not work. - The device & printers system tray redirect may not work on some Windows 11 configurations, as Microsoft hardcoded the redirect to the Settings app in certain shell code paths. This could change in future if correct documentation is found. - +- The mod is not compatible with 32 bit based operating systems. It requires a 64-bit version of Windows (x64 or ARM64). --- @@ -143,7 +144,7 @@ static constexpr DWORD TRAY_CONTEXT_MAX_AGE_MS = 1500; using ICMH_CAODTM_t = bool(ICMH_CALL*)(HMENU, HWND); // CDevicesAndPrintersFolder::_HandleContextMenu has a different second parameter // (unsigned int, not HWND), so it gets its own correctly-typed function pointer type. -using ICMH_HCM_t = bool(ICMH_CALL*)(HMENU, UINT); +using ICMH_HCM_t = bool(ICMH_CALL*)(void* /*pThis*/, HMENU, UINT); static ICMH_CAODTM_t g_icmhOrig_SndVolSSO = nullptr; static ICMH_CAODTM_t g_icmhOrig_pnidui = nullptr; static ICMH_HCM_t g_icmhOrig_Shell32Devices = nullptr; @@ -155,7 +156,7 @@ static HANDLE g_stopEvent = nullptr; static bool ICMH_CALL ICMH_hook_SndVolSSO(HMENU m, HWND w); static bool ICMH_CALL ICMH_hook_pnidui(HMENU m, HWND w); -static bool ICMH_CALL ICMH_hook_Shell32Devices(HMENU m, UINT u); +static bool ICMH_CALL ICMH_hook_Shell32Devices(void* pThis, HMENU m, UINT u); // Constants #define PERS_ROOT L"explorer shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921}" @@ -267,9 +268,9 @@ static bool ICMH_CALL ICMH_hook_pnidui(HMENU m, HWND w) { return false; } -static bool ICMH_CALL ICMH_hook_Shell32Devices(HMENU m, UINT u) { +static bool ICMH_CALL ICMH_hook_Shell32Devices(void* pThis, HMENU m, UINT u) { if (!g_settings.enableRedirects || !g_settings.redirectSystemTray) - return g_icmhOrig_Shell32Devices ? g_icmhOrig_Shell32Devices(m, u) : true; + return g_icmhOrig_Shell32Devices ? g_icmhOrig_Shell32Devices(pThis, m, u) : true; return false; } From c1281960945310a9adf2b3c3c2989e1a20566472 Mon Sep 17 00:00:00 2001 From: babamohammed2022 Date: Fri, 17 Jul 2026 08:22:09 +0200 Subject: [PATCH 25/25] Address the issues --- mods/settings-to-control-panel.wh.cpp | 51 ++++++--------------------- 1 file changed, 11 insertions(+), 40 deletions(-) diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index e9a6cfbd34..faf8ce3a10 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -54,7 +54,7 @@ Panel pages, using only native Windows components. - **[Windows 7/8.1 Action Center Recreation](https://windhawk.net/mods/win7-action-center-recreation)** – recreates the classic Windows 7/8.1 Action Center tray icon and flyout with real-time security status monitoring along with a partial restore of a link inside the Action Center Control Panel page. - **[Classic Taskbar and Start Menu Properties](https://windhawk.net/mods/classic-taskbar-properties)** – recreates the classic Windows 7 "Taskbar and Start Menu Properties" dialog for Windows 10 and 11. -- **[Windows 7 Network Flyout Recreation](https://windhawk.net/mods/win7-network-flyout-recreation)** – recreates the classic Windows 7 network flyout with Wi-Fi list, signal strength, and connection support and, if enabled, partial restore of some links inside the classic "Network and Sharing Center" Contro Panel page. +- **[Windows 7 Network Flyout Recreation](https://windhawk.net/mods/win7-network-flyout-recreation)** – recreates the classic Windows 7 network flyout with Wi-Fi list, signal strength, and connection support and, if enabled, partial restore of some links inside the classic "Network and Sharing Center" Control Panel page. All of these mods are **reversible** and help make Windows 10 and 11 look more like Windows 7 and classic versions of Windows without replacing system files. @@ -75,7 +75,7 @@ All of these mods are **reversible** and help make Windows 10 and 11 look more l $description: "This setting turns the mod on or off. When disabled, Settings opens normally as usual." - RedirectSystemTray: false $name: Redirect System Tray Audio/Network/Device & Printers (EXPERIMENTAL) - $description: "If this setting is enabled, right-clicking the Audio, Network, or Devices & Printers icon near the clock and choosing 'Open Sound settings', 'Open Network settings', or 'Open devices and printers' will open the classic Control Panel instead of the Settings app. It is primarly recommended on Windows 10. Note: the network redirect may stop working after Explorer restarts on certain builds." + $description: "If this setting is enabled, right-clicking the Audio, Network, or Devices & Printers icon near the clock and choosing 'Open Sound settings', 'Open Network settings', or 'Open devices and printers' will open the classic Control Panel instead of the Settings app. It is primarily recommended on Windows 10. Note: the network redirect may stop working after Explorer restarts on certain builds." - UIOnlyRedirects: false $name: Non-Invasive Mode $description: "This setting changes the behavior of the mod by only redirecting Settings links clicked in the UI. Programs and background processes that open Settings directly are not affected. It is recommended on Windows 11 for safety. On Windows 10, leaving this off gives better coverage as it has more parts of the Control Panel compared to the successor." @@ -92,9 +92,9 @@ All of these mods are **reversible** and help make Windows 10 and 11 look more l - MaxLaunchesPerUri: 3 $name: Anti-Loop Limit (per window, every 5 seconds) $description: "This is a safety measure: if the same window gets opened too many times within a few seconds, the mod stops reopening it. Do not set this to 0 — without this limit, a redirect loop can open windows endlessly and freeze Explorer." -- ComActivationRedirect: true +- ComActivationRedirect: false $name: COM-activation Redirect (EXPERIMENTAL) - $description: "This setting intercepts Settings launches that happen through the COM interface rather than the normal shell. Recommended on Windows 10. On Windows 11, this affects all app launches process-wide, so only enable it if you have a specific issue it fixes (such as tray icons opening Settings instead of Control Panel on certain builds)." + $description: "This setting intercepts Settings launches that happen through the COM interface rather than the normal shell. On Windows 11, this affects all app launches process-wide, so only enable it if you have a specific issue it fixes (such as tray icons opening Settings instead of Control Panel on certain builds). On Windows 10 this setting has no effect." - LegacyNameMappingFix: true $name: Fix Legacy Name Mapping $description: "This option fixes a shell issue where certain classic Control Panel pages show up blank or silently redirect to the modern Settings app. Recommended on both Windows 10 and 11." @@ -113,9 +113,6 @@ All of these mods are **reversible** and help make Windows 10 and 11 look more l #include #include #include -#ifdef _MSC_VER -#include -#endif // Manually defined GUIDs to avoid requiring -luuid / static ole32 linkage. // {45BA127D-10A8-46EA-8AB7-56EA9078943C} = CLSID_ApplicationActivationManager @@ -135,11 +132,8 @@ static DWORD g_trayContextTick = 0; static std::mutex g_trayContextMutex; static constexpr DWORD TRAY_CONTEXT_MAX_AGE_MS = 1500; -#ifdef _WIN64 +// x86-64 only (@architecture x86-64); _WIN64 is always defined. #define ICMH_CALL __cdecl -#else -#define ICMH_CALL __stdcall -#endif using ICMH_CAODTM_t = bool(ICMH_CALL*)(HMENU, HWND); // CDevicesAndPrintersFolder::_HandleContextMenu has a different second parameter @@ -250,7 +244,7 @@ struct ModSettings { int fallbackMode = 2; bool win11CompatibilityMode = false; int maxLaunchesPerUri = 3; - bool comActivationRedirect = true; + bool comActivationRedirect = false; bool legacyNameMappingFix = true; }; @@ -393,7 +387,7 @@ static const std::unordered_set g_win11SafeClsids = { L"shell:::{d450a8a1-9568-45c7-9c0e-b4f9fb4537bd}", L"shell:::{d555645e-d4f8-4c29-a827-d93c859c4f2a}", L"shell:::{d9ef8727-cac2-4e60-809e-86f80a666c91}", - L"shell:::{ecd0924-4208-451e-8ee0-373c0956de16}", + L"shell:::{ecdb0924-4208-451e-8ee0-373c0956de16}", L"shell:::{ed7ba470-8e54-465e-825c-99712043e01c}", L"shell:::{f02c1a0d-be21-4350-88b0-7367fc96ef3c}", }; @@ -732,13 +726,8 @@ static BOOL WINAPI CommonTrackPopupMenuEx_Hook( BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND hWnd, const TPMPARAMS* lptpm) { // Capture the real caller before entering the shared implementation. - // Using _ReturnAddress here avoids depending on stack depth after this - // wrapper was introduced, which is especially important on 32-bit builds. -#ifdef _MSC_VER - void* callerRetAddr = _ReturnAddress(); -#else + // Windhawk builds with Clang, so __builtin_return_address is always available. void* callerRetAddr = __builtin_return_address(0); -#endif return CommonTrackPopupMenuEx_Hook(hMenu, uFlags, x, y, hWnd, lptpm, callerRetAddr, g_origTrackPopupMenuEx, L"TRAY-HOOK"); } @@ -1478,13 +1467,7 @@ static bool TryInstallPniduiHook() { WindhawkUtils::SYMBOL_HOOK pnidui_dll_hooks[] = {{ { - L"bool " -#ifdef _WIN64 - L"__cdecl" -#else - L"__stdcall" -#endif - L" ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu" + L"bool __cdecl ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu" L"(struct HMENU__ *,struct HWND__ *)" }, (void**)&g_icmhOrig_pnidui, @@ -1511,13 +1494,7 @@ static void InstallImmersiveMenuHooks() { if (hMod) { WindhawkUtils::SYMBOL_HOOK sndVolSSO_dll_hooks[] = {{ { - L"bool " -#ifdef _WIN64 - L"__cdecl" -#else - L"__stdcall" -#endif - L" ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu" + L"bool __cdecl ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu" L"(struct HMENU__ *,struct HWND__ *)" }, (void**)&g_icmhOrig_SndVolSSO, @@ -1553,13 +1530,7 @@ static void InstallShell32Hooks() { WindhawkUtils::SYMBOL_HOOK shell32_dll_hooks[] = { { { - L"bool " -#ifdef _WIN64 - L"__cdecl" -#else - L"__stdcall" -#endif - L" CDevicesAndPrintersFolder::_HandleContextMenu" + L"bool __cdecl CDevicesAndPrintersFolder::_HandleContextMenu" L"(struct HMENU__ *,unsigned int)" }, (void**)&g_icmhOrig_Shell32Devices,