From e4be1bd47218f30ab33fecca01659453ab118628 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Wed, 26 Aug 2026 01:45:25 +0900 Subject: [PATCH 01/51] Windows 7 Blocked Shutdown UX & Logoff Sequence 1.0 --- mods/win7-bsdr.wh.cpp | 3577 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3577 insertions(+) create mode 100644 mods/win7-bsdr.wh.cpp diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp new file mode 100644 index 0000000000..7e8e7bbcfa --- /dev/null +++ b/mods/win7-bsdr.wh.cpp @@ -0,0 +1,3577 @@ +// ==WindhawkMod== +// @id win7-bsdr +// @name Windows 7 Blocked Shutdown UX & Logoff Sequence +// @description Bring back Windows 7's shutdown experience +// @version 1.0 +// @author Ingan121 +// @github https://github.com/Ingan121 +// @twitter https://twitter.com/Ingan121 +// @homepage https://www.ingan121.com/ +// @include LogonUI.exe +// @include winlogon.exe +// @architecture x86-64 +// @compilerOptions -lgdi32 -lmincore -lwindowscodecs -luxtheme -lmsimg32 -lcomctl32 -lshcore -luuid +// @license GPLv3 +// ==/WindhawkMod== + +// ==WindhawkModReadme== +/* +# Windows 7 Blocked Shutdown UX & Logoff Sequence +* This mod brings back the old blocked shutdown screen from Windows 7, which appears when you try to shut down or restart the computer while programs prevent shutdown. +* The blocked shutdown UX part is a direct port of my [AuthUX BSDR fork](https://github.com/Ingan121/AuthUX/tree/bsdr). + * This mod's UX part has no effect if AuthUX BSDR is installed. +* This mod also optionally restores Windows 7's logoff sequence, where the system would switch to a 'logging off' screen after closing all applications, unlike Windows 8 and newer. + * This part is compatible with AuthUX BSDR as well. + * It may not work on the first logoff after installing the mod because of additional safety checks. It should work from then on. +* Loading resources from an external DLL is supported for localization. This requires one of the following: + * `winsrv.dll` and `winsrv.dll.mui` from Windows 7. Put `winsrv.dll` in some folder, create a folder named after your locale (e.g. `en-US`), and put `winsrv.dll.mui` there. + * `AuthUX.dll` from a build of AuthUX that supports BSDR. + * Set the DLL path in the mod settings afterwards. You may also point this directly to `winsrv.dll.mui` instead of setting up the directory structure mentioned above. + * Warning: **never, ever replace `C:\Windows\System32\winsrv.dll` (or mui) with the Windows 7 version**; this will brick your Windows installation! + * This is not necessary. Hardcoded resources will be used instead if the path is not set or the file is missing. +## ⚠ Important usage note ⚠ + +In order to use this mod, you must allow Windhawk to inject into the **dwm.exe** +system process. To do so, add it to the process inclusion list in the advanced +settings. If you do not do this, it will silently fail to inject. + +![Advanced settings screenshot](https://i.imgur.com/LRhREtJ.png) +*/ +// ==/WindhawkModReadme== + +// ==WindhawkModSettings== +/* +- resDllPath: "" + $name: AuthUX.dll/winsrv.dll path + $name:ko-KR: AuthUX.dll/winsrv.dll 경로 + $description: Path to your copy of winsrv.dll from Windows 7 or AuthUX.dll build with BSDR. Hardcoded resources will be used instead if this is not set or missing. +- themeScrollbar: false + $name: Apply visual styles to the scroll bar + $name:ko-KR: 스크롤 막대에 시각 테마 적용 +- disableAsyncLogoff: true + $name: Restore Windows 7's logoff sequence + $name:ko-KR: Windows 7의 로그오프 절차를 복원합니다. +*/ +// ==/WindhawkModSettings== + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +#define WM_ADD_APP (WM_USER + 1) +#define WM_REMOVE_APP (WM_USER + 2) + +#define BSDR_CLASSNAME L"BlockedShutdownResolver_WH" + +#define RETURN_IF_FAILED(x) do { \ + HRESULT hr = (x); \ + if (FAILED(hr)) return hr; \ +} while(0) + +#pragma region resources +// Resource IDs +#define IDD_BSDR_DLG 20 +#define IDC_BSDR_TITLE 261 +#define IDC_BSDR_SEPARATOR_TOP 269 +#define IDC_BSDR_APPLIST 266 +#define IDC_BSDR_SCROLLBAR 268 +#define IDC_BSDR_SEPARATOR_BOTTOM 270 +#define IDC_BSDR_DESC 262 +#define IDC_BSDR_FORCE_BTN 260 +#define IDC_BSDR_WARNING 271 + +#define IDB_BSDR_SEPARATOR 528 +#define IDB_BSDR_BTN_NORMAL 536 +#define IDB_BSDR_BTN_HOVER 537 +#define IDB_BSDR_BTN_PRESSED 538 +#define IDB_BSDR_BTN_SELECTED 539 +#define IDB_BSDR_BTN_SELECTED_HOVER 540 + +#define IDS_BSDR_DESC_LOGOFF 1025 +#define IDS_BSDR_BTN_LOGOFF 1026 +#define IDS_BSDR_DESC_RESTART 1027 +#define IDS_BSDR_BTN_RESTART 1028 +#define IDS_BSDR_BLOCKINGAPP_SHUTDOWN 1029 +#define IDS_BSDR_BLOCKINGAPP_LOGOFF 1030 +#define IDS_BSDR_BLOCKINGAPP_RESTART 1031 +#define IDS_BSDR_WAITINGFOR 1035 +#define IDS_BSDR_BLOCKING_BGAPPS 1036 +#define IDS_BSDR_WARNING_LOGOFF 1037 +#define IDS_BSDR_WARNING_RESTART 1038 +#define IDS_BSDR_BLOCKINGAPPCOUNT_MULTI 1039 +#define IDS_BSDR_BLOCKINGAPPCOUNT_SINGLE 1040 + +HINSTANCE hResDll; +bool isUsingHardcodedRes = true; + +// Hardcoded resources + +// https://github.com/Ingan121/AuthUX/blob/42909c132f429337e6169cf67c3a253405e916d0/shell/auth/authux/consolelogon/dll/consolelogon.rc#L471 +alignas(DWORD) +static constexpr unsigned char RES_DIALOG[] = { + 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0xc0, 0x40, + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x53, 0x00, 0x65, 0x00, 0x67, 0x00, 0x6f, 0x00, 0x65, 0x00, + 0x20, 0x00, 0x55, 0x00, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x50, 0x00, 0x00, 0x14, 0x00, 0x0a, 0x01, 0x0e, 0x00, 0x05, 0x01, 0x00, 0x00, + 0xff, 0xff, 0x82, 0x00, 0x54, 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x66, 0x00, 0x6f, 0x00, + 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20, 0x00, + 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x67, 0x00, 0x72, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x73, 0x00, + 0x20, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x65, 0x00, 0x64, 0x00, 0x20, 0x00, 0x74, 0x00, 0x6f, 0x00, + 0x20, 0x00, 0x63, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x65, 0x00, 0x3a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x50, + 0x00, 0x00, 0x2e, 0x00, 0x0a, 0x01, 0x01, 0x00, 0x0d, 0x01, 0x00, 0x00, 0xff, 0xff, 0x82, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x50, + 0x00, 0x00, 0x34, 0x00, 0x00, 0x01, 0x55, 0x00, 0x0a, 0x01, 0x00, 0x00, 0xff, 0xff, 0x82, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x50, + 0x00, 0x01, 0x34, 0x00, 0x0a, 0x00, 0x55, 0x00, 0x0c, 0x01, 0x00, 0x00, 0xff, 0xff, 0x84, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x50, + 0x00, 0x00, 0x8f, 0x00, 0x0a, 0x01, 0x01, 0x00, 0x0e, 0x01, 0x00, 0x00, 0xff, 0xff, 0x82, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50, + 0x00, 0x00, 0x98, 0x00, 0x0a, 0x01, 0x1e, 0x00, 0x06, 0x01, 0x00, 0x00, 0xff, 0xff, 0x82, 0x00, + 0x54, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x65, 0x00, + 0x20, 0x00, 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, + 0x67, 0x00, 0x72, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x20, 0x00, 0x74, 0x00, 0x68, 0x00, 0x61, 0x00, + 0x74, 0x00, 0x20, 0x00, 0x69, 0x00, 0x73, 0x00, 0x20, 0x00, 0x70, 0x00, 0x72, 0x00, 0x65, 0x00, + 0x76, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20, 0x00, + 0x57, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x73, 0x00, 0x20, 0x00, + 0x66, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x0a, 0x00, 0x73, 0x00, 0x68, 0x00, 0x75, 0x00, + 0x74, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20, 0x00, 0x64, 0x00, 0x6f, 0x00, + 0x77, 0x00, 0x6e, 0x00, 0x2c, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x63, 0x00, + 0x6b, 0x00, 0x20, 0x00, 0x43, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x63, 0x00, 0x65, 0x00, 0x6c, 0x00, + 0x2c, 0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20, 0x00, 0x74, 0x00, 0x68, 0x00, + 0x65, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x65, 0x00, + 0x20, 0x00, 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, + 0x67, 0x00, 0x72, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x03, 0x50, 0xc1, 0x00, 0xbf, 0x00, + 0x46, 0x00, 0x0d, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0x80, 0x00, 0x26, 0x00, 0x43, 0x00, + 0x61, 0x00, 0x6e, 0x00, 0x63, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x01, 0x50, 0x73, 0x00, 0xbf, 0x00, + 0x46, 0x00, 0x0d, 0x00, 0x04, 0x01, 0x00, 0x00, 0xff, 0xff, 0x80, 0x00, 0x26, 0x00, 0x46, 0x00, + 0x6f, 0x00, 0x72, 0x00, 0x63, 0x00, 0x65, 0x00, 0x20, 0x00, 0x73, 0x00, 0x68, 0x00, 0x75, 0x00, + 0x74, 0x00, 0x20, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50, 0x00, 0x00, 0x98, 0x00, + 0x0a, 0x01, 0x1e, 0x00, 0x0f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x82, 0x00, 0x49, 0x00, 0x66, 0x00, + 0x20, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x20, 0x00, 0x66, 0x00, 0x6f, 0x00, 0x72, 0x00, + 0x63, 0x00, 0x65, 0x00, 0x20, 0x00, 0x73, 0x00, 0x68, 0x00, 0x75, 0x00, 0x74, 0x00, 0x20, 0x00, + 0x64, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00, + 0x20, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x79, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x73, 0x00, + 0x65, 0x00, 0x20, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6b, 0x00, 0x20, 0x00, 0x74, 0x00, + 0x68, 0x00, 0x61, 0x00, 0x74, 0x00, 0x20, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x20, 0x00, + 0x68, 0x00, 0x61, 0x00, 0x76, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x27, 0x00, 0x74, 0x00, 0x20, 0x00, + 0x73, 0x00, 0x61, 0x00, 0x76, 0x00, 0x65, 0x00, 0x64, 0x00, 0x2e, 0x00, 0x0a, 0x00, 0x44, 0x00, + 0x6f, 0x00, 0x20, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x20, 0x00, 0x73, 0x00, 0x74, 0x00, + 0x69, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x77, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x74, 0x00, + 0x20, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x66, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x63, 0x00, + 0x65, 0x00, 0x20, 0x00, 0x73, 0x00, 0x68, 0x00, 0x75, 0x00, 0x74, 0x00, 0x64, 0x00, 0x6f, 0x00, + 0x77, 0x00, 0x6e, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x03, 0x50, 0xc1, 0x00, 0xbf, 0x00, 0x46, 0x00, 0x0d, 0x00, + 0x07, 0x00, 0x00, 0x00, 0xff, 0xff, 0x80, 0x00, 0x26, 0x00, 0x4e, 0x00, 0x6f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x01, 0x50, + 0x73, 0x00, 0xbf, 0x00, 0x46, 0x00, 0x0d, 0x00, 0x06, 0x00, 0x00, 0x00, 0xff, 0xff, 0x80, 0x00, + 0x26, 0x00, 0x59, 0x00, 0x65, 0x00, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +alignas(BITMAPINFOHEADER) +static constexpr unsigned char RES_BSDR_SEPARATOR[] = { + 0x28, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc4, 0x0e, 0x00, 0x00, 0xc4, 0x0e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x01, 0xff, 0xff, 0xff, 0x02, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0x06, + 0xff, 0xff, 0xff, 0x09, 0xff, 0xff, 0xff, 0x0d, 0xff, 0xff, 0xff, 0x13, 0xff, 0xff, 0xff, 0x1a, + 0xff, 0xff, 0xff, 0x21, 0xff, 0xff, 0xff, 0x2a, 0xff, 0xff, 0xff, 0x32, 0xff, 0xff, 0xff, 0x3a, + 0xff, 0xff, 0xff, 0x42, 0xff, 0xff, 0xff, 0x48, 0xff, 0xff, 0xff, 0x4d, 0xff, 0xff, 0xff, 0x51, + 0xff, 0xff, 0xff, 0x54, 0xff, 0xff, 0xff, 0x57, 0xff, 0xff, 0xff, 0x58, 0xff, 0xff, 0xff, 0x59, + 0xff, 0xff, 0xff, 0x59, 0xff, 0xff, 0xff, 0x59, 0xff, 0xff, 0xff, 0x59, 0xff, 0xff, 0xff, 0x59, + 0xff, 0xff, 0xff, 0x59, 0xff, 0xff, 0xff, 0x59, 0xff, 0xff, 0xff, 0x59, 0xff, 0xff, 0xff, 0x58, + 0xff, 0xff, 0xff, 0x57, 0xff, 0xff, 0xff, 0x54, 0xff, 0xff, 0xff, 0x51, 0xff, 0xff, 0xff, 0x4d, + 0xff, 0xff, 0xff, 0x48, 0xff, 0xff, 0xff, 0x42, 0xff, 0xff, 0xff, 0x3a, 0xff, 0xff, 0xff, 0x32, + 0xff, 0xff, 0xff, 0x2a, 0xff, 0xff, 0xff, 0x21, 0xff, 0xff, 0xff, 0x1a, 0xff, 0xff, 0xff, 0x13, + 0xff, 0xff, 0xff, 0x0d, 0xff, 0xff, 0xff, 0x09, 0xff, 0xff, 0xff, 0x06, 0xff, 0xff, 0xff, 0x03, + 0xff, 0xff, 0xff, 0x02, 0xff, 0xff, 0xff, 0x01, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00 +}; +alignas(BITMAPINFOHEADER) +static constexpr unsigned char RES_BSDR_BTN_NORMAL[] = { + 0x28, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc4, 0x0e, 0x00, 0x00, 0xc4, 0x0e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, + 0x00, 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00, 0xb3, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xb3, 0x00, 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x23, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x0e, 0x0c, 0x0a, 0xc5, 0xa6, 0x9f, 0x90, 0xe2, + 0xeb, 0xdf, 0xc0, 0xe1, 0xfd, 0xef, 0xca, 0xde, 0xfd, 0xef, 0xca, 0xde, 0xfd, 0xef, 0xca, 0xde, + 0xfd, 0xef, 0xca, 0xde, 0xfd, 0xef, 0xca, 0xde, 0xfd, 0xef, 0xca, 0xde, 0xfd, 0xef, 0xca, 0xde, + 0xfd, 0xef, 0xca, 0xde, 0xfd, 0xef, 0xca, 0xde, 0xfd, 0xef, 0xca, 0xde, 0xfd, 0xef, 0xca, 0xde, + 0xfd, 0xef, 0xca, 0xde, 0xfd, 0xef, 0xca, 0xde, 0xfd, 0xef, 0xca, 0xde, 0xfd, 0xef, 0xca, 0xde, + 0xfd, 0xef, 0xca, 0xde, 0xfd, 0xef, 0xca, 0xde, 0xfd, 0xef, 0xca, 0xde, 0xfd, 0xef, 0xca, 0xde, + 0xfd, 0xef, 0xca, 0xde, 0xfd, 0xef, 0xca, 0xde, 0xfd, 0xef, 0xca, 0xde, 0xfd, 0xef, 0xca, 0xde, + 0xfd, 0xef, 0xca, 0xde, 0xfd, 0xef, 0xca, 0xde, 0xfd, 0xef, 0xca, 0xde, 0xfd, 0xef, 0xca, 0xde, + 0xeb, 0xdf, 0xc0, 0xe1, 0xa6, 0x9f, 0x90, 0xe2, 0x0f, 0x0d, 0x0a, 0xc6, 0x00, 0x00, 0x00, 0x21, + 0x00, 0x00, 0x00, 0x76, 0xa0, 0x99, 0x8b, 0xe2, 0xf7, 0xdc, 0x9f, 0xc6, 0xf1, 0xc1, 0x58, 0xa3, + 0xf0, 0xbb, 0x46, 0x9c, 0xf0, 0xbb, 0x46, 0x9c, 0xf0, 0xbb, 0x46, 0x9c, 0xf0, 0xbb, 0x46, 0x9c, + 0xf0, 0xbb, 0x46, 0x9c, 0xf0, 0xbb, 0x46, 0x9c, 0xf0, 0xbb, 0x46, 0x9c, 0xf0, 0xbb, 0x46, 0x9c, + 0xf0, 0xbb, 0x46, 0x9c, 0xf0, 0xbb, 0x46, 0x9c, 0xf0, 0xbb, 0x46, 0x9c, 0xf0, 0xbb, 0x46, 0x9c, + 0xf0, 0xbb, 0x46, 0x9c, 0xf0, 0xbb, 0x46, 0x9c, 0xf0, 0xbb, 0x46, 0x9c, 0xf0, 0xbb, 0x46, 0x9c, + 0xf0, 0xbb, 0x46, 0x9c, 0xf0, 0xbb, 0x46, 0x9c, 0xf0, 0xbb, 0x46, 0x9c, 0xf0, 0xbb, 0x46, 0x9c, + 0xf0, 0xbb, 0x46, 0x9c, 0xf0, 0xbb, 0x46, 0x9c, 0xf0, 0xbb, 0x46, 0x9c, 0xf0, 0xbb, 0x46, 0x9c, + 0xf0, 0xbb, 0x46, 0x9c, 0xf0, 0xbb, 0x46, 0x9c, 0xf0, 0xbb, 0x46, 0x9c, 0xf1, 0xc1, 0x58, 0xa3, + 0xf7, 0xdc, 0x9f, 0xc6, 0xa2, 0x9b, 0x8c, 0xe2, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0xb4, + 0xe7, 0xd7, 0xb8, 0xe1, 0xe7, 0xb0, 0x48, 0xa3, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, + 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, + 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, + 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, + 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, + 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, + 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, + 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe7, 0xb0, 0x48, 0xa3, + 0xe7, 0xd7, 0xb8, 0xe1, 0x00, 0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0xc0, 0xf3, 0xdd, 0xb9, 0xde, + 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, + 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, + 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, + 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, + 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, + 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, + 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, + 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xf3, 0xdd, 0xb9, 0xde, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0xef, 0xd6, 0xb5, 0xde, 0xcd, 0x82, 0x14, 0x9c, + 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, + 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, + 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, + 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, + 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, + 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, + 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, + 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xef, 0xd6, 0xb5, 0xde, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0xeb, 0xd1, 0xb1, 0xde, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, + 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, + 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, + 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, + 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, + 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, + 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, + 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, + 0xc2, 0x71, 0x08, 0x9c, 0xeb, 0xd1, 0xb1, 0xde, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0xe7, 0xcc, 0xae, 0xde, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, + 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, + 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, + 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, + 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, + 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, + 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, + 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, + 0xe7, 0xcc, 0xae, 0xde, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0xe2, 0xc6, 0xae, 0xde, + 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, + 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, + 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, + 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, + 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, + 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, + 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, + 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xe2, 0xc6, 0xae, 0xde, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0xdc, 0xc1, 0xae, 0xde, 0x97, 0x42, 0x01, 0x9c, + 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, + 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, + 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, + 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, + 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, + 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, + 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, + 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0xdc, 0xc1, 0xae, 0xde, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0xd9, 0xbe, 0xae, 0xde, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, + 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, + 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, + 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, + 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, + 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, + 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, + 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, + 0x8c, 0x39, 0x01, 0x9c, 0xd9, 0xbe, 0xae, 0xde, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0xd7, 0xbd, 0xae, 0xde, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, + 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, + 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, + 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, + 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, + 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, + 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, + 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, + 0xd7, 0xbd, 0xae, 0xde, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0xd5, 0xbc, 0xae, 0xde, + 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, + 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, + 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, + 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, + 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, + 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, + 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, + 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0xd5, 0xbc, 0xae, 0xde, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0xd3, 0xbb, 0xb1, 0xda, 0x72, 0x25, 0x01, 0x91, + 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, + 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, + 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, + 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, + 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, + 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, + 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, + 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0xd3, 0xbb, 0xb1, 0xda, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0xea, 0xd2, 0xbc, 0xda, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, + 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, + 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, + 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, + 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, + 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, + 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, + 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, + 0xba, 0x6c, 0x27, 0x91, 0xea, 0xd2, 0xbc, 0xda, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0xea, 0xd4, 0xbe, 0xda, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, + 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, + 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, + 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, + 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, + 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, + 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, + 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, + 0xea, 0xd4, 0xbe, 0xda, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0xeb, 0xd5, 0xc0, 0xda, + 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, + 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, + 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, + 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, + 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, + 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, + 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, + 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xeb, 0xd5, 0xc0, 0xda, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0xeb, 0xd6, 0xc2, 0xda, 0xc2, 0x78, 0x39, 0x91, + 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, + 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, + 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, + 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, + 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, + 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, + 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, + 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xeb, 0xd6, 0xc2, 0xda, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0xeb, 0xd6, 0xc4, 0xda, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, + 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, + 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, + 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, + 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, + 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, + 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, + 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, + 0xc2, 0x79, 0x3f, 0x91, 0xeb, 0xd6, 0xc4, 0xda, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0xec, 0xd7, 0xc7, 0xda, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, + 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, + 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, + 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, + 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, + 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, + 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, + 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, + 0xec, 0xd7, 0xc7, 0xda, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0xec, 0xd8, 0xca, 0xda, + 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, + 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, + 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, + 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, + 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, + 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, + 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, + 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xec, 0xd8, 0xca, 0xda, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0xee, 0xda, 0xce, 0xda, 0xc9, 0x8a, 0x5d, 0x91, + 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, + 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, + 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, + 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, + 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, + 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, + 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, + 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xee, 0xda, 0xce, 0xda, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0xef, 0xde, 0xd3, 0xda, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, + 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, + 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, + 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, + 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, + 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, + 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, + 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, + 0xce, 0x94, 0x6d, 0x91, 0xef, 0xde, 0xd3, 0xda, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0xf1, 0xe2, 0xd9, 0xda, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, + 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, + 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, + 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, + 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, + 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, + 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, + 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, + 0xf1, 0xe2, 0xd9, 0xda, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xb4, 0xe4, 0xd8, 0xd2, 0xdd, + 0xde, 0xb5, 0x9b, 0x98, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, + 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, + 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, + 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, + 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, + 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, + 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, + 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xde, 0xb5, 0x9b, 0x98, 0xe4, 0xd8, 0xd2, 0xdd, + 0x00, 0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x76, 0x9d, 0x99, 0x96, 0xdf, 0xef, 0xdb, 0xcf, 0xc0, + 0xe4, 0xc1, 0xab, 0x9b, 0xe0, 0xb8, 0x9e, 0x91, 0xe0, 0xb8, 0x9e, 0x91, 0xe0, 0xb8, 0x9e, 0x91, + 0xe0, 0xb8, 0x9e, 0x91, 0xe0, 0xb8, 0x9e, 0x91, 0xe0, 0xb8, 0x9e, 0x91, 0xe0, 0xb8, 0x9e, 0x91, + 0xe0, 0xb8, 0x9e, 0x91, 0xe0, 0xb8, 0x9e, 0x91, 0xe0, 0xb8, 0x9e, 0x91, 0xe0, 0xb8, 0x9e, 0x91, + 0xe0, 0xb8, 0x9e, 0x91, 0xe0, 0xb8, 0x9e, 0x91, 0xe0, 0xb8, 0x9e, 0x91, 0xe0, 0xb8, 0x9e, 0x91, + 0xe0, 0xb8, 0x9e, 0x91, 0xe0, 0xb8, 0x9e, 0x91, 0xe0, 0xb8, 0x9e, 0x91, 0xe0, 0xb8, 0x9e, 0x91, + 0xe0, 0xb8, 0x9e, 0x91, 0xe0, 0xb8, 0x9e, 0x91, 0xe0, 0xb8, 0x9e, 0x91, 0xe0, 0xb8, 0x9e, 0x91, + 0xe0, 0xb8, 0x9e, 0x91, 0xe0, 0xb8, 0x9e, 0x91, 0xe0, 0xb8, 0x9e, 0x91, 0xe0, 0xb8, 0x9e, 0x91, + 0xe4, 0xc1, 0xab, 0x9b, 0xef, 0xdb, 0xcf, 0xc0, 0x9d, 0x99, 0x96, 0xdf, 0x00, 0x00, 0x00, 0x76, + 0x00, 0x00, 0x00, 0x1d, 0x0d, 0x0c, 0x0a, 0xc4, 0x9b, 0x96, 0x94, 0xdf, 0xe2, 0xd9, 0xd4, 0xde, + 0xf6, 0xec, 0xe5, 0xda, 0xf6, 0xec, 0xe5, 0xda, 0xf6, 0xec, 0xe5, 0xda, 0xf6, 0xec, 0xe5, 0xda, + 0xf6, 0xec, 0xe5, 0xda, 0xf6, 0xec, 0xe5, 0xda, 0xf6, 0xec, 0xe5, 0xda, 0xf6, 0xec, 0xe5, 0xda, + 0xf6, 0xec, 0xe5, 0xda, 0xf6, 0xec, 0xe5, 0xda, 0xf6, 0xec, 0xe5, 0xda, 0xf6, 0xec, 0xe5, 0xda, + 0xf6, 0xec, 0xe5, 0xda, 0xf6, 0xec, 0xe5, 0xda, 0xf6, 0xec, 0xe5, 0xda, 0xf6, 0xec, 0xe5, 0xda, + 0xf6, 0xec, 0xe5, 0xda, 0xf6, 0xec, 0xe5, 0xda, 0xf6, 0xec, 0xe5, 0xda, 0xf6, 0xec, 0xe5, 0xda, + 0xf6, 0xec, 0xe5, 0xda, 0xf6, 0xec, 0xe5, 0xda, 0xf6, 0xec, 0xe5, 0xda, 0xf6, 0xec, 0xe5, 0xda, + 0xf6, 0xec, 0xe5, 0xda, 0xf6, 0xec, 0xe5, 0xda, 0xf6, 0xec, 0xe5, 0xda, 0xe2, 0xd9, 0xd4, 0xde, + 0x9b, 0x96, 0x94, 0xdf, 0x0d, 0x0c, 0x0a, 0xc4, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0xb1, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xb1, 0x00, 0x00, 0x00, 0x73, + 0x00, 0x00, 0x00, 0x1b, 0xff, 0xff, 0xff, 0x00 +}; +alignas(BITMAPINFOHEADER) +static constexpr const unsigned char RES_BSDR_BTN_HOVER[] = { + 0x28, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc4, 0x0e, 0x00, 0x00, 0xc4, 0x0e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, + 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x43, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x38, 0x05, 0x05, 0x05, 0xff, 0x80, 0x7b, 0x6d, 0xfd, + 0xdd, 0xd1, 0xaa, 0xf5, 0xfc, 0xec, 0xbb, 0xf1, 0xfc, 0xec, 0xbb, 0xf1, 0xfc, 0xec, 0xbb, 0xf1, + 0xfc, 0xec, 0xbb, 0xf1, 0xfc, 0xec, 0xbb, 0xf1, 0xfc, 0xec, 0xbb, 0xf1, 0xfc, 0xec, 0xbb, 0xf1, + 0xfc, 0xec, 0xbb, 0xf1, 0xfc, 0xec, 0xbb, 0xf1, 0xfc, 0xec, 0xbb, 0xf1, 0xfc, 0xec, 0xbb, 0xf1, + 0xfc, 0xec, 0xbb, 0xf1, 0xfc, 0xec, 0xbb, 0xf1, 0xfc, 0xec, 0xbb, 0xf1, 0xfc, 0xec, 0xbb, 0xf1, + 0xfc, 0xec, 0xbb, 0xf1, 0xfc, 0xec, 0xbb, 0xf1, 0xfc, 0xec, 0xbb, 0xf1, 0xfc, 0xec, 0xbb, 0xf1, + 0xfc, 0xec, 0xbb, 0xf1, 0xfc, 0xec, 0xbb, 0xf1, 0xfc, 0xec, 0xbb, 0xf1, 0xfc, 0xec, 0xbb, 0xf1, + 0xfc, 0xec, 0xbb, 0xf1, 0xfc, 0xec, 0xbb, 0xf1, 0xfc, 0xec, 0xbb, 0xf1, 0xfc, 0xec, 0xbb, 0xf1, + 0xdd, 0xd1, 0xaa, 0xf5, 0x80, 0x7b, 0x6d, 0xfd, 0x05, 0x05, 0x05, 0xff, 0x00, 0x00, 0x00, 0x3e, + 0x00, 0x00, 0x00, 0xc7, 0x79, 0x73, 0x66, 0xfd, 0xf4, 0xd6, 0x8e, 0xe7, 0xef, 0xc3, 0x5b, 0xda, + 0xee, 0xbf, 0x4f, 0xd6, 0xee, 0xbf, 0x4f, 0xd6, 0xee, 0xbf, 0x4f, 0xd6, 0xee, 0xbf, 0x4f, 0xd6, + 0xee, 0xbf, 0x4f, 0xd6, 0xee, 0xbf, 0x4f, 0xd6, 0xee, 0xbf, 0x4f, 0xd6, 0xee, 0xbf, 0x4f, 0xd6, + 0xee, 0xbf, 0x4f, 0xd6, 0xee, 0xbf, 0x4f, 0xd6, 0xee, 0xbf, 0x4f, 0xd6, 0xee, 0xbf, 0x4f, 0xd6, + 0xee, 0xbf, 0x4f, 0xd6, 0xee, 0xbf, 0x4f, 0xd6, 0xee, 0xbf, 0x4f, 0xd6, 0xee, 0xbf, 0x4f, 0xd6, + 0xee, 0xbf, 0x4f, 0xd6, 0xee, 0xbf, 0x4f, 0xd6, 0xee, 0xbf, 0x4f, 0xd6, 0xee, 0xbf, 0x4f, 0xd6, + 0xee, 0xbf, 0x4f, 0xd6, 0xee, 0xbf, 0x4f, 0xd6, 0xee, 0xbf, 0x4f, 0xd6, 0xee, 0xbf, 0x4f, 0xd6, + 0xee, 0xbf, 0x4f, 0xd6, 0xee, 0xbf, 0x4f, 0xd6, 0xee, 0xbf, 0x4f, 0xd6, 0xef, 0xc3, 0x5b, 0xda, + 0xf4, 0xd6, 0x8e, 0xe7, 0x7c, 0x76, 0x69, 0xfd, 0x00, 0x00, 0x00, 0xc9, 0x00, 0x00, 0x00, 0xfd, + 0xd9, 0xc5, 0x9f, 0xf5, 0xe3, 0xaf, 0x48, 0xd9, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, + 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, + 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, + 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, + 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, + 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, + 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, + 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe3, 0xaf, 0x48, 0xd9, + 0xd9, 0xc5, 0x9f, 0xf5, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x00, 0xff, 0xec, 0xd0, 0xa2, 0xf1, + 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, + 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, + 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, + 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, + 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, + 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, + 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, + 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xec, 0xd0, 0xa2, 0xf1, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xe6, 0xc6, 0x9a, 0xf1, 0xc7, 0x7e, 0x17, 0xd6, + 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, + 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, + 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, + 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, + 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, + 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, + 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, + 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xe6, 0xc6, 0x9a, 0xf1, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0xe0, 0xbf, 0x94, 0xf1, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, + 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, + 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, + 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, + 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, + 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, + 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, + 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, + 0xba, 0x6b, 0x09, 0xd6, 0xe0, 0xbf, 0x94, 0xf1, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0xda, 0xb7, 0x90, 0xf1, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, + 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, + 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, + 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, + 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, + 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, + 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, + 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, + 0xda, 0xb7, 0x90, 0xf1, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xd3, 0xaf, 0x90, 0xf1, + 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, + 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, + 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, + 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, + 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, + 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, + 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, + 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0xd3, 0xaf, 0x90, 0xf1, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xcb, 0xa7, 0x90, 0xf1, 0x89, 0x36, 0x01, 0xd6, + 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, + 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, + 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, + 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, + 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, + 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, + 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, + 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0xcb, 0xa7, 0x90, 0xf1, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0xc5, 0xa3, 0x90, 0xf1, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, + 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, + 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, + 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, + 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, + 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, + 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, + 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, + 0x7d, 0x2d, 0x01, 0xd6, 0xc5, 0xa3, 0x90, 0xf1, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0xc2, 0xa0, 0x90, 0xf1, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, + 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, + 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, + 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, + 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, + 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, + 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, + 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, + 0xc2, 0xa0, 0x90, 0xf1, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xbf, 0x9e, 0x90, 0xf1, + 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, + 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, + 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, + 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, + 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, + 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, + 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, + 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0xbf, 0x9e, 0x90, 0xf1, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xbd, 0x9c, 0x91, 0xf0, 0x67, 0x1c, 0x01, 0xd1, + 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, + 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, + 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, + 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, + 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, + 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, + 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, + 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0xbd, 0x9c, 0x91, 0xf0, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0xde, 0xbe, 0xa3, 0xf0, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, + 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, + 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, + 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, + 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, + 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, + 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, + 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, + 0xb4, 0x68, 0x2a, 0xd1, 0xde, 0xbe, 0xa3, 0xf0, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0xe0, 0xc1, 0xa6, 0xf0, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, + 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, + 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, + 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, + 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, + 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, + 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, + 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, + 0xe0, 0xc1, 0xa6, 0xf0, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xe2, 0xc2, 0xa9, 0xf0, + 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, + 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, + 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, + 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, + 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, + 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, + 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, + 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xe2, 0xc2, 0xa9, 0xf0, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xe2, 0xc4, 0xac, 0xf0, 0xbd, 0x75, 0x3d, 0xd1, + 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, + 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, + 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, + 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, + 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, + 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, + 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, + 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xe2, 0xc4, 0xac, 0xf0, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0xe2, 0xc4, 0xaf, 0xf0, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, + 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, + 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, + 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, + 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, + 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, + 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, + 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, + 0xbd, 0x77, 0x44, 0xd1, 0xe2, 0xc4, 0xaf, 0xf0, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0xe2, 0xc6, 0xb3, 0xf0, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, + 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, + 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, + 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, + 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, + 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, + 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, + 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, + 0xe2, 0xc6, 0xb3, 0xf0, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xe3, 0xc8, 0xb7, 0xf0, + 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, + 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, + 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, + 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, + 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, + 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, + 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, + 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xe3, 0xc8, 0xb7, 0xf0, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xe5, 0xcc, 0xbd, 0xf0, 0xc5, 0x88, 0x64, 0xd1, + 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, + 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, + 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, + 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, + 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, + 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, + 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, + 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xe5, 0xcc, 0xbd, 0xf0, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0xe8, 0xd1, 0xc4, 0xf0, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, + 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, + 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, + 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, + 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, + 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, + 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, + 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, + 0xca, 0x94, 0x76, 0xd1, 0xe8, 0xd1, 0xc4, 0xf0, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0xeb, 0xd7, 0xcc, 0xf0, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, + 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, + 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, + 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, + 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, + 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, + 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, + 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, + 0xeb, 0xd7, 0xcc, 0xf0, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xfd, 0xd6, 0xc8, 0xc1, 0xf3, + 0xdb, 0xb3, 0xa1, 0xd4, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, + 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, + 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, + 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, + 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, + 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, + 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, + 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xdb, 0xb3, 0xa1, 0xd4, 0xd6, 0xc8, 0xc1, 0xf3, + 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x00, 0xc7, 0x77, 0x73, 0x71, 0xfd, 0xe9, 0xd3, 0xca, 0xe4, + 0xe1, 0xc0, 0xb2, 0xd5, 0xde, 0xba, 0xaa, 0xd1, 0xde, 0xba, 0xaa, 0xd1, 0xde, 0xba, 0xaa, 0xd1, + 0xde, 0xba, 0xaa, 0xd1, 0xde, 0xba, 0xaa, 0xd1, 0xde, 0xba, 0xaa, 0xd1, 0xde, 0xba, 0xaa, 0xd1, + 0xde, 0xba, 0xaa, 0xd1, 0xde, 0xba, 0xaa, 0xd1, 0xde, 0xba, 0xaa, 0xd1, 0xde, 0xba, 0xaa, 0xd1, + 0xde, 0xba, 0xaa, 0xd1, 0xde, 0xba, 0xaa, 0xd1, 0xde, 0xba, 0xaa, 0xd1, 0xde, 0xba, 0xaa, 0xd1, + 0xde, 0xba, 0xaa, 0xd1, 0xde, 0xba, 0xaa, 0xd1, 0xde, 0xba, 0xaa, 0xd1, 0xde, 0xba, 0xaa, 0xd1, + 0xde, 0xba, 0xaa, 0xd1, 0xde, 0xba, 0xaa, 0xd1, 0xde, 0xba, 0xaa, 0xd1, 0xde, 0xba, 0xaa, 0xd1, + 0xde, 0xba, 0xaa, 0xd1, 0xde, 0xba, 0xaa, 0xd1, 0xde, 0xba, 0xaa, 0xd1, 0xde, 0xba, 0xaa, 0xd1, + 0xe1, 0xc0, 0xb2, 0xd5, 0xe9, 0xd3, 0xca, 0xe4, 0x77, 0x73, 0x71, 0xfd, 0x00, 0x00, 0x00, 0xc7, + 0x00, 0x00, 0x00, 0x38, 0x05, 0x05, 0x05, 0xff, 0x75, 0x71, 0x70, 0xfd, 0xd3, 0xc9, 0xc5, 0xf4, + 0xf2, 0xe4, 0xe0, 0xf0, 0xf2, 0xe4, 0xe0, 0xf0, 0xf2, 0xe4, 0xe0, 0xf0, 0xf2, 0xe4, 0xe0, 0xf0, + 0xf2, 0xe4, 0xe0, 0xf0, 0xf2, 0xe4, 0xe0, 0xf0, 0xf2, 0xe4, 0xe0, 0xf0, 0xf2, 0xe4, 0xe0, 0xf0, + 0xf2, 0xe4, 0xe0, 0xf0, 0xf2, 0xe4, 0xe0, 0xf0, 0xf2, 0xe4, 0xe0, 0xf0, 0xf2, 0xe4, 0xe0, 0xf0, + 0xf2, 0xe4, 0xe0, 0xf0, 0xf2, 0xe4, 0xe0, 0xf0, 0xf2, 0xe4, 0xe0, 0xf0, 0xf2, 0xe4, 0xe0, 0xf0, + 0xf2, 0xe4, 0xe0, 0xf0, 0xf2, 0xe4, 0xe0, 0xf0, 0xf2, 0xe4, 0xe0, 0xf0, 0xf2, 0xe4, 0xe0, 0xf0, + 0xf2, 0xe4, 0xe0, 0xf0, 0xf2, 0xe4, 0xe0, 0xf0, 0xf2, 0xe4, 0xe0, 0xf0, 0xf2, 0xe4, 0xe0, 0xf0, + 0xf2, 0xe4, 0xe0, 0xf0, 0xf2, 0xe4, 0xe0, 0xf0, 0xf2, 0xe4, 0xe0, 0xf0, 0xd3, 0xc9, 0xc5, 0xf4, + 0x75, 0x71, 0x70, 0xfd, 0x05, 0x05, 0x05, 0xff, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xc4, + 0x00, 0x00, 0x00, 0x33, 0xff, 0xff, 0xff, 0x00 +}; +alignas(BITMAPINFOHEADER) +static constexpr unsigned char RES_BSDR_BTN_PRESSED[] = { + 0x28, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc4, 0x0e, 0x00, 0x00, 0xc4, 0x0e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, + 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x43, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0xff, 0x0f, 0x0d, 0x06, 0xfe, + 0x2e, 0x26, 0x12, 0xfc, 0x3a, 0x31, 0x17, 0xfa, 0x3a, 0x31, 0x17, 0xfa, 0x3a, 0x31, 0x17, 0xfa, + 0x3a, 0x31, 0x17, 0xfa, 0x3a, 0x31, 0x17, 0xfa, 0x3a, 0x31, 0x17, 0xfa, 0x3a, 0x31, 0x17, 0xfa, + 0x3a, 0x31, 0x17, 0xfa, 0x3a, 0x31, 0x17, 0xfa, 0x3a, 0x31, 0x17, 0xfa, 0x3a, 0x31, 0x17, 0xfa, + 0x3a, 0x31, 0x17, 0xfa, 0x3a, 0x31, 0x17, 0xfa, 0x3a, 0x31, 0x17, 0xfa, 0x3a, 0x31, 0x17, 0xfa, + 0x3a, 0x31, 0x17, 0xfa, 0x3a, 0x31, 0x17, 0xfa, 0x3a, 0x31, 0x17, 0xfa, 0x3a, 0x31, 0x17, 0xfa, + 0x3a, 0x31, 0x17, 0xfa, 0x3a, 0x31, 0x17, 0xfa, 0x3a, 0x31, 0x17, 0xfa, 0x3a, 0x31, 0x17, 0xfa, + 0x3a, 0x31, 0x17, 0xfa, 0x3a, 0x31, 0x17, 0xfa, 0x3a, 0x31, 0x17, 0xfa, 0x3a, 0x31, 0x17, 0xfa, + 0x2e, 0x26, 0x12, 0xfc, 0x0f, 0x0d, 0x06, 0xfe, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x3e, + 0x00, 0x00, 0x00, 0xc7, 0x0e, 0x0b, 0x05, 0xff, 0x56, 0x46, 0x1e, 0xf5, 0x7f, 0x66, 0x2b, 0xed, + 0x87, 0x6d, 0x2e, 0xeb, 0x87, 0x6d, 0x2e, 0xeb, 0x87, 0x6d, 0x2e, 0xeb, 0x87, 0x6d, 0x2e, 0xeb, + 0x87, 0x6d, 0x2e, 0xeb, 0x87, 0x6d, 0x2e, 0xeb, 0x87, 0x6d, 0x2e, 0xeb, 0x87, 0x6d, 0x2e, 0xeb, + 0x87, 0x6d, 0x2e, 0xeb, 0x87, 0x6d, 0x2e, 0xeb, 0x87, 0x6d, 0x2e, 0xeb, 0x87, 0x6d, 0x2e, 0xeb, + 0x87, 0x6d, 0x2e, 0xeb, 0x87, 0x6d, 0x2e, 0xeb, 0x87, 0x6d, 0x2e, 0xeb, 0x87, 0x6d, 0x2e, 0xeb, + 0x87, 0x6d, 0x2e, 0xeb, 0x87, 0x6d, 0x2e, 0xeb, 0x87, 0x6d, 0x2e, 0xeb, 0x87, 0x6d, 0x2e, 0xeb, + 0x87, 0x6d, 0x2e, 0xeb, 0x87, 0x6d, 0x2e, 0xeb, 0x87, 0x6d, 0x2e, 0xeb, 0x87, 0x6d, 0x2e, 0xeb, + 0x87, 0x6d, 0x2e, 0xeb, 0x87, 0x6d, 0x2e, 0xeb, 0x87, 0x6d, 0x2e, 0xeb, 0x7f, 0x66, 0x2b, 0xed, + 0x56, 0x46, 0x1e, 0xf5, 0x0e, 0x0b, 0x05, 0xfe, 0x00, 0x00, 0x00, 0xc9, 0x00, 0x00, 0x00, 0xfd, + 0x2b, 0x20, 0x0c, 0xfb, 0x7a, 0x5c, 0x21, 0xed, 0x81, 0x61, 0x23, 0xeb, 0x81, 0x61, 0x23, 0xeb, + 0x81, 0x61, 0x23, 0xeb, 0x81, 0x61, 0x23, 0xeb, 0x81, 0x61, 0x23, 0xeb, 0x81, 0x61, 0x23, 0xeb, + 0x81, 0x61, 0x23, 0xeb, 0x81, 0x61, 0x23, 0xeb, 0x81, 0x61, 0x23, 0xeb, 0x81, 0x61, 0x23, 0xeb, + 0x81, 0x61, 0x23, 0xeb, 0x81, 0x61, 0x23, 0xeb, 0x81, 0x61, 0x23, 0xeb, 0x81, 0x61, 0x23, 0xeb, + 0x81, 0x61, 0x23, 0xeb, 0x81, 0x61, 0x23, 0xeb, 0x81, 0x61, 0x23, 0xeb, 0x81, 0x61, 0x23, 0xeb, + 0x81, 0x61, 0x23, 0xeb, 0x81, 0x61, 0x23, 0xeb, 0x81, 0x61, 0x23, 0xeb, 0x81, 0x61, 0x23, 0xeb, + 0x81, 0x61, 0x23, 0xeb, 0x81, 0x61, 0x23, 0xeb, 0x81, 0x61, 0x23, 0xeb, 0x81, 0x61, 0x23, 0xeb, + 0x81, 0x61, 0x23, 0xeb, 0x81, 0x61, 0x23, 0xeb, 0x81, 0x61, 0x23, 0xeb, 0x7a, 0x5c, 0x21, 0xed, + 0x2b, 0x20, 0x0c, 0xfb, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x00, 0xff, 0x31, 0x22, 0x0a, 0xfa, + 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, + 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, + 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, + 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, + 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, + 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, + 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, + 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, 0x79, 0x54, 0x18, 0xeb, 0x31, 0x22, 0x0a, 0xfa, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x2e, 0x1d, 0x05, 0xfa, 0x71, 0x48, 0x0d, 0xeb, + 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, + 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, + 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, + 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, + 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, + 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, + 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, + 0x71, 0x48, 0x0d, 0xeb, 0x71, 0x48, 0x0d, 0xeb, 0x2e, 0x1d, 0x05, 0xfa, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x2b, 0x19, 0x02, 0xfa, 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, + 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, + 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, + 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, + 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, + 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, + 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, + 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, 0x69, 0x3d, 0x05, 0xeb, + 0x69, 0x3d, 0x05, 0xeb, 0x2b, 0x19, 0x02, 0xfa, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x28, 0x15, 0x00, 0xfa, 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, + 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, + 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, + 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, + 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, + 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, + 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, + 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, 0x62, 0x33, 0x01, 0xeb, + 0x28, 0x15, 0x00, 0xfa, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x23, 0x10, 0x00, 0xfa, + 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, + 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, + 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, + 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, + 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, + 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, + 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, + 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, 0x57, 0x28, 0x01, 0xeb, 0x23, 0x10, 0x00, 0xfa, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x1f, 0x0c, 0x00, 0xfa, 0x4d, 0x1d, 0x01, 0xeb, + 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, + 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, + 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, + 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, + 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, + 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, + 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, + 0x4d, 0x1d, 0x01, 0xeb, 0x4d, 0x1d, 0x01, 0xeb, 0x1f, 0x0c, 0x00, 0xfa, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x1c, 0x0a, 0x00, 0xfa, 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, + 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, + 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, + 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, + 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, + 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, + 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, + 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, 0x46, 0x19, 0x01, 0xeb, + 0x46, 0x19, 0x01, 0xeb, 0x1c, 0x0a, 0x00, 0xfa, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x1a, 0x08, 0x00, 0xfa, 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, + 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, + 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, + 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, + 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, + 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, + 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, + 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, 0x41, 0x15, 0x01, 0xeb, + 0x1a, 0x08, 0x00, 0xfa, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x18, 0x07, 0x00, 0xfa, + 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, + 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, + 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, + 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, + 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, + 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, + 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, + 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, 0x3d, 0x12, 0x01, 0xeb, 0x18, 0x07, 0x00, 0xfa, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x18, 0x06, 0x00, 0xfa, 0x3a, 0x0f, 0x01, 0xe9, + 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, + 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, + 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, + 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, + 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, + 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, + 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, + 0x3a, 0x0f, 0x01, 0xe9, 0x3a, 0x0f, 0x01, 0xe9, 0x18, 0x06, 0x00, 0xfa, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x2a, 0x19, 0x0a, 0xfa, 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, + 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, + 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, + 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, + 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, + 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, + 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, + 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, 0x66, 0x3b, 0x18, 0xe9, + 0x66, 0x3b, 0x18, 0xe9, 0x2a, 0x19, 0x0a, 0xfa, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x2b, 0x1a, 0x0b, 0xfa, 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, + 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, + 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, + 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, + 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, + 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, + 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, + 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, 0x69, 0x3e, 0x1b, 0xe9, + 0x2b, 0x1a, 0x0b, 0xfa, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x2b, 0x1a, 0x0d, 0xfa, + 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, + 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, + 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, + 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, + 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, + 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, + 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, + 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, 0x6a, 0x40, 0x1f, 0xe9, 0x2b, 0x1a, 0x0d, 0xfa, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x2c, 0x1b, 0x0e, 0xfa, 0x6b, 0x42, 0x23, 0xe9, + 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, + 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, + 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, + 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, + 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, + 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, + 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, + 0x6b, 0x42, 0x23, 0xe9, 0x6b, 0x42, 0x23, 0xe9, 0x2c, 0x1b, 0x0e, 0xfa, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x2c, 0x1b, 0x10, 0xfa, 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, + 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, + 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, + 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, + 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, + 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, + 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, + 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, 0x6b, 0x43, 0x27, 0xe9, + 0x6b, 0x43, 0x27, 0xe9, 0x2c, 0x1b, 0x10, 0xfa, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x2c, 0x1c, 0x12, 0xfa, 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, + 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, + 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, + 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, + 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, + 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, + 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, + 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, 0x6b, 0x45, 0x2b, 0xe9, + 0x2c, 0x1c, 0x12, 0xfa, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x2d, 0x1d, 0x14, 0xfa, + 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, + 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, + 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, + 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, + 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, + 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, + 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, + 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, 0x6d, 0x48, 0x31, 0xe9, 0x2d, 0x1d, 0x14, 0xfa, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x2d, 0x1f, 0x18, 0xfa, 0x6f, 0x4d, 0x39, 0xe9, + 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, + 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, + 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, + 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, + 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, + 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, + 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, + 0x6f, 0x4d, 0x39, 0xe9, 0x6f, 0x4d, 0x39, 0xe9, 0x2d, 0x1f, 0x18, 0xfa, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x2f, 0x22, 0x1c, 0xfa, 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, + 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, + 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, + 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, + 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, + 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, + 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, + 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, 0x73, 0x54, 0x43, 0xe9, + 0x73, 0x54, 0x43, 0xe9, 0x2f, 0x22, 0x1c, 0xfa, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x31, 0x25, 0x20, 0xfa, 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, + 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, + 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, + 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, + 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, + 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, + 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, + 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, 0x77, 0x5c, 0x4e, 0xe9, + 0x31, 0x25, 0x20, 0xfa, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xfd, 0x2a, 0x21, 0x1e, 0xfb, + 0x74, 0x5e, 0x53, 0xeb, 0x7b, 0x63, 0x58, 0xe9, 0x7b, 0x63, 0x58, 0xe9, 0x7b, 0x63, 0x58, 0xe9, + 0x7b, 0x63, 0x58, 0xe9, 0x7b, 0x63, 0x58, 0xe9, 0x7b, 0x63, 0x58, 0xe9, 0x7b, 0x63, 0x58, 0xe9, + 0x7b, 0x63, 0x58, 0xe9, 0x7b, 0x63, 0x58, 0xe9, 0x7b, 0x63, 0x58, 0xe9, 0x7b, 0x63, 0x58, 0xe9, + 0x7b, 0x63, 0x58, 0xe9, 0x7b, 0x63, 0x58, 0xe9, 0x7b, 0x63, 0x58, 0xe9, 0x7b, 0x63, 0x58, 0xe9, + 0x7b, 0x63, 0x58, 0xe9, 0x7b, 0x63, 0x58, 0xe9, 0x7b, 0x63, 0x58, 0xe9, 0x7b, 0x63, 0x58, 0xe9, + 0x7b, 0x63, 0x58, 0xe9, 0x7b, 0x63, 0x58, 0xe9, 0x7b, 0x63, 0x58, 0xe9, 0x7b, 0x63, 0x58, 0xe9, + 0x7b, 0x63, 0x58, 0xe9, 0x7b, 0x63, 0x58, 0xe9, 0x7b, 0x63, 0x58, 0xe9, 0x7b, 0x63, 0x58, 0xe9, + 0x7b, 0x63, 0x58, 0xe9, 0x7b, 0x63, 0x58, 0xe9, 0x74, 0x5e, 0x53, 0xeb, 0x2a, 0x21, 0x1e, 0xfb, + 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x00, 0xc7, 0x0d, 0x0b, 0x0a, 0xff, 0x50, 0x43, 0x3e, 0xf3, + 0x74, 0x62, 0x59, 0xeb, 0x7e, 0x6a, 0x61, 0xe9, 0x7e, 0x6a, 0x61, 0xe9, 0x7e, 0x6a, 0x61, 0xe9, + 0x7e, 0x6a, 0x61, 0xe9, 0x7e, 0x6a, 0x61, 0xe9, 0x7e, 0x6a, 0x61, 0xe9, 0x7e, 0x6a, 0x61, 0xe9, + 0x7e, 0x6a, 0x61, 0xe9, 0x7e, 0x6a, 0x61, 0xe9, 0x7e, 0x6a, 0x61, 0xe9, 0x7e, 0x6a, 0x61, 0xe9, + 0x7e, 0x6a, 0x61, 0xe9, 0x7e, 0x6a, 0x61, 0xe9, 0x7e, 0x6a, 0x61, 0xe9, 0x7e, 0x6a, 0x61, 0xe9, + 0x7e, 0x6a, 0x61, 0xe9, 0x7e, 0x6a, 0x61, 0xe9, 0x7e, 0x6a, 0x61, 0xe9, 0x7e, 0x6a, 0x61, 0xe9, + 0x7e, 0x6a, 0x61, 0xe9, 0x7e, 0x6a, 0x61, 0xe9, 0x7e, 0x6a, 0x61, 0xe9, 0x7e, 0x6a, 0x61, 0xe9, + 0x7e, 0x6a, 0x61, 0xe9, 0x7e, 0x6a, 0x61, 0xe9, 0x7e, 0x6a, 0x61, 0xe9, 0x7e, 0x6a, 0x61, 0xe9, + 0x74, 0x62, 0x59, 0xeb, 0x50, 0x43, 0x3e, 0xf3, 0x0d, 0x0b, 0x0a, 0xff, 0x00, 0x00, 0x00, 0xc7, + 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0xff, 0x0c, 0x0a, 0x0a, 0xff, 0x28, 0x22, 0x21, 0xfb, + 0x34, 0x2c, 0x2a, 0xfa, 0x34, 0x2c, 0x2a, 0xfa, 0x34, 0x2c, 0x2a, 0xfa, 0x34, 0x2c, 0x2a, 0xfa, + 0x34, 0x2c, 0x2a, 0xfa, 0x34, 0x2c, 0x2a, 0xfa, 0x34, 0x2c, 0x2a, 0xfa, 0x34, 0x2c, 0x2a, 0xfa, + 0x34, 0x2c, 0x2a, 0xfa, 0x34, 0x2c, 0x2a, 0xfa, 0x34, 0x2c, 0x2a, 0xfa, 0x34, 0x2c, 0x2a, 0xfa, + 0x34, 0x2c, 0x2a, 0xfa, 0x34, 0x2c, 0x2a, 0xfa, 0x34, 0x2c, 0x2a, 0xfa, 0x34, 0x2c, 0x2a, 0xfa, + 0x34, 0x2c, 0x2a, 0xfa, 0x34, 0x2c, 0x2a, 0xfa, 0x34, 0x2c, 0x2a, 0xfa, 0x34, 0x2c, 0x2a, 0xfa, + 0x34, 0x2c, 0x2a, 0xfa, 0x34, 0x2c, 0x2a, 0xfa, 0x34, 0x2c, 0x2a, 0xfa, 0x34, 0x2c, 0x2a, 0xfa, + 0x34, 0x2c, 0x2a, 0xfa, 0x34, 0x2c, 0x2a, 0xfa, 0x34, 0x2c, 0x2a, 0xfa, 0x28, 0x22, 0x21, 0xfb, + 0x0c, 0x0a, 0x0a, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xc4, + 0x00, 0x00, 0x00, 0x33, 0xff, 0xff, 0xff, 0x00 +}; +alignas(BITMAPINFOHEADER) +static constexpr unsigned char RES_BSDR_BTN_SELECTED[] = { + 0x28, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc4, 0x0e, 0x00, 0x00, 0xc4, 0x0e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x23, + 0x00, 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00, 0xb3, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xb3, 0x00, 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x23, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x13, 0x10, 0x03, 0xc5, 0xd6, 0xd0, 0x18, 0xec, + 0xfc, 0xf9, 0x14, 0xfc, 0xff, 0xfd, 0x13, 0xfe, 0xff, 0xfd, 0x13, 0xfe, 0xff, 0xfd, 0x13, 0xfe, + 0xff, 0xfd, 0x13, 0xfe, 0xff, 0xfd, 0x13, 0xfe, 0xff, 0xfd, 0x13, 0xfe, 0xff, 0xfd, 0x13, 0xfe, + 0xff, 0xfd, 0x13, 0xfe, 0xff, 0xfd, 0x13, 0xfe, 0xff, 0xfd, 0x13, 0xfe, 0xff, 0xfd, 0x13, 0xfe, + 0xff, 0xfd, 0x13, 0xfe, 0xff, 0xfd, 0x13, 0xfe, 0xff, 0xfd, 0x13, 0xfe, 0xff, 0xfd, 0x13, 0xfe, + 0xff, 0xfd, 0x13, 0xfe, 0xff, 0xfd, 0x13, 0xfe, 0xff, 0xfd, 0x13, 0xfe, 0xff, 0xfd, 0x13, 0xfe, + 0xff, 0xfd, 0x13, 0xfe, 0xff, 0xfd, 0x13, 0xfe, 0xff, 0xfd, 0x13, 0xfe, 0xff, 0xfd, 0x13, 0xfe, + 0xff, 0xfd, 0x13, 0xfe, 0xff, 0xfd, 0x13, 0xfe, 0xff, 0xfd, 0x13, 0xfe, 0xff, 0xfd, 0x13, 0xfe, + 0xfc, 0xf9, 0x14, 0xfc, 0xd6, 0xd0, 0x18, 0xec, 0x14, 0x11, 0x03, 0xc6, 0x00, 0x00, 0x00, 0x21, + 0x00, 0x00, 0x00, 0x76, 0xd0, 0xc8, 0x1a, 0xea, 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, + 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, + 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, + 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, + 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, + 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, + 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, + 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, 0xfe, 0xfd, 0x18, 0xfe, + 0xfe, 0xfd, 0x18, 0xfe, 0xd2, 0xca, 0x1a, 0xeb, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0xb4, + 0xfb, 0xf8, 0x20, 0xfc, 0xfe, 0xfc, 0x1f, 0xfe, 0xf0, 0xcd, 0x2c, 0xc2, 0xe5, 0xa8, 0x36, 0x9c, + 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, + 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, + 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, + 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, + 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, + 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, + 0xe5, 0xa8, 0x36, 0x9c, 0xe5, 0xa8, 0x36, 0x9c, 0xef, 0xcc, 0x2c, 0xc0, 0xfe, 0xfc, 0x1f, 0xfe, + 0xfb, 0xf8, 0x20, 0xfc, 0x00, 0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0xc0, 0xfd, 0xfb, 0x26, 0xfe, + 0xfd, 0xfb, 0x26, 0xfe, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, + 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, + 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, + 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, + 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, + 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, + 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, + 0xd9, 0x95, 0x24, 0x9c, 0xd9, 0x95, 0x24, 0x9c, 0xfd, 0xfb, 0x26, 0xfe, 0xfd, 0xfb, 0x26, 0xfe, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0xfd, 0xfa, 0x2e, 0xfe, 0xfd, 0xfa, 0x2e, 0xfe, + 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, + 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, + 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, + 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, + 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, + 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, + 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, 0xcd, 0x82, 0x14, 0x9c, + 0xcd, 0x82, 0x14, 0x9c, 0xfd, 0xfa, 0x2e, 0xfe, 0xfd, 0xfa, 0x2e, 0xfe, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0xfc, 0xfa, 0x36, 0xfe, 0xfc, 0xfa, 0x36, 0xfe, 0xc2, 0x71, 0x08, 0x9c, + 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, + 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, + 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, + 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, + 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, + 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, + 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, 0xc2, 0x71, 0x08, 0x9c, + 0xfc, 0xfa, 0x36, 0xfe, 0xfc, 0xfa, 0x36, 0xfe, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0xfc, 0xf9, 0x3f, 0xfe, 0xfc, 0xf9, 0x3f, 0xfe, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, + 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, + 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, + 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, + 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, + 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, + 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, + 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xb7, 0x62, 0x01, 0x9c, 0xfc, 0xf9, 0x3f, 0xfe, + 0xfc, 0xf9, 0x3f, 0xfe, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0xfb, 0xf8, 0x49, 0xfe, + 0xfb, 0xf8, 0x49, 0xfe, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, + 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, + 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, + 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, + 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, + 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, + 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, + 0xa7, 0x51, 0x01, 0x9c, 0xa7, 0x51, 0x01, 0x9c, 0xfb, 0xf8, 0x49, 0xfe, 0xfb, 0xf8, 0x49, 0xfe, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0xfb, 0xf8, 0x53, 0xfe, 0xfb, 0xf8, 0x53, 0xfe, + 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, + 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, + 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, + 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, + 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, + 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, + 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, 0x97, 0x42, 0x01, 0x9c, + 0x97, 0x42, 0x01, 0x9c, 0xfb, 0xf8, 0x53, 0xfe, 0xfb, 0xf8, 0x53, 0xfe, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0xfa, 0xf7, 0x5e, 0xfe, 0xfa, 0xf7, 0x5e, 0xfe, 0x8c, 0x39, 0x01, 0x9c, + 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, + 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, + 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, + 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, + 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, + 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, + 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, 0x8c, 0x39, 0x01, 0x9c, + 0xfa, 0xf7, 0x5e, 0xfe, 0xfa, 0xf7, 0x5e, 0xfe, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0xfa, 0xf7, 0x68, 0xfe, 0xfa, 0xf7, 0x68, 0xfe, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, + 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, + 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, + 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, + 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, + 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, + 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, + 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0x84, 0x33, 0x01, 0x9c, 0xfa, 0xf7, 0x68, 0xfe, + 0xfa, 0xf7, 0x68, 0xfe, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0xfa, 0xf7, 0x73, 0xfe, + 0xfa, 0xf7, 0x73, 0xfe, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, + 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, + 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, + 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, + 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, + 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, + 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, + 0x7f, 0x2f, 0x01, 0x9c, 0x7f, 0x2f, 0x01, 0x9c, 0xfa, 0xf7, 0x73, 0xfe, 0xfa, 0xf7, 0x73, 0xfe, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0xf9, 0xf7, 0x7d, 0xfe, 0xf9, 0xf7, 0x7d, 0xfe, + 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, + 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, + 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, + 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, + 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, + 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, + 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, 0x72, 0x25, 0x01, 0x91, + 0x72, 0x25, 0x01, 0x91, 0xf9, 0xf7, 0x7d, 0xfe, 0xf9, 0xf7, 0x7d, 0xfe, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0xfc, 0xfa, 0x8a, 0xfe, 0xfc, 0xfa, 0x8a, 0xfe, 0xba, 0x6c, 0x27, 0x91, + 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, + 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, + 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, + 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, + 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, + 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, + 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, 0xba, 0x6c, 0x27, 0x91, + 0xfc, 0xfa, 0x8a, 0xfe, 0xfc, 0xfa, 0x8a, 0xfe, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0xfc, 0xfa, 0x94, 0xfe, 0xfc, 0xfa, 0x94, 0xfe, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, + 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, + 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, + 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, + 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, + 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, + 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, + 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xbe, 0x71, 0x2c, 0x91, 0xfc, 0xfa, 0x94, 0xfe, + 0xfc, 0xfa, 0x94, 0xfe, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0xfd, 0xfa, 0x9f, 0xfe, + 0xfd, 0xfa, 0x9f, 0xfe, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, + 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, + 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, + 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, + 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, + 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, + 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, + 0xc1, 0x75, 0x33, 0x91, 0xc1, 0x75, 0x33, 0x91, 0xfd, 0xfa, 0x9f, 0xfe, 0xfd, 0xfa, 0x9f, 0xfe, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0xfd, 0xfa, 0xaa, 0xfe, 0xfd, 0xfa, 0xaa, 0xfe, + 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, + 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, + 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, + 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, + 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, + 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, + 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, 0xc2, 0x78, 0x39, 0x91, + 0xc2, 0x78, 0x39, 0x91, 0xfd, 0xfa, 0xaa, 0xfe, 0xfd, 0xfa, 0xaa, 0xfe, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0xfd, 0xfa, 0xb4, 0xfe, 0xfd, 0xfa, 0xb4, 0xfe, 0xc2, 0x79, 0x3f, 0x91, + 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, + 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, + 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, + 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, + 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, + 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, + 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, 0xc2, 0x79, 0x3f, 0x91, + 0xfd, 0xfa, 0xb4, 0xfe, 0xfd, 0xfa, 0xb4, 0xfe, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0xfd, 0xfa, 0xbf, 0xfe, 0xfd, 0xfa, 0xbf, 0xfe, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, + 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, + 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, + 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, + 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, + 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, + 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, + 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xc3, 0x7c, 0x47, 0x91, 0xfd, 0xfa, 0xbf, 0xfe, + 0xfd, 0xfa, 0xbf, 0xfe, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0xfd, 0xfa, 0xc8, 0xfe, + 0xfd, 0xfa, 0xc8, 0xfe, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, + 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, + 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, + 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, + 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, + 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, + 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, + 0xc4, 0x82, 0x50, 0x91, 0xc4, 0x82, 0x50, 0x91, 0xfd, 0xfa, 0xc8, 0xfe, 0xfd, 0xfa, 0xc8, 0xfe, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0xfd, 0xfb, 0xd1, 0xfe, 0xfd, 0xfb, 0xd1, 0xfe, + 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, + 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, + 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, + 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, + 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, + 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, + 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, 0xc9, 0x8a, 0x5d, 0x91, + 0xc9, 0x8a, 0x5d, 0x91, 0xfd, 0xfb, 0xd1, 0xfe, 0xfd, 0xfb, 0xd1, 0xfe, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0xfd, 0xfb, 0xda, 0xfe, 0xfd, 0xfb, 0xda, 0xfe, 0xce, 0x94, 0x6d, 0x91, + 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, + 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, + 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, + 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, + 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, + 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, + 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, 0xce, 0x94, 0x6d, 0x91, + 0xfd, 0xfb, 0xda, 0xfe, 0xfd, 0xfb, 0xda, 0xfe, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0xfd, 0xfc, 0xe3, 0xfe, 0xfd, 0xfc, 0xe3, 0xfe, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, + 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, + 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, + 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, + 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, + 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, + 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, + 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xd5, 0xa1, 0x7e, 0x91, 0xfd, 0xfc, 0xe3, 0xfe, + 0xfd, 0xfc, 0xe3, 0xfe, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xb4, 0xfb, 0xf8, 0xe7, 0xfb, + 0xfe, 0xfc, 0xeb, 0xfe, 0xec, 0xd3, 0xbc, 0xbd, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, + 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, + 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, + 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, + 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, + 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, + 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, 0xdb, 0xad, 0x8f, 0x91, + 0xdb, 0xad, 0x8f, 0x91, 0xec, 0xd3, 0xbc, 0xbd, 0xfe, 0xfc, 0xeb, 0xfe, 0xfb, 0xf8, 0xe7, 0xfb, + 0x00, 0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x76, 0xcd, 0xc8, 0xbe, 0xe8, 0xfe, 0xfd, 0xf2, 0xfe, + 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, + 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, + 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, + 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, + 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, + 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, + 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, + 0xfe, 0xfd, 0xf2, 0xfe, 0xfe, 0xfd, 0xf2, 0xfe, 0xcd, 0xc8, 0xbe, 0xe8, 0x00, 0x00, 0x00, 0x76, + 0x00, 0x00, 0x00, 0x1d, 0x12, 0x10, 0x0e, 0xc4, 0xcc, 0xc6, 0xc0, 0xe8, 0xfa, 0xf8, 0xf3, 0xfb, + 0xfe, 0xfd, 0xf8, 0xfe, 0xfe, 0xfd, 0xf8, 0xfe, 0xfe, 0xfd, 0xf8, 0xfe, 0xfe, 0xfd, 0xf8, 0xfe, + 0xfe, 0xfd, 0xf8, 0xfe, 0xfe, 0xfd, 0xf8, 0xfe, 0xfe, 0xfd, 0xf8, 0xfe, 0xfe, 0xfd, 0xf8, 0xfe, + 0xfe, 0xfd, 0xf8, 0xfe, 0xfe, 0xfd, 0xf8, 0xfe, 0xfe, 0xfd, 0xf8, 0xfe, 0xfe, 0xfd, 0xf8, 0xfe, + 0xfe, 0xfd, 0xf8, 0xfe, 0xfe, 0xfd, 0xf8, 0xfe, 0xfe, 0xfd, 0xf8, 0xfe, 0xfe, 0xfd, 0xf8, 0xfe, + 0xfe, 0xfd, 0xf8, 0xfe, 0xfe, 0xfd, 0xf8, 0xfe, 0xfe, 0xfd, 0xf8, 0xfe, 0xfe, 0xfd, 0xf8, 0xfe, + 0xfe, 0xfd, 0xf8, 0xfe, 0xfe, 0xfd, 0xf8, 0xfe, 0xfe, 0xfd, 0xf8, 0xfe, 0xfe, 0xfd, 0xf8, 0xfe, + 0xfe, 0xfd, 0xf8, 0xfe, 0xfe, 0xfd, 0xf8, 0xfe, 0xfe, 0xfd, 0xf8, 0xfe, 0xfa, 0xf8, 0xf3, 0xfb, + 0xcc, 0xc6, 0xc0, 0xe8, 0x12, 0x10, 0x0e, 0xc4, 0x00, 0x00, 0x00, 0x1d, 0xff, 0xff, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0xb1, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xb1, 0x00, 0x00, 0x00, 0x73, + 0x00, 0x00, 0x00, 0x1b, 0xff, 0xff, 0xff, 0x00 +}; +alignas(BITMAPINFOHEADER) +static constexpr unsigned char RES_BSDR_BTN_SELECTED_HOVER[] = { + 0x28, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc4, 0x0e, 0x00, 0x00, 0xc4, 0x0e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x43, + 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x43, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x38, 0x08, 0x07, 0x01, 0xff, 0xbd, 0xb7, 0x1a, 0xfc, + 0xf9, 0xf6, 0x18, 0xfe, 0xff, 0xfc, 0x15, 0xff, 0xff, 0xfc, 0x15, 0xff, 0xff, 0xfc, 0x15, 0xff, + 0xff, 0xfc, 0x15, 0xff, 0xff, 0xfc, 0x15, 0xff, 0xff, 0xfc, 0x15, 0xff, 0xff, 0xfc, 0x15, 0xff, + 0xff, 0xfc, 0x15, 0xff, 0xff, 0xfc, 0x15, 0xff, 0xff, 0xfc, 0x15, 0xff, 0xff, 0xfc, 0x15, 0xff, + 0xff, 0xfc, 0x15, 0xff, 0xff, 0xfc, 0x15, 0xff, 0xff, 0xfc, 0x15, 0xff, 0xff, 0xfc, 0x15, 0xff, + 0xff, 0xfc, 0x15, 0xff, 0xff, 0xfc, 0x15, 0xff, 0xff, 0xfc, 0x15, 0xff, 0xff, 0xfc, 0x15, 0xff, + 0xff, 0xfc, 0x15, 0xff, 0xff, 0xfc, 0x15, 0xff, 0xff, 0xfc, 0x15, 0xff, 0xff, 0xfc, 0x15, 0xff, + 0xff, 0xfc, 0x15, 0xff, 0xff, 0xfc, 0x15, 0xff, 0xff, 0xfc, 0x15, 0xff, 0xff, 0xfc, 0x15, 0xff, + 0xf9, 0xf6, 0x18, 0xfe, 0xbd, 0xb7, 0x1a, 0xfc, 0x08, 0x07, 0x01, 0xff, 0x00, 0x00, 0x00, 0x3e, + 0x00, 0x00, 0x00, 0xc7, 0xb5, 0xac, 0x1b, 0xfc, 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, + 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, + 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, + 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, + 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, + 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, + 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, + 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, 0xfe, 0xfb, 0x1a, 0xff, + 0xfe, 0xfb, 0x1a, 0xff, 0xb8, 0xb0, 0x1b, 0xfc, 0x00, 0x00, 0x00, 0xc9, 0x00, 0x00, 0x00, 0xfd, + 0xf8, 0xf3, 0x21, 0xfe, 0xfd, 0xfa, 0x20, 0xff, 0xeb, 0xc6, 0x33, 0xe6, 0xe2, 0xaa, 0x3d, 0xd6, + 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, + 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, + 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, + 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, + 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, + 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, + 0xe2, 0xaa, 0x3d, 0xd6, 0xe2, 0xaa, 0x3d, 0xd6, 0xeb, 0xc5, 0x33, 0xe6, 0xfd, 0xfa, 0x20, 0xff, + 0xf8, 0xf3, 0x21, 0xfe, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x00, 0xff, 0xfc, 0xf9, 0x26, 0xff, + 0xfc, 0xf9, 0x26, 0xff, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, + 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, + 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, + 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, + 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, + 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, + 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, + 0xd4, 0x94, 0x29, 0xd6, 0xd4, 0x94, 0x29, 0xd6, 0xfc, 0xf9, 0x26, 0xff, 0xfc, 0xf9, 0x26, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xfc, 0xf7, 0x2e, 0xff, 0xfc, 0xf7, 0x2e, 0xff, + 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, + 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, + 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, + 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, + 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, + 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, + 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, 0xc7, 0x7e, 0x17, 0xd6, + 0xc7, 0x7e, 0x17, 0xd6, 0xfc, 0xf7, 0x2e, 0xff, 0xfc, 0xf7, 0x2e, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0xfb, 0xf6, 0x35, 0xff, 0xfb, 0xf6, 0x35, 0xff, 0xba, 0x6b, 0x09, 0xd6, + 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, + 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, + 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, + 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, + 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, + 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, + 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, 0xba, 0x6b, 0x09, 0xd6, + 0xfb, 0xf6, 0x35, 0xff, 0xfb, 0xf6, 0x35, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0xfa, 0xf5, 0x3d, 0xff, 0xfa, 0xf5, 0x3d, 0xff, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, + 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, + 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, + 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, + 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, + 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, + 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, + 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xad, 0x5a, 0x01, 0xd6, 0xfa, 0xf5, 0x3d, 0xff, + 0xfa, 0xf5, 0x3d, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xf9, 0xf4, 0x48, 0xff, + 0xf9, 0xf4, 0x48, 0xff, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, + 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, + 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, + 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, + 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, + 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, + 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, + 0x9b, 0x47, 0x01, 0xd6, 0x9b, 0x47, 0x01, 0xd6, 0xf9, 0xf4, 0x48, 0xff, 0xf9, 0xf4, 0x48, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xf8, 0xf3, 0x51, 0xff, 0xf8, 0xf3, 0x51, 0xff, + 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, + 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, + 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, + 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, + 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, + 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, + 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, 0x89, 0x36, 0x01, 0xd6, + 0x89, 0x36, 0x01, 0xd6, 0xf8, 0xf3, 0x51, 0xff, 0xf8, 0xf3, 0x51, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0xf7, 0xf2, 0x5b, 0xff, 0xf7, 0xf2, 0x5b, 0xff, 0x7d, 0x2d, 0x01, 0xd6, + 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, + 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, + 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, + 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, + 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, + 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, + 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, 0x7d, 0x2d, 0x01, 0xd6, + 0xf7, 0xf2, 0x5b, 0xff, 0xf7, 0xf2, 0x5b, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0xf6, 0xf2, 0x66, 0xff, 0xf6, 0xf2, 0x66, 0xff, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, + 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, + 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, + 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, + 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, + 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, + 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, + 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0x74, 0x26, 0x01, 0xd6, 0xf6, 0xf2, 0x66, 0xff, + 0xf6, 0xf2, 0x66, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xf6, 0xf2, 0x70, 0xff, + 0xf6, 0xf2, 0x70, 0xff, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, + 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, + 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, + 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, + 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, + 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, + 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, + 0x6e, 0x21, 0x01, 0xd6, 0x6e, 0x21, 0x01, 0xd6, 0xf6, 0xf2, 0x70, 0xff, 0xf6, 0xf2, 0x70, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xf6, 0xf1, 0x7a, 0xff, 0xf6, 0xf1, 0x7a, 0xff, + 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, + 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, + 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, + 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, + 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, + 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, + 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, 0x67, 0x1c, 0x01, 0xd1, + 0x67, 0x1c, 0x01, 0xd1, 0xf6, 0xf1, 0x7a, 0xff, 0xf6, 0xf1, 0x7a, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0xfa, 0xf6, 0x87, 0xff, 0xfa, 0xf6, 0x87, 0xff, 0xb4, 0x68, 0x2a, 0xd1, + 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, + 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, + 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, + 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, + 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, + 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, + 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, 0xb4, 0x68, 0x2a, 0xd1, + 0xfa, 0xf6, 0x87, 0xff, 0xfa, 0xf6, 0x87, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0xfb, 0xf6, 0x92, 0xff, 0xfb, 0xf6, 0x92, 0xff, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, + 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, + 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, + 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, + 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, + 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, + 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, + 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xb9, 0x6e, 0x30, 0xd1, 0xfb, 0xf6, 0x92, 0xff, + 0xfb, 0xf6, 0x92, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xfb, 0xf7, 0x9d, 0xff, + 0xfb, 0xf7, 0x9d, 0xff, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, + 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, + 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, + 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, + 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, + 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, + 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, + 0xbc, 0x72, 0x36, 0xd1, 0xbc, 0x72, 0x36, 0xd1, 0xfb, 0xf7, 0x9d, 0xff, 0xfb, 0xf7, 0x9d, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xfb, 0xf7, 0xa7, 0xff, 0xfb, 0xf7, 0xa7, 0xff, + 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, + 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, + 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, + 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, + 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, + 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, + 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, 0xbd, 0x75, 0x3d, 0xd1, + 0xbd, 0x75, 0x3d, 0xd1, 0xfb, 0xf7, 0xa7, 0xff, 0xfb, 0xf7, 0xa7, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0xfb, 0xf7, 0xb1, 0xff, 0xfb, 0xf7, 0xb1, 0xff, 0xbd, 0x77, 0x44, 0xd1, + 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, + 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, + 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, + 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, + 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, + 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, + 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, 0xbd, 0x77, 0x44, 0xd1, + 0xfb, 0xf7, 0xb1, 0xff, 0xfb, 0xf7, 0xb1, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0xfb, 0xf7, 0xbc, 0xff, 0xfb, 0xf7, 0xbc, 0xff, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, + 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, + 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, + 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, + 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, + 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, + 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, + 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xbe, 0x7a, 0x4d, 0xd1, 0xfb, 0xf7, 0xbc, 0xff, + 0xfb, 0xf7, 0xbc, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xfb, 0xf7, 0xc5, 0xff, + 0xfb, 0xf7, 0xc5, 0xff, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, + 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, + 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, + 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, + 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, + 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, + 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, + 0xc0, 0x80, 0x57, 0xd1, 0xc0, 0x80, 0x57, 0xd1, 0xfb, 0xf7, 0xc5, 0xff, 0xfb, 0xf7, 0xc5, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xfb, 0xf8, 0xcf, 0xff, 0xfb, 0xf8, 0xcf, 0xff, + 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, + 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, + 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, + 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, + 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, + 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, + 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, 0xc5, 0x88, 0x64, 0xd1, + 0xc5, 0x88, 0x64, 0xd1, 0xfb, 0xf8, 0xcf, 0xff, 0xfb, 0xf8, 0xcf, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0xfc, 0xf9, 0xd8, 0xff, 0xfc, 0xf9, 0xd8, 0xff, 0xca, 0x94, 0x76, 0xd1, + 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, + 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, + 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, + 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, + 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, + 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, + 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, 0xca, 0x94, 0x76, 0xd1, + 0xfc, 0xf9, 0xd8, 0xff, 0xfc, 0xf9, 0xd8, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0xfc, 0xfa, 0xe1, 0xff, 0xfc, 0xfa, 0xe1, 0xff, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, + 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, + 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, + 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, + 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, + 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, + 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, + 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xd1, 0xa2, 0x88, 0xd1, 0xfc, 0xfa, 0xe1, 0xff, + 0xfc, 0xfa, 0xe1, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xfd, 0xf8, 0xf3, 0xe3, 0xfe, + 0xfd, 0xfa, 0xe9, 0xff, 0xe5, 0xc9, 0xb7, 0xe3, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, + 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, + 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, + 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, + 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, + 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, + 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, 0xd8, 0xae, 0x9b, 0xd1, + 0xd8, 0xae, 0x9b, 0xd1, 0xe5, 0xc9, 0xb7, 0xe3, 0xfd, 0xfa, 0xe9, 0xff, 0xf8, 0xf3, 0xe3, 0xfe, + 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x00, 0xc7, 0xb2, 0xac, 0xa4, 0xfb, 0xfd, 0xfb, 0xf0, 0xff, + 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, + 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, + 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, + 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, + 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, + 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, + 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, + 0xfd, 0xfb, 0xf0, 0xff, 0xfd, 0xfb, 0xf0, 0xff, 0xb2, 0xac, 0xa4, 0xfb, 0x00, 0x00, 0x00, 0xc7, + 0x00, 0x00, 0x00, 0x38, 0x08, 0x07, 0x07, 0xff, 0xaf, 0xaa, 0xa7, 0xfb, 0xf7, 0xf3, 0xee, 0xfd, + 0xfd, 0xfb, 0xf6, 0xff, 0xfd, 0xfb, 0xf6, 0xff, 0xfd, 0xfb, 0xf6, 0xff, 0xfd, 0xfb, 0xf6, 0xff, + 0xfd, 0xfb, 0xf6, 0xff, 0xfd, 0xfb, 0xf6, 0xff, 0xfd, 0xfb, 0xf6, 0xff, 0xfd, 0xfb, 0xf6, 0xff, + 0xfd, 0xfb, 0xf6, 0xff, 0xfd, 0xfb, 0xf6, 0xff, 0xfd, 0xfb, 0xf6, 0xff, 0xfd, 0xfb, 0xf6, 0xff, + 0xfd, 0xfb, 0xf6, 0xff, 0xfd, 0xfb, 0xf6, 0xff, 0xfd, 0xfb, 0xf6, 0xff, 0xfd, 0xfb, 0xf6, 0xff, + 0xfd, 0xfb, 0xf6, 0xff, 0xfd, 0xfb, 0xf6, 0xff, 0xfd, 0xfb, 0xf6, 0xff, 0xfd, 0xfb, 0xf6, 0xff, + 0xfd, 0xfb, 0xf6, 0xff, 0xfd, 0xfb, 0xf6, 0xff, 0xfd, 0xfb, 0xf6, 0xff, 0xfd, 0xfb, 0xf6, 0xff, + 0xfd, 0xfb, 0xf6, 0xff, 0xfd, 0xfb, 0xf6, 0xff, 0xfd, 0xfb, 0xf6, 0xff, 0xf7, 0xf3, 0xee, 0xfd, + 0xaf, 0xaa, 0xa7, 0xfb, 0x08, 0x07, 0x07, 0xff, 0x00, 0x00, 0x00, 0x38, 0xff, 0xff, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xc4, + 0x00, 0x00, 0x00, 0x33, 0xff, 0xff, 0xff, 0x00 +}; + +void GetString(UINT uID, LPWSTR lpBuffer, int cchBufferMax, bool forceHardcoded = false) { + if (isUsingHardcodedRes || !hResDll || forceHardcoded) { + LPCWSTR str = nullptr; + switch (uID) { + case IDS_BSDR_DESC_LOGOFF: + str = L"To close the program that is preventing Windows from\nlogging off, click Cancel, and then close the program."; + break; + case IDS_BSDR_BTN_LOGOFF: + str = L"&Force log off"; + break; + case IDS_BSDR_DESC_RESTART: + str = L"To close the program that is preventing Windows from\nrestarting, click Cancel, and then close the program."; + break; + case IDS_BSDR_BTN_RESTART: + str = L"&Force restart"; + break; + case IDS_BSDR_BLOCKINGAPP_SHUTDOWN: + str = L"This program is preventing Windows from shutting down."; + break; + case IDS_BSDR_BLOCKINGAPP_LOGOFF: + str = L"This program is preventing Windows from logging off."; + break; + case IDS_BSDR_BLOCKINGAPP_RESTART: + str = L"This program is preventing Windows from restarting."; + break; + case IDS_BSDR_WAITINGFOR: + str = L"(Waiting for)"; + break; + case IDS_BSDR_BLOCKING_BGAPPS: + str = L"Waiting for background programs to close."; + break; + case IDS_BSDR_WARNING_LOGOFF: + str = L"If you force log off you may lose work that you haven't saved.\nDo you still want to force log off?"; + break; + case IDS_BSDR_WARNING_RESTART: + str = L"If you force restart you may lose work that you haven't saved.\nDo you still want to force restart?"; + break; + case IDS_BSDR_BLOCKINGAPPCOUNT_MULTI: + str = L"%d programs still need to close:"; + break; + case IDS_BSDR_BLOCKINGAPPCOUNT_SINGLE: + str = L"1 program still needs to close:"; + break; + default: + str = L"Error: Unknown string!"; + break; + } + wcsncpy_s(lpBuffer, cchBufferMax, str, _TRUNCATE); + } else { + if (LoadStringW(hResDll, uID, lpBuffer, cchBufferMax) == 0) { // load failed + Wh_Log(L"Failed to load string from DLL; falling back to hardcoded one..."); + GetString(uID, lpBuffer, cchBufferMax, true); + } + } +} +#pragma endregion Resources + +#pragma region logoncontroller.h and CustomBSDR.h +struct DECLSPEC_NOVTABLE IShutdownBlockingApp : IInspectable { + virtual HRESULT STDMETHODCALLTYPE get_Icon(ABI::Windows::Storage::Streams::IRandomAccessStream**) PURE; + virtual HRESULT STDMETHODCALLTYPE get_Caption(HSTRING*) PURE; + virtual HRESULT STDMETHODCALLTYPE get_BlockReason(HSTRING*) PURE; + virtual HRESULT STDMETHODCALLTYPE get_IsBlocking(BOOLEAN*) PURE; + virtual HRESULT STDMETHODCALLTYPE get_Id(UINT*) PURE; +}; + +enum LogonUIState { + LogonUIState_Start = 0, + LogonUIState_Welcome = 1, + LogonUIState_Logon = 2, + LogonUIState_Authenticate = 3, + LogonUIState_LoggingOn = 4, + LogonUIState_LoggingOff = 5, + LogonUIState_StandingBy = 6, + LogonUIState_ShuttingDown = 7, + LogonUIState_Restarting = 8, +}; + +enum BlockedShutdownResolution { + BlockedShutdownResolution_None = 0, + BlockedShutdownResolution_Force = 1, + BlockedShutdownResolution_Cancel = 2, +}; + +struct IBlockedShutdownResolverUX : IInspectable { + virtual HRESULT STDMETHODCALLTYPE Start(void*, void*) PURE; + virtual HRESULT STDMETHODCALLTYPE get_ScaleFactor(UINT*) PURE; + virtual HRESULT STDMETHODCALLTYPE get_WasClicked(BOOLEAN*) PURE; + virtual HRESULT STDMETHODCALLTYPE AddApplication(IShutdownBlockingApp*) PURE; + virtual HRESULT STDMETHODCALLTYPE RemoveApplication(UINT) PURE; + virtual HRESULT STDMETHODCALLTYPE add_Resolved(ABI::Windows::Foundation::ITypedEventHandler*, EventRegistrationToken*) PURE; + virtual HRESULT STDMETHODCALLTYPE remove_Resolved(EventRegistrationToken) PURE; + virtual HRESULT STDMETHODCALLTYPE Hide() PURE; + virtual HRESULT STDMETHODCALLTYPE Stop() PURE; +}; + +MIDL_INTERFACE("00915c44-56ec-46c4-a0bd-154182b2a893") +ILogonUIStateInfo : IInspectable { + virtual HRESULT get_CurrentLogonUIState(LogonUIState*) PURE; +}; + +// Windhawk lacks some WinRT header files needed for EventSource so just handle it with std map +using ResolvedHandler = ABI::Windows::Foundation::ITypedEventHandler; +Microsoft::WRL::ComPtr _Resolved; +mutex resolvedMutex; +BlockedShutdownResolution resolvedValue = BlockedShutdownResolution_None; +bool _wasClicked = false; + +void Resolve(BlockedShutdownResolution resolution) { + Microsoft::WRL::ComPtr resolvedLocal; + + { + lock_guard lock(resolvedMutex); + _wasClicked = true; + resolvedValue = resolution; + resolvedLocal = _Resolved; + } + + if (resolvedLocal) { + resolvedLocal->Invoke(nullptr, resolution); + } +} + +namespace CustomBSDR { + void Start(LogonUIState state); + void Hide(); + void Stop(); + void AddApplication(IShutdownBlockingApp* app); + void RemoveApplication(UINT appId); + + static LogonUIState _logonUIState = LogonUIState::LogonUIState_LoggingOff; + + // handles + static HANDLE hThread = nullptr; + static HANDLE hStopEvent = nullptr; + static HWND hDlg = nullptr; + static HWND hBgWnd = nullptr; + static HWND hTitleText = nullptr; + static HWND hAppList = nullptr; + static HWND hAppListScroll = nullptr; + static HWND hScrollBar = nullptr; + static HWND hWarningText = nullptr; + static HWND hForceButton = nullptr; + static HWND hCancelButton = nullptr; + static HWND hDescText = nullptr; + static HWND hYesButton = nullptr; + static HWND hNoButton = nullptr; + static HWND hHoverButton = nullptr; + static HFONT hTitleFont = nullptr; + static HFONT hDescFont = nullptr; + + // functions + void CenterWindow(HWND hWnd); + bool IsHighContrast(); + HBITMAP LoadAlphaBitmap(UINT resourceId, bool forceHardcoded = false); + void DrawSeparator(HDC hdc, LPDRAWITEMSTRUCT pDIS); + void DrawButton(HDC hdc, LPDRAWITEMSTRUCT pDIS); + void CreateAppTileControls(IShutdownBlockingApp* blockingApp); + void RemoveAppTileControls(UINT appId); + void UpdateAppListLayout(); + LRESULT CALLBACK ButtonSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData); + LRESULT CALLBACK AppListSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData); + INT_PTR CALLBACK DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); + LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + DWORD WINAPI ThreadProc(LPVOID lpParameter); + + // bitmaps + static HBITMAP bgBitmap = nullptr; + static HBITMAP separatorBitmap = nullptr; + static HBITMAP btnNormalBitmap = nullptr; + static HBITMAP btnHoverBitmap = nullptr; + static HBITMAP btnPressedBitmap = nullptr; + static HBITMAP btnSelectedBitmap = nullptr; + static HBITMAP btnSelectedHoverBitmap = nullptr; + + // variables + static int bgOffsetX = 0; + static int bgOffsetY = 0; + static int bgWidth = 0; + static int bgHeight = 0; + static int scrollPos = 0; + static int totalContentHeight = 0; + static bool isOnSecureDesktop = true; + + // app list data stuff + struct AppTile { + UINT appId; + BOOLEAN isBlocking; + HWND hIcon; + HWND hTitle; + HWND hBlockReason; + HBITMAP hIconBitmap; + Microsoft::WRL::ComPtr app; + }; + static vector appTiles; + static vector> pendingApps; +}; +#pragma endregion logoncontroller.h and CustomBSDR.h + +#pragma region wicutil.cpp +enum LOAD_IMAGE_WITH_WIC_OPTION { + LIWW_NONE, + LIWW_ORIENTATE, + LIWW_ORIENTATE_AND_CACHE, +}; + +bool WICIsOrientationSupported(IWICBitmapDecoder* pDecoder) { + const GUID GUID_ContainerFormatJpegXL = { 0xfec14e3f, 0x427a, 0x4736, { 0xaa, 0xe6, 0x27, 0xed, 0x84, 0xf6, 0x93, 0x22 } }; + + GUID guidContainerFormat = {}; + if (FAILED(pDecoder->GetContainerFormat(&guidContainerFormat))) + return false; + + return guidContainerFormat == GUID_ContainerFormatJpeg + || guidContainerFormat == GUID_ContainerFormatAdng + || guidContainerFormat == GUID_ContainerFormatHeif + || guidContainerFormat == GUID_ContainerFormatRaw + || guidContainerFormat == GUID_ContainerFormatJpegXL; +} + +HRESULT WICGetTransformOptionFromMetadata(IWICMetadataQueryReader* pQueryReader, WICBitmapTransformOptions* pOptions) { + *pOptions = WICBitmapTransformRotate0; + + PROPVARIANT varOrientation = {}; + HRESULT hr = pQueryReader->GetMetadataByName(L"System.Photo.Orientation", &varOrientation); + if (hr == WINCODEC_ERR_PROPERTYNOTFOUND || varOrientation.vt != VT_UI2) { + *pOptions = WICBitmapTransformRotate0; + hr = S_OK; + } else if (SUCCEEDED(hr)) { + switch (varOrientation.uiVal) { + case 1: + *pOptions = WICBitmapTransformRotate0; + break; + case 2: + *pOptions = WICBitmapTransformFlipHorizontal; + break; + case 3: + *pOptions = WICBitmapTransformRotate180; + break; + case 4: + *pOptions = WICBitmapTransformFlipVertical; + break; + case 5: + *pOptions = (WICBitmapTransformOptions)(WICBitmapTransformFlipVertical | WICBitmapTransformRotate90); + break; + case 6: + *pOptions = WICBitmapTransformRotate90; + break; + case 7: + *pOptions = (WICBitmapTransformOptions)(WICBitmapTransformFlipVertical | WICBitmapTransformRotate270); + break; + case 8: + *pOptions = WICBitmapTransformRotate270; + break; + default: + *pOptions = WICBitmapTransformRotate0; + break; + } + } + PropVariantClear(&varOrientation); + return hr; +} + +HRESULT WICCreateCachedOrientedBitmapSource(IWICImagingFactory* pFactory, IWICBitmapSource* pSourceBitmap, + IWICMetadataQueryReader* pQueryReader, IWICBitmapSource** ppOutputBitmap) { + *ppOutputBitmap = nullptr; + + WICBitmapTransformOptions transformOptions = WICBitmapTransformRotate0; + HRESULT hr = WICGetTransformOptionFromMetadata(pQueryReader, &transformOptions); + if (SUCCEEDED(hr)) { + if (transformOptions == WICBitmapTransformRotate0) { + *ppOutputBitmap = pSourceBitmap; + pSourceBitmap->AddRef(); + } else { + IWICBitmapFlipRotator* pFlipRotator = nullptr; + hr = pFactory->CreateBitmapFlipRotator(&pFlipRotator); + if (SUCCEEDED(hr)) { + if ((transformOptions & (WICBitmapTransformRotate90 | WICBitmapTransformFlipVertical)) != 0 || transformOptions == WICBitmapTransformRotate180) { + IWICBitmap* pCachedBitmap = nullptr; + hr = pFactory->CreateBitmapFromSource(pSourceBitmap, WICBitmapCacheOnLoad, &pCachedBitmap); + if (SUCCEEDED(hr)) { + hr = pFlipRotator->Initialize(pCachedBitmap, transformOptions); + pCachedBitmap->Release(); + } + } else { + hr = pFlipRotator->Initialize(pSourceBitmap, transformOptions); + } + + if (SUCCEEDED(hr)) { + *ppOutputBitmap = pFlipRotator; + pFlipRotator->AddRef(); + } + + pFlipRotator->Release(); + } + } + } + + return hr; +} + +HRESULT WICCreateOrientedBitmapSource(IWICImagingFactory* pFactory, IWICBitmapSource* pSourceBitmap, + IWICMetadataQueryReader* pQueryReader, IWICBitmapSource** ppOrientedBitmap) { + *ppOrientedBitmap = nullptr; + + WICBitmapTransformOptions transformOptions = WICBitmapTransformRotate0; + HRESULT hr = WICGetTransformOptionFromMetadata(pQueryReader, &transformOptions); + if (SUCCEEDED(hr)) { + IWICBitmapFlipRotator* pFlipRotator = nullptr; + hr = pFactory->CreateBitmapFlipRotator(&pFlipRotator); + if (SUCCEEDED(hr)) { + hr = pFlipRotator->Initialize(pSourceBitmap, transformOptions); + if (SUCCEEDED(hr)) { + *ppOrientedBitmap = pFlipRotator; + pFlipRotator->AddRef(); + } + + pFlipRotator->Release(); + } + } + + return hr; +} + +HRESULT WICOrientateFrame(IWICImagingFactory* pFactory, IWICBitmapDecoder* pDecoder, + IWICBitmapFrameDecode* pFrameDecode, bool fCacheBitmap, IWICBitmapSource** ppOutputBitmap) { + *ppOutputBitmap = nullptr; + HRESULT hr = S_OK; + + if (WICIsOrientationSupported(pDecoder)) { + IWICMetadataQueryReader* pQueryReader; + pQueryReader = nullptr; + if (SUCCEEDED(pFrameDecode->GetMetadataQueryReader(&pQueryReader))) { + if (fCacheBitmap) { + hr = WICCreateCachedOrientedBitmapSource(pFactory, pFrameDecode, pQueryReader, ppOutputBitmap); + } else { + hr = WICCreateOrientedBitmapSource(pFactory, pFrameDecode, pQueryReader, ppOutputBitmap); + } + pQueryReader->Release(); + } else { + *ppOutputBitmap = pFrameDecode; + pFrameDecode->AddRef(); + } + } else { + *ppOutputBitmap = pFrameDecode; + pFrameDecode->AddRef(); + } + + return hr; +} + +HRESULT LoadImageWithWIC(IWICImagingFactory* pWICImagingFactory, IStream* pStream, LOAD_IMAGE_WITH_WIC_OPTION option, + IWICBitmapSource** ppWICBitmapSource, IWICBitmapFrameDecode** ppWICBitmapFrameDecode, GUID* pguidContainerFormat) { + *ppWICBitmapSource = nullptr; + if (ppWICBitmapFrameDecode) + *ppWICBitmapFrameDecode = nullptr; + if (pguidContainerFormat) + *pguidContainerFormat = GUID_NULL; + + HRESULT hr = S_OK; + + Microsoft::WRL::ComPtr spWICImagingFactory(pWICImagingFactory); + if (!spWICImagingFactory.Get()) { + hr = CoCreateInstance( + CLSID_WICImagingFactory2, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spWICImagingFactory)); + } + if (SUCCEEDED(hr)) { + Microsoft::WRL::ComPtr spDecoder; + hr = spWICImagingFactory->CreateDecoderFromStream( + pStream, &GUID_VendorMicrosoftBuiltIn, WICDecodeMetadataCacheOnDemand, &spDecoder); + if (SUCCEEDED(hr) && pguidContainerFormat) { + hr = spDecoder->GetContainerFormat(pguidContainerFormat); + } + if (SUCCEEDED(hr)) { + Microsoft::WRL::ComPtr spBitmapFrameDecode; + hr = spDecoder->GetFrame(0, &spBitmapFrameDecode); + if (SUCCEEDED(hr)) { + if (option != LIWW_NONE) { + hr = WICOrientateFrame( + spWICImagingFactory.Get(), spDecoder.Get(), spBitmapFrameDecode.Get(), + option == LIWW_ORIENTATE_AND_CACHE, ppWICBitmapSource); + } else { + hr = spBitmapFrameDecode.CopyTo(ppWICBitmapSource); + } + } + if (SUCCEEDED(hr) && ppWICBitmapFrameDecode) { + *ppWICBitmapFrameDecode = spBitmapFrameDecode.Detach(); + } + } + } + + return hr; +} + +HRESULT LoadImageWithWIC(IWICImagingFactory* pWICImagingFactory, LPCWSTR pszPath, LOAD_IMAGE_WITH_WIC_OPTION option, + IWICBitmapSource** ppWICBitmapSource, IWICBitmapFrameDecode** ppWICBitmapFrameDecode, GUID* pguidContainerFormat) { + *ppWICBitmapSource = nullptr; + if (ppWICBitmapFrameDecode) + *ppWICBitmapFrameDecode = nullptr; + if (pguidContainerFormat) + *pguidContainerFormat = GUID_NULL; + + HRESULT hr = S_OK; + + Microsoft::WRL::ComPtr spWICImagingFactory(pWICImagingFactory); + if (!spWICImagingFactory.Get()) { + hr = CoCreateInstance(CLSID_WICImagingFactory2, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spWICImagingFactory)); + } + if (SUCCEEDED(hr)) { + Microsoft::WRL::ComPtr spDecoder; + hr = spWICImagingFactory->CreateDecoderFromFilename( + pszPath, &GUID_VendorMicrosoftBuiltIn, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &spDecoder); + if (SUCCEEDED(hr) && pguidContainerFormat) { + hr = spDecoder->GetContainerFormat(pguidContainerFormat); + } + if (SUCCEEDED(hr)) { + Microsoft::WRL::ComPtr spBitmapFrameDecode; + hr = spDecoder->GetFrame(0, &spBitmapFrameDecode); + if (SUCCEEDED(hr)) { + if (option != LIWW_NONE) { + hr = WICOrientateFrame( + spWICImagingFactory.Get(), spDecoder.Get(), spBitmapFrameDecode.Get(), + option == LIWW_ORIENTATE_AND_CACHE, ppWICBitmapSource); + } else { + hr = spBitmapFrameDecode.CopyTo(ppWICBitmapSource); + } + } + if (SUCCEEDED(hr) && ppWICBitmapFrameDecode) { + *ppWICBitmapFrameDecode = spBitmapFrameDecode.Detach(); + } + } + } + + return hr; +} + +HRESULT ConvertWICBitmapPixelFormat(IWICImagingFactory* pWICImagingFactory, IWICBitmapSource* pWICBitmapSource, + GUID guidPixelFormatSource, WICBitmapDitherType bitmapDitherType, IWICBitmapSource** ppWICBitmapSource) { + *ppWICBitmapSource = nullptr; + + Microsoft::WRL::ComPtr spWICFormatConverter; + HRESULT hr = pWICImagingFactory->CreateFormatConverter(&spWICFormatConverter); + if (SUCCEEDED(hr)) { + hr = spWICFormatConverter->Initialize( + pWICBitmapSource, guidPixelFormatSource, bitmapDitherType, nullptr, 0.0, WICBitmapPaletteTypeCustom); + if (SUCCEEDED(hr)) { + hr = spWICFormatConverter->QueryInterface(IID_PPV_ARGS(ppWICBitmapSource)); + } + } + + return hr; +} + +HRESULT Convert32bppWICBitmapSourceToHBITMAP(IWICBitmapSource* pWICBitmapSource, HBITMAP* phbmBitmap) { + *phbmBitmap = nullptr; + + UINT uWidth, uHeight; + HRESULT hr = pWICBitmapSource->GetSize(&uWidth, &uHeight); + if (SUCCEEDED(hr)) { + BITMAPINFO bmi = {}; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = (LONG)uWidth; + bmi.bmiHeader.biHeight = -(LONG)uHeight; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + + void* pBits; + + HBITMAP hBitmap = CreateDIBSection(nullptr, &bmi, DIB_RGB_COLORS, &pBits, nullptr, 0); + if (hBitmap) { + WICRect rect = { 0, 0, (INT)uWidth, (INT)uHeight }; + hr = pWICBitmapSource->CopyPixels(&rect, 4 * uWidth, 4 * uHeight * uWidth, (BYTE*)pBits); + if (SUCCEEDED(hr)) { + *phbmBitmap = hBitmap; + } else { + DeleteObject(hBitmap); + } + } else { + hr = E_OUTOFMEMORY; + } + } + + return hr; +} + +HRESULT ConvertWICBitmapToHBITMAP(IWICImagingFactory* pWICImagingFactory, IWICBitmapSource* pWICBitmapSource, + HBITMAP* phbmImage) { + *phbmImage = nullptr; + HRESULT hr = S_OK; + + Microsoft::WRL::ComPtr spWICImagingFactory = pWICImagingFactory; + if (!spWICImagingFactory.Get()) { + hr = CoCreateInstance(CLSID_WICImagingFactory2, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spWICImagingFactory)); + } + if (SUCCEEDED(hr)) { + Microsoft::WRL::ComPtr spBitmapSourceConverted; + hr = ConvertWICBitmapPixelFormat( + spWICImagingFactory.Get(), pWICBitmapSource, GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone, &spBitmapSourceConverted); + if (SUCCEEDED(hr)) { + hr = Convert32bppWICBitmapSourceToHBITMAP(spBitmapSourceConverted.Get(), phbmImage); + } + } + + return hr; +} + +HRESULT GetBitmapFromRandomStream(Microsoft::WRL::ComPtr stream, HBITMAP* outBitmap) { + Microsoft::WRL::ComPtr spStream; + RETURN_IF_FAILED(CreateStreamOverRandomAccessStream(stream.Get(), IID_PPV_ARGS(&spStream))); + + Microsoft::WRL::ComPtr spWICFactory; + RETURN_IF_FAILED(CoCreateInstance(CLSID_WICImagingFactory2, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spWICFactory))); + + Microsoft::WRL::ComPtr spWICBitmapSource; + RETURN_IF_FAILED(LoadImageWithWIC(spWICFactory.Get(), spStream.Get(), LIWW_NONE, &spWICBitmapSource, nullptr, nullptr)); + + RETURN_IF_FAILED(ConvertWICBitmapToHBITMAP(spWICFactory.Get(), spWICBitmapSource.Get(), outBitmap)); + return S_OK; +} +#pragma endregion wicutil.cpp + +#pragma region CustomBSDR.cpp +void CustomBSDR::CenterWindow(HWND hWnd) { + RECT rcWindow, rcWorkArea; + GetWindowRect(hWnd, &rcWindow); + SystemParametersInfoW(SPI_GETWORKAREA, 0, &rcWorkArea, 0); + int windowWidth = rcWindow.right - rcWindow.left; + int windowHeight = rcWindow.bottom - rcWindow.top; + int xPos = rcWorkArea.left + (rcWorkArea.right - rcWorkArea.left - windowWidth) / 2 - GetSystemMetrics(SM_XVIRTUALSCREEN); + int yPos = rcWorkArea.top + (rcWorkArea.bottom - rcWorkArea.top - windowHeight) / 2 - GetSystemMetrics(SM_YVIRTUALSCREEN); + SetWindowPos(hWnd, 0, xPos, yPos, 0, 0, SWP_NOZORDER | SWP_NOSIZE); +} + +bool CustomBSDR::IsHighContrast() { + HIGHCONTRASTW highContrast = {}; + highContrast.cbSize = sizeof(HIGHCONTRASTW); + if (SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(highContrast), &highContrast, 0)) { + return (highContrast.dwFlags & HCF_HIGHCONTRASTON) != 0; + } + return false; +} + +// Load winsrv alpha bitmaps properly +HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { + const void* pResourceData = nullptr; + + if (forceHardcoded) { + Wh_Log(L"Failed to load bitmap from DLL; falling back to hardcoded one..."); + } + + if (isUsingHardcodedRes || !hResDll || forceHardcoded) { + switch (resourceId) { + case IDB_BSDR_SEPARATOR: + pResourceData = RES_BSDR_SEPARATOR; + break; + case IDB_BSDR_BTN_NORMAL: + pResourceData = RES_BSDR_BTN_NORMAL; + break; + case IDB_BSDR_BTN_HOVER: + pResourceData = RES_BSDR_BTN_HOVER; + break; + case IDB_BSDR_BTN_PRESSED: + pResourceData = RES_BSDR_BTN_PRESSED; + break; + case IDB_BSDR_BTN_SELECTED: + pResourceData = RES_BSDR_BTN_SELECTED; + break; + case IDB_BSDR_BTN_SELECTED_HOVER: + pResourceData = RES_BSDR_BTN_SELECTED_HOVER; + break; + default: + Wh_Log(L"Unknown image resource ID: %d", resourceId); + return nullptr; + } + } else { + HRSRC hResource = FindResourceW(hResDll, MAKEINTRESOURCEW(resourceId), RT_BITMAP); + if (!hResource) + return CustomBSDR::LoadAlphaBitmap(resourceId, true); + + HGLOBAL hGlobal = LoadResource(hResDll, hResource); + if (!hGlobal) + return CustomBSDR::LoadAlphaBitmap(resourceId, true); + + pResourceData = LockResource(hGlobal); + if (!pResourceData) + return CustomBSDR::LoadAlphaBitmap(resourceId, true); + } + + const BITMAPINFO* pBitmapInfo = static_cast(pResourceData); + const BITMAPINFOHEADER* pHeader = &pBitmapInfo->bmiHeader; + + if (pHeader->biBitCount != 32) { + if (isUsingHardcodedRes || !hResDll) { + Wh_Log(L"Image load error: not 32bpp"); + return nullptr; + } + return LoadBitmapW(hResDll, MAKEINTRESOURCEW(resourceId)); + } + + int width = pHeader->biWidth; + int height = abs(pHeader->biHeight); + bool isTopDown = pHeader->biHeight < 0; + + BITMAPINFO bmi = {}; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = -height; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + + void* pBits = nullptr; + HBITMAP hBitmap = CreateDIBSection(nullptr, &bmi, DIB_RGB_COLORS, &pBits, nullptr, 0); + + if (hBitmap && pBits) { + DWORD dwBitsOffset = pHeader->biSize; + if (pHeader->biCompression == BI_BITFIELDS) { + dwBitsOffset += 12; + } + + const unsigned char* pSrc = static_cast(pResourceData) + dwBitsOffset; + DWORD dwSize = width * height * 4; + + if (isTopDown) { + memcpy(pBits, pSrc, dwSize); + } else { + for (int y = 0; y < height; y++) { + memcpy((unsigned char*)pBits + y * width * 4, + pSrc + (height - 1 - y) * width * 4, + width * 4); + } + } + + unsigned char* pPixels = (unsigned char*)pBits; + for (int i = 0; i < width * height; i++) { + unsigned char b = pPixels[i * 4 + 0]; + unsigned char g = pPixels[i * 4 + 1]; + unsigned char r = pPixels[i * 4 + 2]; + unsigned char a = pPixels[i * 4 + 3]; + + pPixels[i * 4 + 0] = (b * a) / 255; + pPixels[i * 4 + 1] = (g * a) / 255; + pPixels[i * 4 + 2] = (r * a) / 255; + } + } else { + Wh_Log(L"Image load error: no hBitmap/pBits"); + } + + return hBitmap; +} + +void CustomBSDR::DrawSeparator(HDC hdc, LPDRAWITEMSTRUCT pDIS) { + if (separatorBitmap && !IsHighContrast()) { + BITMAP bm; + GetObject(separatorBitmap, sizeof(bm), &bm); + + int width = pDIS->rcItem.right - pDIS->rcItem.left; + int height = pDIS->rcItem.bottom - pDIS->rcItem.top; + + BITMAPINFO bmi = {}; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = -height; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + + void* pBits = nullptr; + HBITMAP hOffscreenBmp = CreateDIBSection(pDIS->hDC, &bmi, DIB_RGB_COLORS, &pBits, nullptr, 0); + if (hOffscreenBmp) { + HDC hdcOffscreen = CreateCompatibleDC(pDIS->hDC); + HBITMAP hOldOffscreen = (HBITMAP)SelectObject(hdcOffscreen, hOffscreenBmp); + + // Proper alpha blending with the background bitmap + if (bgBitmap) { + POINT pt = { pDIS->rcItem.left, pDIS->rcItem.top }; + MapWindowPoints(pDIS->hwndItem, hBgWnd, &pt, 1); + pt.x -= bgOffsetX; + pt.y -= bgOffsetY; + + HDC hdcBg = CreateCompatibleDC(pDIS->hDC); + HBITMAP hOldBg = (HBITMAP)SelectObject(hdcBg, bgBitmap); + BitBlt(hdcOffscreen, 0, 0, width, height, hdcBg, pt.x, pt.y, SRCCOPY); + SelectObject(hdcBg, hOldBg); + DeleteDC(hdcBg); + } + + HDC hdcSep = CreateCompatibleDC(pDIS->hDC); + HBITMAP hOldSep = (HBITMAP)SelectObject(hdcSep, separatorBitmap); + + BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; + + // Skipped leftmost transparent pixels * 4 + opacity gradient pixels on left * 19 + fully opaque center pixels * 8 + opacity gradient pixels on right * 19 = + // 4 + 19 + 8 + 19 = 50 (total width of the source bitmap) + // Original Win7 shutdown resolver somehow decided to shrink the 19 pixels part to just 6 pixels + // Also somehow the leftmost pixel is skipped and drawing begins from the second one + const int srcLeftSkip = 4; + const int srcLeftGradient = 19; + const int srcCenter = 8; + const int srcRightGradient = 19; + + const int dstLeftSkip = 1; + const int dstLeftGradient = 6; + const int dstRightGradient = 6; + const int dstCenter = width - dstLeftSkip - dstLeftGradient - dstRightGradient; + + // Left gradient + AlphaBlend(hdcOffscreen, dstLeftSkip, 0, dstLeftGradient, 1, + hdcSep, srcLeftSkip, 1, srcLeftGradient, 1, bf); + + // Center + AlphaBlend(hdcOffscreen, dstLeftSkip + dstLeftGradient, 0, dstCenter, 1, + hdcSep, srcLeftSkip + srcLeftGradient, 1, srcCenter, 1, bf); + + // Right gradient + AlphaBlend(hdcOffscreen, dstLeftSkip + dstLeftGradient + dstCenter, 0, dstRightGradient, 1, + hdcSep, srcLeftSkip + srcLeftGradient + srcCenter, 1, srcRightGradient, 1, bf); + + SelectObject(hdcSep, hOldSep); + DeleteDC(hdcSep); + + BitBlt(pDIS->hDC, pDIS->rcItem.left, pDIS->rcItem.top, width, height, hdcOffscreen, 0, 0, SRCCOPY); + + SelectObject(hdcOffscreen, hOldOffscreen); + DeleteDC(hdcOffscreen); + DeleteObject(hOffscreenBmp); + } + } else { + HBRUSH hBrush = CreateSolidBrush(GetSysColor(COLOR_WINDOWTEXT)); + RECT lineRect = { pDIS->rcItem.left, pDIS->rcItem.top, pDIS->rcItem.right, pDIS->rcItem.top + 1 }; + FillRect(pDIS->hDC, &lineRect, hBrush); + DeleteObject(hBrush); + } +} + +void CustomBSDR::DrawButton(HDC hdc, LPDRAWITEMSTRUCT pDIS) { + bool isPressed = (pDIS->itemState & ODS_SELECTED); + bool isFocused = (pDIS->itemState & ODS_FOCUS); + bool isHighContrast = IsHighContrast(); + + RECT rcButton = pDIS->rcItem; + + if (isHighContrast) { + UINT uState = DFCS_BUTTONPUSH; + if (isPressed) + uState |= DFCS_PUSHED; + DrawFrameControl(hdc, &rcButton, DFC_BUTTON, uState); + } else { + HBITMAP hBitmap = nullptr; + bool isHover = (hHoverButton == pDIS->hwndItem); + + if (isPressed) { + hBitmap = btnPressedBitmap; + } else if (isFocused && isHover) { + hBitmap = btnSelectedHoverBitmap; + } else if (isFocused) { + hBitmap = btnSelectedBitmap; + } else if (isHover) { + hBitmap = btnHoverBitmap; + } else { + hBitmap = btnNormalBitmap; + } + + if (hBitmap) { + BITMAP bm; + GetObject(hBitmap, sizeof(bm), &bm); + + int width = rcButton.right - rcButton.left; + int height = rcButton.bottom - rcButton.top; + + const int borderSize = 4; + const int borderSizeDraw = MulDiv(borderSize, GetDpiForWindow(hDlg), 96); + + int srcW = bm.bmWidth; + int srcH = bm.bmHeight; + int dstW = width; + int dstH = height; + + int left = rcButton.left; + int top = rcButton.top; + + BITMAPINFO bmi = {}; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = dstW; + bmi.bmiHeader.biHeight = -dstH; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + + void* pBits = nullptr; + HBITMAP hOffscreenBmp = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &pBits, nullptr, 0); + if (hOffscreenBmp) { + HDC hdcOffscreen = CreateCompatibleDC(hdc); + HBITMAP hOldOffscreen = (HBITMAP)SelectObject(hdcOffscreen, hOffscreenBmp); + + // Proper alpha blending with the background bitmap + if (bgBitmap) { + POINT pt = { rcButton.left, rcButton.top }; + MapWindowPoints(pDIS->hwndItem, hBgWnd, &pt, 1); + pt.x -= bgOffsetX; + pt.y -= bgOffsetY; + + HDC hdcBg = CreateCompatibleDC(hdc); + HBITMAP hOldBg = (HBITMAP)SelectObject(hdcBg, bgBitmap); + BitBlt(hdcOffscreen, 0, 0, dstW, dstH, hdcBg, pt.x, pt.y, SRCCOPY); + SelectObject(hdcBg, hOldBg); + DeleteDC(hdcBg); + } + + HDC hdcSrc = CreateCompatibleDC(hdc); + HBITMAP hOldSrc = (HBITMAP)SelectObject(hdcSrc, hBitmap); + + BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; + + // 1 2 3 + // 4 5 6 + // 7 8 9 + // Corners + AlphaBlend(hdcOffscreen, 0, 0, borderSizeDraw, borderSizeDraw, hdcSrc, 0, 0, borderSize, borderSize, bf); // 1 + AlphaBlend(hdcOffscreen, dstW - borderSizeDraw, 0, borderSizeDraw, borderSizeDraw, hdcSrc, srcW - borderSize, 0, borderSize, borderSize, bf); // 3 + AlphaBlend(hdcOffscreen, 0, dstH - borderSizeDraw, borderSizeDraw, borderSizeDraw, hdcSrc, 0, srcH - borderSize, borderSize, borderSize, bf); // 7 + AlphaBlend(hdcOffscreen, dstW - borderSizeDraw, dstH - borderSizeDraw, borderSizeDraw, borderSizeDraw, hdcSrc, srcW - borderSize, srcH - borderSize, borderSize, borderSize, bf); // 9 + + // Edges + AlphaBlend(hdcOffscreen, borderSizeDraw, 0, dstW - 2 * borderSizeDraw, borderSizeDraw, hdcSrc, borderSize, 0, srcW - 2 * borderSize, borderSize, bf); // 2 + AlphaBlend(hdcOffscreen, borderSizeDraw, dstH - borderSizeDraw, dstW - 2 * borderSizeDraw, borderSizeDraw, hdcSrc, borderSize, srcH - borderSize, srcW - 2 * borderSize, borderSize, bf); // 8 + AlphaBlend(hdcOffscreen, 0, borderSizeDraw, borderSizeDraw, dstH - 2 * borderSizeDraw, hdcSrc, 0, borderSize, borderSize, srcH - 2 * borderSize, bf); // 4 + AlphaBlend(hdcOffscreen, dstW - borderSizeDraw, borderSizeDraw, borderSizeDraw, dstH - 2 * borderSizeDraw, hdcSrc, srcW - borderSize, borderSize, borderSize, srcH - 2 * borderSize, bf); // 6 + + // Center + AlphaBlend(hdcOffscreen, borderSizeDraw, borderSizeDraw, dstW - 2 * borderSizeDraw, dstH - 2 * borderSizeDraw, hdcSrc, borderSize, borderSize, srcW - 2 * borderSize, srcH - 2 * borderSize, bf); // 5 + + SelectObject(hdcSrc, hOldSrc); + DeleteDC(hdcSrc); + + BitBlt(hdc, left, top, dstW, dstH, hdcOffscreen, 0, 0, SRCCOPY); + + SelectObject(hdcOffscreen, hOldOffscreen); + DeleteDC(hdcOffscreen); + DeleteObject(hOffscreenBmp); + } + } + } + + wchar_t buttonText[256] = {}; + GetWindowTextW(pDIS->hwndItem, buttonText, _countof(buttonText)); + + if (wcslen(buttonText) > 0) { + if (!isHighContrast) { + SetBkMode(hdc, TRANSPARENT); + SetTextColor(hdc, RGB(255, 255, 255)); + } else { + SetBkMode(hdc, OPAQUE); + SetTextColor(hdc, GetSysColor(COLOR_BTNTEXT)); + } + UINT format = DT_CENTER | DT_VCENTER | DT_SINGLELINE; + if (GetPropW(pDIS->hwndItem, L"CustomBSDR_HideAccel")) { + format |= DT_HIDEPREFIX; + } + DrawTextW(hdc, buttonText, -1, &rcButton, format); + } + + if (isHighContrast && isFocused && !GetPropW(pDIS->hwndItem, L"CustomBSDR_HideFocus")) { + int cxEdge = 2 * GetSystemMetrics(SM_CXEDGE); + int cxBorder = GetSystemMetrics(SM_CXBORDER) + cxEdge; + int cyEdge = 2 * GetSystemMetrics(SM_CYEDGE); + int cyBorder = GetSystemMetrics(SM_CYBORDER) + cyEdge; + + InflateRect(&rcButton, -cxBorder, -cyBorder); + DrawFocusRect(hdc, &rcButton); + } +} + +LRESULT CALLBACK CustomBSDR::ButtonSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) { + switch (uMsg) { + case WM_MOUSEMOVE: { + if (hHoverButton != hWnd) { + if (hHoverButton) { + RedrawWindow(hHoverButton, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW); + } + hHoverButton = hWnd; + RedrawWindow(hWnd, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW); + } + + TRACKMOUSEEVENT tme = { sizeof(tme) }; + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = hWnd; + TrackMouseEvent(&tme); + break; + } + case WM_MOUSELEAVE: { + if (hHoverButton == hWnd) { + hHoverButton = nullptr; + RedrawWindow(hWnd, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW); + } + break; + } + case WM_UPDATEUISTATE: { + switch (HIWORD(wParam)) { + case UISF_HIDEFOCUS: + SetPropW(hWnd, L"CustomBSDR_HideFocus", (HANDLE)(LOWORD(wParam) == UIS_SET)); + break; + case UISF_HIDEACCEL: + SetPropW(hWnd, L"CustomBSDR_HideAccel", (HANDLE)(LOWORD(wParam) == UIS_SET)); + break; + } + break; + } + case WM_NCDESTROY: { + RemoveWindowSubclass(hWnd, ButtonSubclassProc, uIdSubclass); + break; + } + } + return DefSubclassProc(hWnd, uMsg, wParam, lParam); +} + +LRESULT CALLBACK CustomBSDR::AppListSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) { + switch (uMsg) { + case WM_CTLCOLORSTATIC: { + if (!IsHighContrast()) { + SetBkMode((HDC)wParam, TRANSPARENT); + SetStretchBltMode((HDC)wParam, HALFTONE); + + HWND hControl = (HWND)lParam; + bool isBlockReason = false; + for (auto& tile : appTiles) { + if (tile.hBlockReason == hControl) { + isBlockReason = true; + break; + } + } + + if (isBlockReason) { + SetTextColor((HDC)wParam, RGB(255, 204, 153)); + } else { + SetTextColor((HDC)wParam, RGB(255, 255, 255)); + } + + return (INT_PTR)GetStockObject(NULL_BRUSH); + } + break; + } + case WM_NCDESTROY: { + RemoveWindowSubclass(hWnd, AppListSubclassProc, uIdSubclass); + break; + } + } + return DefSubclassProc(hWnd, uMsg, wParam, lParam); +} + +void CustomBSDR::CreateAppTileControls(IShutdownBlockingApp* blockingApp) { + if (!hDlg || !blockingApp) { + return; + } + + int dpi = GetDpiForWindow(hDlg); + + AppTile tile = {}; + blockingApp->get_Id(&tile.appId); + blockingApp->get_IsBlocking(&tile.isBlocking); + tile.app = blockingApp; + tile.hIconBitmap = nullptr; + + HSTRING caption, blockReason; + wstring titleText; + wstring blockReasonText; + + if (SUCCEEDED(blockingApp->get_Caption(&caption))) { + const wchar_t* captionStr = WindowsGetStringRawBuffer(caption, nullptr); + if (tile.isBlocking) { + wchar_t waitingFor[256] = {}; + GetString(IDS_BSDR_WAITINGFOR, waitingFor, _countof(waitingFor)); + titleText = wstring(waitingFor) + L" " + captionStr; + } else { + titleText = captionStr; + } + WindowsDeleteString(caption); + } + + if (SUCCEEDED(blockingApp->get_BlockReason(&blockReason))) { + const wchar_t* reasonStr = WindowsGetStringRawBuffer(blockReason, nullptr); + if (reasonStr && wcslen(reasonStr) > 0) { + blockReasonText = reasonStr; + } + WindowsDeleteString(blockReason); + } + + if (blockReasonText.empty() && tile.isBlocking) { + UINT stringId = IDS_BSDR_BLOCKINGAPP_SHUTDOWN; + if (_logonUIState == LogonUIState_LoggingOff) + stringId = IDS_BSDR_BLOCKINGAPP_LOGOFF; + else if (_logonUIState == LogonUIState_Restarting) + stringId = IDS_BSDR_BLOCKINGAPP_RESTART; + + wchar_t defaultReason[256] = {}; + GetString(stringId, defaultReason, _countof(defaultReason)); + blockReasonText = defaultReason; + } + + int iconSize = MulDiv(32, dpi, 96); + + tile.hIcon = CreateWindowExW(0, L"Static", nullptr, WS_CHILD | WS_VISIBLE | SS_BITMAP, 0, 0, iconSize, iconSize, hAppListScroll, nullptr, nullptr, nullptr); + tile.hTitle = CreateWindowExW(0, L"Static", titleText.c_str(), WS_CHILD | WS_VISIBLE | SS_ENDELLIPSIS, 0, 0, 100, 20, hAppListScroll, nullptr, nullptr, nullptr); + + if (!blockReasonText.empty()) { + tile.hBlockReason = CreateWindowExW(0, L"Static", blockReasonText.c_str(), WS_CHILD | WS_VISIBLE | SS_EDITCONTROL, 0, 0, 100, 40, hAppListScroll, nullptr, nullptr, nullptr); + } else { + tile.hBlockReason = nullptr; + } + + if (tile.hTitle) { + SendMessageW(tile.hTitle, WM_SETFONT, (WPARAM)hDescFont, FALSE); + HDC hdc = GetDC(tile.hTitle); + HFONT hOldFont = (HFONT)SelectObject(hdc, hDescFont); + SIZE textSize; + GetTextExtentPoint32W(hdc, titleText.c_str(), (int)titleText.length(), &textSize); + SelectObject(hdc, hOldFont); + ReleaseDC(tile.hTitle, hdc); + SetWindowPos(tile.hTitle, nullptr, 0, 0, textSize.cx, textSize.cy, SWP_NOMOVE | SWP_NOZORDER); // Width is corrected later + } + HFONT hFont = (HFONT)SendMessageW(hDlg, WM_GETFONT, 0, 0); + if (tile.hBlockReason) { + SendMessageW(tile.hBlockReason, WM_SETFONT, (WPARAM)hFont, FALSE); + HDC hdc = GetDC(tile.hBlockReason); + HFONT hOldFont = (HFONT)SelectObject(hdc, hFont); + SIZE textSize; + GetTextExtentPoint32W(hdc, blockReasonText.c_str(), (int)blockReasonText.length(), &textSize); + SelectObject(hdc, hOldFont); + ReleaseDC(tile.hBlockReason, hdc); + SetWindowPos(tile.hBlockReason, nullptr, 0, 0, textSize.cx, textSize.cy * 2, SWP_NOMOVE | SWP_NOZORDER); // Always two lines + } + + ABI::Windows::Storage::Streams::IRandomAccessStream* iconStream = nullptr; + if (SUCCEEDED(blockingApp->get_Icon(&iconStream)) && iconStream) { + GetBitmapFromRandomStream(iconStream, &tile.hIconBitmap); + if (tile.hIcon && tile.hIconBitmap) { + SendMessageW(tile.hIcon, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)tile.hIconBitmap); + } + iconStream->Release(); + } + + if (tile.isBlocking) { + appTiles.insert(appTiles.begin(), tile); + } else { + appTiles.push_back(tile); + } + + UpdateAppListLayout(); +} + +void CustomBSDR::RemoveAppTileControls(UINT appId) { + for (auto it = appTiles.begin(); it != appTiles.end(); ++it) { + if (it->appId == appId) { + if (it->hIcon) { + DestroyWindow(it->hIcon); + } + if (it->hTitle) { + DestroyWindow(it->hTitle); + } + if (it->hBlockReason) { + DestroyWindow(it->hBlockReason); + } + if (it->hIconBitmap) { + DeleteObject(it->hIconBitmap); + } + + appTiles.erase(it); + + UpdateAppListLayout(); + return; + } + } +} + +void CustomBSDR::UpdateAppListLayout() { + if (!hDlg) { + return; + } + + int dpi = GetDpiForWindow(hDlg); + + const int iconSize = MulDiv(32, dpi, 96); + const int topMargin = MulDiv(11, dpi, 96); + const int iconTopMargin = MulDiv(6, dpi, 96); + const int reasonTopMargin = MulDiv(25, dpi, 96); + const int itemHeight = MulDiv(83, dpi, 96); + const int itemHeightNoReason = MulDiv(62, dpi, 96); + const int iconTextGap = MulDiv(8, dpi, 96); + int maxWidth = MulDiv(700, dpi, 96); + int visibleHeight = MulDiv(300, dpi, 96); + if (hAppList) { + RECT rcContainer; + GetClientRect(hAppList, &rcContainer); + visibleHeight = rcContainer.bottom - rcContainer.top; + maxWidth = rcContainer.right - rcContainer.left - iconSize - iconTextGap; + } + + totalContentHeight = 0; + for (auto& tile : appTiles) { + totalContentHeight += (tile.hBlockReason != nullptr) ? itemHeight : itemHeightNoReason; + } + + int maxScroll = totalContentHeight - visibleHeight; + if (maxScroll < 0) maxScroll = 0; + + if (scrollPos > maxScroll) { + scrollPos = maxScroll; + } + + if (maxScroll > 0) { + ShowWindow(hScrollBar, SW_SHOW); + + SCROLLINFO si = {}; + si.cbSize = sizeof(SCROLLINFO); + si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS; + si.nMin = 0; + si.nMax = totalContentHeight; + si.nPage = visibleHeight; + si.nPos = scrollPos; + SetScrollInfo(hScrollBar, SB_CTL, &si, TRUE); + } else { + ShowWindow(hScrollBar, SW_HIDE); + scrollPos = 0; + } + + int yPos = topMargin; + + for (auto& tile : appTiles) { + bool hasBlockReason = (tile.hBlockReason != nullptr); + int height = hasBlockReason ? itemHeight : itemHeightNoReason; + + if (tile.hIcon) { + SetWindowPos(tile.hIcon, nullptr, 0, yPos + iconTopMargin, iconSize, iconSize, SWP_NOZORDER | SWP_SHOWWINDOW); + } + + if (tile.hTitle) { + RECT rcTitle; + GetWindowRect(tile.hTitle, &rcTitle); + int origHeight = rcTitle.bottom - rcTitle.top; + + int titleYOffset = 0; + if (!hasBlockReason) { + // Vertically center the title when there's no block reason + titleYOffset = MulDiv(8, dpi, 96); + } + + SetWindowPos(tile.hTitle, nullptr, iconSize + iconTextGap, yPos + titleYOffset, maxWidth, origHeight, SWP_NOZORDER | SWP_SHOWWINDOW); + } + + if (tile.hBlockReason) { + RECT rcBlockReason; + GetWindowRect(tile.hBlockReason, &rcBlockReason); + int origHeight = rcBlockReason.bottom - rcBlockReason.top; + SetWindowPos(tile.hBlockReason, nullptr, iconSize + iconTextGap, yPos + reasonTopMargin, maxWidth, origHeight, SWP_NOZORDER | SWP_SHOWWINDOW); + } + + yPos += height; + } + + SetWindowPos(hAppListScroll, nullptr, 0, -scrollPos, 0, 0, SWP_NOZORDER | SWP_NOSIZE); + + // Set the title text based on the number of apps on the list + if (hTitleText) { + wchar_t titleFormat[256] = {}; + wchar_t titleText[256] = {}; + // Redraw the entire title control area to prevent artifacts from previous longer text when the number of apps decreases + InvalidateRect(hTitleText, nullptr, TRUE); + if (appTiles.size() == 1) { + GetString(IDS_BSDR_BLOCKINGAPPCOUNT_SINGLE, titleFormat, _countof(titleFormat)); + } else if (appTiles.size() == 0) { + GetString(IDS_BSDR_BLOCKING_BGAPPS, titleFormat, _countof(titleFormat)); + } else { + GetString(IDS_BSDR_BLOCKINGAPPCOUNT_MULTI, titleFormat, _countof(titleFormat)); + } + swprintf_s(titleText, _countof(titleText), titleFormat, (int)appTiles.size()); + SetWindowTextW(hTitleText, titleText); + } +} + +INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_INITDIALOG: { + CustomBSDR::hDlg = hDlg; + + // Hide title bar + SetWindowLongW(hDlg, GWL_STYLE, GetWindowLongW(hDlg, GWL_STYLE) & ~WS_CAPTION); + // Use composited style on dialog, not bg window, to prevent the scrollbar control from flickering on drag + SetWindowLongW(hDlg, GWL_EXSTYLE, GetWindowLongW(hDlg, GWL_EXSTYLE) | WS_EX_COMPOSITED); + + hTitleText = GetDlgItem(hDlg, IDC_BSDR_TITLE); + hAppList = GetDlgItem(hDlg, IDC_BSDR_APPLIST); + hScrollBar = GetDlgItem(hDlg, IDC_BSDR_SCROLLBAR); + hWarningText = GetDlgItem(hDlg, IDC_BSDR_WARNING); + hForceButton = GetDlgItem(hDlg, IDC_BSDR_FORCE_BTN); + hCancelButton = GetDlgItem(hDlg, IDCANCEL); + hDescText = GetDlgItem(hDlg, IDC_BSDR_DESC); + hYesButton = GetDlgItem(hDlg, IDYES); + hNoButton = GetDlgItem(hDlg, IDNO); + + // Hide the warning message controls + ShowWindow(hWarningText, SW_HIDE); + ShowWindow(hYesButton, SW_HIDE); + ShowWindow(hNoButton, SW_HIDE); + + if (Wh_GetIntSetting(L"themeScrollbar", 0)) { + SetWindowTheme(hScrollBar, L" ", L""); + } + + if (hAppList) { + hAppListScroll = CreateWindowExW(0, L"STATIC", nullptr, WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN, 0, 0, 100, 100, hAppList, nullptr, nullptr, nullptr); + SetWindowSubclass(hAppList, AppListSubclassProc, 0, 0); + SetWindowSubclass(hAppListScroll, AppListSubclassProc, 0, 0); + + // Calculate the height of the app list container then resize it, also moving the controls below it + int dpi = GetDpiForWindow(hDlg); + + int itemHeight = MulDiv(83, dpi, 96); + int itemHeightNoReason = MulDiv(62, dpi, 96); + + int totalItemsHeight = 0; + for (auto& app : pendingApps) { + BOOLEAN isBlocking = FALSE; + if (SUCCEEDED(app->get_IsBlocking(&isBlocking)) && isBlocking) { + totalItemsHeight += itemHeight; + } else { + totalItemsHeight += itemHeightNoReason; + } + } + + RECT rcAppList, rcScrollBar; + GetWindowRect(hAppList, &rcAppList); + GetWindowRect(hScrollBar, &rcScrollBar); + int currentWidth = rcAppList.right - rcAppList.left; + int scrollBarWidth = rcScrollBar.right - rcScrollBar.left; + + int screenHeight = GetSystemMetrics(SM_CYSCREEN); + int minHeight = rcAppList.bottom - rcAppList.top; + int maxHeight = screenHeight - MulDiv(335, dpi, 96); + + if (maxHeight < minHeight) + maxHeight = minHeight; + + int newHeight = totalItemsHeight; + if (newHeight < minHeight) + newHeight = minHeight; + if (newHeight > maxHeight) + newHeight = maxHeight; + + int heightDiff = newHeight - minHeight; + + SetWindowPos(hAppList, nullptr, 0, 0, currentWidth, newHeight, SWP_NOMOVE | SWP_NOZORDER); + SetWindowPos(hAppListScroll, nullptr, 0, 0, currentWidth, totalItemsHeight, SWP_NOMOVE | SWP_NOZORDER); + SetWindowPos(hScrollBar, nullptr, 0, 0, scrollBarWidth, newHeight, SWP_NOMOVE | SWP_NOZORDER); + + for (HWND hwndSibling = GetWindow(hScrollBar, GW_HWNDNEXT); hwndSibling; hwndSibling = GetWindow(hwndSibling, GW_HWNDNEXT)) { + RECT rcSibling; + GetWindowRect(hwndSibling, &rcSibling); + MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rcSibling, 2); + OffsetRect(&rcSibling, 0, heightDiff); + SetWindowPos(hwndSibling, nullptr, rcSibling.left, rcSibling.top, 0, 0, SWP_NOSIZE | SWP_NOZORDER); + } + + RECT rcDialog, rcTitle, rcNo; + GetWindowRect(hDlg, &rcDialog); + OffsetRect(&rcDialog, -GetSystemMetrics(SM_XVIRTUALSCREEN), -GetSystemMetrics(SM_YVIRTUALSCREEN)); + GetWindowRect(hTitleText, &rcTitle); + MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rcTitle, 2); + GetWindowRect(hNoButton, &rcNo); + MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rcNo, 2); + int topPadding = rcTitle.top - rcDialog.top; + int dialogWidth = rcTitle.right - rcDialog.left; + int newDialogHeight = rcNo.bottom - rcDialog.top + topPadding - 1; + SetWindowPos(hDlg, nullptr, 0, 0, dialogWidth, newDialogHeight, SWP_NOMOVE | SWP_NOZORDER); + } + + SetWindowSubclass(hForceButton, ButtonSubclassProc, 0, 0); + LRESULT forceButtonUIState = SendMessageW(hForceButton, WM_QUERYUISTATE, 0, 0); + SetPropW(hForceButton, L"CustomBSDR_HideFocus", (HANDLE)(forceButtonUIState & UISF_HIDEFOCUS)); + SetPropW(hForceButton, L"CustomBSDR_HideAccel", (HANDLE)(forceButtonUIState & UISF_HIDEACCEL)); + + SetWindowSubclass(hCancelButton, ButtonSubclassProc, 0, 0); + LRESULT cancelButtonUIState = SendMessageW(hCancelButton, WM_QUERYUISTATE, 0, 0); + SetPropW(hCancelButton, L"CustomBSDR_HideFocus", (HANDLE)(cancelButtonUIState & UISF_HIDEFOCUS)); + SetPropW(hCancelButton, L"CustomBSDR_HideAccel", (HANDLE)(cancelButtonUIState & UISF_HIDEACCEL)); + + SetWindowSubclass(hYesButton, ButtonSubclassProc, 0, 0); + LRESULT yesButtonUIState = SendMessageW(hYesButton, WM_QUERYUISTATE, 0, 0); + SetPropW(hYesButton, L"CustomBSDR_HideFocus", (HANDLE)(yesButtonUIState & UISF_HIDEFOCUS)); + SetPropW(hYesButton, L"CustomBSDR_HideAccel", (HANDLE)(yesButtonUIState & UISF_HIDEACCEL)); + + SetWindowSubclass(hNoButton, ButtonSubclassProc, 0, 0); + LRESULT noButtonUIState = SendMessageW(hNoButton, WM_QUERYUISTATE, 0, 0); + SetPropW(hNoButton, L"CustomBSDR_HideFocus", (HANDLE)(noButtonUIState & UISF_HIDEFOCUS)); + SetPropW(hNoButton, L"CustomBSDR_HideAccel", (HANDLE)(noButtonUIState & UISF_HIDEACCEL)); + + // Set font for title and description/warning texts + HFONT hDialogFont = (HFONT)SendMessageW(hDlg, WM_GETFONT, 0, 0); + LOGFONTW lf = {}; + if (hDialogFont && GetObjectW(hDialogFont, sizeof(lf), &lf)) { + int originalHeight = lf.lfHeight; + + lf.lfHeight = MulDiv(originalHeight, 160, 100); + hTitleFont = CreateFontIndirectW(&lf); + if (hTitleFont && hTitleText) { + SendMessageW(hTitleText, WM_SETFONT, (WPARAM)hTitleFont, FALSE); + } + + lf.lfHeight = MulDiv(originalHeight, 120, 100); + hDescFont = CreateFontIndirectW(&lf); + if (hDescFont) { + if (hWarningText) + SendMessageW(hWarningText, WM_SETFONT, (WPARAM)hDescFont, FALSE); + if (hDescText) + SendMessageW(hDescText, WM_SETFONT, (WPARAM)hDescFont, FALSE); + } + } + + for (auto& app : pendingApps) { + CreateAppTileControls(app.Get()); + } + pendingApps.clear(); + + // Load and set the appropriate strings based on the current LogonUI state + wchar_t desc[256] = {}, warning[256] = {}, btnText[256] = {}; + switch (_logonUIState) { + case LogonUIState_LoggingOff: + GetString(IDS_BSDR_DESC_LOGOFF, desc, _countof(desc)); + GetString(IDS_BSDR_WARNING_LOGOFF, warning, _countof(warning)); + GetString(IDS_BSDR_BTN_LOGOFF, btnText, _countof(btnText)); + break; + case LogonUIState_Restarting: + GetString(IDS_BSDR_DESC_RESTART, desc, _countof(desc)); + GetString(IDS_BSDR_WARNING_RESTART, warning, _countof(warning)); + GetString(IDS_BSDR_BTN_RESTART, btnText, _countof(btnText)); + break; + default: + break; + } + if (_logonUIState != LogonUIState_ShuttingDown) { + hDescText&& SetWindowTextW(hDescText, desc); + hWarningText&& SetWindowTextW(hWarningText, warning); + hForceButton&& SetWindowTextW(hForceButton, btnText); + } + + CenterWindow(hDlg); + return TRUE; + } + case WM_CTLCOLORDLG: // make dialog transparent + if (!IsHighContrast()) { + return (INT_PTR)GetStockObject(NULL_BRUSH); + } + break; + case WM_CTLCOLORSTATIC: // make (direct) children bg transparent and text white + if (!IsHighContrast()) { + SetBkMode((HDC)wParam, TRANSPARENT); + SetStretchBltMode((HDC)wParam, HALFTONE); + SetTextColor((HDC)wParam, RGB(255, 255, 255)); + return (INT_PTR)GetStockObject(NULL_BRUSH); + } + break; + case WM_PAINT: { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hDlg, &ps); + if (bgBitmap) { + RECT rcDlg; + GetClientRect(hDlg, &rcDlg); + MapWindowPoints(hDlg, hBgWnd, (LPPOINT)&rcDlg, 2); + HDC memDC = CreateCompatibleDC(hdc); + HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, bgBitmap); + BitBlt(hdc, 0, 0, rcDlg.right, rcDlg.bottom, memDC, rcDlg.left, rcDlg.top, SRCCOPY); + SelectObject(memDC, oldBitmap); + DeleteDC(memDC); + } + EndPaint(hDlg, &ps); + return 0; + } + case WM_ERASEBKGND: + if (!IsHighContrast()) { + HDC hdc = (HDC)wParam; + RECT rcDlg; + GetClientRect(hDlg, &rcDlg); + HBRUSH hBrush = (HBRUSH)GetStockObject(BLACK_BRUSH); + FillRect(hdc, &rcDlg, hBrush); + return TRUE; + } + break; + case WM_DRAWITEM: { + LPDRAWITEMSTRUCT pDIS = (LPDRAWITEMSTRUCT)lParam; + if (pDIS->CtlID == IDC_BSDR_SEPARATOR_TOP || pDIS->CtlID == IDC_BSDR_SEPARATOR_BOTTOM) { + DrawSeparator(pDIS->hDC, pDIS); + return TRUE; + } else if (pDIS->CtlType == ODT_BUTTON) { + DrawButton(pDIS->hDC, pDIS); + return TRUE; + } + break; + } + case WM_COMMAND: { + switch (LOWORD(wParam)) { + case IDCANCEL: + Resolve(BlockedShutdownResolution_Cancel); + DestroyWindow(hDlg); + DestroyWindow(hBgWnd); + if (!isOnSecureDesktop) { + // If BSDR is forced to show on the default desktop with the Windhawk mod, LogonUI.exe won't exit for some reason on cancel, + // causing issues with subsequent session ends, unless it's killed manually or Ctrl+Alt+Del is pressed once + // so force exit the process as a dirty workaround + ExitProcess(0); + } + return TRUE; + case IDC_BSDR_FORCE_BTN: + ShowWindow(hWarningText, SW_SHOW); + ShowWindow(hYesButton, SW_SHOW); + ShowWindow(hNoButton, SW_SHOW); + ShowWindow(hDescText, SW_HIDE); + ShowWindow(hCancelButton, SW_HIDE); + ShowWindow(hForceButton, SW_HIDE); + return TRUE; + case IDYES: + Resolve(BlockedShutdownResolution_Force); + DestroyWindow(hDlg); + DestroyWindow(hBgWnd); + return TRUE; + case IDNO: + ShowWindow(hWarningText, SW_HIDE); + ShowWindow(hYesButton, SW_HIDE); + ShowWindow(hNoButton, SW_HIDE); + ShowWindow(hDescText, SW_SHOW); + ShowWindow(hCancelButton, SW_SHOW); + ShowWindow(hForceButton, SW_SHOW); + return TRUE; + } + break; + } + case WM_ADD_APP: { + Microsoft::WRL::ComPtr app = (IShutdownBlockingApp*)lParam; + CreateAppTileControls(app.Get()); + app->Release(); + return TRUE; + } + case WM_REMOVE_APP: { + UINT appId = (UINT)wParam; + RemoveAppTileControls(appId); + return TRUE; + } + case WM_MOUSEWHEEL: { + int dpi = GetDpiForWindow(hDlg); + int visibleHeight = MulDiv(300, dpi, 96); + + if (hAppList) { + RECT rcContainer; + GetClientRect(hAppList, &rcContainer); + visibleHeight = rcContainer.bottom - rcContainer.top; + } + + int delta = GET_WHEEL_DELTA_WPARAM(wParam); + int oldPos = scrollPos; + scrollPos -= delta / 4; + if (scrollPos < 0) scrollPos = 0; + + int maxScroll = totalContentHeight - visibleHeight; + if (maxScroll < 0) maxScroll = 0; + if (scrollPos > maxScroll) scrollPos = maxScroll; + + if (oldPos != scrollPos) { + SCROLLINFO si = {}; + si.cbSize = sizeof(SCROLLINFO); + si.fMask = SIF_POS; + si.nPos = scrollPos; + SetScrollInfo(hScrollBar, SB_CTL, &si, TRUE); + SetWindowPos(hAppListScroll, nullptr, 0, -scrollPos, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOREDRAW); + RedrawWindow(hAppList, nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_ALLCHILDREN); + } + return TRUE; + } + case WM_VSCROLL: { + int dpi = GetDpiForWindow(hDlg); + int visibleHeight = MulDiv(300, dpi, 96); + + if (hAppList) { + RECT rcContainer; + GetClientRect(hAppList, &rcContainer); + visibleHeight = rcContainer.bottom - rcContainer.top; + } + + int oldPos = scrollPos; + switch (LOWORD(wParam)) { + case SB_LINEUP: scrollPos -= MulDiv(20, dpi, 96); break; + case SB_LINEDOWN: scrollPos += MulDiv(20, dpi, 96); break; + case SB_PAGEUP: scrollPos -= MulDiv(100, dpi, 96); break; + case SB_PAGEDOWN: scrollPos += MulDiv(100, dpi, 96); break; + case SB_THUMBTRACK: + case SB_THUMBPOSITION: scrollPos = HIWORD(wParam); break; + } + + if (scrollPos < 0) scrollPos = 0; + int maxScroll = totalContentHeight - visibleHeight; + if (maxScroll < 0) maxScroll = 0; + if (scrollPos > maxScroll) scrollPos = maxScroll; + + if (oldPos != scrollPos) { + SCROLLINFO si = {}; + si.cbSize = sizeof(SCROLLINFO); + si.fMask = SIF_POS; + si.nPos = scrollPos; + SetScrollInfo(hScrollBar, SB_CTL, &si, FALSE); + SetWindowPos(hAppListScroll, nullptr, 0, -scrollPos, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOREDRAW); + RedrawWindow(hAppList, nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_ALLCHILDREN); + } + return TRUE; + } + } + return FALSE; +} + +LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { + switch (message) { + case WM_CREATE: { + if (!isUsingHardcodedRes && hResDll) { + hDlg = CreateDialogParamW(hResDll, MAKEINTRESOURCEW(IDD_BSDR_DLG), hWnd, DlgProc, lParam); + if (!hDlg) { + Wh_Log(L"CreateDialogParamW failed: %d", GetLastError()); + // Retry with hardcoded one + hDlg = CreateDialogIndirectParamW(nullptr, reinterpret_cast(RES_DIALOG), hWnd, DlgProc, lParam); + } + } else { + hDlg = CreateDialogIndirectParamW(nullptr, reinterpret_cast(RES_DIALOG), hWnd, DlgProc, lParam); + } + if (hDlg) { + ShowWindow(hDlg, SW_SHOW); + } else { + // Bail out + Wh_Log(L"Dialog creation failed!!"); + MessageBoxW(nullptr, L"Dialog creation failed!", L"Windows 7 Blocked Shutdown UX & Logoff Sequence", MB_OK); + Resolve(BlockedShutdownResolution_Force); + return 0; + } + + if (!IsHighContrast()) { + // Screenshot desktop and dim it + HDC hDC = GetDC(nullptr); + HDC hMemDC = CreateCompatibleDC(hDC); + bgBitmap = CreateCompatibleBitmap(hDC, bgWidth, bgHeight); + HBITMAP oldBitmap = (HBITMAP)SelectObject(hMemDC, bgBitmap); + + RECT rect = { 0, 0, bgWidth, bgHeight }; + HBRUSH hBrush = (HBRUSH)GetStockObject(BLACK_BRUSH); + FillRect(hMemDC, &rect, hBrush); + + AlphaBlend(hMemDC, 0, 0, bgWidth, bgHeight, hDC, bgOffsetX, bgOffsetY, bgWidth, bgHeight, { AC_SRC_OVER, 0, 31, 0 }); + SelectObject(hMemDC, oldBitmap); + DeleteDC(hMemDC); + ReleaseDC(nullptr, hDC); + + separatorBitmap = LoadAlphaBitmap(IDB_BSDR_SEPARATOR); + btnNormalBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_NORMAL); + btnHoverBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_HOVER); + btnPressedBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_PRESSED); + btnSelectedBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_SELECTED); + btnSelectedHoverBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_SELECTED_HOVER); + } + return 0; + } + case WM_PAINT: { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hWnd, &ps); + if (bgBitmap) { + HDC memDC = CreateCompatibleDC(hdc); + HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, bgBitmap); + BitBlt(hdc, 0, 0, bgWidth, bgHeight, memDC, 0, 0, SRCCOPY); + SelectObject(memDC, oldBitmap); + DeleteDC(memDC); + } + EndPaint(hWnd, &ps); + return 0; + } + case WM_WINDOWPOSCHANGING: { + // Deny window movement/resize from workarea resize, etc. + WINDOWPOS* pPos = (WINDOWPOS*)lParam; + pPos->flags |= SWP_NOMOVE | SWP_NOSIZE; + return 0; + } + case WM_DISPLAYCHANGE: { + int x = GetSystemMetrics(SM_XVIRTUALSCREEN); + int y = GetSystemMetrics(SM_YVIRTUALSCREEN); + int cx = GetSystemMetrics(SM_CXVIRTUALSCREEN); + int cy = GetSystemMetrics(SM_CYVIRTUALSCREEN); + SetWindowPos(hWnd, nullptr, x, y, cx, cy, SWP_NOSENDCHANGING); + CenterWindow(hDlg); + UpdateAppListLayout(); + RedrawWindow(hDlg, nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_UPDATENOW); + return 0; + } + case WM_CLOSE: { + for (auto& tile : appTiles) { + if (tile.hIconBitmap) DeleteObject(tile.hIconBitmap); + } + appTiles.clear(); + + if (hDlg) { + DestroyWindow(hDlg); + hDlg = nullptr; + } + + if (hBgWnd) { + DestroyWindow(hBgWnd); + hBgWnd = nullptr; + } + + if (bgBitmap) { + DeleteObject(bgBitmap); + bgBitmap = nullptr; + } + if (separatorBitmap) { + DeleteObject(separatorBitmap); + separatorBitmap = nullptr; + } + if (btnNormalBitmap) { + DeleteObject(btnNormalBitmap); + btnNormalBitmap = nullptr; + } + if (btnHoverBitmap) { + DeleteObject(btnHoverBitmap); + btnHoverBitmap = nullptr; + } + if (btnPressedBitmap) { + DeleteObject(btnPressedBitmap); + btnPressedBitmap = nullptr; + } + if (btnSelectedBitmap) { + DeleteObject(btnSelectedBitmap); + btnSelectedBitmap = nullptr; + } + if (btnSelectedHoverBitmap) { + DeleteObject(btnSelectedHoverBitmap); + btnSelectedHoverBitmap = nullptr; + } + if (hTitleFont) { + DeleteObject(hTitleFont); + hTitleFont = nullptr; + } + if (hDescFont) { + DeleteObject(hDescFont); + hDescFont = nullptr; + } + return 0; + } + case WM_DESTROY: + PostQuitMessage(0); + return 0; + } + return DefWindowProcW(hWnd, message, wParam, lParam); +} + +DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { + // Attempt to create window on the input desktop, as the thread is always running in secure desktop at this point, + // but the Windhawk mod can force this phase of session end to run in the default desktop + HDESK hDesktop = OpenInputDesktop(0, FALSE, DESKTOP_CREATEWINDOW | DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS); + if (hDesktop) { + wchar_t desktopName[256] = {}; + if (GetUserObjectInformationW(hDesktop, UOI_NAME, desktopName, sizeof(desktopName), nullptr)) { + isOnSecureDesktop = (_wcsicmp(desktopName, L"winlogon") == 0); + } + if (!SetThreadDesktop(hDesktop)) { + CloseDesktop(hDesktop); + return GetLastError(); + } + CloseDesktop(hDesktop); + } + + WNDCLASSEXW wndClass = {}; + + wndClass.cbSize = sizeof(WNDCLASSEXW); + wndClass.style = CS_GLOBALCLASS; + wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); + wndClass.hInstance = GetModuleHandleW(nullptr); + wndClass.hCursor = LoadCursorW(nullptr, IDC_ARROW); + wndClass.lpfnWndProc = WndProc; + wndClass.lpszClassName = BSDR_CLASSNAME; + + if (RegisterClassExW(&wndClass) == 0) { + int gle = GetLastError(); + if (gle != ERROR_CLASS_ALREADY_EXISTS) { + return gle; + } + } + + bgOffsetX = GetSystemMetrics(SM_XVIRTUALSCREEN); + bgOffsetY = GetSystemMetrics(SM_YVIRTUALSCREEN); + bgWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN); + bgHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN); + + hBgWnd = CreateWindowExW(WS_EX_TOPMOST, wndClass.lpszClassName, NULL, WS_POPUP | WS_VISIBLE, bgOffsetX, bgOffsetY, bgWidth, bgHeight, NULL, NULL, NULL, NULL); + + if (!hBgWnd) { + return GetLastError(); + } + + HANDLE waitHandles[1] = { hStopEvent }; + BOOL bQuit = FALSE; + while (TRUE) { + DWORD result = MsgWaitForMultipleObjects( + 1, + waitHandles, + FALSE, + INFINITE, + QS_ALLINPUT + ); + + if (result == WAIT_OBJECT_0) { // hStopEvent + if (hBgWnd && IsWindow(hBgWnd)) { + SendMessageW(hBgWnd, WM_CLOSE, 0, 0); + } else { + break; + } + } else if (result == WAIT_OBJECT_0 + 1) { + MSG msg; + while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) { + bQuit = TRUE; + break; + } + if (msg.message == WM_SYSKEYDOWN && msg.wParam == VK_MENU) { + SetPropW(hForceButton, L"CustomBSDR_HideAccel", (HANDLE)FALSE); + SetPropW(hCancelButton, L"CustomBSDR_HideAccel", (HANDLE)FALSE); + SetPropW(hYesButton, L"CustomBSDR_HideAccel", (HANDLE)FALSE); + SetPropW(hNoButton, L"CustomBSDR_HideAccel", (HANDLE)FALSE); + RedrawWindow(hDlg, nullptr, nullptr, RDW_FRAME | RDW_INVALIDATE); + } + if (!IsDialogMessageW(hDlg, &msg)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + if (bQuit) { + break; + } + } + } + return 0; +} + +void CustomBSDR::Start(LogonUIState state) { + _logonUIState = state; + if (hThread) { + Wh_Log(L"CustomBSDR has already started once!"); + return; + } + hThread = CreateThread(nullptr, 0, ThreadProc, nullptr, 0, nullptr); + if (!hThread) { + Wh_Log(L"Failed to create CustomBSDR thread, GLE=%d", GetLastError()); + return; + } + Wh_Log(L"Created CustomBSDR thread"); +} + +void CustomBSDR::AddApplication(IShutdownBlockingApp* blockingApp) { + if (hDlg && IsWindow(hDlg)) { + blockingApp->AddRef(); + if (!PostMessageW(hDlg, WM_ADD_APP, 0, reinterpret_cast(blockingApp))) { + blockingApp->Release(); + } + } else { + pendingApps.push_back(blockingApp); + } +} + +void CustomBSDR::RemoveApplication(UINT appid) { + if (hDlg && IsWindow(hDlg)) { + PostMessageW(hDlg, WM_REMOVE_APP, (WPARAM)appid, 0); + } +} + +void CustomBSDR::Hide() { + if (hDlg) { + ShowWindow(hDlg, SW_HIDE); + } +} + +void CustomBSDR::Stop() { + if (hStopEvent) { + SetEvent(hStopEvent); + } +} +#pragma endregion CustomBSDR.cpp + +#pragma region LogonUI BSDR Hooks +long __fastcall BlockedShutdownUXImpl_Start_hook(void* thisPtr, void* userSettingManager, ILogonUIStateInfo* logonUIStateInfo) { + Wh_Log(L"BlockedShutdownUXImpl::Start"); + LogonUIState logonUIState = LogonUIState_Start; + logonUIStateInfo->get_CurrentLogonUIState(&logonUIState); + CustomBSDR::Start(logonUIState); + return S_OK; +} + +long __fastcall BlockedShutdownUXImpl_get_ScaleFactor_hook(void* thisPtr, unsigned int* scaleFactor) { + Wh_Log(L"BlockedShutdownUXImpl::get_ScaleFactor"); + *scaleFactor = 100; + return S_OK; +}; + +long __fastcall BlockedShutdownUXImpl_get_WasClicked_hook(void* thisPtr, unsigned char* wasClicked) { + Wh_Log(L"BlockedShutdownUXImpl::get_WasClicked"); + + { + lock_guard lock(resolvedMutex); + *wasClicked = _wasClicked; + } + + return S_OK; +}; + +long __fastcall BlockedShutdownUXImpl_AddApplication_hook(void* thisPtr, IShutdownBlockingApp* blockingApp) { + std::wstring logMessage = L"BlockedShutdownUXImpl::AddApplication, AppId="; + UINT appId = 0; + if (SUCCEEDED(blockingApp->get_Id(&appId))) { + logMessage += std::to_wstring(appId); + } + BOOLEAN isBlocking = FALSE; + if (SUCCEEDED(blockingApp->get_IsBlocking(&isBlocking))) { + logMessage += L", IsBlocking=" + std::wstring(isBlocking ? L"true" : L"false"); + } + HSTRING caption, blockReason; + if (SUCCEEDED(blockingApp->get_Caption(&caption))) { + // Log the caption of the blocking app + logMessage += L", Caption=" + std::wstring(WindowsGetStringRawBuffer(caption, NULL)); + WindowsDeleteString(caption); + } + if (SUCCEEDED(blockingApp->get_BlockReason(&blockReason))) { + // Log the block reason of the blocking app + logMessage += L", BlockReason=" + std::wstring(WindowsGetStringRawBuffer(blockReason, NULL)); + WindowsDeleteString(blockReason); + } + Wh_Log(L"%s", logMessage.c_str()); + + CustomBSDR::AddApplication(blockingApp); + return S_OK; +} + +long __fastcall BlockedShutdownUXImpl_RemoveApplication_hook(void* thisPtr, unsigned int appid) { + Wh_Log(L"BlockedShutdownUXImpl::RemoveApplication, AppId=%d", appid); + CustomBSDR::RemoveApplication(appid); + return S_OK; +} + +long __fastcall BlockedShutdownUXImpl_add_Resolved_hook(void* thisPtr, ABI::Windows::Foundation::ITypedEventHandler* eventHandler, EventRegistrationToken* eventToken) { + Wh_Log(L"BlockedShutdownUXImpl::add_Resolved"); + if (!eventHandler || !eventToken) + return E_POINTER; + + eventToken->value = 0; + + BlockedShutdownResolution resolvedValueLocal; + + { + lock_guard lock(resolvedMutex); + resolvedValueLocal = resolvedValue; + + if (resolvedValueLocal == BlockedShutdownResolution_None) { + _Resolved = eventHandler; + } + } + + // For bailing out early + if (resolvedValueLocal != BlockedShutdownResolution_None) { + return eventHandler->Invoke(nullptr, resolvedValueLocal); + } + + return S_OK; +} + +long __fastcall BlockedShutdownUXImpl_remove_Resolved_hook(void* thisPtr, EventRegistrationToken eventToken) { + Wh_Log(L"BlockedShutdownUXImpl::remove_Resolved"); + + if (eventToken.value == 0) { + lock_guard lock(resolvedMutex); + _Resolved.Reset(); + } + + return S_OK; +} + +long __fastcall BlockedShutdownUXImpl_Hide_hook(void* thisPtr) { + Wh_Log(L"BlockedShutdownUXImpl::Hide"); + CustomBSDR::Hide(); + return S_OK; +} + +long __fastcall BlockedShutdownUXImpl_Stop_hook(void* thisPtr) { + Wh_Log(L"BlockedShutdownUXImpl::Stop"); + CustomBSDR::Stop(); + return S_OK; +} + +// Windows.UI.BlockedShutdown.dll +WindhawkUtils::SYMBOL_HOOK hooks[] = { + { + { + L"public: virtual long __cdecl BlockedShutdownUXImpl::Start(struct Windows::Internal::UI::Logon::Controller::IUserSettingManager *,struct Windows::Internal::UI::Logon::Controller::ILogonUIStateInfo *)", + }, + NULL, + (void*)BlockedShutdownUXImpl_Start_hook, + FALSE + }, + { + { + L"public: virtual long __cdecl BlockedShutdownUXImpl::get_ScaleFactor(unsigned int *)", + }, + NULL, + (void*)BlockedShutdownUXImpl_get_ScaleFactor_hook, + FALSE + }, + { + { + L"public: virtual long __cdecl BlockedShutdownUXImpl::get_WasClicked(unsigned char *)", + }, + NULL, + (void*)BlockedShutdownUXImpl_get_WasClicked_hook, + FALSE + }, + { + { + L"public: virtual long __cdecl BlockedShutdownUXImpl::AddApplication(struct Windows::Internal::UI::Logon::Controller::IShutdownBlockingApp *)", + }, + NULL, + (void*)BlockedShutdownUXImpl_AddApplication_hook, + FALSE + }, + { + { + L"public: virtual long __cdecl BlockedShutdownUXImpl::RemoveApplication(unsigned int)", + }, + NULL, + (void*)BlockedShutdownUXImpl_RemoveApplication_hook, + FALSE + }, + { + { + L"public: virtual long __cdecl BlockedShutdownUXImpl::add_Resolved(struct Windows::Foundation::ITypedEventHandler *,struct EventRegistrationToken *)", + }, + NULL, + (void*)BlockedShutdownUXImpl_add_Resolved_hook, + FALSE + }, + { + { + L"public: virtual long __cdecl BlockedShutdownUXImpl::remove_Resolved(struct EventRegistrationToken)", + }, + NULL, + (void*)BlockedShutdownUXImpl_remove_Resolved_hook, + FALSE + }, + { + { + L"public: virtual long __cdecl BlockedShutdownUXImpl::Hide(void)", + }, + NULL, + (void*)BlockedShutdownUXImpl_Hide_hook, + FALSE + }, + { + { + L"public: virtual long __cdecl BlockedShutdownUXImpl::Stop(void)", + }, + NULL, + (void*)BlockedShutdownUXImpl_Stop_hook, + FALSE + } +}; +#pragma endregion LogonUI BSDR Hooks + +#pragma region Winlogon hooks (disable async logoff) +int* p_g_fShutdownResolverDisabled = nullptr; + +typedef void __fastcall (*ShutdownWindowsWorkerThread_t)(void* Instance, void* Context); +ShutdownWindowsWorkerThread_t ShutdownWindowsWorkerThread_original; +void __fastcall ShutdownWindowsWorkerThread_hook(void* Instance, void* Context) { + if (p_g_fShutdownResolverDisabled) { + if (Wh_GetIntValue(L"LogonUIHookOK", 0)) { + *p_g_fShutdownResolverDisabled = 1; // Disable the async logoff resolver + Wh_Log(L"ShutdownWindowsWorkerThread_hook: Set g_fShutdownResolverDisabled to 1"); + } else { + // User did not read the instruction and LogonUI is not added to the inclusion list + // The stock immersive BSDR does not support being displayed on the default desktop, so if it shows, + // it will get stuck in the invisible secure desktop, and users can become clueless. + // Pressing ctrl alt del can get out of this state but lets add this minimal safeguard + Wh_Log(L"ShutdownWindowsWorkerThread_hook: Not setting g_fShutdownResolverDisabled as LogonUI hooks are not ready"); + } + } + ShutdownWindowsWorkerThread_original(Instance, Context); +} + +WindhawkUtils::SYMBOL_HOOK winlogonExeHooks[] = { + { + { + L"void __cdecl ShutdownWindowsWorkerThread(struct _TP_CALLBACK_INSTANCE *,void *)", + }, + (void**)&ShutdownWindowsWorkerThread_original, + (void*)ShutdownWindowsWorkerThread_hook, + FALSE + }, + { + { + L"int g_fShutdownResolverDisabled", + }, + (void**)&p_g_fShutdownResolverDisabled, + NULL, + FALSE + } +}; +#pragma endregion Winlogon hooks (disable async logoff) + +// The mod is being initialized, load settings, hook functions, and do other +// initialization stuff if required. +BOOL Wh_ModInit() { + Wh_Log(L"Init"); + + wchar_t exeName[MAX_PATH]; + GetModuleFileNameW(NULL, exeName, MAX_PATH); + bool isWinlogon = wcsstr(_wcsupr(exeName), L"\\WINLOGON.EXE") != NULL; + if (isWinlogon) { + if (!Wh_GetIntSetting(L"disableAsyncLogoff")) { + Wh_Log(L"Running in winlogon.exe, but winlogon hooks are disabled"); + return FALSE; + } + HMODULE winlogon = GetModuleHandleW(NULL); + if (winlogon) { + if (!WindhawkUtils::HookSymbols(winlogon, winlogonExeHooks, ARRAYSIZE(winlogonExeHooks))) { + Wh_Log(L"HookSymbols ShutdownWindowsWorkerThread failed"); + return FALSE; + } + } + return TRUE; + } + + LPCWSTR dllPath = Wh_GetStringSetting(L"resDllPath"); + if (dllPath && *dllPath != L'\0') { + hResDll = LoadLibraryExW(dllPath, nullptr, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE); + Wh_FreeStringSetting(dllPath); + if (hResDll) { + isUsingHardcodedRes = false; + } else { + Wh_Log(L"Failed to load the resource DLL! Using hardcoded resources..."); + } + } else { + Wh_Log(L"Using hardcoded resources..."); + } + + CustomBSDR::hStopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + if (!CustomBSDR::hStopEvent) { + Wh_Log(L"CreateEventW failed, GLE=%u", GetLastError()); + return FALSE; + } + + HMODULE blockedShutdownDll = LoadLibraryExW(L"Windows.UI.BlockedShutdown.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (blockedShutdownDll) { + if (!WindhawkUtils::HookSymbols(blockedShutdownDll, hooks, ARRAYSIZE(hooks))) { + Wh_Log(L"Failed to hook symbols in Windows.UI.BlockedShutdown.dll"); + Wh_SetIntValue(L"LogonUIHookOK", 0); + return FALSE; + } + } else { + Wh_Log(L"Failed to load Windows.UI.BlockedShutdown.dll"); + Wh_SetIntValue(L"LogonUIHookOK", 0); + return FALSE; + } + + Wh_SetIntValue(L"LogonUIHookOK", 1); + return TRUE; +} + +void Wh_ModUninit() { + Wh_Log(L"Uninit"); + if (CustomBSDR::hThread) { + if (CustomBSDR::hStopEvent) { + SetEvent(CustomBSDR::hStopEvent); + } + WaitForSingleObject(CustomBSDR::hThread, INFINITE); + CloseHandle(CustomBSDR::hThread); + UnregisterClassW(BSDR_CLASSNAME, GetModuleHandleW(nullptr)); // Only unregister if thread is started once + } + if (CustomBSDR::hStopEvent) { + CloseHandle(CustomBSDR::hStopEvent); + } + _Resolved.Reset(); + if (hResDll) { + FreeLibrary(hResDll); + } +} + +BOOL Wh_ModSettingsChanged(BOOL* bReload) { + // Handle winlogon hook status change + // resDllPath: only used by LogonUI during shutdown sequence which is unlikely timing for a settings change + *bReload = TRUE; + return TRUE; +} From 0b1aed4249881df334ad14713d8b172279c25a9c Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Wed, 26 Aug 2026 01:50:59 +0900 Subject: [PATCH 02/51] Fix themeScrollbar condition --- mods/win7-bsdr.wh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 7e8e7bbcfa..f35cc031fb 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -2683,7 +2683,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM ShowWindow(hYesButton, SW_HIDE); ShowWindow(hNoButton, SW_HIDE); - if (Wh_GetIntSetting(L"themeScrollbar", 0)) { + if (!Wh_GetIntSetting(L"themeScrollbar", 0)) { SetWindowTheme(hScrollBar, L" ", L""); } From 1f92dff1fbc81ea10948628cb52c6626ff5f255a Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Wed, 26 Aug 2026 01:58:10 +0900 Subject: [PATCH 03/51] Update license id --- mods/win7-bsdr.wh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index f35cc031fb..82d9697ad6 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -11,7 +11,7 @@ // @include winlogon.exe // @architecture x86-64 // @compilerOptions -lgdi32 -lmincore -lwindowscodecs -luxtheme -lmsimg32 -lcomctl32 -lshcore -luuid -// @license GPLv3 +// @license GPL-3.0-only // ==/WindhawkMod== // ==WindhawkModReadme== From d7e5efc286a0f1a037e72027032014f01e128ebe Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Wed, 26 Aug 2026 02:17:26 +0900 Subject: [PATCH 04/51] Fix WH 1.6.1 compat --- mods/win7-bsdr.wh.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 82d9697ad6..2d80ceb545 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -3373,13 +3373,17 @@ long __fastcall BlockedShutdownUXImpl_Stop_hook(void* thisPtr) { return S_OK; } +// Unused variable that only exists to make Windhawk 1.6.1 happy +// Putting nullptr in originalFunction only works on WH 1.7 somehow (unlike what the SYMBOL_HOOK comment says) +void** dummyOrig = nullptr; + // Windows.UI.BlockedShutdown.dll WindhawkUtils::SYMBOL_HOOK hooks[] = { { { L"public: virtual long __cdecl BlockedShutdownUXImpl::Start(struct Windows::Internal::UI::Logon::Controller::IUserSettingManager *,struct Windows::Internal::UI::Logon::Controller::ILogonUIStateInfo *)", }, - NULL, + dummyOrig, (void*)BlockedShutdownUXImpl_Start_hook, FALSE }, @@ -3387,7 +3391,7 @@ WindhawkUtils::SYMBOL_HOOK hooks[] = { { L"public: virtual long __cdecl BlockedShutdownUXImpl::get_ScaleFactor(unsigned int *)", }, - NULL, + dummyOrig, (void*)BlockedShutdownUXImpl_get_ScaleFactor_hook, FALSE }, @@ -3395,7 +3399,7 @@ WindhawkUtils::SYMBOL_HOOK hooks[] = { { L"public: virtual long __cdecl BlockedShutdownUXImpl::get_WasClicked(unsigned char *)", }, - NULL, + dummyOrig, (void*)BlockedShutdownUXImpl_get_WasClicked_hook, FALSE }, @@ -3403,7 +3407,7 @@ WindhawkUtils::SYMBOL_HOOK hooks[] = { { L"public: virtual long __cdecl BlockedShutdownUXImpl::AddApplication(struct Windows::Internal::UI::Logon::Controller::IShutdownBlockingApp *)", }, - NULL, + dummyOrig, (void*)BlockedShutdownUXImpl_AddApplication_hook, FALSE }, @@ -3411,7 +3415,7 @@ WindhawkUtils::SYMBOL_HOOK hooks[] = { { L"public: virtual long __cdecl BlockedShutdownUXImpl::RemoveApplication(unsigned int)", }, - NULL, + dummyOrig, (void*)BlockedShutdownUXImpl_RemoveApplication_hook, FALSE }, @@ -3419,7 +3423,7 @@ WindhawkUtils::SYMBOL_HOOK hooks[] = { { L"public: virtual long __cdecl BlockedShutdownUXImpl::add_Resolved(struct Windows::Foundation::ITypedEventHandler *,struct EventRegistrationToken *)", }, - NULL, + dummyOrig, (void*)BlockedShutdownUXImpl_add_Resolved_hook, FALSE }, @@ -3427,7 +3431,7 @@ WindhawkUtils::SYMBOL_HOOK hooks[] = { { L"public: virtual long __cdecl BlockedShutdownUXImpl::remove_Resolved(struct EventRegistrationToken)", }, - NULL, + dummyOrig, (void*)BlockedShutdownUXImpl_remove_Resolved_hook, FALSE }, @@ -3435,7 +3439,7 @@ WindhawkUtils::SYMBOL_HOOK hooks[] = { { L"public: virtual long __cdecl BlockedShutdownUXImpl::Hide(void)", }, - NULL, + dummyOrig, (void*)BlockedShutdownUXImpl_Hide_hook, FALSE }, @@ -3443,7 +3447,7 @@ WindhawkUtils::SYMBOL_HOOK hooks[] = { { L"public: virtual long __cdecl BlockedShutdownUXImpl::Stop(void)", }, - NULL, + dummyOrig, (void*)BlockedShutdownUXImpl_Stop_hook, FALSE } From 136b150b9156b090f556fc4f1f4303389255c74f Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Wed, 26 Aug 2026 02:34:20 +0900 Subject: [PATCH 05/51] Update documentation in win7-bsdr.wh.cpp Added known issues section and important usage note for the mod. --- mods/win7-bsdr.wh.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 2d80ceb545..7863c68306 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -29,6 +29,8 @@ * Set the DLL path in the mod settings afterwards. You may also point this directly to `winsrv.dll.mui` instead of setting up the directory structure mentioned above. * Warning: **never, ever replace `C:\Windows\System32\winsrv.dll` (or mui) with the Windows 7 version**; this will brick your Windows installation! * This is not necessary. Hardcoded resources will be used instead if the path is not set or the file is missing. +* Known issues + * If you are using the AuthUX logon screen and your account has no password, enabling the logoff sequence option will make the system automatically log on again after logging off. ## ⚠ Important usage note ⚠ In order to use this mod, you must allow Windhawk to inject into the **dwm.exe** From 6649a11bd0218b39521e301639bac37bfbb43deb Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Wed, 26 Aug 2026 02:46:42 +0900 Subject: [PATCH 06/51] Fix outdated comment --- mods/win7-bsdr.wh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 7863c68306..91532eb7f4 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -1582,7 +1582,7 @@ ILogonUIStateInfo : IInspectable { virtual HRESULT get_CurrentLogonUIState(LogonUIState*) PURE; }; -// Windhawk lacks some WinRT header files needed for EventSource so just handle it with std map +// Windhawk lacks some WinRT header files needed for EventSource. Since add_Resolved is only called once, just handle one handler using ResolvedHandler = ABI::Windows::Foundation::ITypedEventHandler; Microsoft::WRL::ComPtr _Resolved; mutex resolvedMutex; From 5569164eafba1d2d6157e33aabab5a508178bde8 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Wed, 26 Aug 2026 05:03:55 +0900 Subject: [PATCH 07/51] Apply fixes --- mods/win7-bsdr.wh.cpp | 109 ++++++++++++++++++++++++++---------------- 1 file changed, 68 insertions(+), 41 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 91532eb7f4..f4fbcce53b 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -30,10 +30,10 @@ * Warning: **never, ever replace `C:\Windows\System32\winsrv.dll` (or mui) with the Windows 7 version**; this will brick your Windows installation! * This is not necessary. Hardcoded resources will be used instead if the path is not set or the file is missing. * Known issues - * If you are using the AuthUX logon screen and your account has no password, enabling the logoff sequence option will make the system automatically log on again after logging off. + * If your user account has no password, enabling the logoff sequence option may make the system automatically log on again after logging off. ## ⚠ Important usage note ⚠ -In order to use this mod, you must allow Windhawk to inject into the **dwm.exe** +In order to use this mod, you must allow Windhawk to inject into the **LogonUI.exe** system process. To do so, add it to the process inclusion list in the advanced settings. If you do not do this, it will silently fail to inject. @@ -52,7 +52,7 @@ settings. If you do not do this, it will silently fail to inject. $name:ko-KR: 스크롤 막대에 시각 테마 적용 - disableAsyncLogoff: true $name: Restore Windows 7's logoff sequence - $name:ko-KR: Windows 7의 로그오프 절차를 복원합니다. + $name:ko-KR: Windows 7의 로그오프 절차 복원 */ // ==/WindhawkModSettings== @@ -68,8 +68,8 @@ settings. If you do not do this, it will silently fail to inject. using namespace std; -#define WM_ADD_APP (WM_USER + 1) -#define WM_REMOVE_APP (WM_USER + 2) +#define WM_ADD_APP (WM_APP + 1) +#define WM_REMOVE_APP (WM_APP + 2) #define BSDR_CLASSNAME L"BlockedShutdownResolver_WH" @@ -78,6 +78,9 @@ using namespace std; if (FAILED(hr)) return hr; \ } while(0) +EXTERN_C IMAGE_DOS_HEADER __ImageBase; +#define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase) + #pragma region resources // Resource IDs #define IDD_BSDR_DLG 20 @@ -1582,9 +1585,9 @@ ILogonUIStateInfo : IInspectable { virtual HRESULT get_CurrentLogonUIState(LogonUIState*) PURE; }; -// Windhawk lacks some WinRT header files needed for EventSource. Since add_Resolved is only called once, just handle one handler +// Windhawk lacks some WinRT header files needed for EventSource. Since add_Resolved is only called once, just handle only one handler using ResolvedHandler = ABI::Windows::Foundation::ITypedEventHandler; -Microsoft::WRL::ComPtr _Resolved; +[[clang::no_destroy]] Microsoft::WRL::ComPtr _Resolved; mutex resolvedMutex; BlockedShutdownResolution resolvedValue = BlockedShutdownResolution_None; bool _wasClicked = false; @@ -1675,8 +1678,9 @@ namespace CustomBSDR { HBITMAP hIconBitmap; Microsoft::WRL::ComPtr app; }; - static vector appTiles; - static vector> pendingApps; + static optional> appTiles; + static optional>> pendingApps; + mutex pendingAppsMutex; }; #pragma endregion logoncontroller.h and CustomBSDR.h @@ -2406,7 +2410,7 @@ LRESULT CALLBACK CustomBSDR::AppListSubclassProc(HWND hWnd, UINT uMsg, WPARAM wP HWND hControl = (HWND)lParam; bool isBlockReason = false; - for (auto& tile : appTiles) { + for (auto& tile : *appTiles) { if (tile.hBlockReason == hControl) { isBlockReason = true; break; @@ -2523,16 +2527,16 @@ void CustomBSDR::CreateAppTileControls(IShutdownBlockingApp* blockingApp) { } if (tile.isBlocking) { - appTiles.insert(appTiles.begin(), tile); + appTiles->insert(appTiles->begin(), tile); } else { - appTiles.push_back(tile); + appTiles->push_back(tile); } UpdateAppListLayout(); } void CustomBSDR::RemoveAppTileControls(UINT appId) { - for (auto it = appTiles.begin(); it != appTiles.end(); ++it) { + for (auto it = appTiles->begin(); it != appTiles->end(); ++it) { if (it->appId == appId) { if (it->hIcon) { DestroyWindow(it->hIcon); @@ -2547,7 +2551,7 @@ void CustomBSDR::RemoveAppTileControls(UINT appId) { DeleteObject(it->hIconBitmap); } - appTiles.erase(it); + appTiles->erase(it); UpdateAppListLayout(); return; @@ -2579,7 +2583,7 @@ void CustomBSDR::UpdateAppListLayout() { } totalContentHeight = 0; - for (auto& tile : appTiles) { + for (auto& tile : *appTiles) { totalContentHeight += (tile.hBlockReason != nullptr) ? itemHeight : itemHeightNoReason; } @@ -2608,7 +2612,7 @@ void CustomBSDR::UpdateAppListLayout() { int yPos = topMargin; - for (auto& tile : appTiles) { + for (auto& tile : *appTiles) { bool hasBlockReason = (tile.hBlockReason != nullptr); int height = hasBlockReason ? itemHeight : itemHeightNoReason; @@ -2648,14 +2652,14 @@ void CustomBSDR::UpdateAppListLayout() { wchar_t titleText[256] = {}; // Redraw the entire title control area to prevent artifacts from previous longer text when the number of apps decreases InvalidateRect(hTitleText, nullptr, TRUE); - if (appTiles.size() == 1) { + if (appTiles->size() == 1) { GetString(IDS_BSDR_BLOCKINGAPPCOUNT_SINGLE, titleFormat, _countof(titleFormat)); - } else if (appTiles.size() == 0) { + } else if (appTiles->size() == 0) { GetString(IDS_BSDR_BLOCKING_BGAPPS, titleFormat, _countof(titleFormat)); } else { GetString(IDS_BSDR_BLOCKINGAPPCOUNT_MULTI, titleFormat, _countof(titleFormat)); } - swprintf_s(titleText, _countof(titleText), titleFormat, (int)appTiles.size()); + swprintf_s(titleText, _countof(titleText), titleFormat, (int)appTiles->size()); SetWindowTextW(hTitleText, titleText); } } @@ -2701,12 +2705,15 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM int itemHeightNoReason = MulDiv(62, dpi, 96); int totalItemsHeight = 0; - for (auto& app : pendingApps) { - BOOLEAN isBlocking = FALSE; - if (SUCCEEDED(app->get_IsBlocking(&isBlocking)) && isBlocking) { - totalItemsHeight += itemHeight; - } else { - totalItemsHeight += itemHeightNoReason; + { + lock_guard lock(pendingAppsMutex); + for (auto& app : *pendingApps) { + BOOLEAN isBlocking = FALSE; + if (SUCCEEDED(app->get_IsBlocking(&isBlocking)) && isBlocking) { + totalItemsHeight += itemHeight; + } else { + totalItemsHeight += itemHeightNoReason; + } } } @@ -2798,10 +2805,13 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM } } - for (auto& app : pendingApps) { - CreateAppTileControls(app.Get()); + { + lock_guard lock(pendingAppsMutex); + for (auto& app : *pendingApps) { + CreateAppTileControls(app.Get()); + } + pendingApps->clear(); } - pendingApps.clear(); // Load and set the appropriate strings based on the current LogonUI state wchar_t desc[256] = {}, warning[256] = {}, btnText[256] = {}; @@ -2888,6 +2898,11 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM // If BSDR is forced to show on the default desktop with the Windhawk mod, LogonUI.exe won't exit for some reason on cancel, // causing issues with subsequent session ends, unless it's killed manually or Ctrl+Alt+Del is pressed once // so force exit the process as a dirty workaround + // + // This also happens with AuthUX BSDR where AuthUX.dll fully replaces Windows.UI.BlockedShutdown.dll + // LogonUI.exe thread is stuck at WaitForSingleObjectEx from LogonController.dll (prob mutex) + // It gets stuck between get_WasClicked and remove_Resolved. Pressing C+A+D makes the thread continue and remove_Resolved/Hide/Stop gets called. + // Still haven't figured out the culprit yet ExitProcess(0); } return TRUE; @@ -3075,10 +3090,10 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA return 0; } case WM_CLOSE: { - for (auto& tile : appTiles) { + for (auto& tile : *appTiles) { if (tile.hIconBitmap) DeleteObject(tile.hIconBitmap); } - appTiles.clear(); + appTiles->clear(); if (hDlg) { DestroyWindow(hDlg); @@ -3136,6 +3151,8 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA } DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { + HRESULT hrCo = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + // Attempt to create window on the input desktop, as the thread is always running in secure desktop at this point, // but the Windhawk mod can force this phase of session end to run in the default desktop HDESK hDesktop = OpenInputDesktop(0, FALSE, DESKTOP_CREATEWINDOW | DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS); @@ -3156,16 +3173,15 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { wndClass.cbSize = sizeof(WNDCLASSEXW); wndClass.style = CS_GLOBALCLASS; wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); - wndClass.hInstance = GetModuleHandleW(nullptr); + wndClass.hInstance = HINST_THISCOMPONENT; wndClass.hCursor = LoadCursorW(nullptr, IDC_ARROW); wndClass.lpfnWndProc = WndProc; wndClass.lpszClassName = BSDR_CLASSNAME; if (RegisterClassExW(&wndClass) == 0) { int gle = GetLastError(); - if (gle != ERROR_CLASS_ALREADY_EXISTS) { - return gle; - } + Wh_Log(L"RegisterClassExW failed, GLE=%d", gle); + return gle; } bgOffsetX = GetSystemMetrics(SM_XVIRTUALSCREEN); @@ -3176,7 +3192,9 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { hBgWnd = CreateWindowExW(WS_EX_TOPMOST, wndClass.lpszClassName, NULL, WS_POPUP | WS_VISIBLE, bgOffsetX, bgOffsetY, bgWidth, bgHeight, NULL, NULL, NULL, NULL); if (!hBgWnd) { - return GetLastError(); + int gle = GetLastError(); + Wh_Log(L"CreateWindowExW failed, GLE=%d", gle); + return gle; } HANDLE waitHandles[1] = { hStopEvent }; @@ -3220,12 +3238,20 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { } } } + if (UnregisterClassW(BSDR_CLASSNAME, HINST_THISCOMPONENT) == 0) { + Wh_Log(L"UnregisterClassW failed, GLE=%d", GetLastError()); + } + + if (SUCCEEDED(hrCo)) CoUninitialize(); return 0; } void CustomBSDR::Start(LogonUIState state) { _logonUIState = state; if (hThread) { + // This is one-shot, as this mod highly relies on the fact that LogonUI calls the BSDR interface in the following order: + // (Winlogon) ShutdownWindowsWorkerThread -> LogonUI launch -> BSDR::Start -> add_Resolved -> AddApplication * n -> + // (Force logoff chosen) -> BSDR::Hide -> get_WasClicked -> BSDR::Stop -> LogonUI exit -> Session teardown (Winlogon exit) Wh_Log(L"CustomBSDR has already started once!"); return; } @@ -3238,13 +3264,14 @@ void CustomBSDR::Start(LogonUIState state) { } void CustomBSDR::AddApplication(IShutdownBlockingApp* blockingApp) { + lock_guard lock(pendingAppsMutex); if (hDlg && IsWindow(hDlg)) { blockingApp->AddRef(); if (!PostMessageW(hDlg, WM_ADD_APP, 0, reinterpret_cast(blockingApp))) { blockingApp->Release(); } } else { - pendingApps.push_back(blockingApp); + pendingApps->push_back(blockingApp); } } @@ -3267,7 +3294,7 @@ void CustomBSDR::Stop() { } #pragma endregion CustomBSDR.cpp -#pragma region LogonUI BSDR Hooks +#pragma region LogonUI BSDR Hooks (Interface) long __fastcall BlockedShutdownUXImpl_Start_hook(void* thisPtr, void* userSettingManager, ILogonUIStateInfo* logonUIStateInfo) { Wh_Log(L"BlockedShutdownUXImpl::Start"); LogonUIState logonUIState = LogonUIState_Start; @@ -3284,12 +3311,10 @@ long __fastcall BlockedShutdownUXImpl_get_ScaleFactor_hook(void* thisPtr, unsign long __fastcall BlockedShutdownUXImpl_get_WasClicked_hook(void* thisPtr, unsigned char* wasClicked) { Wh_Log(L"BlockedShutdownUXImpl::get_WasClicked"); - { lock_guard lock(resolvedMutex); *wasClicked = _wasClicked; } - return S_OK; }; @@ -3454,7 +3479,7 @@ WindhawkUtils::SYMBOL_HOOK hooks[] = { FALSE } }; -#pragma endregion LogonUI BSDR Hooks +#pragma endregion LogonUI BSDR Hooks (Interface) #pragma region Winlogon hooks (disable async logoff) int* p_g_fShutdownResolverDisabled = nullptr; @@ -3462,6 +3487,7 @@ int* p_g_fShutdownResolverDisabled = nullptr; typedef void __fastcall (*ShutdownWindowsWorkerThread_t)(void* Instance, void* Context); ShutdownWindowsWorkerThread_t ShutdownWindowsWorkerThread_original; void __fastcall ShutdownWindowsWorkerThread_hook(void* Instance, void* Context) { + int origResolverDisabledState = *p_g_fShutdownResolverDisabled; if (p_g_fShutdownResolverDisabled) { if (Wh_GetIntValue(L"LogonUIHookOK", 0)) { *p_g_fShutdownResolverDisabled = 1; // Disable the async logoff resolver @@ -3475,6 +3501,8 @@ void __fastcall ShutdownWindowsWorkerThread_hook(void* Instance, void* Context) } } ShutdownWindowsWorkerThread_original(Instance, Context); + *p_g_fShutdownResolverDisabled = origResolverDisabledState; + Wh_Log(L"ShutdownWindowsWorkerThread ended; restored p_g_fShutdownResolverDisabled to %d", origResolverDisabledState); } WindhawkUtils::SYMBOL_HOOK winlogonExeHooks[] = { @@ -3564,7 +3592,6 @@ void Wh_ModUninit() { } WaitForSingleObject(CustomBSDR::hThread, INFINITE); CloseHandle(CustomBSDR::hThread); - UnregisterClassW(BSDR_CLASSNAME, GetModuleHandleW(nullptr)); // Only unregister if thread is started once } if (CustomBSDR::hStopEvent) { CloseHandle(CustomBSDR::hStopEvent); From b528c57e1ff7232311a66d2ace42542b80627985 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Thu, 27 Aug 2026 03:40:29 +0900 Subject: [PATCH 08/51] Apply fixes --- mods/win7-bsdr.wh.cpp | 270 +++++++++++++++++++++++++++--------------- 1 file changed, 174 insertions(+), 96 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index f4fbcce53b..755512dcdb 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -31,12 +31,17 @@ * This is not necessary. Hardcoded resources will be used instead if the path is not set or the file is missing. * Known issues * If your user account has no password, enabling the logoff sequence option may make the system automatically log on again after logging off. + +![Screenshot](https://raw.githubusercontent.com/Ingan121/files/refs/heads/master/vmware_gj0gqnHj8e.png) ## ⚠ Important usage note ⚠ In order to use this mod, you must allow Windhawk to inject into the **LogonUI.exe** system process. To do so, add it to the process inclusion list in the advanced settings. If you do not do this, it will silently fail to inject. +You must add it to the inclusion list even if you have disabled the critical system process +exclusion option. Otherwise the logoff sequence portion of mod will not function. + ![Advanced settings screenshot](https://i.imgur.com/LRhREtJ.png) */ // ==/WindhawkModReadme== @@ -50,6 +55,7 @@ settings. If you do not do this, it will silently fail to inject. - themeScrollbar: false $name: Apply visual styles to the scroll bar $name:ko-KR: 스크롤 막대에 시각 테마 적용 + $description: The original Windows 7 blocked shutdown resolver had an unthemed scroll bar. Enable this if you find it ugly. - disableAsyncLogoff: true $name: Restore Windows 7's logoff sequence $name:ko-KR: Windows 7의 로그오프 절차 복원 @@ -59,6 +65,7 @@ settings. If you do not do this, it will silently fail to inject. #include #include #include +#include #include #include #include @@ -2029,6 +2036,7 @@ bool CustomBSDR::IsHighContrast() { // Load winsrv alpha bitmaps properly HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { + HRSRC hResource = nullptr; const void* pResourceData = nullptr; if (forceHardcoded) { @@ -2060,7 +2068,7 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { return nullptr; } } else { - HRSRC hResource = FindResourceW(hResDll, MAKEINTRESOURCEW(resourceId), RT_BITMAP); + hResource = FindResourceW(hResDll, MAKEINTRESOURCEW(resourceId), RT_BITMAP); if (!hResource) return CustomBSDR::LoadAlphaBitmap(resourceId, true); @@ -2106,7 +2114,12 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { } const unsigned char* pSrc = static_cast(pResourceData) + dwBitsOffset; - DWORD dwSize = width * height * 4; + DWORD dwSize = 0; + if (isUsingHardcodedRes) { // Don't trust bmp header from external dll + dwSize = SizeofResource(hResDll, hResource) - sizeof(BITMAPINFOHEADER); + } else { + dwSize = width * height * 4; + } if (isTopDown) { memcpy(pBits, pSrc, dwSize); @@ -2383,13 +2396,11 @@ LRESULT CALLBACK CustomBSDR::ButtonSubclassProc(HWND hWnd, UINT uMsg, WPARAM wPa break; } case WM_UPDATEUISTATE: { - switch (HIWORD(wParam)) { - case UISF_HIDEFOCUS: - SetPropW(hWnd, L"CustomBSDR_HideFocus", (HANDLE)(LOWORD(wParam) == UIS_SET)); - break; - case UISF_HIDEACCEL: - SetPropW(hWnd, L"CustomBSDR_HideAccel", (HANDLE)(LOWORD(wParam) == UIS_SET)); - break; + if ((HIWORD(wParam) & UISF_HIDEFOCUS) == UISF_HIDEFOCUS) { + SetPropW(hWnd, L"CustomBSDR_HideFocus", (HANDLE)((LOWORD(wParam) & UIS_SET) == UIS_SET)); + } + if ((HIWORD(wParam) & UISF_HIDEACCEL) == UISF_HIDEACCEL) { + SetPropW(hWnd, L"CustomBSDR_HideAccel", (HANDLE)((LOWORD(wParam) & UIS_SET) == UIS_SET)); } break; } @@ -2649,7 +2660,6 @@ void CustomBSDR::UpdateAppListLayout() { // Set the title text based on the number of apps on the list if (hTitleText) { wchar_t titleFormat[256] = {}; - wchar_t titleText[256] = {}; // Redraw the entire title control area to prevent artifacts from previous longer text when the number of apps decreases InvalidateRect(hTitleText, nullptr, TRUE); if (appTiles->size() == 1) { @@ -2659,8 +2669,9 @@ void CustomBSDR::UpdateAppListLayout() { } else { GetString(IDS_BSDR_BLOCKINGAPPCOUNT_MULTI, titleFormat, _countof(titleFormat)); } - swprintf_s(titleText, _countof(titleText), titleFormat, (int)appTiles->size()); - SetWindowTextW(hTitleText, titleText); + // Avoid swprintf with format string from user supplied dll + wstring titleText = regex_replace(titleFormat, wregex(L"%d"), to_wstring(appTiles->size())); + SetWindowTextW(hTitleText, titleText.c_str()); } } @@ -2892,8 +2903,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM switch (LOWORD(wParam)) { case IDCANCEL: Resolve(BlockedShutdownResolution_Cancel); - DestroyWindow(hDlg); - DestroyWindow(hBgWnd); + ShowWindow(hBgWnd, SW_HIDE); if (!isOnSecureDesktop) { // If BSDR is forced to show on the default desktop with the Windhawk mod, LogonUI.exe won't exit for some reason on cancel, // causing issues with subsequent session ends, unless it's killed manually or Ctrl+Alt+Del is pressed once @@ -2905,6 +2915,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM // Still haven't figured out the culprit yet ExitProcess(0); } + SendMessageW(hBgWnd, WM_CLOSE, 0, 0); return TRUE; case IDC_BSDR_FORCE_BTN: ShowWindow(hWarningText, SW_SHOW); @@ -2916,8 +2927,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM return TRUE; case IDYES: Resolve(BlockedShutdownResolution_Force); - DestroyWindow(hDlg); - DestroyWindow(hBgWnd); + SendMessageW(hBgWnd, WM_CLOSE, 0, 0); return TRUE; case IDNO: ShowWindow(hWarningText, SW_HIDE); @@ -3029,7 +3039,8 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA } else { // Bail out Wh_Log(L"Dialog creation failed!!"); - MessageBoxW(nullptr, L"Dialog creation failed!", L"Windows 7 Blocked Shutdown UX & Logoff Sequence", MB_OK); + // Note: Windows also does force logoff when the BSDR is not available, e.g. the BSDR DLL is missing, on Server Core, etc. + // Probably this is better than leaving a system that never shuts down (if the mod is repeatedly failing) Resolve(BlockedShutdownResolution_Force); return 0; } @@ -3087,6 +3098,8 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA CenterWindow(hDlg); UpdateAppListLayout(); RedrawWindow(hDlg, nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_UPDATENOW); + // Retaking screenshot requires temporarily hiding the window + // Windows 7 doesn't do that either so skip that return 0; } case WM_CLOSE: { @@ -3171,78 +3184,80 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { WNDCLASSEXW wndClass = {}; wndClass.cbSize = sizeof(WNDCLASSEXW); - wndClass.style = CS_GLOBALCLASS; wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wndClass.hInstance = HINST_THISCOMPONENT; wndClass.hCursor = LoadCursorW(nullptr, IDC_ARROW); wndClass.lpfnWndProc = WndProc; wndClass.lpszClassName = BSDR_CLASSNAME; - if (RegisterClassExW(&wndClass) == 0) { - int gle = GetLastError(); - Wh_Log(L"RegisterClassExW failed, GLE=%d", gle); - return gle; - } + if (RegisterClassExW(&wndClass)) { + bgOffsetX = GetSystemMetrics(SM_XVIRTUALSCREEN); + bgOffsetY = GetSystemMetrics(SM_YVIRTUALSCREEN); + bgWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN); + bgHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN); - bgOffsetX = GetSystemMetrics(SM_XVIRTUALSCREEN); - bgOffsetY = GetSystemMetrics(SM_YVIRTUALSCREEN); - bgWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN); - bgHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN); + hBgWnd = CreateWindowExW(WS_EX_TOPMOST, wndClass.lpszClassName, NULL, WS_POPUP | WS_VISIBLE, bgOffsetX, bgOffsetY, bgWidth, bgHeight, NULL, NULL, NULL, NULL); - hBgWnd = CreateWindowExW(WS_EX_TOPMOST, wndClass.lpszClassName, NULL, WS_POPUP | WS_VISIBLE, bgOffsetX, bgOffsetY, bgWidth, bgHeight, NULL, NULL, NULL, NULL); + if (hBgWnd) { + HANDLE waitHandles[1] = { hStopEvent }; + BOOL bQuit = FALSE; + while (TRUE) { + DWORD result = MsgWaitForMultipleObjects( + 1, + waitHandles, + FALSE, + INFINITE, + QS_ALLINPUT + ); + + if (result == WAIT_OBJECT_0) { // hStopEvent + if (hBgWnd && IsWindow(hBgWnd)) { + SendMessageW(hBgWnd, WM_CLOSE, 0, 0); + } else { + break; + } + } else if (result == WAIT_OBJECT_0 + 1) { + MSG msg; + while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) { + bQuit = TRUE; + break; + } + if (msg.message == WM_SYSKEYDOWN && msg.wParam == VK_MENU) { + SetPropW(hForceButton, L"CustomBSDR_HideAccel", (HANDLE)FALSE); + SetPropW(hCancelButton, L"CustomBSDR_HideAccel", (HANDLE)FALSE); + SetPropW(hYesButton, L"CustomBSDR_HideAccel", (HANDLE)FALSE); + SetPropW(hNoButton, L"CustomBSDR_HideAccel", (HANDLE)FALSE); + RedrawWindow(hDlg, nullptr, nullptr, RDW_FRAME | RDW_INVALIDATE); + } + if (!IsDialogMessageW(hDlg, &msg)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + if (bQuit) { + break; + } + } + } + } else { + int gle = GetLastError(); + Wh_Log(L"CreateWindowExW failed, GLE=%d", gle); + return gle; + } - if (!hBgWnd) { + if (UnregisterClassW(BSDR_CLASSNAME, HINST_THISCOMPONENT) == 0) { + Wh_Log(L"UnregisterClassW failed, GLE=%d", GetLastError()); + } + } else { int gle = GetLastError(); - Wh_Log(L"CreateWindowExW failed, GLE=%d", gle); + Wh_Log(L"RegisterClassExW failed, GLE=%d", gle); return gle; } - HANDLE waitHandles[1] = { hStopEvent }; - BOOL bQuit = FALSE; - while (TRUE) { - DWORD result = MsgWaitForMultipleObjects( - 1, - waitHandles, - FALSE, - INFINITE, - QS_ALLINPUT - ); - - if (result == WAIT_OBJECT_0) { // hStopEvent - if (hBgWnd && IsWindow(hBgWnd)) { - SendMessageW(hBgWnd, WM_CLOSE, 0, 0); - } else { - break; - } - } else if (result == WAIT_OBJECT_0 + 1) { - MSG msg; - while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { - if (msg.message == WM_QUIT) { - bQuit = TRUE; - break; - } - if (msg.message == WM_SYSKEYDOWN && msg.wParam == VK_MENU) { - SetPropW(hForceButton, L"CustomBSDR_HideAccel", (HANDLE)FALSE); - SetPropW(hCancelButton, L"CustomBSDR_HideAccel", (HANDLE)FALSE); - SetPropW(hYesButton, L"CustomBSDR_HideAccel", (HANDLE)FALSE); - SetPropW(hNoButton, L"CustomBSDR_HideAccel", (HANDLE)FALSE); - RedrawWindow(hDlg, nullptr, nullptr, RDW_FRAME | RDW_INVALIDATE); - } - if (!IsDialogMessageW(hDlg, &msg)) { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - } - if (bQuit) { - break; - } - } - } - if (UnregisterClassW(BSDR_CLASSNAME, HINST_THISCOMPONENT) == 0) { - Wh_Log(L"UnregisterClassW failed, GLE=%d", GetLastError()); + if (SUCCEEDED(hrCo)) { + CoUninitialize(); } - - if (SUCCEEDED(hrCo)) CoUninitialize(); return 0; } @@ -3482,23 +3497,70 @@ WindhawkUtils::SYMBOL_HOOK hooks[] = { #pragma endregion LogonUI BSDR Hooks (Interface) #pragma region Winlogon hooks (disable async logoff) +// Safeguard: check if user read the readme +// The stock immersive BSDR does not support being displayed on the default desktop, so if it shows, +// it will get stuck in the invisible secure desktop, and users can become clueless. +// Pressing ctrl alt del can get out of this state but lets add this minimal safeguard +// +// Can't think of better appraoch because of the execution sequence mentioned in CustomBSDR::Start +// ShutdownWindowsWorkerThread runs before LogonUI exec so checking live LUI injection status isn't possible +// and will be always one step behind (e.g. will detect as LUI not injected on first logoff after mod install) +// I know the global inclusion key is not everything that affects that injection but this is minimal safeguard anyway +// Let's just hope users don't mess with mod specific advanced settings to force exclude LogonUI.exe lol +bool IsLogonUiInjectionEnabled() { + HKEY hKey; + int res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Windhawk\\Engine\\Settings", 0, KEY_READ, &hKey); + if (res != ERROR_SUCCESS) { + Wh_Log(L"WH inclusion check failed (RegOpenKeyExW), error=%d", res); + return false; + } + + DWORD size = 0; + res = RegQueryValueExW(hKey, L"Include", nullptr, nullptr, nullptr, &size); + if (res != ERROR_SUCCESS && res != ERROR_MORE_DATA) { + Wh_Log(L"WH inclusion check failed (size query), GLE=%d", res); + return false; + } + + wchar_t* data = new wchar_t[size / sizeof(wchar_t) + 1]; + res = RegQueryValueExW(hKey, L"Include", nullptr, nullptr, (LPBYTE)data, &size); + if (res != ERROR_SUCCESS) { + Wh_Log(L"WH inclusion check failed, error=%d", res); + return false; + } + + res = RegCloseKey(hKey); + if (res != ERROR_SUCCESS) { + Wh_Log(L"WH inclusion check RegCloseKey failed, error=%d", res); + } + + data[size / sizeof(wchar_t)] = L'\0'; + int isLogonUiThere = wcsstr(_wcsupr(data), L"LOGONUI.EXE") != NULL; + delete[] data; + + return isLogonUiThere; +} + int* p_g_fShutdownResolverDisabled = nullptr; typedef void __fastcall (*ShutdownWindowsWorkerThread_t)(void* Instance, void* Context); ShutdownWindowsWorkerThread_t ShutdownWindowsWorkerThread_original; void __fastcall ShutdownWindowsWorkerThread_hook(void* Instance, void* Context) { + if (!p_g_fShutdownResolverDisabled) { + return ShutdownWindowsWorkerThread_original(Instance, Context); + } + int origResolverDisabledState = *p_g_fShutdownResolverDisabled; - if (p_g_fShutdownResolverDisabled) { - if (Wh_GetIntValue(L"LogonUIHookOK", 0)) { - *p_g_fShutdownResolverDisabled = 1; // Disable the async logoff resolver - Wh_Log(L"ShutdownWindowsWorkerThread_hook: Set g_fShutdownResolverDisabled to 1"); - } else { - // User did not read the instruction and LogonUI is not added to the inclusion list - // The stock immersive BSDR does not support being displayed on the default desktop, so if it shows, - // it will get stuck in the invisible secure desktop, and users can become clueless. - // Pressing ctrl alt del can get out of this state but lets add this minimal safeguard - Wh_Log(L"ShutdownWindowsWorkerThread_hook: Not setting g_fShutdownResolverDisabled as LogonUI hooks are not ready"); - } + if (IsLogonUiInjectionEnabled()) { + *p_g_fShutdownResolverDisabled = 1; // Disable the async logoff resolver + Wh_Log(L"ShutdownWindowsWorkerThread_hook: Set g_fShutdownResolverDisabled to 1"); + } else { + // User did not read the instruction and LogonUI is not added to the inclusion list + // The stock immersive BSDR does not support being displayed on the default desktop, so if it shows, + // it will get stuck in the invisible secure desktop, and users can become clueless. + // Pressing ctrl alt del can get out of this state but lets add this minimal safeguard + // This is + Wh_Log(L"ShutdownWindowsWorkerThread_hook: Not setting g_fShutdownResolverDisabled as LogonUI hooks are not ready"); } ShutdownWindowsWorkerThread_original(Instance, Context); *p_g_fShutdownResolverDisabled = origResolverDisabledState; @@ -3548,8 +3610,11 @@ BOOL Wh_ModInit() { return TRUE; } + CustomBSDR::appTiles.emplace(); + CustomBSDR::pendingApps.emplace(); + LPCWSTR dllPath = Wh_GetStringSetting(L"resDllPath"); - if (dllPath && *dllPath != L'\0') { + if (*dllPath != L'\0') { hResDll = LoadLibraryExW(dllPath, nullptr, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE); Wh_FreeStringSetting(dllPath); if (hResDll) { @@ -3560,43 +3625,56 @@ BOOL Wh_ModInit() { } else { Wh_Log(L"Using hardcoded resources..."); } - - CustomBSDR::hStopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); - if (!CustomBSDR::hStopEvent) { - Wh_Log(L"CreateEventW failed, GLE=%u", GetLastError()); - return FALSE; - } HMODULE blockedShutdownDll = LoadLibraryExW(L"Windows.UI.BlockedShutdown.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); if (blockedShutdownDll) { if (!WindhawkUtils::HookSymbols(blockedShutdownDll, hooks, ARRAYSIZE(hooks))) { Wh_Log(L"Failed to hook symbols in Windows.UI.BlockedShutdown.dll"); - Wh_SetIntValue(L"LogonUIHookOK", 0); return FALSE; } } else { Wh_Log(L"Failed to load Windows.UI.BlockedShutdown.dll"); - Wh_SetIntValue(L"LogonUIHookOK", 0); return FALSE; } - Wh_SetIntValue(L"LogonUIHookOK", 1); + CustomBSDR::hStopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + if (!CustomBSDR::hStopEvent) { + Wh_Log(L"CreateEventW failed, GLE=%u", GetLastError()); + return FALSE; + } + return TRUE; } void Wh_ModUninit() { Wh_Log(L"Uninit"); + if (CustomBSDR::hThread) { + bool needsResolve; + { + lock_guard lock(resolvedMutex); + needsResolve = resolvedValue == BlockedShutdownResolution_None; + } + if (needsResolve) { + Resolve(BlockedShutdownResolution_Cancel); + } + if (CustomBSDR::hStopEvent) { SetEvent(CustomBSDR::hStopEvent); } WaitForSingleObject(CustomBSDR::hThread, INFINITE); CloseHandle(CustomBSDR::hThread); } + + CustomBSDR::appTiles.reset(); + CustomBSDR::pendingApps.reset(); + if (CustomBSDR::hStopEvent) { CloseHandle(CustomBSDR::hStopEvent); } + _Resolved.Reset(); + if (hResDll) { FreeLibrary(hResDll); } From d70bc9c7e2d186bdc1e21a93a4c5c51f856fa028 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Thu, 27 Aug 2026 03:45:46 +0900 Subject: [PATCH 09/51] Redraw on layout update --- mods/win7-bsdr.wh.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 755512dcdb..a88ae396c5 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -2673,6 +2673,8 @@ void CustomBSDR::UpdateAppListLayout() { wstring titleText = regex_replace(titleFormat, wregex(L"%d"), to_wstring(appTiles->size())); SetWindowTextW(hTitleText, titleText.c_str()); } + + RedrawWindow(hDlg, nullptr, nullptr, RDW_FRAME | RDW_INVALIDATE); } INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { From ab5d4e2afa421842fb83f7d68aa6e93ff36e052d Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Thu, 27 Aug 2026 06:29:04 +0900 Subject: [PATCH 10/51] Apply review fixes and fix drawing issue with scrolling to late-added app --- mods/win7-bsdr.wh.cpp | 92 ++++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 37 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index a88ae396c5..1126e97598 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -22,7 +22,6 @@ * This mod's UX part has no effect if AuthUX BSDR is installed. * This mod also optionally restores Windows 7's logoff sequence, where the system would switch to a 'logging off' screen after closing all applications, unlike Windows 8 and newer. * This part is compatible with AuthUX BSDR as well. - * It may not work on the first logoff after installing the mod because of additional safety checks. It should work from then on. * Loading resources from an external DLL is supported for localization. This requires one of the following: * `winsrv.dll` and `winsrv.dll.mui` from Windows 7. Put `winsrv.dll` in some folder, create a folder named after your locale (e.g. `en-US`), and put `winsrv.dll.mui` there. * `AuthUX.dll` from a build of AuthUX that supports BSDR. @@ -51,7 +50,7 @@ exclusion option. Otherwise the logoff sequence portion of mod will not function - resDllPath: "" $name: AuthUX.dll/winsrv.dll path $name:ko-KR: AuthUX.dll/winsrv.dll 경로 - $description: Path to your copy of winsrv.dll from Windows 7 or AuthUX.dll build with BSDR. Hardcoded resources will be used instead if this is not set or missing. + $description: Path to your copy of winsrv.dll from Windows 7 or AuthUX.dll build with BSDR. Hardcoded resources will be used instead if this is not set or missing. - themeScrollbar: false $name: Apply visual styles to the scroll bar $name:ko-KR: 스크롤 막대에 시각 테마 적용 @@ -1683,10 +1682,9 @@ namespace CustomBSDR { HWND hTitle; HWND hBlockReason; HBITMAP hIconBitmap; - Microsoft::WRL::ComPtr app; }; - static optional> appTiles; - static optional>> pendingApps; + [[clang::no_destroy]] static optional> appTiles{in_place}; + [[clang::no_destroy]] static optional>> pendingApps{in_place}; mutex pendingAppsMutex; }; #pragma endregion logoncontroller.h and CustomBSDR.h @@ -2114,15 +2112,16 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { } const unsigned char* pSrc = static_cast(pResourceData) + dwBitsOffset; - DWORD dwSize = 0; - if (isUsingHardcodedRes) { // Don't trust bmp header from external dll - dwSize = SizeofResource(hResDll, hResource) - sizeof(BITMAPINFOHEADER); - } else { - dwSize = width * height * 4; + if (!isUsingHardcodedRes) { // Don't trust bmp header from external dll + DWORD dwSize = SizeofResource(hResDll, hResource) - dwBitsOffset; + if (dwSize < 0 || (DWORD)width * height * 4 > dwSize) { + Wh_Log(L"Image load error: invalid size"); + return nullptr; + } } if (isTopDown) { - memcpy(pBits, pSrc, dwSize); + memcpy(pBits, pSrc, width * height * 4); } else { for (int y = 0; y < height; y++) { memcpy((unsigned char*)pBits + y * width * 4, @@ -2456,7 +2455,6 @@ void CustomBSDR::CreateAppTileControls(IShutdownBlockingApp* blockingApp) { AppTile tile = {}; blockingApp->get_Id(&tile.appId); blockingApp->get_IsBlocking(&tile.isBlocking); - tile.app = blockingApp; tile.hIconBitmap = nullptr; HSTRING caption, blockReason; @@ -2612,7 +2610,7 @@ void CustomBSDR::UpdateAppListLayout() { si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS; si.nMin = 0; - si.nMax = totalContentHeight; + si.nMax = totalContentHeight - 1; si.nPage = visibleHeight; si.nPos = scrollPos; SetScrollInfo(hScrollBar, SB_CTL, &si, TRUE); @@ -2655,7 +2653,10 @@ void CustomBSDR::UpdateAppListLayout() { yPos += height; } - SetWindowPos(hAppListScroll, nullptr, 0, -scrollPos, 0, 0, SWP_NOZORDER | SWP_NOSIZE); + RECT rcAppList; + GetWindowRect(hAppList, &rcAppList); + int currentWidth = rcAppList.right - rcAppList.left; + SetWindowPos(hAppListScroll, nullptr, 0, -scrollPos, currentWidth, totalContentHeight, SWP_NOZORDER); // Set the title text based on the number of apps on the list if (hTitleText) { @@ -3166,6 +3167,8 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA } DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { + int ret = 0; + HRESULT hrCo = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); // Attempt to create window on the input desktop, as the thread is always running in secure desktop at this point, @@ -3178,9 +3181,11 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { } if (!SetThreadDesktop(hDesktop)) { CloseDesktop(hDesktop); - return GetLastError(); + Wh_Log(L"SetThreadDesktop failed, GLE=%d", GetLastError()); } CloseDesktop(hDesktop); + } else { + Wh_Log(L"OpenInputDesktop failed, GLE=%d", GetLastError()); } WNDCLASSEXW wndClass = {}; @@ -3243,18 +3248,21 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { } } } else { - int gle = GetLastError(); - Wh_Log(L"CreateWindowExW failed, GLE=%d", gle); - return gle; + int ret = GetLastError(); + Wh_Log(L"CreateWindowExW failed, GLE=%d", ret); } if (UnregisterClassW(BSDR_CLASSNAME, HINST_THISCOMPONENT) == 0) { Wh_Log(L"UnregisterClassW failed, GLE=%d", GetLastError()); } } else { - int gle = GetLastError(); - Wh_Log(L"RegisterClassExW failed, GLE=%d", gle); - return gle; + int ret = GetLastError(); + Wh_Log(L"RegisterClassExW failed, GLE=%d", ret); + } + + if (ret != 0) { + Wh_Log(L"CustomBSDR thread init failed; resolving with force..."); + Resolve(BlockedShutdownResolution_Force); } if (SUCCEEDED(hrCo)) { @@ -3293,8 +3301,20 @@ void CustomBSDR::AddApplication(IShutdownBlockingApp* blockingApp) { } void CustomBSDR::RemoveApplication(UINT appid) { + lock_guard lock(pendingAppsMutex); if (hDlg && IsWindow(hDlg)) { PostMessageW(hDlg, WM_REMOVE_APP, (WPARAM)appid, 0); + } else { + for (size_t i = 0; i < pendingApps->size(); i++) { + IShutdownBlockingApp* app = (*pendingApps)[i].Get(); + UINT iterId = 0; + if (SUCCEEDED(app->get_Id(&iterId))) { + if (iterId == appid) { + pendingApps->erase(pendingApps->begin() + i); + return; + } + } + } } } @@ -3518,6 +3538,7 @@ bool IsLogonUiInjectionEnabled() { } DWORD size = 0; + // WH inclusion key: pipe separated REG_SZ res = RegQueryValueExW(hKey, L"Include", nullptr, nullptr, nullptr, &size); if (res != ERROR_SUCCESS && res != ERROR_MORE_DATA) { Wh_Log(L"WH inclusion check failed (size query), GLE=%d", res); @@ -3612,22 +3633,6 @@ BOOL Wh_ModInit() { return TRUE; } - CustomBSDR::appTiles.emplace(); - CustomBSDR::pendingApps.emplace(); - - LPCWSTR dllPath = Wh_GetStringSetting(L"resDllPath"); - if (*dllPath != L'\0') { - hResDll = LoadLibraryExW(dllPath, nullptr, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE); - Wh_FreeStringSetting(dllPath); - if (hResDll) { - isUsingHardcodedRes = false; - } else { - Wh_Log(L"Failed to load the resource DLL! Using hardcoded resources..."); - } - } else { - Wh_Log(L"Using hardcoded resources..."); - } - HMODULE blockedShutdownDll = LoadLibraryExW(L"Windows.UI.BlockedShutdown.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); if (blockedShutdownDll) { if (!WindhawkUtils::HookSymbols(blockedShutdownDll, hooks, ARRAYSIZE(hooks))) { @@ -3645,6 +3650,19 @@ BOOL Wh_ModInit() { return FALSE; } + LPCWSTR dllPath = Wh_GetStringSetting(L"resDllPath"); + if (*dllPath != L'\0') { + hResDll = LoadLibraryExW(dllPath, nullptr, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE); + Wh_FreeStringSetting(dllPath); + if (hResDll) { + isUsingHardcodedRes = false; + } else { + Wh_Log(L"Failed to load the resource DLL! Using hardcoded resources..."); + } + } else { + Wh_Log(L"Using hardcoded resources..."); + } + return TRUE; } From 376c284e27fd3a8dbfffea14ab5f50fe48b5dd41 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Thu, 27 Aug 2026 06:35:28 +0900 Subject: [PATCH 11/51] Update win7-bsdr.wh.cpp --- mods/win7-bsdr.wh.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 1126e97598..b4f4670491 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -3542,21 +3542,19 @@ bool IsLogonUiInjectionEnabled() { res = RegQueryValueExW(hKey, L"Include", nullptr, nullptr, nullptr, &size); if (res != ERROR_SUCCESS && res != ERROR_MORE_DATA) { Wh_Log(L"WH inclusion check failed (size query), GLE=%d", res); + RegCloseKey(hKey); return false; } wchar_t* data = new wchar_t[size / sizeof(wchar_t) + 1]; res = RegQueryValueExW(hKey, L"Include", nullptr, nullptr, (LPBYTE)data, &size); + RegCloseKey(hKey); if (res != ERROR_SUCCESS) { Wh_Log(L"WH inclusion check failed, error=%d", res); + delete[] data; return false; } - res = RegCloseKey(hKey); - if (res != ERROR_SUCCESS) { - Wh_Log(L"WH inclusion check RegCloseKey failed, error=%d", res); - } - data[size / sizeof(wchar_t)] = L'\0'; int isLogonUiThere = wcsstr(_wcsupr(data), L"LOGONUI.EXE") != NULL; delete[] data; From 2cbd7b48e4ca0d83b5e34c52f9e0af2dc275dbe2 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Thu, 27 Aug 2026 17:23:12 +0900 Subject: [PATCH 12/51] Apply fixes --- mods/win7-bsdr.wh.cpp | 350 ++++++++++-------------------------------- 1 file changed, 82 insertions(+), 268 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index b4f4670491..a642ee433e 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -22,7 +22,7 @@ * This mod's UX part has no effect if AuthUX BSDR is installed. * This mod also optionally restores Windows 7's logoff sequence, where the system would switch to a 'logging off' screen after closing all applications, unlike Windows 8 and newer. * This part is compatible with AuthUX BSDR as well. -* Loading resources from an external DLL is supported for localization. This requires one of the following: +* (Optional) Loading resources from an external DLL is supported for localization. This requires one of the following: * `winsrv.dll` and `winsrv.dll.mui` from Windows 7. Put `winsrv.dll` in some folder, create a folder named after your locale (e.g. `en-US`), and put `winsrv.dll.mui` there. * `AuthUX.dll` from a build of AuthUX that supports BSDR. * Set the DLL path in the mod settings afterwards. You may also point this directly to `winsrv.dll.mui` instead of setting up the directory structure mentioned above. @@ -72,8 +72,6 @@ exclusion option. Otherwise the logoff sequence portion of mod will not function #include #include -using namespace std; - #define WM_ADD_APP (WM_APP + 1) #define WM_REMOVE_APP (WM_APP + 2) @@ -87,6 +85,8 @@ using namespace std; EXTERN_C IMAGE_DOS_HEADER __ImageBase; #define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase) +HINSTANCE hBlockedShutdownDll; + #pragma region resources // Resource IDs #define IDD_BSDR_DLG 20 @@ -1594,7 +1594,7 @@ ILogonUIStateInfo : IInspectable { // Windhawk lacks some WinRT header files needed for EventSource. Since add_Resolved is only called once, just handle only one handler using ResolvedHandler = ABI::Windows::Foundation::ITypedEventHandler; [[clang::no_destroy]] Microsoft::WRL::ComPtr _Resolved; -mutex resolvedMutex; +std::mutex resolvedMutex; BlockedShutdownResolution resolvedValue = BlockedShutdownResolution_None; bool _wasClicked = false; @@ -1602,7 +1602,7 @@ void Resolve(BlockedShutdownResolution resolution) { Microsoft::WRL::ComPtr resolvedLocal; { - lock_guard lock(resolvedMutex); + std::lock_guard lock(resolvedMutex); _wasClicked = true; resolvedValue = resolution; resolvedLocal = _Resolved; @@ -1683,9 +1683,9 @@ namespace CustomBSDR { HWND hBlockReason; HBITMAP hIconBitmap; }; - [[clang::no_destroy]] static optional> appTiles{in_place}; - [[clang::no_destroy]] static optional>> pendingApps{in_place}; - mutex pendingAppsMutex; + static std::vector appTiles; + [[clang::no_destroy]] static std::optional>> pendingApps{std::in_place}; + std::mutex pendingAppsMutex; }; #pragma endregion logoncontroller.h and CustomBSDR.h @@ -1696,151 +1696,6 @@ enum LOAD_IMAGE_WITH_WIC_OPTION { LIWW_ORIENTATE_AND_CACHE, }; -bool WICIsOrientationSupported(IWICBitmapDecoder* pDecoder) { - const GUID GUID_ContainerFormatJpegXL = { 0xfec14e3f, 0x427a, 0x4736, { 0xaa, 0xe6, 0x27, 0xed, 0x84, 0xf6, 0x93, 0x22 } }; - - GUID guidContainerFormat = {}; - if (FAILED(pDecoder->GetContainerFormat(&guidContainerFormat))) - return false; - - return guidContainerFormat == GUID_ContainerFormatJpeg - || guidContainerFormat == GUID_ContainerFormatAdng - || guidContainerFormat == GUID_ContainerFormatHeif - || guidContainerFormat == GUID_ContainerFormatRaw - || guidContainerFormat == GUID_ContainerFormatJpegXL; -} - -HRESULT WICGetTransformOptionFromMetadata(IWICMetadataQueryReader* pQueryReader, WICBitmapTransformOptions* pOptions) { - *pOptions = WICBitmapTransformRotate0; - - PROPVARIANT varOrientation = {}; - HRESULT hr = pQueryReader->GetMetadataByName(L"System.Photo.Orientation", &varOrientation); - if (hr == WINCODEC_ERR_PROPERTYNOTFOUND || varOrientation.vt != VT_UI2) { - *pOptions = WICBitmapTransformRotate0; - hr = S_OK; - } else if (SUCCEEDED(hr)) { - switch (varOrientation.uiVal) { - case 1: - *pOptions = WICBitmapTransformRotate0; - break; - case 2: - *pOptions = WICBitmapTransformFlipHorizontal; - break; - case 3: - *pOptions = WICBitmapTransformRotate180; - break; - case 4: - *pOptions = WICBitmapTransformFlipVertical; - break; - case 5: - *pOptions = (WICBitmapTransformOptions)(WICBitmapTransformFlipVertical | WICBitmapTransformRotate90); - break; - case 6: - *pOptions = WICBitmapTransformRotate90; - break; - case 7: - *pOptions = (WICBitmapTransformOptions)(WICBitmapTransformFlipVertical | WICBitmapTransformRotate270); - break; - case 8: - *pOptions = WICBitmapTransformRotate270; - break; - default: - *pOptions = WICBitmapTransformRotate0; - break; - } - } - PropVariantClear(&varOrientation); - return hr; -} - -HRESULT WICCreateCachedOrientedBitmapSource(IWICImagingFactory* pFactory, IWICBitmapSource* pSourceBitmap, - IWICMetadataQueryReader* pQueryReader, IWICBitmapSource** ppOutputBitmap) { - *ppOutputBitmap = nullptr; - - WICBitmapTransformOptions transformOptions = WICBitmapTransformRotate0; - HRESULT hr = WICGetTransformOptionFromMetadata(pQueryReader, &transformOptions); - if (SUCCEEDED(hr)) { - if (transformOptions == WICBitmapTransformRotate0) { - *ppOutputBitmap = pSourceBitmap; - pSourceBitmap->AddRef(); - } else { - IWICBitmapFlipRotator* pFlipRotator = nullptr; - hr = pFactory->CreateBitmapFlipRotator(&pFlipRotator); - if (SUCCEEDED(hr)) { - if ((transformOptions & (WICBitmapTransformRotate90 | WICBitmapTransformFlipVertical)) != 0 || transformOptions == WICBitmapTransformRotate180) { - IWICBitmap* pCachedBitmap = nullptr; - hr = pFactory->CreateBitmapFromSource(pSourceBitmap, WICBitmapCacheOnLoad, &pCachedBitmap); - if (SUCCEEDED(hr)) { - hr = pFlipRotator->Initialize(pCachedBitmap, transformOptions); - pCachedBitmap->Release(); - } - } else { - hr = pFlipRotator->Initialize(pSourceBitmap, transformOptions); - } - - if (SUCCEEDED(hr)) { - *ppOutputBitmap = pFlipRotator; - pFlipRotator->AddRef(); - } - - pFlipRotator->Release(); - } - } - } - - return hr; -} - -HRESULT WICCreateOrientedBitmapSource(IWICImagingFactory* pFactory, IWICBitmapSource* pSourceBitmap, - IWICMetadataQueryReader* pQueryReader, IWICBitmapSource** ppOrientedBitmap) { - *ppOrientedBitmap = nullptr; - - WICBitmapTransformOptions transformOptions = WICBitmapTransformRotate0; - HRESULT hr = WICGetTransformOptionFromMetadata(pQueryReader, &transformOptions); - if (SUCCEEDED(hr)) { - IWICBitmapFlipRotator* pFlipRotator = nullptr; - hr = pFactory->CreateBitmapFlipRotator(&pFlipRotator); - if (SUCCEEDED(hr)) { - hr = pFlipRotator->Initialize(pSourceBitmap, transformOptions); - if (SUCCEEDED(hr)) { - *ppOrientedBitmap = pFlipRotator; - pFlipRotator->AddRef(); - } - - pFlipRotator->Release(); - } - } - - return hr; -} - -HRESULT WICOrientateFrame(IWICImagingFactory* pFactory, IWICBitmapDecoder* pDecoder, - IWICBitmapFrameDecode* pFrameDecode, bool fCacheBitmap, IWICBitmapSource** ppOutputBitmap) { - *ppOutputBitmap = nullptr; - HRESULT hr = S_OK; - - if (WICIsOrientationSupported(pDecoder)) { - IWICMetadataQueryReader* pQueryReader; - pQueryReader = nullptr; - if (SUCCEEDED(pFrameDecode->GetMetadataQueryReader(&pQueryReader))) { - if (fCacheBitmap) { - hr = WICCreateCachedOrientedBitmapSource(pFactory, pFrameDecode, pQueryReader, ppOutputBitmap); - } else { - hr = WICCreateOrientedBitmapSource(pFactory, pFrameDecode, pQueryReader, ppOutputBitmap); - } - pQueryReader->Release(); - } else { - *ppOutputBitmap = pFrameDecode; - pFrameDecode->AddRef(); - } - } else { - *ppOutputBitmap = pFrameDecode; - pFrameDecode->AddRef(); - } - - return hr; -} - HRESULT LoadImageWithWIC(IWICImagingFactory* pWICImagingFactory, IStream* pStream, LOAD_IMAGE_WITH_WIC_OPTION option, IWICBitmapSource** ppWICBitmapSource, IWICBitmapFrameDecode** ppWICBitmapFrameDecode, GUID* pguidContainerFormat) { *ppWICBitmapSource = nullptr; @@ -1868,51 +1723,7 @@ HRESULT LoadImageWithWIC(IWICImagingFactory* pWICImagingFactory, IStream* pStrea hr = spDecoder->GetFrame(0, &spBitmapFrameDecode); if (SUCCEEDED(hr)) { if (option != LIWW_NONE) { - hr = WICOrientateFrame( - spWICImagingFactory.Get(), spDecoder.Get(), spBitmapFrameDecode.Get(), - option == LIWW_ORIENTATE_AND_CACHE, ppWICBitmapSource); - } else { - hr = spBitmapFrameDecode.CopyTo(ppWICBitmapSource); - } - } - if (SUCCEEDED(hr) && ppWICBitmapFrameDecode) { - *ppWICBitmapFrameDecode = spBitmapFrameDecode.Detach(); - } - } - } - - return hr; -} - -HRESULT LoadImageWithWIC(IWICImagingFactory* pWICImagingFactory, LPCWSTR pszPath, LOAD_IMAGE_WITH_WIC_OPTION option, - IWICBitmapSource** ppWICBitmapSource, IWICBitmapFrameDecode** ppWICBitmapFrameDecode, GUID* pguidContainerFormat) { - *ppWICBitmapSource = nullptr; - if (ppWICBitmapFrameDecode) - *ppWICBitmapFrameDecode = nullptr; - if (pguidContainerFormat) - *pguidContainerFormat = GUID_NULL; - - HRESULT hr = S_OK; - - Microsoft::WRL::ComPtr spWICImagingFactory(pWICImagingFactory); - if (!spWICImagingFactory.Get()) { - hr = CoCreateInstance(CLSID_WICImagingFactory2, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spWICImagingFactory)); - } - if (SUCCEEDED(hr)) { - Microsoft::WRL::ComPtr spDecoder; - hr = spWICImagingFactory->CreateDecoderFromFilename( - pszPath, &GUID_VendorMicrosoftBuiltIn, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &spDecoder); - if (SUCCEEDED(hr) && pguidContainerFormat) { - hr = spDecoder->GetContainerFormat(pguidContainerFormat); - } - if (SUCCEEDED(hr)) { - Microsoft::WRL::ComPtr spBitmapFrameDecode; - hr = spDecoder->GetFrame(0, &spBitmapFrameDecode); - if (SUCCEEDED(hr)) { - if (option != LIWW_NONE) { - hr = WICOrientateFrame( - spWICImagingFactory.Get(), spDecoder.Get(), spBitmapFrameDecode.Get(), - option == LIWW_ORIENTATE_AND_CACHE, ppWICBitmapSource); + hr = E_NOTIMPL; } else { hr = spBitmapFrameDecode.CopyTo(ppWICBitmapSource); } @@ -2083,7 +1894,7 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { const BITMAPINFOHEADER* pHeader = &pBitmapInfo->bmiHeader; if (pHeader->biBitCount != 32) { - if (isUsingHardcodedRes || !hResDll) { + if (isUsingHardcodedRes || !hResDll || forceHardcoded) { Wh_Log(L"Image load error: not 32bpp"); return nullptr; } @@ -2112,9 +1923,9 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { } const unsigned char* pSrc = static_cast(pResourceData) + dwBitsOffset; - if (!isUsingHardcodedRes) { // Don't trust bmp header from external dll - DWORD dwSize = SizeofResource(hResDll, hResource) - dwBitsOffset; - if (dwSize < 0 || (DWORD)width * height * 4 > dwSize) { + if (hResource) { // Size validation for external dll (don't trust its header) + DWORD dwSize = SizeofResource(hResDll, hResource); + if (dwSize < dwBitsOffset || (DWORD)width * height * 4 > dwSize - dwBitsOffset) { Wh_Log(L"Image load error: invalid size"); return nullptr; } @@ -2420,7 +2231,7 @@ LRESULT CALLBACK CustomBSDR::AppListSubclassProc(HWND hWnd, UINT uMsg, WPARAM wP HWND hControl = (HWND)lParam; bool isBlockReason = false; - for (auto& tile : *appTiles) { + for (auto& tile : appTiles) { if (tile.hBlockReason == hControl) { isBlockReason = true; break; @@ -2458,15 +2269,15 @@ void CustomBSDR::CreateAppTileControls(IShutdownBlockingApp* blockingApp) { tile.hIconBitmap = nullptr; HSTRING caption, blockReason; - wstring titleText; - wstring blockReasonText; + std::wstring titleText; + std::wstring blockReasonText; if (SUCCEEDED(blockingApp->get_Caption(&caption))) { const wchar_t* captionStr = WindowsGetStringRawBuffer(caption, nullptr); if (tile.isBlocking) { wchar_t waitingFor[256] = {}; GetString(IDS_BSDR_WAITINGFOR, waitingFor, _countof(waitingFor)); - titleText = wstring(waitingFor) + L" " + captionStr; + titleText = std::wstring(waitingFor) + L" " + captionStr; } else { titleText = captionStr; } @@ -2536,16 +2347,16 @@ void CustomBSDR::CreateAppTileControls(IShutdownBlockingApp* blockingApp) { } if (tile.isBlocking) { - appTiles->insert(appTiles->begin(), tile); + appTiles.insert(appTiles.begin(), tile); } else { - appTiles->push_back(tile); + appTiles.push_back(tile); } UpdateAppListLayout(); } void CustomBSDR::RemoveAppTileControls(UINT appId) { - for (auto it = appTiles->begin(); it != appTiles->end(); ++it) { + for (auto it = appTiles.begin(); it != appTiles.end(); ++it) { if (it->appId == appId) { if (it->hIcon) { DestroyWindow(it->hIcon); @@ -2560,7 +2371,7 @@ void CustomBSDR::RemoveAppTileControls(UINT appId) { DeleteObject(it->hIconBitmap); } - appTiles->erase(it); + appTiles.erase(it); UpdateAppListLayout(); return; @@ -2582,17 +2393,19 @@ void CustomBSDR::UpdateAppListLayout() { const int itemHeight = MulDiv(83, dpi, 96); const int itemHeightNoReason = MulDiv(62, dpi, 96); const int iconTextGap = MulDiv(8, dpi, 96); + int currentWidth = MulDiv(256, dpi, 96); int maxWidth = MulDiv(700, dpi, 96); int visibleHeight = MulDiv(300, dpi, 96); if (hAppList) { RECT rcContainer; GetClientRect(hAppList, &rcContainer); visibleHeight = rcContainer.bottom - rcContainer.top; - maxWidth = rcContainer.right - rcContainer.left - iconSize - iconTextGap; + currentWidth = rcContainer.right - rcContainer.left; + maxWidth = currentWidth - iconSize - iconTextGap; } totalContentHeight = 0; - for (auto& tile : *appTiles) { + for (auto& tile : appTiles) { totalContentHeight += (tile.hBlockReason != nullptr) ? itemHeight : itemHeightNoReason; } @@ -2621,7 +2434,7 @@ void CustomBSDR::UpdateAppListLayout() { int yPos = topMargin; - for (auto& tile : *appTiles) { + for (auto& tile : appTiles) { bool hasBlockReason = (tile.hBlockReason != nullptr); int height = hasBlockReason ? itemHeight : itemHeightNoReason; @@ -2653,9 +2466,6 @@ void CustomBSDR::UpdateAppListLayout() { yPos += height; } - RECT rcAppList; - GetWindowRect(hAppList, &rcAppList); - int currentWidth = rcAppList.right - rcAppList.left; SetWindowPos(hAppListScroll, nullptr, 0, -scrollPos, currentWidth, totalContentHeight, SWP_NOZORDER); // Set the title text based on the number of apps on the list @@ -2663,15 +2473,15 @@ void CustomBSDR::UpdateAppListLayout() { wchar_t titleFormat[256] = {}; // Redraw the entire title control area to prevent artifacts from previous longer text when the number of apps decreases InvalidateRect(hTitleText, nullptr, TRUE); - if (appTiles->size() == 1) { + if (appTiles.size() == 1) { GetString(IDS_BSDR_BLOCKINGAPPCOUNT_SINGLE, titleFormat, _countof(titleFormat)); - } else if (appTiles->size() == 0) { + } else if (appTiles.size() == 0) { GetString(IDS_BSDR_BLOCKING_BGAPPS, titleFormat, _countof(titleFormat)); } else { GetString(IDS_BSDR_BLOCKINGAPPCOUNT_MULTI, titleFormat, _countof(titleFormat)); } // Avoid swprintf with format string from user supplied dll - wstring titleText = regex_replace(titleFormat, wregex(L"%d"), to_wstring(appTiles->size())); + std::wstring titleText = regex_replace(titleFormat, std::wregex(L"%d"), std::to_wstring(appTiles.size())); SetWindowTextW(hTitleText, titleText.c_str()); } @@ -2681,7 +2491,12 @@ void CustomBSDR::UpdateAppListLayout() { INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_INITDIALOG: { - CustomBSDR::hDlg = hDlg; + std::vector> pendingAppsLocal; + { + std::lock_guard lock(pendingAppsMutex); + CustomBSDR::hDlg = hDlg; + pendingAppsLocal.swap(*pendingApps); + } // Hide title bar SetWindowLongW(hDlg, GWL_STYLE, GetWindowLongW(hDlg, GWL_STYLE) & ~WS_CAPTION); @@ -2703,7 +2518,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM ShowWindow(hYesButton, SW_HIDE); ShowWindow(hNoButton, SW_HIDE); - if (!Wh_GetIntSetting(L"themeScrollbar", 0)) { + if (!Wh_GetIntSetting(L"themeScrollbar")) { SetWindowTheme(hScrollBar, L" ", L""); } @@ -2719,15 +2534,12 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM int itemHeightNoReason = MulDiv(62, dpi, 96); int totalItemsHeight = 0; - { - lock_guard lock(pendingAppsMutex); - for (auto& app : *pendingApps) { - BOOLEAN isBlocking = FALSE; - if (SUCCEEDED(app->get_IsBlocking(&isBlocking)) && isBlocking) { - totalItemsHeight += itemHeight; - } else { - totalItemsHeight += itemHeightNoReason; - } + for (auto& app : pendingAppsLocal) { + BOOLEAN isBlocking = FALSE; + if (SUCCEEDED(app->get_IsBlocking(&isBlocking)) && isBlocking) { + totalItemsHeight += itemHeight; + } else { + totalItemsHeight += itemHeightNoReason; } } @@ -2819,13 +2631,10 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM } } - { - lock_guard lock(pendingAppsMutex); - for (auto& app : *pendingApps) { - CreateAppTileControls(app.Get()); - } - pendingApps->clear(); + for (auto& app : pendingAppsLocal) { + CreateAppTileControls(app.Get()); } + pendingApps->clear(); // Load and set the appropriate strings based on the current LogonUI state wchar_t desc[256] = {}, warning[256] = {}, btnText[256] = {}; @@ -2844,9 +2653,12 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM break; } if (_logonUIState != LogonUIState_ShuttingDown) { - hDescText&& SetWindowTextW(hDescText, desc); - hWarningText&& SetWindowTextW(hWarningText, warning); - hForceButton&& SetWindowTextW(hForceButton, btnText); + if (hDescText) + SetWindowTextW(hDescText, desc); + if (hWarningText) + SetWindowTextW(hWarningText, warning); + if (hForceButton) + SetWindowTextW(hForceButton, btnText); } CenterWindow(hDlg); @@ -2916,6 +2728,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM // LogonUI.exe thread is stuck at WaitForSingleObjectEx from LogonController.dll (prob mutex) // It gets stuck between get_WasClicked and remove_Resolved. Pressing C+A+D makes the thread continue and remove_Resolved/Hide/Stop gets called. // Still haven't figured out the culprit yet + Sleep(1000); // Make sure the resolve request reaches winlogon ExitProcess(0); } SendMessageW(hBgWnd, WM_CLOSE, 0, 0); @@ -2944,9 +2757,11 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM break; } case WM_ADD_APP: { - Microsoft::WRL::ComPtr app = (IShutdownBlockingApp*)lParam; - CreateAppTileControls(app.Get()); - app->Release(); + Microsoft::WRL::ComPtr app; + app.Attach((IShutdownBlockingApp*)lParam); + if (app) { + CreateAppTileControls(app.Get()); + } return TRUE; } case WM_REMOVE_APP: { @@ -3106,14 +2921,17 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA return 0; } case WM_CLOSE: { - for (auto& tile : *appTiles) { + for (auto& tile : appTiles) { if (tile.hIconBitmap) DeleteObject(tile.hIconBitmap); } - appTiles->clear(); + appTiles.clear(); if (hDlg) { DestroyWindow(hDlg); - hDlg = nullptr; + { + std::lock_guard lock(pendingAppsMutex); + hDlg = nullptr; + } } if (hBgWnd) { @@ -3173,6 +2991,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { // Attempt to create window on the input desktop, as the thread is always running in secure desktop at this point, // but the Windhawk mod can force this phase of session end to run in the default desktop + // (This is only an attempt and not critical if failed) HDESK hDesktop = OpenInputDesktop(0, FALSE, DESKTOP_CREATEWINDOW | DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS); if (hDesktop) { wchar_t desktopName[256] = {}; @@ -3180,7 +2999,6 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { isOnSecureDesktop = (_wcsicmp(desktopName, L"winlogon") == 0); } if (!SetThreadDesktop(hDesktop)) { - CloseDesktop(hDesktop); Wh_Log(L"SetThreadDesktop failed, GLE=%d", GetLastError()); } CloseDesktop(hDesktop); @@ -3237,7 +3055,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { SetPropW(hNoButton, L"CustomBSDR_HideAccel", (HANDLE)FALSE); RedrawWindow(hDlg, nullptr, nullptr, RDW_FRAME | RDW_INVALIDATE); } - if (!IsDialogMessageW(hDlg, &msg)) { + if (hDlg && !IsDialogMessageW(hDlg, &msg)) { TranslateMessage(&msg); DispatchMessageW(&msg); } @@ -3248,7 +3066,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { } } } else { - int ret = GetLastError(); + ret = GetLastError(); Wh_Log(L"CreateWindowExW failed, GLE=%d", ret); } @@ -3256,7 +3074,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { Wh_Log(L"UnregisterClassW failed, GLE=%d", GetLastError()); } } else { - int ret = GetLastError(); + ret = GetLastError(); Wh_Log(L"RegisterClassExW failed, GLE=%d", ret); } @@ -3275,7 +3093,7 @@ void CustomBSDR::Start(LogonUIState state) { _logonUIState = state; if (hThread) { // This is one-shot, as this mod highly relies on the fact that LogonUI calls the BSDR interface in the following order: - // (Winlogon) ShutdownWindowsWorkerThread -> LogonUI launch -> BSDR::Start -> add_Resolved -> AddApplication * n -> + // (Winlogon) ShutdownWindowsWorkerThread -> LogonUI launch -> BSDR::Start -> add_Resolved -> AddApplication * n (either before or after dlg open) -> // (Force logoff chosen) -> BSDR::Hide -> get_WasClicked -> BSDR::Stop -> LogonUI exit -> Session teardown (Winlogon exit) Wh_Log(L"CustomBSDR has already started once!"); return; @@ -3289,7 +3107,7 @@ void CustomBSDR::Start(LogonUIState state) { } void CustomBSDR::AddApplication(IShutdownBlockingApp* blockingApp) { - lock_guard lock(pendingAppsMutex); + std::lock_guard lock(pendingAppsMutex); if (hDlg && IsWindow(hDlg)) { blockingApp->AddRef(); if (!PostMessageW(hDlg, WM_ADD_APP, 0, reinterpret_cast(blockingApp))) { @@ -3301,7 +3119,7 @@ void CustomBSDR::AddApplication(IShutdownBlockingApp* blockingApp) { } void CustomBSDR::RemoveApplication(UINT appid) { - lock_guard lock(pendingAppsMutex); + std::lock_guard lock(pendingAppsMutex); if (hDlg && IsWindow(hDlg)) { PostMessageW(hDlg, WM_REMOVE_APP, (WPARAM)appid, 0); } else { @@ -3349,7 +3167,7 @@ long __fastcall BlockedShutdownUXImpl_get_ScaleFactor_hook(void* thisPtr, unsign long __fastcall BlockedShutdownUXImpl_get_WasClicked_hook(void* thisPtr, unsigned char* wasClicked) { Wh_Log(L"BlockedShutdownUXImpl::get_WasClicked"); { - lock_guard lock(resolvedMutex); + std::lock_guard lock(resolvedMutex); *wasClicked = _wasClicked; } return S_OK; @@ -3396,9 +3214,8 @@ long __fastcall BlockedShutdownUXImpl_add_Resolved_hook(void* thisPtr, ABI::Wind eventToken->value = 0; BlockedShutdownResolution resolvedValueLocal; - { - lock_guard lock(resolvedMutex); + std::lock_guard lock(resolvedMutex); resolvedValueLocal = resolvedValue; if (resolvedValueLocal == BlockedShutdownResolution_None) { @@ -3418,7 +3235,7 @@ long __fastcall BlockedShutdownUXImpl_remove_Resolved_hook(void* thisPtr, EventR Wh_Log(L"BlockedShutdownUXImpl::remove_Resolved"); if (eventToken.value == 0) { - lock_guard lock(resolvedMutex); + std::lock_guard lock(resolvedMutex); _Resolved.Reset(); } @@ -3577,10 +3394,6 @@ void __fastcall ShutdownWindowsWorkerThread_hook(void* Instance, void* Context) Wh_Log(L"ShutdownWindowsWorkerThread_hook: Set g_fShutdownResolverDisabled to 1"); } else { // User did not read the instruction and LogonUI is not added to the inclusion list - // The stock immersive BSDR does not support being displayed on the default desktop, so if it shows, - // it will get stuck in the invisible secure desktop, and users can become clueless. - // Pressing ctrl alt del can get out of this state but lets add this minimal safeguard - // This is Wh_Log(L"ShutdownWindowsWorkerThread_hook: Not setting g_fShutdownResolverDisabled as LogonUI hooks are not ready"); } ShutdownWindowsWorkerThread_original(Instance, Context); @@ -3631,9 +3444,9 @@ BOOL Wh_ModInit() { return TRUE; } - HMODULE blockedShutdownDll = LoadLibraryExW(L"Windows.UI.BlockedShutdown.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); - if (blockedShutdownDll) { - if (!WindhawkUtils::HookSymbols(blockedShutdownDll, hooks, ARRAYSIZE(hooks))) { + hBlockedShutdownDll = LoadLibraryExW(L"Windows.UI.BlockedShutdown.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (hBlockedShutdownDll) { + if (!WindhawkUtils::HookSymbols(hBlockedShutdownDll, hooks, ARRAYSIZE(hooks))) { Wh_Log(L"Failed to hook symbols in Windows.UI.BlockedShutdown.dll"); return FALSE; } @@ -3648,10 +3461,9 @@ BOOL Wh_ModInit() { return FALSE; } - LPCWSTR dllPath = Wh_GetStringSetting(L"resDllPath"); - if (*dllPath != L'\0') { + auto dllPath = WindhawkUtils::StringSetting::make(L"resDllPath"); + if (*dllPath && *dllPath != L'\0') { hResDll = LoadLibraryExW(dllPath, nullptr, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE); - Wh_FreeStringSetting(dllPath); if (hResDll) { isUsingHardcodedRes = false; } else { @@ -3670,7 +3482,7 @@ void Wh_ModUninit() { if (CustomBSDR::hThread) { bool needsResolve; { - lock_guard lock(resolvedMutex); + std::lock_guard lock(resolvedMutex); needsResolve = resolvedValue == BlockedShutdownResolution_None; } if (needsResolve) { @@ -3684,7 +3496,6 @@ void Wh_ModUninit() { CloseHandle(CustomBSDR::hThread); } - CustomBSDR::appTiles.reset(); CustomBSDR::pendingApps.reset(); if (CustomBSDR::hStopEvent) { @@ -3696,6 +3507,9 @@ void Wh_ModUninit() { if (hResDll) { FreeLibrary(hResDll); } + if (hBlockedShutdownDll) { + FreeLibrary(hBlockedShutdownDll); + } } BOOL Wh_ModSettingsChanged(BOOL* bReload) { From a7ccd300626a6d4f03133c37619d7d826a76d441 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Thu, 27 Aug 2026 18:33:56 +0900 Subject: [PATCH 13/51] Apply fixes Refactor LoadImageWithWIC function to simplify parameters and remove unused options. Update related calls and ensure proper resource size validation. --- mods/win7-bsdr.wh.cpp | 77 ++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 45 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index a642ee433e..41304bcefc 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -10,7 +10,7 @@ // @include LogonUI.exe // @include winlogon.exe // @architecture x86-64 -// @compilerOptions -lgdi32 -lmincore -lwindowscodecs -luxtheme -lmsimg32 -lcomctl32 -lshcore -luuid +// @compilerOptions -lgdi32 -lmincore -lwindowscodecs -luxtheme -lmsimg32 -lcomctl32 -lshcore // @license GPL-3.0-only // ==/WindhawkMod== @@ -39,7 +39,7 @@ system process. To do so, add it to the process inclusion list in the advanced settings. If you do not do this, it will silently fail to inject. You must add it to the inclusion list even if you have disabled the critical system process -exclusion option. Otherwise the logoff sequence portion of mod will not function. +exclusion option. Otherwise the logoff sequence portion of the mod will not function. ![Advanced settings screenshot](https://i.imgur.com/LRhREtJ.png) */ @@ -58,6 +58,7 @@ exclusion option. Otherwise the logoff sequence portion of mod will not function - disableAsyncLogoff: true $name: Restore Windows 7's logoff sequence $name:ko-KR: Windows 7의 로그오프 절차 복원 + $description: Switch to the 'logging off' screen after all applications have been closed. */ // ==/WindhawkModSettings== @@ -1690,19 +1691,8 @@ namespace CustomBSDR { #pragma endregion logoncontroller.h and CustomBSDR.h #pragma region wicutil.cpp -enum LOAD_IMAGE_WITH_WIC_OPTION { - LIWW_NONE, - LIWW_ORIENTATE, - LIWW_ORIENTATE_AND_CACHE, -}; - -HRESULT LoadImageWithWIC(IWICImagingFactory* pWICImagingFactory, IStream* pStream, LOAD_IMAGE_WITH_WIC_OPTION option, - IWICBitmapSource** ppWICBitmapSource, IWICBitmapFrameDecode** ppWICBitmapFrameDecode, GUID* pguidContainerFormat) { +HRESULT LoadImageWithWIC(IWICImagingFactory* pWICImagingFactory, IStream* pStream, IWICBitmapSource** ppWICBitmapSource) { *ppWICBitmapSource = nullptr; - if (ppWICBitmapFrameDecode) - *ppWICBitmapFrameDecode = nullptr; - if (pguidContainerFormat) - *pguidContainerFormat = GUID_NULL; HRESULT hr = S_OK; @@ -1715,21 +1705,11 @@ HRESULT LoadImageWithWIC(IWICImagingFactory* pWICImagingFactory, IStream* pStrea Microsoft::WRL::ComPtr spDecoder; hr = spWICImagingFactory->CreateDecoderFromStream( pStream, &GUID_VendorMicrosoftBuiltIn, WICDecodeMetadataCacheOnDemand, &spDecoder); - if (SUCCEEDED(hr) && pguidContainerFormat) { - hr = spDecoder->GetContainerFormat(pguidContainerFormat); - } if (SUCCEEDED(hr)) { Microsoft::WRL::ComPtr spBitmapFrameDecode; hr = spDecoder->GetFrame(0, &spBitmapFrameDecode); if (SUCCEEDED(hr)) { - if (option != LIWW_NONE) { - hr = E_NOTIMPL; - } else { - hr = spBitmapFrameDecode.CopyTo(ppWICBitmapSource); - } - } - if (SUCCEEDED(hr) && ppWICBitmapFrameDecode) { - *ppWICBitmapFrameDecode = spBitmapFrameDecode.Detach(); + hr = spBitmapFrameDecode.CopyTo(ppWICBitmapSource); } } } @@ -1786,8 +1766,7 @@ HRESULT Convert32bppWICBitmapSourceToHBITMAP(IWICBitmapSource* pWICBitmapSource, return hr; } -HRESULT ConvertWICBitmapToHBITMAP(IWICImagingFactory* pWICImagingFactory, IWICBitmapSource* pWICBitmapSource, - HBITMAP* phbmImage) { +HRESULT ConvertWICBitmapToHBITMAP(IWICImagingFactory* pWICImagingFactory, IWICBitmapSource* pWICBitmapSource, HBITMAP* phbmImage) { *phbmImage = nullptr; HRESULT hr = S_OK; @@ -1815,7 +1794,7 @@ HRESULT GetBitmapFromRandomStream(Microsoft::WRL::ComPtr spWICBitmapSource; - RETURN_IF_FAILED(LoadImageWithWIC(spWICFactory.Get(), spStream.Get(), LIWW_NONE, &spWICBitmapSource, nullptr, nullptr)); + RETURN_IF_FAILED(LoadImageWithWIC(spWICFactory.Get(), spStream.Get(), &spWICBitmapSource)); RETURN_IF_FAILED(ConvertWICBitmapToHBITMAP(spWICFactory.Get(), spWICBitmapSource.Get(), outBitmap)); return S_OK; @@ -1890,6 +1869,15 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { return CustomBSDR::LoadAlphaBitmap(resourceId, true); } + DWORD dwSize = 0; + if (hResource) { // Size validation for external dll + dwSize = SizeofResource(hResDll, hResource); + if (dwSize < sizeof(BITMAPINFOHEADER)) { + Wh_Log(L"Image load error: resource too small"); + return LoadAlphaBitmap(resourceId, true); + } + } + const BITMAPINFO* pBitmapInfo = static_cast(pResourceData); const BITMAPINFOHEADER* pHeader = &pBitmapInfo->bmiHeader; @@ -1922,15 +1910,13 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { dwBitsOffset += 12; } - const unsigned char* pSrc = static_cast(pResourceData) + dwBitsOffset; - if (hResource) { // Size validation for external dll (don't trust its header) - DWORD dwSize = SizeofResource(hResDll, hResource); - if (dwSize < dwBitsOffset || (DWORD)width * height * 4 > dwSize - dwBitsOffset) { - Wh_Log(L"Image load error: invalid size"); - return nullptr; - } + if (hResource && (dwSize < dwBitsOffset || (DWORD)width * height * 4 > dwSize - dwBitsOffset)) { + Wh_Log(L"Image load error: invalid size"); + return LoadAlphaBitmap(resourceId, true); } + const unsigned char* pSrc = static_cast(pResourceData) + dwBitsOffset; + if (isTopDown) { memcpy(pBits, pSrc, width * height * 4); } else { @@ -2207,10 +2193,10 @@ LRESULT CALLBACK CustomBSDR::ButtonSubclassProc(HWND hWnd, UINT uMsg, WPARAM wPa } case WM_UPDATEUISTATE: { if ((HIWORD(wParam) & UISF_HIDEFOCUS) == UISF_HIDEFOCUS) { - SetPropW(hWnd, L"CustomBSDR_HideFocus", (HANDLE)((LOWORD(wParam) & UIS_SET) == UIS_SET)); + SetPropW(hWnd, L"CustomBSDR_HideFocus", (HANDLE)(LOWORD(wParam) == UIS_SET)); } if ((HIWORD(wParam) & UISF_HIDEACCEL) == UISF_HIDEACCEL) { - SetPropW(hWnd, L"CustomBSDR_HideAccel", (HANDLE)((LOWORD(wParam) & UIS_SET) == UIS_SET)); + SetPropW(hWnd, L"CustomBSDR_HideAccel", (HANDLE)(LOWORD(wParam) == UIS_SET)); } break; } @@ -2634,7 +2620,6 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM for (auto& app : pendingAppsLocal) { CreateAppTileControls(app.Get()); } - pendingApps->clear(); // Load and set the appropriate strings based on the current LogonUI state wchar_t desc[256] = {}, warning[256] = {}, btnText[256] = {}; @@ -2927,16 +2912,16 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA appTiles.clear(); if (hDlg) { - DestroyWindow(hDlg); { std::lock_guard lock(pendingAppsMutex); hDlg = nullptr; } + DestroyWindow(hDlg); } if (hBgWnd) { - DestroyWindow(hBgWnd); hBgWnd = nullptr; + DestroyWindow(hBgWnd); } if (bgBitmap) { @@ -3063,7 +3048,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { if (bQuit) { break; } - } + } else break; } } else { ret = GetLastError(); @@ -3079,7 +3064,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { } if (ret != 0) { - Wh_Log(L"CustomBSDR thread init failed; resolving with force..."); + Wh_Log(L"CustomBSDR thread init failed"); Resolve(BlockedShutdownResolution_Force); } @@ -3096,11 +3081,13 @@ void CustomBSDR::Start(LogonUIState state) { // (Winlogon) ShutdownWindowsWorkerThread -> LogonUI launch -> BSDR::Start -> add_Resolved -> AddApplication * n (either before or after dlg open) -> // (Force logoff chosen) -> BSDR::Hide -> get_WasClicked -> BSDR::Stop -> LogonUI exit -> Session teardown (Winlogon exit) Wh_Log(L"CustomBSDR has already started once!"); + Resolve(BlockedShutdownResolution_Force); return; } hThread = CreateThread(nullptr, 0, ThreadProc, nullptr, 0, nullptr); if (!hThread) { Wh_Log(L"Failed to create CustomBSDR thread, GLE=%d", GetLastError()); + Resolve(BlockedShutdownResolution_Force); return; } Wh_Log(L"Created CustomBSDR thread"); @@ -3137,8 +3124,8 @@ void CustomBSDR::RemoveApplication(UINT appid) { } void CustomBSDR::Hide() { - if (hDlg) { - ShowWindow(hDlg, SW_HIDE); + if (hBgWnd) { + ShowWindow(hBgWnd, SW_HIDE); } } @@ -3462,7 +3449,7 @@ BOOL Wh_ModInit() { } auto dllPath = WindhawkUtils::StringSetting::make(L"resDllPath"); - if (*dllPath && *dllPath != L'\0') { + if (*dllPath) { hResDll = LoadLibraryExW(dllPath, nullptr, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE); if (hResDll) { isUsingHardcodedRes = false; From 2d59a970038c66547cf0bd8244be2629f9cba233 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Thu, 27 Aug 2026 19:20:19 +0900 Subject: [PATCH 14/51] Apply fixes --- mods/win7-bsdr.wh.cpp | 107 +++++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 44 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 41304bcefc..010ef4b52f 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -62,8 +62,8 @@ exclusion option. Otherwise the logoff sequence portion of the mod will not func */ // ==/WindhawkModSettings== -#include -#include +#include // Includes , , , +#include // Required for WindowsGetStringRawBuffer/WindowsDeleteString and ComPtr #include #include #include @@ -1912,6 +1912,7 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { if (hResource && (dwSize < dwBitsOffset || (DWORD)width * height * 4 > dwSize - dwBitsOffset)) { Wh_Log(L"Image load error: invalid size"); + DeleteObject(hBitmap); return LoadAlphaBitmap(resourceId, true); } @@ -2192,10 +2193,10 @@ LRESULT CALLBACK CustomBSDR::ButtonSubclassProc(HWND hWnd, UINT uMsg, WPARAM wPa break; } case WM_UPDATEUISTATE: { - if ((HIWORD(wParam) & UISF_HIDEFOCUS) == UISF_HIDEFOCUS) { + if ((HIWORD(wParam) & UISF_HIDEFOCUS) == UISF_HIDEFOCUS && LOWORD(wParam) != UIS_INITIALIZE) { SetPropW(hWnd, L"CustomBSDR_HideFocus", (HANDLE)(LOWORD(wParam) == UIS_SET)); } - if ((HIWORD(wParam) & UISF_HIDEACCEL) == UISF_HIDEACCEL) { + if ((HIWORD(wParam) & UISF_HIDEACCEL) == UISF_HIDEACCEL && LOWORD(wParam) != UIS_INITIALIZE) { SetPropW(hWnd, L"CustomBSDR_HideAccel", (HANDLE)(LOWORD(wParam) == UIS_SET)); } break; @@ -2474,30 +2475,30 @@ void CustomBSDR::UpdateAppListLayout() { RedrawWindow(hDlg, nullptr, nullptr, RDW_FRAME | RDW_INVALIDATE); } -INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { +INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_INITDIALOG: { std::vector> pendingAppsLocal; { std::lock_guard lock(pendingAppsMutex); - CustomBSDR::hDlg = hDlg; + CustomBSDR::hDlg = hWndDlg; pendingAppsLocal.swap(*pendingApps); } // Hide title bar - SetWindowLongW(hDlg, GWL_STYLE, GetWindowLongW(hDlg, GWL_STYLE) & ~WS_CAPTION); + SetWindowLongW(hWndDlg, GWL_STYLE, GetWindowLongW(hWndDlg, GWL_STYLE) & ~WS_CAPTION); // Use composited style on dialog, not bg window, to prevent the scrollbar control from flickering on drag - SetWindowLongW(hDlg, GWL_EXSTYLE, GetWindowLongW(hDlg, GWL_EXSTYLE) | WS_EX_COMPOSITED); + SetWindowLongW(hWndDlg, GWL_EXSTYLE, GetWindowLongW(hWndDlg, GWL_EXSTYLE) | WS_EX_COMPOSITED); - hTitleText = GetDlgItem(hDlg, IDC_BSDR_TITLE); - hAppList = GetDlgItem(hDlg, IDC_BSDR_APPLIST); - hScrollBar = GetDlgItem(hDlg, IDC_BSDR_SCROLLBAR); - hWarningText = GetDlgItem(hDlg, IDC_BSDR_WARNING); - hForceButton = GetDlgItem(hDlg, IDC_BSDR_FORCE_BTN); - hCancelButton = GetDlgItem(hDlg, IDCANCEL); - hDescText = GetDlgItem(hDlg, IDC_BSDR_DESC); - hYesButton = GetDlgItem(hDlg, IDYES); - hNoButton = GetDlgItem(hDlg, IDNO); + hTitleText = GetDlgItem(hWndDlg, IDC_BSDR_TITLE); + hAppList = GetDlgItem(hWndDlg, IDC_BSDR_APPLIST); + hScrollBar = GetDlgItem(hWndDlg, IDC_BSDR_SCROLLBAR); + hWarningText = GetDlgItem(hWndDlg, IDC_BSDR_WARNING); + hForceButton = GetDlgItem(hWndDlg, IDC_BSDR_FORCE_BTN); + hCancelButton = GetDlgItem(hWndDlg, IDCANCEL); + hDescText = GetDlgItem(hWndDlg, IDC_BSDR_DESC); + hYesButton = GetDlgItem(hWndDlg, IDYES); + hNoButton = GetDlgItem(hWndDlg, IDNO); // Hide the warning message controls ShowWindow(hWarningText, SW_HIDE); @@ -2514,7 +2515,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM SetWindowSubclass(hAppListScroll, AppListSubclassProc, 0, 0); // Calculate the height of the app list container then resize it, also moving the controls below it - int dpi = GetDpiForWindow(hDlg); + int dpi = GetDpiForWindow(hWndDlg); int itemHeight = MulDiv(83, dpi, 96); int itemHeightNoReason = MulDiv(62, dpi, 96); @@ -2522,8 +2523,17 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM int totalItemsHeight = 0; for (auto& app : pendingAppsLocal) { BOOLEAN isBlocking = FALSE; + HSTRING blockReason; if (SUCCEEDED(app->get_IsBlocking(&isBlocking)) && isBlocking) { totalItemsHeight += itemHeight; + } else if (SUCCEEDED(app->get_BlockReason(&blockReason))) { + const wchar_t* reasonStr = WindowsGetStringRawBuffer(blockReason, nullptr); + if (reasonStr && wcslen(reasonStr) > 0) { + totalItemsHeight += itemHeight; + } else { + totalItemsHeight += itemHeightNoReason; + } + WindowsDeleteString(blockReason); } else { totalItemsHeight += itemHeightNoReason; } @@ -2557,22 +2567,22 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM for (HWND hwndSibling = GetWindow(hScrollBar, GW_HWNDNEXT); hwndSibling; hwndSibling = GetWindow(hwndSibling, GW_HWNDNEXT)) { RECT rcSibling; GetWindowRect(hwndSibling, &rcSibling); - MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rcSibling, 2); + MapWindowPoints(HWND_DESKTOP, hWndDlg, (LPPOINT)&rcSibling, 2); OffsetRect(&rcSibling, 0, heightDiff); SetWindowPos(hwndSibling, nullptr, rcSibling.left, rcSibling.top, 0, 0, SWP_NOSIZE | SWP_NOZORDER); } RECT rcDialog, rcTitle, rcNo; - GetWindowRect(hDlg, &rcDialog); + GetWindowRect(hWndDlg, &rcDialog); OffsetRect(&rcDialog, -GetSystemMetrics(SM_XVIRTUALSCREEN), -GetSystemMetrics(SM_YVIRTUALSCREEN)); GetWindowRect(hTitleText, &rcTitle); - MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rcTitle, 2); + MapWindowPoints(HWND_DESKTOP, hWndDlg, (LPPOINT)&rcTitle, 2); GetWindowRect(hNoButton, &rcNo); - MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rcNo, 2); + MapWindowPoints(HWND_DESKTOP, hWndDlg, (LPPOINT)&rcNo, 2); int topPadding = rcTitle.top - rcDialog.top; int dialogWidth = rcTitle.right - rcDialog.left; int newDialogHeight = rcNo.bottom - rcDialog.top + topPadding - 1; - SetWindowPos(hDlg, nullptr, 0, 0, dialogWidth, newDialogHeight, SWP_NOMOVE | SWP_NOZORDER); + SetWindowPos(hWndDlg, nullptr, 0, 0, dialogWidth, newDialogHeight, SWP_NOMOVE | SWP_NOZORDER); } SetWindowSubclass(hForceButton, ButtonSubclassProc, 0, 0); @@ -2596,7 +2606,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM SetPropW(hNoButton, L"CustomBSDR_HideAccel", (HANDLE)(noButtonUIState & UISF_HIDEACCEL)); // Set font for title and description/warning texts - HFONT hDialogFont = (HFONT)SendMessageW(hDlg, WM_GETFONT, 0, 0); + HFONT hDialogFont = (HFONT)SendMessageW(hWndDlg, WM_GETFONT, 0, 0); LOGFONTW lf = {}; if (hDialogFont && GetObjectW(hDialogFont, sizeof(lf), &lf)) { int originalHeight = lf.lfHeight; @@ -2646,7 +2656,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM SetWindowTextW(hForceButton, btnText); } - CenterWindow(hDlg); + CenterWindow(hWndDlg); return TRUE; } case WM_CTLCOLORDLG: // make dialog transparent @@ -2664,25 +2674,25 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM break; case WM_PAINT: { PAINTSTRUCT ps; - HDC hdc = BeginPaint(hDlg, &ps); + HDC hdc = BeginPaint(hWndDlg, &ps); if (bgBitmap) { RECT rcDlg; - GetClientRect(hDlg, &rcDlg); - MapWindowPoints(hDlg, hBgWnd, (LPPOINT)&rcDlg, 2); + GetClientRect(hWndDlg, &rcDlg); + MapWindowPoints(hWndDlg, hBgWnd, (LPPOINT)&rcDlg, 2); HDC memDC = CreateCompatibleDC(hdc); HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, bgBitmap); BitBlt(hdc, 0, 0, rcDlg.right, rcDlg.bottom, memDC, rcDlg.left, rcDlg.top, SRCCOPY); SelectObject(memDC, oldBitmap); DeleteDC(memDC); } - EndPaint(hDlg, &ps); + EndPaint(hWndDlg, &ps); return 0; } case WM_ERASEBKGND: if (!IsHighContrast()) { HDC hdc = (HDC)wParam; RECT rcDlg; - GetClientRect(hDlg, &rcDlg); + GetClientRect(hWndDlg, &rcDlg); HBRUSH hBrush = (HBRUSH)GetStockObject(BLACK_BRUSH); FillRect(hdc, &rcDlg, hBrush); return TRUE; @@ -2755,7 +2765,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM return TRUE; } case WM_MOUSEWHEEL: { - int dpi = GetDpiForWindow(hDlg); + int dpi = GetDpiForWindow(hWndDlg); int visibleHeight = MulDiv(300, dpi, 96); if (hAppList) { @@ -2785,7 +2795,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM return TRUE; } case WM_VSCROLL: { - int dpi = GetDpiForWindow(hDlg); + int dpi = GetDpiForWindow(hWndDlg); int visibleHeight = MulDiv(300, dpi, 96); if (hAppList) { @@ -2827,18 +2837,19 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: { + HWND hDlgLocal = nullptr; if (!isUsingHardcodedRes && hResDll) { - hDlg = CreateDialogParamW(hResDll, MAKEINTRESOURCEW(IDD_BSDR_DLG), hWnd, DlgProc, lParam); - if (!hDlg) { + hDlgLocal = CreateDialogParamW(hResDll, MAKEINTRESOURCEW(IDD_BSDR_DLG), hWnd, DlgProc, lParam); + if (!hDlgLocal) { Wh_Log(L"CreateDialogParamW failed: %d", GetLastError()); // Retry with hardcoded one - hDlg = CreateDialogIndirectParamW(nullptr, reinterpret_cast(RES_DIALOG), hWnd, DlgProc, lParam); + hDlgLocal = CreateDialogIndirectParamW(nullptr, reinterpret_cast(RES_DIALOG), hWnd, DlgProc, lParam); } } else { - hDlg = CreateDialogIndirectParamW(nullptr, reinterpret_cast(RES_DIALOG), hWnd, DlgProc, lParam); + hDlgLocal = CreateDialogIndirectParamW(nullptr, reinterpret_cast(RES_DIALOG), hWnd, DlgProc, lParam); } - if (hDlg) { - ShowWindow(hDlg, SW_SHOW); + if (hDlgLocal) { + ShowWindow(hDlgLocal, SW_SHOW); } else { // Bail out Wh_Log(L"Dialog creation failed!!"); @@ -2912,16 +2923,18 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA appTiles.clear(); if (hDlg) { + HWND hDlgLocal = hDlg; { std::lock_guard lock(pendingAppsMutex); hDlg = nullptr; } - DestroyWindow(hDlg); + DestroyWindow(hDlgLocal); } if (hBgWnd) { + HWND hBgWndLocal = hBgWnd; hBgWnd = nullptr; - DestroyWindow(hBgWnd); + DestroyWindow(hBgWndLocal); } if (bgBitmap) { @@ -3048,7 +3061,11 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { if (bQuit) { break; } - } else break; + } else { + ret = GetLastError(); + Wh_Log(L"MsgWaitForMultipleObjects returned %d, GLE=%d", result, ret); + break; + } } } else { ret = GetLastError(); @@ -3075,7 +3092,6 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { } void CustomBSDR::Start(LogonUIState state) { - _logonUIState = state; if (hThread) { // This is one-shot, as this mod highly relies on the fact that LogonUI calls the BSDR interface in the following order: // (Winlogon) ShutdownWindowsWorkerThread -> LogonUI launch -> BSDR::Start -> add_Resolved -> AddApplication * n (either before or after dlg open) -> @@ -3084,6 +3100,7 @@ void CustomBSDR::Start(LogonUIState state) { Resolve(BlockedShutdownResolution_Force); return; } + _logonUIState = state; hThread = CreateThread(nullptr, 0, ThreadProc, nullptr, 0, nullptr); if (!hThread) { Wh_Log(L"Failed to create CustomBSDR thread, GLE=%d", GetLastError()); @@ -3139,8 +3156,10 @@ void CustomBSDR::Stop() { #pragma region LogonUI BSDR Hooks (Interface) long __fastcall BlockedShutdownUXImpl_Start_hook(void* thisPtr, void* userSettingManager, ILogonUIStateInfo* logonUIStateInfo) { Wh_Log(L"BlockedShutdownUXImpl::Start"); - LogonUIState logonUIState = LogonUIState_Start; - logonUIStateInfo->get_CurrentLogonUIState(&logonUIState); + LogonUIState logonUIState = LogonUIState_LoggingOff; + if (logonUIStateInfo) { + logonUIStateInfo->get_CurrentLogonUIState(&logonUIState); + } CustomBSDR::Start(logonUIState); return S_OK; } From 1bf3a9cefe2e82b78850d2cfac620a620b5eebab Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Sat, 29 Aug 2026 12:40:16 +0900 Subject: [PATCH 15/51] Apply fixes --- mods/win7-bsdr.wh.cpp | 364 +++++++++++++++++++++++++++++------------- 1 file changed, 251 insertions(+), 113 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 010ef4b52f..c6f51230bd 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -30,6 +30,7 @@ * This is not necessary. Hardcoded resources will be used instead if the path is not set or the file is missing. * Known issues * If your user account has no password, enabling the logoff sequence option may make the system automatically log on again after logging off. + * * Not compatible with a portable Windhawk installation, even when run as admin, because it doesn't survive a logoff long enough to handle BSDR. ![Screenshot](https://raw.githubusercontent.com/Ingan121/files/refs/heads/master/vmware_gj0gqnHj8e.png) ## ⚠ Important usage note ⚠ @@ -38,9 +39,6 @@ In order to use this mod, you must allow Windhawk to inject into the **LogonUI.e system process. To do so, add it to the process inclusion list in the advanced settings. If you do not do this, it will silently fail to inject. -You must add it to the inclusion list even if you have disabled the critical system process -exclusion option. Otherwise the logoff sequence portion of the mod will not function. - ![Advanced settings screenshot](https://i.imgur.com/LRhREtJ.png) */ // ==/WindhawkModReadme== @@ -62,7 +60,10 @@ exclusion option. Otherwise the logoff sequence portion of the mod will not func */ // ==/WindhawkModSettings== -#include // Includes , , , +#include +#include +#include +#include #include // Required for WindowsGetStringRawBuffer/WindowsDeleteString and ComPtr #include #include @@ -124,72 +125,177 @@ HINSTANCE hBlockedShutdownDll; HINSTANCE hResDll; bool isUsingHardcodedRes = true; -// Hardcoded resources - -// https://github.com/Ingan121/AuthUX/blob/42909c132f429337e6169cf67c3a253405e916d0/shell/auth/authux/consolelogon/dll/consolelogon.rc#L471 -alignas(DWORD) -static constexpr unsigned char RES_DIALOG[] = { - 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0xc0, 0x40, - 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x53, 0x00, 0x65, 0x00, 0x67, 0x00, 0x6f, 0x00, 0x65, 0x00, - 0x20, 0x00, 0x55, 0x00, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x02, 0x50, 0x00, 0x00, 0x14, 0x00, 0x0a, 0x01, 0x0e, 0x00, 0x05, 0x01, 0x00, 0x00, - 0xff, 0xff, 0x82, 0x00, 0x54, 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x66, 0x00, 0x6f, 0x00, - 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20, 0x00, - 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x67, 0x00, 0x72, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x73, 0x00, - 0x20, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x65, 0x00, 0x64, 0x00, 0x20, 0x00, 0x74, 0x00, 0x6f, 0x00, - 0x20, 0x00, 0x63, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x65, 0x00, 0x3a, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x50, - 0x00, 0x00, 0x2e, 0x00, 0x0a, 0x01, 0x01, 0x00, 0x0d, 0x01, 0x00, 0x00, 0xff, 0xff, 0x82, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x50, - 0x00, 0x00, 0x34, 0x00, 0x00, 0x01, 0x55, 0x00, 0x0a, 0x01, 0x00, 0x00, 0xff, 0xff, 0x82, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x50, - 0x00, 0x01, 0x34, 0x00, 0x0a, 0x00, 0x55, 0x00, 0x0c, 0x01, 0x00, 0x00, 0xff, 0xff, 0x84, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x50, - 0x00, 0x00, 0x8f, 0x00, 0x0a, 0x01, 0x01, 0x00, 0x0e, 0x01, 0x00, 0x00, 0xff, 0xff, 0x82, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50, - 0x00, 0x00, 0x98, 0x00, 0x0a, 0x01, 0x1e, 0x00, 0x06, 0x01, 0x00, 0x00, 0xff, 0xff, 0x82, 0x00, - 0x54, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x65, 0x00, - 0x20, 0x00, 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, - 0x67, 0x00, 0x72, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x20, 0x00, 0x74, 0x00, 0x68, 0x00, 0x61, 0x00, - 0x74, 0x00, 0x20, 0x00, 0x69, 0x00, 0x73, 0x00, 0x20, 0x00, 0x70, 0x00, 0x72, 0x00, 0x65, 0x00, - 0x76, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20, 0x00, - 0x57, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x73, 0x00, 0x20, 0x00, - 0x66, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x0a, 0x00, 0x73, 0x00, 0x68, 0x00, 0x75, 0x00, - 0x74, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20, 0x00, 0x64, 0x00, 0x6f, 0x00, - 0x77, 0x00, 0x6e, 0x00, 0x2c, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x63, 0x00, - 0x6b, 0x00, 0x20, 0x00, 0x43, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x63, 0x00, 0x65, 0x00, 0x6c, 0x00, - 0x2c, 0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20, 0x00, 0x74, 0x00, 0x68, 0x00, - 0x65, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x65, 0x00, - 0x20, 0x00, 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, - 0x67, 0x00, 0x72, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x03, 0x50, 0xc1, 0x00, 0xbf, 0x00, - 0x46, 0x00, 0x0d, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0x80, 0x00, 0x26, 0x00, 0x43, 0x00, - 0x61, 0x00, 0x6e, 0x00, 0x63, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x01, 0x50, 0x73, 0x00, 0xbf, 0x00, - 0x46, 0x00, 0x0d, 0x00, 0x04, 0x01, 0x00, 0x00, 0xff, 0xff, 0x80, 0x00, 0x26, 0x00, 0x46, 0x00, - 0x6f, 0x00, 0x72, 0x00, 0x63, 0x00, 0x65, 0x00, 0x20, 0x00, 0x73, 0x00, 0x68, 0x00, 0x75, 0x00, - 0x74, 0x00, 0x20, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50, 0x00, 0x00, 0x98, 0x00, - 0x0a, 0x01, 0x1e, 0x00, 0x0f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x82, 0x00, 0x49, 0x00, 0x66, 0x00, - 0x20, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x20, 0x00, 0x66, 0x00, 0x6f, 0x00, 0x72, 0x00, - 0x63, 0x00, 0x65, 0x00, 0x20, 0x00, 0x73, 0x00, 0x68, 0x00, 0x75, 0x00, 0x74, 0x00, 0x20, 0x00, - 0x64, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00, - 0x20, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x79, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x73, 0x00, - 0x65, 0x00, 0x20, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6b, 0x00, 0x20, 0x00, 0x74, 0x00, - 0x68, 0x00, 0x61, 0x00, 0x74, 0x00, 0x20, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x20, 0x00, - 0x68, 0x00, 0x61, 0x00, 0x76, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x27, 0x00, 0x74, 0x00, 0x20, 0x00, - 0x73, 0x00, 0x61, 0x00, 0x76, 0x00, 0x65, 0x00, 0x64, 0x00, 0x2e, 0x00, 0x0a, 0x00, 0x44, 0x00, - 0x6f, 0x00, 0x20, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x20, 0x00, 0x73, 0x00, 0x74, 0x00, - 0x69, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x77, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x74, 0x00, - 0x20, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x66, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x63, 0x00, - 0x65, 0x00, 0x20, 0x00, 0x73, 0x00, 0x68, 0x00, 0x75, 0x00, 0x74, 0x00, 0x64, 0x00, 0x6f, 0x00, - 0x77, 0x00, 0x6e, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x03, 0x50, 0xc1, 0x00, 0xbf, 0x00, 0x46, 0x00, 0x0d, 0x00, - 0x07, 0x00, 0x00, 0x00, 0xff, 0xff, 0x80, 0x00, 0x26, 0x00, 0x4e, 0x00, 0x6f, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x01, 0x50, - 0x73, 0x00, 0xbf, 0x00, 0x46, 0x00, 0x0d, 0x00, 0x06, 0x00, 0x00, 0x00, 0xff, 0xff, 0x80, 0x00, - 0x26, 0x00, 0x59, 0x00, 0x65, 0x00, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00 +// Hardcoded resources +template +struct DLGITEMTEMPLATEEX { + DWORD helpID; + DWORD exStyle; + DWORD style; + short x; + short y; + short cx; + short cy; + DWORD id; + struct { + WORD classMarker; + WORD classOrdinal; + } windowClass; + wchar_t title[N]; + WORD extraCount; +}; + +typedef struct { + WORD dlgVer; + WORD signature; + DWORD helpID; + DWORD exStyle; + DWORD style; + WORD cDlgItems; + short x; + short y; + short cx; + short cy; + WORD menu; // unused, only 0000 + WORD windowClass; // ditto + WCHAR title[1]; // L"" + WORD pointsize; + WORD weight; + BYTE italic; + BYTE charset; + WCHAR typeface[9]; // L"Segoe UI" + + DLGITEMTEMPLATEEX<38> item1; + DLGITEMTEMPLATEEX<1> item2; + DLGITEMTEMPLATEEX<1> item3; + DLGITEMTEMPLATEEX<1> item4; + DLGITEMTEMPLATEEX<1> item5; + DLGITEMTEMPLATEEX<110> item6; + DLGITEMTEMPLATEEX<8> item7; + DLGITEMTEMPLATEEX<17> item8; + DLGITEMTEMPLATEEX<102> item9; + DLGITEMTEMPLATEEX<4> item10; + DLGITEMTEMPLATEEX<5> item11; +} DLGTEMPLATEEX; + +static constexpr DLGTEMPLATEEX RES_DIALOG = { + .dlgVer = 1, + .signature = 0xffff, + .helpID = NULL, + .exStyle = NULL, + .style = DS_SETFONT | WS_CAPTION | WS_CHILD, + .cDlgItems = 11, + .x = 0, + .y = 0, + .cx = 266, + .cy = 224, + .menu = NULL, + .windowClass = NULL, + .title = L"", + .pointsize = 10, + .weight = FW_DONTCARE, + .italic = false, + .charset = DEFAULT_CHARSET, + .typeface = L"Segoe UI", + + .item1 = { + NULL, NULL, + SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, + 0, 20, 266, 14, + IDC_BSDR_TITLE, + { 0xffff, 0x82 }, // STATIC + L"The following programs need to close:", + 0 + }, + .item2 = { + NULL, NULL, + SS_OWNERDRAW | WS_CHILD | WS_VISIBLE, + 0, 46, 266, 1, + IDC_BSDR_SEPARATOR_TOP, + { 0xffff, 0x82 }, // STATIC + L"", + 0 + }, + .item3 = { + NULL, NULL, + SS_USERITEM | WS_CHILD | WS_VISIBLE, + 0, 52, 256, 85, + IDC_BSDR_APPLIST, + { 0xffff, 0x82 }, // STATIC + L"", + 0 + }, + .item4 = { + NULL, NULL, + SBS_VERT | WS_CHILD | WS_VISIBLE, + 256, 52, 10, 85, + IDC_BSDR_SCROLLBAR, + { 0xffff, 0x84 }, // SCROLLBAR + L"", + 0 + }, + .item5 = { + NULL, NULL, + SS_OWNERDRAW | WS_CHILD | WS_VISIBLE, + 0, 143, 266, 1, + IDC_BSDR_SEPARATOR_BOTTOM, + { 0xffff, 0x82 }, // STATIC + L"", + 0 + }, + .item6 = { + NULL, NULL, + SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, + 0, 152, 266, 30, + IDC_BSDR_DESC, + { 0xffff, 0x82 }, // STATIC + L"To close the program that is preventing Windows from\nshutting down, click Cancel, and then close the program.", + 0 + }, + .item7 = { + NULL, NULL, + BS_OWNERDRAW | WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP, + 193, 191, 70, 13, + IDCANCEL, + { 0xffff, 0x80 }, // BUTTON + L"&Cancel", + 0 + }, + .item8 = { + NULL, NULL, + BS_OWNERDRAW | WS_CHILD | WS_VISIBLE | WS_TABSTOP, + 115, 191, 70, 13, + IDC_BSDR_FORCE_BTN, + { 0xffff, 0x80 }, // BUTTON + L"&Force shut down", + 0 + }, + .item9 = { + NULL, NULL, + SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, + 0, 152, 266, 30, + IDC_BSDR_WARNING, + { 0xffff, 0x82 }, // STATIC + L"If you force shut down you may lose work that you haven't saved.\nDo you still want to force shutdown?", + 0 + }, + .item10 = { + NULL, NULL, + BS_OWNERDRAW | WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP, + 193, 191, 70, 13, + IDNO, + { 0xffff, 0x80 }, // BUTTON + L"&No", + 0 + }, + .item11 = { + NULL, NULL, + BS_OWNERDRAW | WS_CHILD | WS_VISIBLE | WS_TABSTOP, + 115, 191, 70, 13, + IDYES, + { 0xffff, 0x80 }, // BUTTON + L"&Yes", + 0 + } }; alignas(BITMAPINFOHEADER) @@ -2843,10 +2949,10 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA if (!hDlgLocal) { Wh_Log(L"CreateDialogParamW failed: %d", GetLastError()); // Retry with hardcoded one - hDlgLocal = CreateDialogIndirectParamW(nullptr, reinterpret_cast(RES_DIALOG), hWnd, DlgProc, lParam); + hDlgLocal = CreateDialogIndirectParamW(nullptr, reinterpret_cast(&RES_DIALOG), hWnd, DlgProc, lParam); } } else { - hDlgLocal = CreateDialogIndirectParamW(nullptr, reinterpret_cast(RES_DIALOG), hWnd, DlgProc, lParam); + hDlgLocal = CreateDialogIndirectParamW(nullptr, reinterpret_cast(&RES_DIALOG), hWnd, DlgProc, lParam); } if (hDlgLocal) { ShowWindow(hDlgLocal, SW_SHOW); @@ -3260,17 +3366,13 @@ long __fastcall BlockedShutdownUXImpl_Stop_hook(void* thisPtr) { return S_OK; } -// Unused variable that only exists to make Windhawk 1.6.1 happy -// Putting nullptr in originalFunction only works on WH 1.7 somehow (unlike what the SYMBOL_HOOK comment says) -void** dummyOrig = nullptr; - // Windows.UI.BlockedShutdown.dll WindhawkUtils::SYMBOL_HOOK hooks[] = { { { L"public: virtual long __cdecl BlockedShutdownUXImpl::Start(struct Windows::Internal::UI::Logon::Controller::IUserSettingManager *,struct Windows::Internal::UI::Logon::Controller::ILogonUIStateInfo *)", }, - dummyOrig, + (void**)nullptr, // Cast is not needed on WH 1.7+ (void*)BlockedShutdownUXImpl_Start_hook, FALSE }, @@ -3278,7 +3380,7 @@ WindhawkUtils::SYMBOL_HOOK hooks[] = { { L"public: virtual long __cdecl BlockedShutdownUXImpl::get_ScaleFactor(unsigned int *)", }, - dummyOrig, + (void**)nullptr, (void*)BlockedShutdownUXImpl_get_ScaleFactor_hook, FALSE }, @@ -3286,7 +3388,7 @@ WindhawkUtils::SYMBOL_HOOK hooks[] = { { L"public: virtual long __cdecl BlockedShutdownUXImpl::get_WasClicked(unsigned char *)", }, - dummyOrig, + (void**)nullptr, (void*)BlockedShutdownUXImpl_get_WasClicked_hook, FALSE }, @@ -3294,7 +3396,7 @@ WindhawkUtils::SYMBOL_HOOK hooks[] = { { L"public: virtual long __cdecl BlockedShutdownUXImpl::AddApplication(struct Windows::Internal::UI::Logon::Controller::IShutdownBlockingApp *)", }, - dummyOrig, + (void**)nullptr, (void*)BlockedShutdownUXImpl_AddApplication_hook, FALSE }, @@ -3302,7 +3404,7 @@ WindhawkUtils::SYMBOL_HOOK hooks[] = { { L"public: virtual long __cdecl BlockedShutdownUXImpl::RemoveApplication(unsigned int)", }, - dummyOrig, + (void**)nullptr, (void*)BlockedShutdownUXImpl_RemoveApplication_hook, FALSE }, @@ -3310,7 +3412,7 @@ WindhawkUtils::SYMBOL_HOOK hooks[] = { { L"public: virtual long __cdecl BlockedShutdownUXImpl::add_Resolved(struct Windows::Foundation::ITypedEventHandler *,struct EventRegistrationToken *)", }, - dummyOrig, + (void**)nullptr, (void*)BlockedShutdownUXImpl_add_Resolved_hook, FALSE }, @@ -3318,7 +3420,7 @@ WindhawkUtils::SYMBOL_HOOK hooks[] = { { L"public: virtual long __cdecl BlockedShutdownUXImpl::remove_Resolved(struct EventRegistrationToken)", }, - dummyOrig, + (void**)nullptr, (void*)BlockedShutdownUXImpl_remove_Resolved_hook, FALSE }, @@ -3326,7 +3428,7 @@ WindhawkUtils::SYMBOL_HOOK hooks[] = { { L"public: virtual long __cdecl BlockedShutdownUXImpl::Hide(void)", }, - dummyOrig, + (void**)nullptr, (void*)BlockedShutdownUXImpl_Hide_hook, FALSE }, @@ -3334,7 +3436,7 @@ WindhawkUtils::SYMBOL_HOOK hooks[] = { { L"public: virtual long __cdecl BlockedShutdownUXImpl::Stop(void)", }, - dummyOrig, + (void**)nullptr, (void*)BlockedShutdownUXImpl_Stop_hook, FALSE } @@ -3348,41 +3450,49 @@ WindhawkUtils::SYMBOL_HOOK hooks[] = { // Pressing ctrl alt del can get out of this state but lets add this minimal safeguard // // Can't think of better appraoch because of the execution sequence mentioned in CustomBSDR::Start -// ShutdownWindowsWorkerThread runs before LogonUI exec so checking live LUI injection status isn't possible -// and will be always one step behind (e.g. will detect as LUI not injected on first logoff after mod install) -// I know the global inclusion key is not everything that affects that injection but this is minimal safeguard anyway -// Let's just hope users don't mess with mod specific advanced settings to force exclude LogonUI.exe lol +// ShutdownWindowsWorkerThread runs before LogonUI exec so checking live LUI injection status is tricky +// Relying on natural execution will result in detection being one step behind +// (e.g. will detect as LUI not injected on first logoff after mod install) +// +// This approach, derived from the WH tool mod code, also doesn't mess with internal Windhawk configuration bool IsLogonUiInjectionEnabled() { - HKEY hKey; - int res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Windhawk\\Engine\\Settings", 0, KEY_READ, &hKey); - if (res != ERROR_SUCCESS) { - Wh_Log(L"WH inclusion check failed (RegOpenKeyExW), error=%d", res); + STARTUPINFO si = {0}; + si.cb = sizeof(si); + PROCESS_INFORMATION pi = {0}; + + srand((unsigned int)time(nullptr)); + DWORD myPid = GetCurrentProcessId(); + + if (!CreateProcessW( + L"C:\\Windows\\System32\\LogonUI.exe", + (LPWSTR)(L"LogonUI.exe /wh-load-check " + std::to_wstring(myPid)).c_str(), + nullptr, + nullptr, + false, + 0, + nullptr, + nullptr, + &si, + &pi + )) { + Wh_Log(L"LogonUI load check exec failed"); return false; } - DWORD size = 0; - // WH inclusion key: pipe separated REG_SZ - res = RegQueryValueExW(hKey, L"Include", nullptr, nullptr, nullptr, &size); - if (res != ERROR_SUCCESS && res != ERROR_MORE_DATA) { - Wh_Log(L"WH inclusion check failed (size query), GLE=%d", res); - RegCloseKey(hKey); - return false; + // LogonUI normally auto exits when ran with invalid argument, even without this mod's injected code + DWORD result = WaitForSingleObject(pi.hProcess, 2000); + if (result != WAIT_OBJECT_0) { + Wh_Log(L"LogonUI wait timed out or failed"); + TerminateProcess(pi.hProcess, 0); } - wchar_t* data = new wchar_t[size / sizeof(wchar_t) + 1]; - res = RegQueryValueExW(hKey, L"Include", nullptr, nullptr, (LPBYTE)data, &size); - RegCloseKey(hKey); - if (res != ERROR_SUCCESS) { - Wh_Log(L"WH inclusion check failed, error=%d", res); - delete[] data; - return false; + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + + if ((DWORD)Wh_GetIntValue(L"LogonUiLoadCheck", -1) == myPid + pi.dwProcessId) { + return true; } - - data[size / sizeof(wchar_t)] = L'\0'; - int isLogonUiThere = wcsstr(_wcsupr(data), L"LOGONUI.EXE") != NULL; - delete[] data; - - return isLogonUiThere; + return false; } int* p_g_fShutdownResolverDisabled = nullptr; @@ -3461,6 +3571,34 @@ BOOL Wh_ModInit() { return FALSE; } + if (wcsstr(exeName, L"\\LOGONUI.EXE") == NULL) { + Wh_Log(L"Loaded in unexpected process!"); + return FALSE; + } + + int checkValue = -1; + int argc; + LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); + if (!argv) { + Wh_Log(L"CommandLineToArgvW failed"); + return FALSE; + } + + for (int i = 1; i < argc - 1; i++) { + if (wcscmp(argv[i], L"/wh-load-check") == 0) { + checkValue = _wtoi(argv[i + 1]); + DWORD pid = GetCurrentProcessId(); + if (checkValue != -1) { + Wh_Log(L"Writing load check value %d+%d=%d...", checkValue, pid, checkValue + pid); + Wh_SetIntValue(L"LogonUiLoadCheck", checkValue + pid); + ExitProcess(0); + } + break; + } + } + + LocalFree(argv); + CustomBSDR::hStopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); if (!CustomBSDR::hStopEvent) { Wh_Log(L"CreateEventW failed, GLE=%u", GetLastError()); From 9e8972ae1fe9e95ecd84941a8c9fa08cd0f792a6 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Sat, 29 Aug 2026 12:44:21 +0900 Subject: [PATCH 16/51] Misc fix --- mods/win7-bsdr.wh.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index c6f51230bd..2c8987ecbf 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -30,7 +30,7 @@ * This is not necessary. Hardcoded resources will be used instead if the path is not set or the file is missing. * Known issues * If your user account has no password, enabling the logoff sequence option may make the system automatically log on again after logging off. - * * Not compatible with a portable Windhawk installation, even when run as admin, because it doesn't survive a logoff long enough to handle BSDR. + * Not compatible with a portable Windhawk installation, even when run as admin, because it doesn't survive a logoff long enough to handle BSDR. ![Screenshot](https://raw.githubusercontent.com/Ingan121/files/refs/heads/master/vmware_gj0gqnHj8e.png) ## ⚠ Important usage note ⚠ @@ -3460,7 +3460,6 @@ bool IsLogonUiInjectionEnabled() { si.cb = sizeof(si); PROCESS_INFORMATION pi = {0}; - srand((unsigned int)time(nullptr)); DWORD myPid = GetCurrentProcessId(); if (!CreateProcessW( From fe0671d230b6569ca41425f7df7b131ad4e12fac Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Sat, 29 Aug 2026 14:59:58 +0900 Subject: [PATCH 17/51] Apply fixes, detect AuthUX, and deny portable --- mods/win7-bsdr.wh.cpp | 140 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 122 insertions(+), 18 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 2c8987ecbf..5def982ab6 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -57,20 +57,27 @@ settings. If you do not do this, it will silently fail to inject. $name: Restore Windows 7's logoff sequence $name:ko-KR: Windows 7의 로그오프 절차 복원 $description: Switch to the 'logging off' screen after all applications have been closed. +- noSafetyChecks: false + $name: (Advanced) Skip safety checks before restoring the classic logoff sequence + $name:ko-KR: 고전 로그오프 절차를 복원하기 전 안전 검사 생략 + $description: This mod verifies that LogonUI injection was successful on every logoff before enabling the old sequence, to prevent the modern BSDR from showing on the invisible secure desktop, thus making the logoff sequence stuck. Before enabling this option, remember to press Ctrl+Alt+Del if logoff gets stuck. */ // ==/WindhawkModSettings== #include #include #include -#include -#include // Required for WindowsGetStringRawBuffer/WindowsDeleteString and ComPtr #include #include #include +#include +#include +#include +#include +#include #include #include -#include +#include #include #include @@ -1909,6 +1916,10 @@ HRESULT GetBitmapFromRandomStream(Microsoft::WRL::ComPtr(&ssp), sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) { + if (ssp.dwCurrentState != SERVICE_RUNNING) { + // Service not running, portable + CloseServiceHandle(hService); + CloseServiceHandle(hSCM); + return false; + } + } else { + Wh_Log(L"Windhawk service query failed, GLE=%d", GetLastError()); + CloseServiceHandle(hService); + CloseServiceHandle(hSCM); + return false; + } + + CloseServiceHandle(hService); + CloseServiceHandle(hSCM); + Wh_Log(L"Windhawk service running"); + + // Active LogonUI injection probe STARTUPINFO si = {0}; si.cb = sizeof(si); PROCESS_INFORMATION pi = {0}; DWORD myPid = GetCurrentProcessId(); + std::wstring cmdline = L"LogonUI.exe /wh-load-check " + std::to_wstring(myPid); + + wchar_t logonUiPath[MAX_PATH]; + UINT len = GetSystemDirectoryW(logonUiPath, MAX_PATH); + if (!len || len >= MAX_PATH - ARRAYSIZE(L"\\LogonUI.exe")) { + return false; + } + wcscat_s(logonUiPath, L"\\LogonUI.exe"); + + Wh_SetIntValue(L"LogonUiLoadCheck", -1); if (!CreateProcessW( - L"C:\\Windows\\System32\\LogonUI.exe", - (LPWSTR)(L"LogonUI.exe /wh-load-check " + std::to_wstring(myPid)).c_str(), + logonUiPath, + cmdline.data(), nullptr, nullptr, false, @@ -3481,6 +3569,7 @@ bool IsLogonUiInjectionEnabled() { // LogonUI normally auto exits when ran with invalid argument, even without this mod's injected code DWORD result = WaitForSingleObject(pi.hProcess, 2000); if (result != WAIT_OBJECT_0) { + // Should exit immediately but just to be safe Wh_Log(L"LogonUI wait timed out or failed"); TerminateProcess(pi.hProcess, 0); } @@ -3556,22 +3645,26 @@ BOOL Wh_ModInit() { return FALSE; } } + Wh_Log(L"lui? %d", IsLogonUiInjectionEnabled()); return TRUE; } - hBlockedShutdownDll = LoadLibraryExW(L"Windows.UI.BlockedShutdown.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); - if (hBlockedShutdownDll) { - if (!WindhawkUtils::HookSymbols(hBlockedShutdownDll, hooks, ARRAYSIZE(hooks))) { - Wh_Log(L"Failed to hook symbols in Windows.UI.BlockedShutdown.dll"); - return FALSE; - } - } else { - Wh_Log(L"Failed to load Windows.UI.BlockedShutdown.dll"); + if (wcsstr(exeName, L"\\LOGONUI.EXE") == NULL) { + // User decided to mess with the mod advanced settings + Wh_Log(L"Loaded in unexpected process!"); return FALSE; } - if (wcsstr(exeName, L"\\LOGONUI.EXE") == NULL) { - Wh_Log(L"Loaded in unexpected process!"); + // Needed for getting at least the logoff sequence part to work with AuthUX on Windhawk portable + // Otherwise Windhawk fails to uninitialize the mod properly during logoff (both for winlogon and LogonUI) and force resolves without showing the dialog + // 14:46:12.220 3396 winlogon.exe [WH] [local@win7-bsdr] [3598:ShutdownWindowsWorkerThread_hook]: ShutdownWindowsWoread_hook: Set g_fShutdownResolverDisabled to 1 + // 14:46:12.230 6084 LogonUI.exe [WH] [local@win7-bsdr] [3631:Wh_ModInit]: Init + // 14:46:12.233 6084 LogonUI.exe [WH] [local@win7-bsdr] [3659:Wh_ModInit]: AuthUX installed, skipping LogonUI hooks. + // 14:46:12.390 3396 winlogon.exe [WH] [local@win7-bsdr] [3719:Wh_ModUninit]: Uninit + // 14:46:19.559 3396 winlogon.exe [WH] [local@win7-bsdr] [3605:ShutdownWindowsWorkerThread_hook]: ShutdownWindowsWoread ended; restored p_g_fShutdownResolverDisabled to 0 // ???? + // (Doesn't happen with WH service, and stopping it while the BSDR dialog is open cleanly results in the cancel resolve path on uninit) + if (IsAuthUxInstalled()) { + Wh_Log(L"AuthUX installed, skipping LogonUI hooks..."); return FALSE; } @@ -3590,7 +3683,7 @@ BOOL Wh_ModInit() { if (checkValue != -1) { Wh_Log(L"Writing load check value %d+%d=%d...", checkValue, pid, checkValue + pid); Wh_SetIntValue(L"LogonUiLoadCheck", checkValue + pid); - ExitProcess(0); + return FALSE; // Let LogonUI automatically exit immediately (because it lacks proper args) } break; } @@ -3598,6 +3691,17 @@ BOOL Wh_ModInit() { LocalFree(argv); + hBlockedShutdownDll = LoadLibraryExW(L"Windows.UI.BlockedShutdown.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (hBlockedShutdownDll) { + if (!WindhawkUtils::HookSymbols(hBlockedShutdownDll, hooks, ARRAYSIZE(hooks))) { + Wh_Log(L"Failed to hook symbols in Windows.UI.BlockedShutdown.dll"); + return FALSE; + } + } else { + Wh_Log(L"Failed to load Windows.UI.BlockedShutdown.dll"); + return FALSE; + } + CustomBSDR::hStopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); if (!CustomBSDR::hStopEvent) { Wh_Log(L"CreateEventW failed, GLE=%u", GetLastError()); From 86f8a0ecf3024b39cce6700b121f2ee1c5d0f6f4 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Sat, 29 Aug 2026 15:01:26 +0900 Subject: [PATCH 18/51] Remove debug log --- mods/win7-bsdr.wh.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 5def982ab6..5b09edbd78 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -3645,7 +3645,6 @@ BOOL Wh_ModInit() { return FALSE; } } - Wh_Log(L"lui? %d", IsLogonUiInjectionEnabled()); return TRUE; } From 28bb75a3f7f4fce455a873f11f0cfc8196f23474 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Sat, 29 Aug 2026 19:28:06 +0900 Subject: [PATCH 19/51] Apply fixes --- mods/win7-bsdr.wh.cpp | 378 ++++++++++++++++++++++++++---------------- 1 file changed, 235 insertions(+), 143 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 5b09edbd78..157af39139 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -94,7 +94,10 @@ settings. If you do not do this, it will silently fail to inject. EXTERN_C IMAGE_DOS_HEADER __ImageBase; #define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase) -HINSTANCE hBlockedShutdownDll; +HINSTANCE g_hLogonControllerDll; +HINSTANCE g_hBlockedShutdownDll; + +HANDLE g_doModalExitEvent = nullptr; #pragma region resources // Resource IDs @@ -129,8 +132,8 @@ HINSTANCE hBlockedShutdownDll; #define IDS_BSDR_BLOCKINGAPPCOUNT_MULTI 1039 #define IDS_BSDR_BLOCKINGAPPCOUNT_SINGLE 1040 -HINSTANCE hResDll; -bool isUsingHardcodedRes = true; +HINSTANCE g_hResDll; +bool g_isUsingHardcodedRes = true; // Hardcoded resources template @@ -305,6 +308,7 @@ static constexpr DLGTEMPLATEEX RES_DIALOG = { } }; +// Bitmaps (originals for accuracy) alignas(BITMAPINFOHEADER) static constexpr unsigned char RES_BSDR_SEPARATOR[] = { 0x28, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, @@ -1605,7 +1609,7 @@ static constexpr unsigned char RES_BSDR_BTN_SELECTED_HOVER[] = { }; void GetString(UINT uID, LPWSTR lpBuffer, int cchBufferMax, bool forceHardcoded = false) { - if (isUsingHardcodedRes || !hResDll || forceHardcoded) { + if (g_isUsingHardcodedRes || !g_hResDll || forceHardcoded) { LPCWSTR str = nullptr; switch (uID) { case IDS_BSDR_DESC_LOGOFF: @@ -1653,7 +1657,7 @@ void GetString(UINT uID, LPWSTR lpBuffer, int cchBufferMax, bool forceHardcoded } wcsncpy_s(lpBuffer, cchBufferMax, str, _TRUNCATE); } else { - if (LoadStringW(hResDll, uID, lpBuffer, cchBufferMax) == 0) { // load failed + if (LoadStringW(g_hResDll, uID, lpBuffer, cchBufferMax) == 0) { // load failed Wh_Log(L"Failed to load string from DLL; falling back to hardcoded one..."); GetString(uID, lpBuffer, cchBufferMax, true); } @@ -1707,19 +1711,21 @@ ILogonUIStateInfo : IInspectable { // Windhawk lacks some WinRT header files needed for EventSource. Since add_Resolved is only called once, just handle only one handler using ResolvedHandler = ABI::Windows::Foundation::ITypedEventHandler; -[[clang::no_destroy]] Microsoft::WRL::ComPtr _Resolved; -std::mutex resolvedMutex; -BlockedShutdownResolution resolvedValue = BlockedShutdownResolution_None; -bool _wasClicked = false; +[[clang::no_destroy]] Microsoft::WRL::ComPtr g_Resolved; +std::mutex g_resolvedMutex; +BlockedShutdownResolution g_resolvedValue = BlockedShutdownResolution_None; +bool g_wasClicked = false; void Resolve(BlockedShutdownResolution resolution) { Microsoft::WRL::ComPtr resolvedLocal; - { - std::lock_guard lock(resolvedMutex); - _wasClicked = true; - resolvedValue = resolution; - resolvedLocal = _Resolved; + std::lock_guard lock(g_resolvedMutex); + if (g_resolvedValue != BlockedShutdownResolution_None) { + return; // already resolved + } + g_wasClicked = true; + g_resolvedValue = resolution; + resolvedLocal = g_Resolved; } if (resolvedLocal) { @@ -1739,6 +1745,7 @@ namespace CustomBSDR { // handles static HANDLE hThread = nullptr; static HANDLE hStopEvent = nullptr; + static HDESK hDesktop = nullptr; static HWND hDlg = nullptr; static HWND hBgWnd = nullptr; static HWND hTitleText = nullptr; @@ -1759,7 +1766,7 @@ namespace CustomBSDR { void CenterWindow(HWND hWnd); bool IsHighContrast(); HBITMAP LoadAlphaBitmap(UINT resourceId, bool forceHardcoded = false); - void DrawSeparator(HDC hdc, LPDRAWITEMSTRUCT pDIS); + void DrawSeparator(LPDRAWITEMSTRUCT pDIS); void DrawButton(HDC hdc, LPDRAWITEMSTRUCT pDIS); void CreateAppTileControls(IShutdownBlockingApp* blockingApp); void RemoveAppTileControls(UINT appId); @@ -1811,8 +1818,7 @@ HRESULT LoadImageWithWIC(IWICImagingFactory* pWICImagingFactory, IStream* pStrea Microsoft::WRL::ComPtr spWICImagingFactory(pWICImagingFactory); if (!spWICImagingFactory.Get()) { - hr = CoCreateInstance( - CLSID_WICImagingFactory2, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spWICImagingFactory)); + hr = E_ABORT; } if (SUCCEEDED(hr)) { Microsoft::WRL::ComPtr spDecoder; @@ -1948,7 +1954,7 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { Wh_Log(L"Failed to load bitmap from DLL; falling back to hardcoded one..."); } - if (isUsingHardcodedRes || !hResDll || forceHardcoded) { + if (g_isUsingHardcodedRes || !g_hResDll || forceHardcoded) { switch (resourceId) { case IDB_BSDR_SEPARATOR: pResourceData = RES_BSDR_SEPARATOR; @@ -1973,11 +1979,11 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { return nullptr; } } else { - hResource = FindResourceW(hResDll, MAKEINTRESOURCEW(resourceId), RT_BITMAP); + hResource = FindResourceW(g_hResDll, MAKEINTRESOURCEW(resourceId), RT_BITMAP); if (!hResource) return CustomBSDR::LoadAlphaBitmap(resourceId, true); - HGLOBAL hGlobal = LoadResource(hResDll, hResource); + HGLOBAL hGlobal = LoadResource(g_hResDll, hResource); if (!hGlobal) return CustomBSDR::LoadAlphaBitmap(resourceId, true); @@ -1988,7 +1994,7 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { DWORD dwSize = 0; if (hResource) { // Size validation for external dll - dwSize = SizeofResource(hResDll, hResource); + dwSize = SizeofResource(g_hResDll, hResource); if (dwSize < sizeof(BITMAPINFOHEADER)) { Wh_Log(L"Image load error: resource too small"); return LoadAlphaBitmap(resourceId, true); @@ -1998,16 +2004,46 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { const BITMAPINFO* pBitmapInfo = static_cast(pResourceData); const BITMAPINFOHEADER* pHeader = &pBitmapInfo->bmiHeader; - if (pHeader->biBitCount != 32) { - if (isUsingHardcodedRes || !hResDll || forceHardcoded) { - Wh_Log(L"Image load error: not 32bpp"); - return nullptr; + if (hResource) { + if (dwSize < sizeof(BITMAPINFOHEADER) || + pHeader->biSize < sizeof(BITMAPINFOHEADER) || + pHeader->biSize > dwSize || + pHeader->biWidth <= 0 || + pHeader->biHeight == 0 || + pHeader->biHeight == LONG_MIN || + pHeader->biPlanes != 1 || + pHeader->biBitCount != 32 || + (pHeader->biCompression != BI_RGB && + pHeader->biCompression != BI_BITFIELDS)) { + Wh_Log(L"Image load error: invalid bitmap header"); + return LoadAlphaBitmap(resourceId, true); } - return LoadBitmapW(hResDll, MAKEINTRESOURCEW(resourceId)); } - int width = pHeader->biWidth; - int height = abs(pHeader->biHeight); + uint32_t width = static_cast(pHeader->biWidth); + uint32_t height = static_cast( + pHeader->biHeight < 0 + ? -static_cast(pHeader->biHeight) + : pHeader->biHeight + ); + + uint64_t bitsOffset = pHeader->biSize; + + if (pHeader->biSize == sizeof(BITMAPINFOHEADER) && + pHeader->biCompression == BI_BITFIELDS) { + bitsOffset += 3 * sizeof(DWORD); + } + + uint64_t requiredSize = + static_cast(width) * height * 4; + + if (hResource && + (bitsOffset > dwSize || + requiredSize > static_cast(dwSize) - bitsOffset)) { + Wh_Log(L"Image load error: truncated bitmap data"); + return LoadAlphaBitmap(resourceId, true); + } + bool isTopDown = pHeader->biHeight < 0; BITMAPINFO bmi = {}; @@ -2038,7 +2074,7 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { if (isTopDown) { memcpy(pBits, pSrc, width * height * 4); } else { - for (int y = 0; y < height; y++) { + for (uint32_t y = 0; y < height; y++) { memcpy((unsigned char*)pBits + y * width * 4, pSrc + (height - 1 - y) * width * 4, width * 4); @@ -2046,7 +2082,7 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { } unsigned char* pPixels = (unsigned char*)pBits; - for (int i = 0; i < width * height; i++) { + for (uint32_t i = 0; i < width * height; i++) { unsigned char b = pPixels[i * 4 + 0]; unsigned char g = pPixels[i * 4 + 1]; unsigned char r = pPixels[i * 4 + 2]; @@ -2063,11 +2099,8 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { return hBitmap; } -void CustomBSDR::DrawSeparator(HDC hdc, LPDRAWITEMSTRUCT pDIS) { +void CustomBSDR::DrawSeparator(LPDRAWITEMSTRUCT pDIS) { if (separatorBitmap && !IsHighContrast()) { - BITMAP bm; - GetObject(separatorBitmap, sizeof(bm), &bm); - int width = pDIS->rcItem.right - pDIS->rcItem.left; int height = pDIS->rcItem.bottom - pDIS->rcItem.top; @@ -2818,7 +2851,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA case WM_DRAWITEM: { LPDRAWITEMSTRUCT pDIS = (LPDRAWITEMSTRUCT)lParam; if (pDIS->CtlID == IDC_BSDR_SEPARATOR_TOP || pDIS->CtlID == IDC_BSDR_SEPARATOR_BOTTOM) { - DrawSeparator(pDIS->hDC, pDIS); + DrawSeparator(pDIS); return TRUE; } else if (pDIS->CtlType == ODT_BUTTON) { DrawButton(pDIS->hDC, pDIS); @@ -2834,14 +2867,19 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA if (!isOnSecureDesktop) { // If BSDR is forced to show on the default desktop with the Windhawk mod, LogonUI.exe won't exit for some reason on cancel, // causing issues with subsequent session ends, unless it's killed manually or Ctrl+Alt+Del is pressed once - // so force exit the process as a dirty workaround - // - // This also happens with AuthUX BSDR where AuthUX.dll fully replaces Windows.UI.BlockedShutdown.dll - // LogonUI.exe thread is stuck at WaitForSingleObjectEx from LogonController.dll (prob mutex) - // It gets stuck between get_WasClicked and remove_Resolved. Pressing C+A+D makes the thread continue and remove_Resolved/Hide/Stop gets called. - // Still haven't figured out the culprit yet - Sleep(1000); // Make sure the resolve request reaches winlogon - ExitProcess(0); + // It happens because the event created in CLogonController::DoModal never gets fired in this state somehow + // and makes the thread stuck in WaitForSingleObject + // So force signal the event as a not so clean workaround (still better than previous ExitProcess workaround) + // (I still haven't found neither the culprint nor which code signals that event) + if (g_doModalExitEvent) { + SetEvent(g_doModalExitEvent); + } else { + // Event capture failed, oh noes! + // Here comes the old terrible workaround + Wh_Log(L"Force exiting LogonUI!"); + Sleep(1000); // Make sure the resolve request reaches winlogon + ExitProcess(0); + } } SendMessageW(hBgWnd, WM_CLOSE, 0, 0); return TRUE; @@ -2955,8 +2993,8 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA switch (message) { case WM_CREATE: { HWND hDlgLocal = nullptr; - if (!isUsingHardcodedRes && hResDll) { - hDlgLocal = CreateDialogParamW(hResDll, MAKEINTRESOURCEW(IDD_BSDR_DLG), hWnd, DlgProc, lParam); + if (!g_isUsingHardcodedRes && g_hResDll) { + hDlgLocal = CreateDialogParamW(g_hResDll, MAKEINTRESOURCEW(IDD_BSDR_DLG), hWnd, DlgProc, lParam); if (!hDlgLocal) { Wh_Log(L"CreateDialogParamW failed: %d", GetLastError()); // Retry with hardcoded one @@ -3102,12 +3140,10 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { int ret = 0; - HRESULT hrCo = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - // Attempt to create window on the input desktop, as the thread is always running in secure desktop at this point, // but the Windhawk mod can force this phase of session end to run in the default desktop // (This is only an attempt and not critical if failed) - HDESK hDesktop = OpenInputDesktop(0, FALSE, DESKTOP_CREATEWINDOW | DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS); + hDesktop = OpenInputDesktop(0, FALSE, DESKTOP_CREATEWINDOW | DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS); if (hDesktop) { wchar_t desktopName[256] = {}; if (GetUserObjectInformationW(hDesktop, UOI_NAME, desktopName, sizeof(desktopName), nullptr)) { @@ -3116,11 +3152,12 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { if (!SetThreadDesktop(hDesktop)) { Wh_Log(L"SetThreadDesktop failed, GLE=%d", GetLastError()); } - CloseDesktop(hDesktop); } else { Wh_Log(L"OpenInputDesktop failed, GLE=%d", GetLastError()); } + HRESULT hrCo = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + WNDCLASSEXW wndClass = {}; wndClass.cbSize = sizeof(WNDCLASSEXW); @@ -3200,14 +3237,15 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { Wh_Log(L"RegisterClassExW failed, GLE=%d", ret); } + if (SUCCEEDED(hrCo)) { + CoUninitialize(); + } + if (ret != 0) { Wh_Log(L"CustomBSDR thread init failed"); Resolve(BlockedShutdownResolution_Force); } - if (SUCCEEDED(hrCo)) { - CoUninitialize(); - } return 0; } @@ -3293,8 +3331,8 @@ long __fastcall BlockedShutdownUXImpl_get_ScaleFactor_hook(void* thisPtr, unsign long __fastcall BlockedShutdownUXImpl_get_WasClicked_hook(void* thisPtr, unsigned char* wasClicked) { Wh_Log(L"BlockedShutdownUXImpl::get_WasClicked"); { - std::lock_guard lock(resolvedMutex); - *wasClicked = _wasClicked; + std::lock_guard lock(g_resolvedMutex); + *wasClicked = g_wasClicked; } return S_OK; } @@ -3341,11 +3379,11 @@ long __fastcall BlockedShutdownUXImpl_add_Resolved_hook(void* thisPtr, ABI::Wind BlockedShutdownResolution resolvedValueLocal; { - std::lock_guard lock(resolvedMutex); - resolvedValueLocal = resolvedValue; + std::lock_guard lock(g_resolvedMutex); + resolvedValueLocal = g_resolvedValue; if (resolvedValueLocal == BlockedShutdownResolution_None) { - _Resolved = eventHandler; + g_Resolved = eventHandler; } } @@ -3361,8 +3399,8 @@ long __fastcall BlockedShutdownUXImpl_remove_Resolved_hook(void* thisPtr, EventR Wh_Log(L"BlockedShutdownUXImpl::remove_Resolved"); if (eventToken.value == 0) { - std::lock_guard lock(resolvedMutex); - _Resolved.Reset(); + std::lock_guard lock(g_resolvedMutex); + g_Resolved.Reset(); } return S_OK; @@ -3381,7 +3419,7 @@ long __fastcall BlockedShutdownUXImpl_Stop_hook(void* thisPtr) { } // Windows.UI.BlockedShutdown.dll -WindhawkUtils::SYMBOL_HOOK hooks[] = { +WindhawkUtils::SYMBOL_HOOK blockedShutdownDllHooks[] = { { { L"public: virtual long __cdecl BlockedShutdownUXImpl::Start(struct Windows::Internal::UI::Logon::Controller::IUserSettingManager *,struct Windows::Internal::UI::Logon::Controller::ILogonUIStateInfo *)", @@ -3455,21 +3493,63 @@ WindhawkUtils::SYMBOL_HOOK hooks[] = { FALSE } }; + +thread_local bool g_enteringDoModal = false; + +typedef long (__cdecl *CLogonController__DoModal_t)(void* pThis, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5); +CLogonController__DoModal_t CLogonController__DoModal_orig; +long __cdecl CLogonController__DoModal_hook(void* pThis, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5) { + g_enteringDoModal = true; + Wh_Log(L"DoModal"); + int result = CLogonController__DoModal_orig(pThis, a2, a3, a4, a5); + g_enteringDoModal = false; + return result; +} + +WindhawkUtils::SYMBOL_HOOK logonControllerDllHooks[] = { + { + { + L"public: virtual long __cdecl CLogonController::DoModal(unsigned long,unsigned long,unsigned long,unsigned long)" + }, + (void**)&CLogonController__DoModal_orig, + (void*)CLogonController__DoModal_hook, + FALSE + } +}; + +using CreateEventW_t = decltype(&CreateEventW); +CreateEventW_t CreateEventW_orig; +HANDLE WINAPI CreateEventW_hook(LPSECURITY_ATTRIBUTES lpEventAttributes, WINBOOL bManualReset, WINBOOL bInitialState, LPCWSTR lpName) { + HANDLE result = CreateEventW_orig(lpEventAttributes, bManualReset, bInitialState, lpName); + if (lpEventAttributes == 0 && bManualReset == 0 && bInitialState == 0 && lpName == 0 && g_enteringDoModal) { + if (g_doModalExitEvent) { + Wh_Log(L"Warning: Overwrote DoModal event"); + } else { + // Works cleanly on LTSC 2021 and 11 25H2 + Wh_Log(L"Caught DoModal event"); + } + g_doModalExitEvent = result; + } + return result; +} #pragma endregion LogonUI BSDR Hooks (Interface) #pragma region Winlogon hooks (disable async logoff) bool IsAuthUxInstalled() { - HKEY hKey; - if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\WindowsRuntime\\ActivatableClassId\\Windows.Internal.UI.Logon.Controller.BlockedShutdownResolverUX", 0, KEY_READ, &hKey) == ERROR_SUCCESS) { - wchar_t dllPath[MAX_PATH]; - DWORD size = sizeof(dllPath); - if (RegQueryValueExW(hKey, L"DllPath", NULL, NULL, (LPBYTE)dllPath, &size) == ERROR_SUCCESS) { - if (wcsstr(_wcsupr(dllPath), L"\\WINDOWS.UI.BLOCKEDSHUTDOWN.DLL") == NULL) { // non stock dll - RegCloseKey(hKey); - return true; - } + wchar_t dllPath[MAX_PATH]; + DWORD size = sizeof(dllPath); + if (RegGetValueW( + HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\WindowsRuntime\\ActivatableClassId\\Windows.Internal.UI.Logon.Controller.BlockedShutdownResolverUX", + L"DllPath", + RRF_RT_REG_SZ, + nullptr, + dllPath, + &size + ) == ERROR_SUCCESS) { + if (wcsstr(_wcsupr(dllPath), L"\\WINDOWS.UI.BLOCKEDSHUTDOWN.DLL") == NULL) { // non stock dll + return true; } - RegCloseKey(hKey); } return false; } @@ -3480,7 +3560,7 @@ bool IsAuthUxInstalled() { // Pressing ctrl alt del can get out of this state but lets add this minimal safeguard // // Can't think of better appraoch because of the execution sequence mentioned in CustomBSDR::Start -// ShutdownWindowsWorkerThread runs before LogonUI exec so checking live LUI injection status is tricky +// ShutdownWindowsWorkerThread (which uses g_fShutdownResolverDisabled) runs before LogonUI exec so checking live LUI injection status is tricky // Relying on natural execution will result in detection being one step behind // (e.g. will detect as LUI not injected on first logoff after mod install) // @@ -3567,7 +3647,8 @@ bool IsLogonUiInjectionEnabled() { } // LogonUI normally auto exits when ran with invalid argument, even without this mod's injected code - DWORD result = WaitForSingleObject(pi.hProcess, 2000); + // 20 seconds: Wait for symbol download (it's not that big; better than infinite. probe path isn't blocking shutdown either) + DWORD result = WaitForSingleObject(pi.hProcess, 20000); if (result != WAIT_OBJECT_0) { // Should exit immediately but just to be safe Wh_Log(L"LogonUI wait timed out or failed"); @@ -3583,37 +3664,11 @@ bool IsLogonUiInjectionEnabled() { return false; } +bool g_isWinlogon = false; int* p_g_fShutdownResolverDisabled = nullptr; - -typedef void __fastcall (*ShutdownWindowsWorkerThread_t)(void* Instance, void* Context); -ShutdownWindowsWorkerThread_t ShutdownWindowsWorkerThread_original; -void __fastcall ShutdownWindowsWorkerThread_hook(void* Instance, void* Context) { - if (!p_g_fShutdownResolverDisabled) { - return ShutdownWindowsWorkerThread_original(Instance, Context); - } - - int origResolverDisabledState = *p_g_fShutdownResolverDisabled; - if (IsLogonUiInjectionEnabled()) { - *p_g_fShutdownResolverDisabled = 1; // Disable the async logoff resolver - Wh_Log(L"ShutdownWindowsWorkerThread_hook: Set g_fShutdownResolverDisabled to 1"); - } else { - // User did not read the instruction and LogonUI is not added to the inclusion list - Wh_Log(L"ShutdownWindowsWorkerThread_hook: Not setting g_fShutdownResolverDisabled as LogonUI hooks are not ready"); - } - ShutdownWindowsWorkerThread_original(Instance, Context); - *p_g_fShutdownResolverDisabled = origResolverDisabledState; - Wh_Log(L"ShutdownWindowsWorkerThread ended; restored p_g_fShutdownResolverDisabled to %d", origResolverDisabledState); -} +int g_origResolverDisabledState = 0; WindhawkUtils::SYMBOL_HOOK winlogonExeHooks[] = { - { - { - L"void __cdecl ShutdownWindowsWorkerThread(struct _TP_CALLBACK_INSTANCE *,void *)", - }, - (void**)&ShutdownWindowsWorkerThread_original, - (void*)ShutdownWindowsWorkerThread_hook, - FALSE - }, { { L"int g_fShutdownResolverDisabled", @@ -3632,8 +3687,8 @@ BOOL Wh_ModInit() { wchar_t exeName[MAX_PATH]; GetModuleFileNameW(NULL, exeName, MAX_PATH); - bool isWinlogon = wcsstr(_wcsupr(exeName), L"\\WINLOGON.EXE") != NULL; - if (isWinlogon) { + g_isWinlogon = wcsstr(_wcsupr(exeName), L"\\WINLOGON.EXE") != NULL; + if (g_isWinlogon) { if (!Wh_GetIntSetting(L"disableAsyncLogoff")) { Wh_Log(L"Running in winlogon.exe, but winlogon hooks are disabled"); return FALSE; @@ -3641,7 +3696,7 @@ BOOL Wh_ModInit() { HMODULE winlogon = GetModuleHandleW(NULL); if (winlogon) { if (!WindhawkUtils::HookSymbols(winlogon, winlogonExeHooks, ARRAYSIZE(winlogonExeHooks))) { - Wh_Log(L"HookSymbols ShutdownWindowsWorkerThread failed"); + Wh_Log(L"HookSymbols winlogon failed"); return FALSE; } } @@ -3655,18 +3710,35 @@ BOOL Wh_ModInit() { } // Needed for getting at least the logoff sequence part to work with AuthUX on Windhawk portable - // Otherwise Windhawk fails to uninitialize the mod properly during logoff (both for winlogon and LogonUI) and force resolves without showing the dialog - // 14:46:12.220 3396 winlogon.exe [WH] [local@win7-bsdr] [3598:ShutdownWindowsWorkerThread_hook]: ShutdownWindowsWoread_hook: Set g_fShutdownResolverDisabled to 1 - // 14:46:12.230 6084 LogonUI.exe [WH] [local@win7-bsdr] [3631:Wh_ModInit]: Init - // 14:46:12.233 6084 LogonUI.exe [WH] [local@win7-bsdr] [3659:Wh_ModInit]: AuthUX installed, skipping LogonUI hooks. - // 14:46:12.390 3396 winlogon.exe [WH] [local@win7-bsdr] [3719:Wh_ModUninit]: Uninit - // 14:46:19.559 3396 winlogon.exe [WH] [local@win7-bsdr] [3605:ShutdownWindowsWorkerThread_hook]: ShutdownWindowsWoread ended; restored p_g_fShutdownResolverDisabled to 0 // ???? - // (Doesn't happen with WH service, and stopping it while the BSDR dialog is open cleanly results in the cancel resolve path on uninit) if (IsAuthUxInstalled()) { Wh_Log(L"AuthUX installed, skipping LogonUI hooks..."); return FALSE; } + if (!Wh_SetFunctionHook((void*)CreateEventW, (void*)CreateEventW_hook, (void**)&CreateEventW_orig)) { + Wh_Log(L"CreateEventW hook failed"); + // not critical, well, there still is a terrrible workaround path + } + + g_hLogonControllerDll = LoadLibraryExW(L"LogonController.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (g_hLogonControllerDll) { + if (!WindhawkUtils::HookSymbols(g_hLogonControllerDll, logonControllerDllHooks, ARRAYSIZE(logonControllerDllHooks))) { + Wh_Log(L"Failed to hook symbols in LogonController.dll"); + // Ditto + } + } + + g_hBlockedShutdownDll = LoadLibraryExW(L"Windows.UI.BlockedShutdown.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (g_hBlockedShutdownDll) { + if (!WindhawkUtils::HookSymbols(g_hBlockedShutdownDll, blockedShutdownDllHooks, ARRAYSIZE(blockedShutdownDllHooks))) { + Wh_Log(L"Failed to hook symbols in Windows.UI.BlockedShutdown.dll"); + return FALSE; + } + } else { + Wh_Log(L"Failed to load Windows.UI.BlockedShutdown.dll"); + return FALSE; + } + int checkValue = -1; int argc; LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); @@ -3690,17 +3762,6 @@ BOOL Wh_ModInit() { LocalFree(argv); - hBlockedShutdownDll = LoadLibraryExW(L"Windows.UI.BlockedShutdown.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); - if (hBlockedShutdownDll) { - if (!WindhawkUtils::HookSymbols(hBlockedShutdownDll, hooks, ARRAYSIZE(hooks))) { - Wh_Log(L"Failed to hook symbols in Windows.UI.BlockedShutdown.dll"); - return FALSE; - } - } else { - Wh_Log(L"Failed to load Windows.UI.BlockedShutdown.dll"); - return FALSE; - } - CustomBSDR::hStopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); if (!CustomBSDR::hStopEvent) { Wh_Log(L"CreateEventW failed, GLE=%u", GetLastError()); @@ -3709,9 +3770,9 @@ BOOL Wh_ModInit() { auto dllPath = WindhawkUtils::StringSetting::make(L"resDllPath"); if (*dllPath) { - hResDll = LoadLibraryExW(dllPath, nullptr, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE); - if (hResDll) { - isUsingHardcodedRes = false; + g_hResDll = LoadLibraryExW(dllPath, nullptr, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE); + if (g_hResDll) { + g_isUsingHardcodedRes = false; } else { Wh_Log(L"Failed to load the resource DLL! Using hardcoded resources..."); } @@ -3722,45 +3783,76 @@ BOOL Wh_ModInit() { return TRUE; } +void Wh_ModAfterInit() { + if (g_isWinlogon && p_g_fShutdownResolverDisabled) { + if (IsLogonUiInjectionEnabled()) { + g_origResolverDisabledState = *p_g_fShutdownResolverDisabled; + *p_g_fShutdownResolverDisabled = 1; // Disable the async logoff resolver + Wh_Log(L"Set g_fShutdownResolverDisabled to 1"); + } else { + // User did not read the instruction and LogonUI is not added to the inclusion list + Wh_Log(L"Not setting g_fShutdownResolverDisabled as LogonUI hooks are not ready"); + } + } +} + void Wh_ModUninit() { Wh_Log(L"Uninit"); - if (CustomBSDR::hThread) { + if (g_isWinlogon && p_g_fShutdownResolverDisabled) { + *p_g_fShutdownResolverDisabled = g_origResolverDisabledState; + return; + } + + using namespace CustomBSDR; + + if (hThread) { bool needsResolve; { - std::lock_guard lock(resolvedMutex); - needsResolve = resolvedValue == BlockedShutdownResolution_None; + std::lock_guard lock(g_resolvedMutex); + needsResolve = g_resolvedValue == BlockedShutdownResolution_None; } if (needsResolve) { Resolve(BlockedShutdownResolution_Cancel); } - if (CustomBSDR::hStopEvent) { - SetEvent(CustomBSDR::hStopEvent); + if (hStopEvent) { + SetEvent(hStopEvent); } - WaitForSingleObject(CustomBSDR::hThread, INFINITE); - CloseHandle(CustomBSDR::hThread); + WaitForSingleObject(hThread, INFINITE); + CloseHandle(hThread); + } + if (hDesktop) { + CloseDesktop(hDesktop); } - CustomBSDR::pendingApps.reset(); + pendingApps.reset(); - if (CustomBSDR::hStopEvent) { - CloseHandle(CustomBSDR::hStopEvent); + if (hStopEvent) { + CloseHandle(hStopEvent); } - _Resolved.Reset(); + g_Resolved.Reset(); - if (hResDll) { - FreeLibrary(hResDll); + if (g_hResDll) { + FreeLibrary(g_hResDll); + } + if (g_hLogonControllerDll) { + FreeLibrary(g_hLogonControllerDll); } - if (hBlockedShutdownDll) { - FreeLibrary(hBlockedShutdownDll); + if (g_hBlockedShutdownDll) { + FreeLibrary(g_hBlockedShutdownDll); } } BOOL Wh_ModSettingsChanged(BOOL* bReload) { - // Handle winlogon hook status change - // resDllPath: only used by LogonUI during shutdown sequence which is unlikely timing for a settings change - *bReload = TRUE; - return TRUE; + if (g_isWinlogon) { + // Handle winlogon hook status change + *bReload = TRUE; + return TRUE; + } else { + // resDllPath: only used by LogonUI during shutdown sequence which is unlikely timing for a settings change + // and reloading already loaded resources/dialog etc. is tedious so just ignore it + return FALSE; + } } From 76cf1321aeeecb08a8d5f138a5256e600c28bdc8 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Sat, 29 Aug 2026 19:29:30 +0900 Subject: [PATCH 20/51] Increase symbol download timeout --- mods/win7-bsdr.wh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 157af39139..7e5b4905b9 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -3648,7 +3648,7 @@ bool IsLogonUiInjectionEnabled() { // LogonUI normally auto exits when ran with invalid argument, even without this mod's injected code // 20 seconds: Wait for symbol download (it's not that big; better than infinite. probe path isn't blocking shutdown either) - DWORD result = WaitForSingleObject(pi.hProcess, 20000); + DWORD result = WaitForSingleObject(pi.hProcess, 40000); if (result != WAIT_OBJECT_0) { // Should exit immediately but just to be safe Wh_Log(L"LogonUI wait timed out or failed"); From 54774ca74da5c2a39102eaeadb60120c2490286a Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Sat, 29 Aug 2026 19:33:32 +0900 Subject: [PATCH 21/51] Deal with CI stuff --- mods/win7-bsdr.wh.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 7e5b4905b9..4f5213ced3 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -3419,7 +3419,7 @@ long __fastcall BlockedShutdownUXImpl_Stop_hook(void* thisPtr) { } // Windows.UI.BlockedShutdown.dll -WindhawkUtils::SYMBOL_HOOK blockedShutdownDllHooks[] = { +WindhawkUtils::SYMBOL_HOOK blockedShutdownHooks[] = { { { L"public: virtual long __cdecl BlockedShutdownUXImpl::Start(struct Windows::Internal::UI::Logon::Controller::IUserSettingManager *,struct Windows::Internal::UI::Logon::Controller::ILogonUIStateInfo *)", @@ -3730,7 +3730,7 @@ BOOL Wh_ModInit() { g_hBlockedShutdownDll = LoadLibraryExW(L"Windows.UI.BlockedShutdown.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); if (g_hBlockedShutdownDll) { - if (!WindhawkUtils::HookSymbols(g_hBlockedShutdownDll, blockedShutdownDllHooks, ARRAYSIZE(blockedShutdownDllHooks))) { + if (!WindhawkUtils::HookSymbols(g_hBlockedShutdownDll, blockedShutdownHooks, ARRAYSIZE(blockedShutdownHooks))) { Wh_Log(L"Failed to hook symbols in Windows.UI.BlockedShutdown.dll"); return FALSE; } From 5d1ed8e0fb318454e3b60a167a98b98946dff527 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Sun, 30 Aug 2026 00:24:38 +0900 Subject: [PATCH 22/51] Apply fixes, and fix multimon button transparency --- mods/win7-bsdr.wh.cpp | 127 ++++++++++++++++++++++++++---------------- 1 file changed, 78 insertions(+), 49 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 4f5213ced3..d0357dc049 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -10,7 +10,7 @@ // @include LogonUI.exe // @include winlogon.exe // @architecture x86-64 -// @compilerOptions -lgdi32 -lmincore -lwindowscodecs -luxtheme -lmsimg32 -lcomctl32 -lshcore +// @compilerOptions -lgdi32 -lmincore -lwindowscodecs -luxtheme -lmsimg32 -lcomctl32 -lshcore -lshlwapi // @license GPL-3.0-only // ==/WindhawkMod== @@ -68,16 +68,19 @@ settings. If you do not do this, it will silently fail to inject. #include #include #include +#include #include #include #include #include +#include #include #include #include #include #include #include +#include #include #include @@ -97,7 +100,7 @@ EXTERN_C IMAGE_DOS_HEADER __ImageBase; HINSTANCE g_hLogonControllerDll; HINSTANCE g_hBlockedShutdownDll; -HANDLE g_doModalExitEvent = nullptr; +std::atomic g_hDoModalExitEvent = nullptr; #pragma region resources // Resource IDs @@ -1792,6 +1795,7 @@ namespace CustomBSDR { static int bgWidth = 0; static int bgHeight = 0; static int scrollPos = 0; + static unsigned int scrollLines = 3; static int totalContentHeight = 0; static bool isOnSecureDesktop = true; @@ -1926,6 +1930,7 @@ void CustomBSDR::CenterWindow(HWND hWnd) { return; } + // Center on primary monitor RECT rcWindow, rcWorkArea; GetWindowRect(hWnd, &rcWindow); SystemParametersInfoW(SPI_GETWORKAREA, 0, &rcWorkArea, 0); @@ -2122,8 +2127,6 @@ void CustomBSDR::DrawSeparator(LPDRAWITEMSTRUCT pDIS) { if (bgBitmap) { POINT pt = { pDIS->rcItem.left, pDIS->rcItem.top }; MapWindowPoints(pDIS->hwndItem, hBgWnd, &pt, 1); - pt.x -= bgOffsetX; - pt.y -= bgOffsetY; HDC hdcBg = CreateCompatibleDC(pDIS->hDC); HBITMAP hOldBg = (HBITMAP)SelectObject(hdcBg, bgBitmap); @@ -2244,8 +2247,6 @@ void CustomBSDR::DrawButton(HDC hdc, LPDRAWITEMSTRUCT pDIS) { if (bgBitmap) { POINT pt = { rcButton.left, rcButton.top }; MapWindowPoints(pDIS->hwndItem, hBgWnd, &pt, 1); - pt.x -= bgOffsetX; - pt.y -= bgOffsetY; HDC hdcBg = CreateCompatibleDC(hdc); HBITMAP hOldBg = (HBITMAP)SelectObject(hdcBg, bgBitmap); @@ -2665,6 +2666,10 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA SetWindowSubclass(hAppListScroll, AppListSubclassProc, 0, 0); // Calculate the height of the app list container then resize it, also moving the controls below it + // Note: Win10+ LogonUI is per-monitor scaled by manifest, and we're only showing the dialog on the primary monitor, + // so handling per-monitor DPI is not much trouble. Screenshotting works fine + // Runtime DPI change might be problematic but the BSDR window isn't movable and the Settings app isn't accessible + // during logoff phase either so it's low priority int dpi = GetDpiForWindow(hWndDlg); int itemHeight = MulDiv(83, dpi, 96); @@ -2735,6 +2740,8 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA SetWindowPos(hWndDlg, nullptr, 0, 0, dialogWidth, newDialogHeight, SWP_NOMOVE | SWP_NOZORDER); } + SystemParametersInfoW(SPI_GETWHEELSCROLLLINES, 0, &scrollLines, 0); + SetWindowSubclass(hForceButton, ButtonSubclassProc, 0, 0); LRESULT forceButtonUIState = SendMessageW(hForceButton, WM_QUERYUISTATE, 0, 0); SetPropW(hForceButton, L"CustomBSDR_HideFocus", (HANDLE)(forceButtonUIState & UISF_HIDEFOCUS)); @@ -2828,10 +2835,11 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA if (bgBitmap) { RECT rcDlg; GetClientRect(hWndDlg, &rcDlg); + SIZE szDlg = { rcDlg.right, rcDlg.bottom }; MapWindowPoints(hWndDlg, hBgWnd, (LPPOINT)&rcDlg, 2); HDC memDC = CreateCompatibleDC(hdc); HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, bgBitmap); - BitBlt(hdc, 0, 0, rcDlg.right, rcDlg.bottom, memDC, rcDlg.left, rcDlg.top, SRCCOPY); + BitBlt(hdc, 0, 0, szDlg.cx, szDlg.cy, memDC, rcDlg.left, rcDlg.top, SRCCOPY); SelectObject(memDC, oldBitmap); DeleteDC(memDC); } @@ -2871,8 +2879,9 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA // and makes the thread stuck in WaitForSingleObject // So force signal the event as a not so clean workaround (still better than previous ExitProcess workaround) // (I still haven't found neither the culprint nor which code signals that event) - if (g_doModalExitEvent) { - SetEvent(g_doModalExitEvent); + HANDLE hDoModalExitEvent = g_hDoModalExitEvent.load(); + if (hDoModalExitEvent) { + SetEvent(hDoModalExitEvent); } else { // Event capture failed, oh noes! // Here comes the old terrible workaround @@ -2881,7 +2890,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA ExitProcess(0); } } - SendMessageW(hBgWnd, WM_CLOSE, 0, 0); + PostMessageW(hBgWnd, WM_CLOSE, 0, 0); return TRUE; case IDC_BSDR_FORCE_BTN: ShowWindow(hWarningText, SW_SHOW); @@ -2893,7 +2902,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA return TRUE; case IDYES: Resolve(BlockedShutdownResolution_Force); - SendMessageW(hBgWnd, WM_CLOSE, 0, 0); + PostMessageW(hBgWnd, WM_CLOSE, 0, 0); return TRUE; case IDNO: ShowWindow(hWarningText, SW_HIDE); @@ -2931,7 +2940,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA int delta = GET_WHEEL_DELTA_WPARAM(wParam); int oldPos = scrollPos; - scrollPos -= delta / 4; + scrollPos -= MulDiv(delta, scrollLines, WHEEL_DELTA); if (scrollPos < 0) scrollPos = 0; int maxScroll = totalContentHeight - visibleHeight; @@ -2985,6 +2994,12 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA } return TRUE; } + case WM_SETTINGCHANGE: { + if (wParam == SPI_SETWHEELSCROLLLINES) { + SystemParametersInfoW(SPI_GETWHEELSCROLLLINES, 0, &scrollLines, 0); + } + return TRUE; + } } return FALSE; } @@ -3059,6 +3074,7 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA return 0; } case WM_DISPLAYCHANGE: { + // Keep bgOffsetX/Y/Width/Height; they are only used in the initial screenshot path and WM_PAINT that redraws the same screenshot int x = GetSystemMetrics(SM_XVIRTUALSCREEN); int y = GetSystemMetrics(SM_YVIRTUALSCREEN); int cx = GetSystemMetrics(SM_CXVIRTUALSCREEN); @@ -3522,13 +3538,13 @@ CreateEventW_t CreateEventW_orig; HANDLE WINAPI CreateEventW_hook(LPSECURITY_ATTRIBUTES lpEventAttributes, WINBOOL bManualReset, WINBOOL bInitialState, LPCWSTR lpName) { HANDLE result = CreateEventW_orig(lpEventAttributes, bManualReset, bInitialState, lpName); if (lpEventAttributes == 0 && bManualReset == 0 && bInitialState == 0 && lpName == 0 && g_enteringDoModal) { - if (g_doModalExitEvent) { - Wh_Log(L"Warning: Overwrote DoModal event"); + if (g_hDoModalExitEvent.load()) { + Wh_Log(L"Warning: DoModal event already captured; ignoring the subsequent one..."); } else { // Works cleanly on LTSC 2021 and 11 25H2 + g_hDoModalExitEvent.store(result); Wh_Log(L"Caught DoModal event"); } - g_doModalExitEvent = result; } return result; } @@ -3547,7 +3563,7 @@ bool IsAuthUxInstalled() { dllPath, &size ) == ERROR_SUCCESS) { - if (wcsstr(_wcsupr(dllPath), L"\\WINDOWS.UI.BLOCKEDSHUTDOWN.DLL") == NULL) { // non stock dll + if (_wcsicmp(PathFindFileNameW(dllPath), L"Windows.UI.BlockedShutdown.dll") != 0) { // non stock dll return true; } } @@ -3647,8 +3663,7 @@ bool IsLogonUiInjectionEnabled() { } // LogonUI normally auto exits when ran with invalid argument, even without this mod's injected code - // 20 seconds: Wait for symbol download (it's not that big; better than infinite. probe path isn't blocking shutdown either) - DWORD result = WaitForSingleObject(pi.hProcess, 40000); + DWORD result = WaitForSingleObject(pi.hProcess, 2000); if (result != WAIT_OBJECT_0) { // Should exit immediately but just to be safe Wh_Log(L"LogonUI wait timed out or failed"); @@ -3685,9 +3700,9 @@ WindhawkUtils::SYMBOL_HOOK winlogonExeHooks[] = { BOOL Wh_ModInit() { Wh_Log(L"Init"); - wchar_t exeName[MAX_PATH]; - GetModuleFileNameW(NULL, exeName, MAX_PATH); - g_isWinlogon = wcsstr(_wcsupr(exeName), L"\\WINLOGON.EXE") != NULL; + wchar_t exePath[MAX_PATH]; + GetModuleFileNameW(NULL, exePath, MAX_PATH); + g_isWinlogon = _wcsicmp(PathFindFileNameW(exePath), L"winlogon.exe") == 0; if (g_isWinlogon) { if (!Wh_GetIntSetting(L"disableAsyncLogoff")) { Wh_Log(L"Running in winlogon.exe, but winlogon hooks are disabled"); @@ -3703,7 +3718,7 @@ BOOL Wh_ModInit() { return TRUE; } - if (wcsstr(exeName, L"\\LOGONUI.EXE") == NULL) { + if (_wcsicmp(PathFindFileNameW(exePath), L"LogonUI.exe") != 0) { // User decided to mess with the mod advanced settings Wh_Log(L"Loaded in unexpected process!"); return FALSE; @@ -3715,30 +3730,6 @@ BOOL Wh_ModInit() { return FALSE; } - if (!Wh_SetFunctionHook((void*)CreateEventW, (void*)CreateEventW_hook, (void**)&CreateEventW_orig)) { - Wh_Log(L"CreateEventW hook failed"); - // not critical, well, there still is a terrrible workaround path - } - - g_hLogonControllerDll = LoadLibraryExW(L"LogonController.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); - if (g_hLogonControllerDll) { - if (!WindhawkUtils::HookSymbols(g_hLogonControllerDll, logonControllerDllHooks, ARRAYSIZE(logonControllerDllHooks))) { - Wh_Log(L"Failed to hook symbols in LogonController.dll"); - // Ditto - } - } - - g_hBlockedShutdownDll = LoadLibraryExW(L"Windows.UI.BlockedShutdown.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); - if (g_hBlockedShutdownDll) { - if (!WindhawkUtils::HookSymbols(g_hBlockedShutdownDll, blockedShutdownHooks, ARRAYSIZE(blockedShutdownHooks))) { - Wh_Log(L"Failed to hook symbols in Windows.UI.BlockedShutdown.dll"); - return FALSE; - } - } else { - Wh_Log(L"Failed to load Windows.UI.BlockedShutdown.dll"); - return FALSE; - } - int checkValue = -1; int argc; LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); @@ -3754,6 +3745,7 @@ BOOL Wh_ModInit() { if (checkValue != -1) { Wh_Log(L"Writing load check value %d+%d=%d...", checkValue, pid, checkValue + pid); Wh_SetIntValue(L"LogonUiLoadCheck", checkValue + pid); + LocalFree(argv); return FALSE; // Let LogonUI automatically exit immediately (because it lacks proper args) } break; @@ -3762,9 +3754,44 @@ BOOL Wh_ModInit() { LocalFree(argv); + HMODULE kernelBase = GetModuleHandleW(L"kernelbase.dll"); + CreateEventW_t pCreateEventW = (CreateEventW_t)GetProcAddress(kernelBase, "CreateEventW"); + if (!WindhawkUtils::SetFunctionHook(pCreateEventW, CreateEventW_hook, &CreateEventW_orig)) { + Wh_Log(L"CreateEventW hook failed"); + // not critical, well, there still is a terrrible workaround path + } + + g_hLogonControllerDll = LoadLibraryExW(L"LogonController.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (g_hLogonControllerDll) { + if (!WindhawkUtils::HookSymbols(g_hLogonControllerDll, logonControllerDllHooks, ARRAYSIZE(logonControllerDllHooks))) { + Wh_Log(L"Failed to hook symbols in LogonController.dll"); + // Ditto + } + } + + g_hBlockedShutdownDll = LoadLibraryExW(L"Windows.UI.BlockedShutdown.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (g_hBlockedShutdownDll) { + if (!WindhawkUtils::HookSymbols(g_hBlockedShutdownDll, blockedShutdownHooks, ARRAYSIZE(blockedShutdownHooks))) { + Wh_Log(L"Failed to hook symbols in Windows.UI.BlockedShutdown.dll"); + if (g_hLogonControllerDll) { + FreeLibrary(g_hLogonControllerDll); + } + return FALSE; + } + } else { + Wh_Log(L"Failed to load Windows.UI.BlockedShutdown.dll"); + return FALSE; + } + CustomBSDR::hStopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); if (!CustomBSDR::hStopEvent) { Wh_Log(L"CreateEventW failed, GLE=%u", GetLastError()); + if (g_hLogonControllerDll) { + FreeLibrary(g_hLogonControllerDll); + } + if (g_hBlockedShutdownDll) { + FreeLibrary(g_hBlockedShutdownDll); + } return FALSE; } @@ -3785,8 +3812,10 @@ BOOL Wh_ModInit() { void Wh_ModAfterInit() { if (g_isWinlogon && p_g_fShutdownResolverDisabled) { - if (IsLogonUiInjectionEnabled()) { - g_origResolverDisabledState = *p_g_fShutdownResolverDisabled; + g_origResolverDisabledState = *p_g_fShutdownResolverDisabled; + if (g_origResolverDisabledState) { // Maybe the allowblockingappsatshutdown registry is set + Wh_Log(L"g_fShutdownResolverDisabled is already set to 1"); + } else if (IsLogonUiInjectionEnabled()) { *p_g_fShutdownResolverDisabled = 1; // Disable the async logoff resolver Wh_Log(L"Set g_fShutdownResolverDisabled to 1"); } else { @@ -3853,6 +3882,6 @@ BOOL Wh_ModSettingsChanged(BOOL* bReload) { } else { // resDllPath: only used by LogonUI during shutdown sequence which is unlikely timing for a settings change // and reloading already loaded resources/dialog etc. is tedious so just ignore it - return FALSE; + return TRUE; } } From 36e85309401754e945e43d97e1398bb49200f5cb Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Sun, 30 Aug 2026 21:43:09 +0900 Subject: [PATCH 23/51] Apply fixes, and make the dialog size increase on new app --- mods/win7-bsdr.wh.cpp | 187 ++++++++++++++++++++++++++++++++---------- 1 file changed, 145 insertions(+), 42 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index d0357dc049..5886e86868 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -49,18 +49,22 @@ settings. If you do not do this, it will silently fail to inject. $name: AuthUX.dll/winsrv.dll path $name:ko-KR: AuthUX.dll/winsrv.dll 경로 $description: Path to your copy of winsrv.dll from Windows 7 or AuthUX.dll build with BSDR. Hardcoded resources will be used instead if this is not set or missing. + $description:ko-KR: Windows 7의 winsrv.dll 혹은 BSDR이 포함된 AuthUX.dll 빌드의 경로를 입력하세요. 비워두거나 파일을 찾을 수 없으면 하드코딩된 리소스가 대신 사용됩니다. - themeScrollbar: false $name: Apply visual styles to the scroll bar $name:ko-KR: 스크롤 막대에 시각 테마 적용 $description: The original Windows 7 blocked shutdown resolver had an unthemed scroll bar. Enable this if you find it ugly. -- disableAsyncLogoff: true + $description:ko-KR: Windows 7의 원본 BSDR은 스크롤 막대에 테마를 적용하지 않았습니다. 그것이 마음에 들지 않는 경우 이 옵션을 활성화하십시오. +- disableAsyncLogoff: false $name: Restore Windows 7's logoff sequence $name:ko-KR: Windows 7의 로그오프 절차 복원 - $description: Switch to the 'logging off' screen after all applications have been closed. + $description: Switch to the 'logging off' screen after all applications have been closed. If your user account has no password set, you might be immediately logged back on automatically after logging off. + $description:ko-KR: 모든 응용 프로그램이 닫힌 이후 '로그오프 중' 화면으로 전환합니다. 사용자 계정에 암호가 설정되지 않은 경우, 로그오프 직후 자동으로 다시 로그온 될 수 있습니다. - noSafetyChecks: false $name: (Advanced) Skip safety checks before restoring the classic logoff sequence - $name:ko-KR: 고전 로그오프 절차를 복원하기 전 안전 검사 생략 - $description: This mod verifies that LogonUI injection was successful on every logoff before enabling the old sequence, to prevent the modern BSDR from showing on the invisible secure desktop, thus making the logoff sequence stuck. Before enabling this option, remember to press Ctrl+Alt+Del if logoff gets stuck. + $name:ko-KR: (고급) 고전 로그오프 절차를 복원하기 전 안전 검사 생략 + $description: This mod verifies that LogonUI injection was successful before enabling the old sequence, to prevent the modern BSDR from showing on the invisible secure desktop, thus making the logoff sequence stuck. Before enabling this option, remember to press Ctrl+Alt+Del if logoff gets stuck. + $description:ko-KR: 이 모드는 신형 BSDR이 보이지 않는 보안 데스크톱에 표시되어 로그오프 절차가 중단되지 않도록 고전 로그오프 절차를 복원하기 전에 LogonUI 인젝션이 성공적인지 검사힙니다. 이 옵션을 활성화하기 전, 로그오프 절차가 멈출 경우 Ctrl+Alt+Del을 누르는 것을 기억하십시오. */ // ==/WindhawkModSettings== @@ -71,6 +75,7 @@ settings. If you do not do this, it will silently fail to inject. #include #include #include +#include #include #include #include @@ -1771,7 +1776,7 @@ namespace CustomBSDR { HBITMAP LoadAlphaBitmap(UINT resourceId, bool forceHardcoded = false); void DrawSeparator(LPDRAWITEMSTRUCT pDIS); void DrawButton(HDC hdc, LPDRAWITEMSTRUCT pDIS); - void CreateAppTileControls(IShutdownBlockingApp* blockingApp); + void CreateAppTileControls(IShutdownBlockingApp* blockingApp, bool noUpdateLayout = false); void RemoveAppTileControls(UINT appId); void UpdateAppListLayout(); LRESULT CALLBACK ButtonSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData); @@ -1798,7 +1803,7 @@ namespace CustomBSDR { static unsigned int scrollLines = 3; static int totalContentHeight = 0; static bool isOnSecureDesktop = true; - + // app list data stuff struct AppTile { UINT appId; @@ -2394,7 +2399,7 @@ LRESULT CALLBACK CustomBSDR::AppListSubclassProc(HWND hWnd, UINT uMsg, WPARAM wP return DefSubclassProc(hWnd, uMsg, wParam, lParam); } -void CustomBSDR::CreateAppTileControls(IShutdownBlockingApp* blockingApp) { +void CustomBSDR::CreateAppTileControls(IShutdownBlockingApp* blockingApp, bool noUpdateLayout) { if (!hDlg || !blockingApp) { return; } @@ -2444,11 +2449,11 @@ void CustomBSDR::CreateAppTileControls(IShutdownBlockingApp* blockingApp) { int iconSize = MulDiv(32, dpi, 96); - tile.hIcon = CreateWindowExW(0, L"Static", nullptr, WS_CHILD | WS_VISIBLE | SS_BITMAP, 0, 0, iconSize, iconSize, hAppListScroll, nullptr, nullptr, nullptr); - tile.hTitle = CreateWindowExW(0, L"Static", titleText.c_str(), WS_CHILD | WS_VISIBLE | SS_ENDELLIPSIS, 0, 0, 100, 20, hAppListScroll, nullptr, nullptr, nullptr); + tile.hIcon = CreateWindowExW(0, L"Static", nullptr, WS_CHILD | WS_VISIBLE | SS_BITMAP, -iconSize, -iconSize, iconSize, iconSize, hAppListScroll, nullptr, nullptr, nullptr); + tile.hTitle = CreateWindowExW(0, L"Static", titleText.c_str(), WS_CHILD | WS_VISIBLE | SS_ENDELLIPSIS, -100, -20, 100, 20, hAppListScroll, nullptr, nullptr, nullptr); if (!blockReasonText.empty()) { - tile.hBlockReason = CreateWindowExW(0, L"Static", blockReasonText.c_str(), WS_CHILD | WS_VISIBLE | SS_EDITCONTROL, 0, 0, 100, 40, hAppListScroll, nullptr, nullptr, nullptr); + tile.hBlockReason = CreateWindowExW(0, L"Static", blockReasonText.c_str(), WS_CHILD | WS_VISIBLE | SS_EDITCONTROL, -100, -40, 100, 40, hAppListScroll, nullptr, nullptr, nullptr); } else { tile.hBlockReason = nullptr; } @@ -2490,7 +2495,9 @@ void CustomBSDR::CreateAppTileControls(IShutdownBlockingApp* blockingApp) { appTiles.push_back(tile); } - UpdateAppListLayout(); + if (!noUpdateLayout) { + UpdateAppListLayout(); + } } void CustomBSDR::RemoveAppTileControls(UINT appId) { @@ -2547,6 +2554,56 @@ void CustomBSDR::UpdateAppListLayout() { totalContentHeight += (tile.hBlockReason != nullptr) ? itemHeight : itemHeightNoReason; } + // Increase the dialog size + // Windows 7 decided the dialog height only on the initial load (and when the screen resolution was decreased), and never resized on late item add/remove + // On Windows 10+, the BSDR backend sends the initial list of apps with random delays (whether pendingApps or WM_ADD_APP is used), + // and never sends newly created windows after initialization. So consider all additions as initial item and always increase the dialog height + RECT rcScrollBar; + GetWindowRect(hScrollBar, &rcScrollBar); + int scrollBarWidth = rcScrollBar.right - rcScrollBar.left; + + int screenHeight = GetSystemMetrics(SM_CYSCREEN); + int minHeight = visibleHeight; + int maxHeight = screenHeight - MulDiv(335, dpi, 96); + + if (maxHeight < minHeight) + maxHeight = minHeight; + + int newHeight = totalContentHeight; + if (newHeight < minHeight) + newHeight = minHeight; + if (newHeight > maxHeight) + newHeight = maxHeight; + int heightDiff = newHeight - minHeight; + + if (heightDiff) { + visibleHeight = newHeight; + + SetWindowPos(hAppList, nullptr, 0, 0, currentWidth, newHeight, SWP_NOMOVE | SWP_NOZORDER); + SetWindowPos(hScrollBar, nullptr, 0, 0, scrollBarWidth, newHeight, SWP_NOMOVE | SWP_NOZORDER); + + for (HWND hwndSibling = GetWindow(hScrollBar, GW_HWNDNEXT); hwndSibling; hwndSibling = GetWindow(hwndSibling, GW_HWNDNEXT)) { + RECT rcSibling; + GetWindowRect(hwndSibling, &rcSibling); + MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rcSibling, 2); + OffsetRect(&rcSibling, 0, heightDiff); + SetWindowPos(hwndSibling, nullptr, rcSibling.left, rcSibling.top, 0, 0, SWP_NOSIZE | SWP_NOZORDER); + } + + RECT rcTitle, rcNo; + GetWindowRect(hTitleText, &rcTitle); + MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rcTitle, 2); + GetWindowRect(hNoButton, &rcNo); + MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rcNo, 2); + int topPadding = rcTitle.top; + int dialogWidth = rcTitle.right; + int newDialogHeight = rcNo.bottom + topPadding - 1; + SetWindowPos(hDlg, nullptr, 0, 0, dialogWidth, newDialogHeight, SWP_NOMOVE | SWP_NOZORDER); + + CenterWindow(hDlg); + } + + // Calculate scroll info int maxScroll = totalContentHeight - visibleHeight; if (maxScroll < 0) maxScroll = 0; @@ -2570,6 +2627,7 @@ void CustomBSDR::UpdateAppListLayout() { scrollPos = 0; } + // Relocate child controls int yPos = topMargin; for (auto& tile : appTiles) { @@ -2727,16 +2785,14 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA SetWindowPos(hwndSibling, nullptr, rcSibling.left, rcSibling.top, 0, 0, SWP_NOSIZE | SWP_NOZORDER); } - RECT rcDialog, rcTitle, rcNo; - GetWindowRect(hWndDlg, &rcDialog); - OffsetRect(&rcDialog, -GetSystemMetrics(SM_XVIRTUALSCREEN), -GetSystemMetrics(SM_YVIRTUALSCREEN)); + RECT rcTitle, rcNo; GetWindowRect(hTitleText, &rcTitle); MapWindowPoints(HWND_DESKTOP, hWndDlg, (LPPOINT)&rcTitle, 2); GetWindowRect(hNoButton, &rcNo); MapWindowPoints(HWND_DESKTOP, hWndDlg, (LPPOINT)&rcNo, 2); - int topPadding = rcTitle.top - rcDialog.top; - int dialogWidth = rcTitle.right - rcDialog.left; - int newDialogHeight = rcNo.bottom - rcDialog.top + topPadding - 1; + int topPadding = rcTitle.top; + int dialogWidth = rcTitle.right; + int newDialogHeight = rcNo.bottom + topPadding - 1; SetWindowPos(hWndDlg, nullptr, 0, 0, dialogWidth, newDialogHeight, SWP_NOMOVE | SWP_NOZORDER); } @@ -2785,8 +2841,9 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA } for (auto& app : pendingAppsLocal) { - CreateAppTileControls(app.Get()); + CreateAppTileControls(app.Get(), true); } + UpdateAppListLayout(); // Load and set the appropriate strings based on the current LogonUI state wchar_t desc[256] = {}, warning[256] = {}, btnText[256] = {}; @@ -2844,7 +2901,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA DeleteDC(memDC); } EndPaint(hWndDlg, &ps); - return 0; + return TRUE; } case WM_ERASEBKGND: if (!IsHighContrast()) { @@ -2853,6 +2910,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA GetClientRect(hWndDlg, &rcDlg); HBRUSH hBrush = (HBRUSH)GetStockObject(BLACK_BRUSH); FillRect(hdc, &rcDlg, hBrush); + SetWindowLongPtrW(hWndDlg, DWLP_MSGRESULT, TRUE); return TRUE; } break; @@ -2860,9 +2918,11 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA LPDRAWITEMSTRUCT pDIS = (LPDRAWITEMSTRUCT)lParam; if (pDIS->CtlID == IDC_BSDR_SEPARATOR_TOP || pDIS->CtlID == IDC_BSDR_SEPARATOR_BOTTOM) { DrawSeparator(pDIS); + SetWindowLongPtrW(hWndDlg, DWLP_MSGRESULT, TRUE); return TRUE; } else if (pDIS->CtlType == ODT_BUTTON) { DrawButton(pDIS->hDC, pDIS); + SetWindowLongPtrW(hWndDlg, DWLP_MSGRESULT, TRUE); return TRUE; } break; @@ -2885,12 +2945,14 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA } else { // Event capture failed, oh noes! // Here comes the old terrible workaround - Wh_Log(L"Force exiting LogonUI!"); Sleep(1000); // Make sure the resolve request reaches winlogon + // Note: this does not cause any user-facing issues because there is no visible LogonUI window in the default desktop at this point + // and winlogon has already got out of the mid-logoff state. Subsequent LogonUI launches (lock/C-A-D) are fine too. + Wh_Log(L"Force exiting LogonUI!"); ExitProcess(0); } } - PostMessageW(hBgWnd, WM_CLOSE, 0, 0); + PostMessageW(hBgWnd, WM_CLOSE, 0, 1337); return TRUE; case IDC_BSDR_FORCE_BTN: ShowWindow(hWarningText, SW_SHOW); @@ -2902,7 +2964,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA return TRUE; case IDYES: Resolve(BlockedShutdownResolution_Force); - PostMessageW(hBgWnd, WM_CLOSE, 0, 0); + PostMessageW(hBgWnd, WM_CLOSE, 0, 1377); return TRUE; case IDNO: ShowWindow(hWarningText, SW_HIDE); @@ -2940,7 +3002,8 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA int delta = GET_WHEEL_DELTA_WPARAM(wParam); int oldPos = scrollPos; - scrollPos -= MulDiv(delta, scrollLines, WHEEL_DELTA); + int lineHeight = MulDiv(20, dpi, 96); + scrollPos -= MulDiv(delta * lineHeight, scrollLines, WHEEL_DELTA); if (scrollPos < 0) scrollPos = 0; int maxScroll = totalContentHeight - visibleHeight; @@ -3031,6 +3094,7 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA if (!IsHighContrast()) { // Screenshot desktop and dim it + // No DPI virtualization involved because LogonUI is per-monitor scaled by manifest HDC hDC = GetDC(nullptr); HDC hMemDC = CreateCompatibleDC(hDC); bgBitmap = CreateCompatibleBitmap(hDC, bgWidth, bgHeight); @@ -3046,7 +3110,7 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA ReleaseDC(nullptr, hDC); separatorBitmap = LoadAlphaBitmap(IDB_BSDR_SEPARATOR); - btnNormalBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_NORMAL); + btnNormalBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_NORMAL); btnHoverBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_HOVER); btnPressedBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_PRESSED); btnSelectedBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_SELECTED); @@ -3088,6 +3152,11 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA return 0; } case WM_CLOSE: { + if (lParam != 1337) { + // Deny Alt+F4 or other external window closes (like Windows 7) + return 0; + } + for (auto& tile : appTiles) { if (tile.hIconBitmap) DeleteObject(tile.hIconBitmap); } @@ -3189,7 +3258,17 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { bgWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN); bgHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN); - hBgWnd = CreateWindowExW(WS_EX_TOPMOST, wndClass.lpszClassName, NULL, WS_POPUP | WS_VISIBLE, bgOffsetX, bgOffsetY, bgWidth, bgHeight, NULL, NULL, NULL, NULL); + hBgWnd = CreateWindowExW( + WS_EX_TOPMOST, + BSDR_CLASSNAME, + nullptr, + WS_POPUP | WS_VISIBLE, + bgOffsetX, bgOffsetY, bgWidth, bgHeight, + nullptr, + nullptr, + HINST_THISCOMPONENT, + nullptr + ); if (hBgWnd) { HANDLE waitHandles[1] = { hStopEvent }; @@ -3205,7 +3284,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { if (result == WAIT_OBJECT_0) { // hStopEvent if (hBgWnd && IsWindow(hBgWnd)) { - SendMessageW(hBgWnd, WM_CLOSE, 0, 0); + SendMessageW(hBgWnd, WM_CLOSE, 0, 1337); } else { break; } @@ -3223,7 +3302,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { SetPropW(hNoButton, L"CustomBSDR_HideAccel", (HANDLE)FALSE); RedrawWindow(hDlg, nullptr, nullptr, RDW_FRAME | RDW_INVALIDATE); } - if (hDlg && !IsDialogMessageW(hDlg, &msg)) { + if (!hDlg || !IsDialogMessageW(hDlg, &msg)) { TranslateMessage(&msg); DispatchMessageW(&msg); } @@ -3234,8 +3313,18 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { } else { ret = GetLastError(); Wh_Log(L"MsgWaitForMultipleObjects returned %d, GLE=%d", result, ret); + + bool needsResolve; + { + std::lock_guard lock(g_resolvedMutex); + needsResolve = g_resolvedValue == BlockedShutdownResolution_None; + } + if (needsResolve) { + Resolve(BlockedShutdownResolution_Cancel); + } + if (hBgWnd && IsWindow(hBgWnd)) { - SendMessageW(hBgWnd, WM_CLOSE, 0, 0); + SendMessageW(hBgWnd, WM_CLOSE, 0, 1337); } break; } @@ -3519,6 +3608,7 @@ long __cdecl CLogonController__DoModal_hook(void* pThis, unsigned long a2, unsig Wh_Log(L"DoModal"); int result = CLogonController__DoModal_orig(pThis, a2, a3, a4, a5); g_enteringDoModal = false; + g_hDoModalExitEvent.store(nullptr); return result; } @@ -3551,6 +3641,11 @@ HANDLE WINAPI CreateEventW_hook(LPSECURITY_ATTRIBUTES lpEventAttributes, WINBOOL #pragma endregion LogonUI BSDR Hooks (Interface) #pragma region Winlogon hooks (disable async logoff) +bool g_isWinlogon = false; +bool g_noSafetyChecks = false; +int* p_g_fShutdownResolverDisabled = nullptr; +int g_origResolverDisabledState = 0; + bool IsAuthUxInstalled() { wchar_t dllPath[MAX_PATH]; DWORD size = sizeof(dllPath); @@ -3583,7 +3678,7 @@ bool IsAuthUxInstalled() { // This approach, derived from the WH tool mod code, also doesn't mess with internal Windhawk configuration bool IsLogonUiInjectionEnabled() { // Skip by user choice - if (Wh_GetIntSetting(L"noSafetyChecks")) { + if (g_noSafetyChecks) { return true; } @@ -3662,12 +3757,12 @@ bool IsLogonUiInjectionEnabled() { return false; } - // LogonUI normally auto exits when ran with invalid argument, even without this mod's injected code + // LogonUI normally auto exits when ran with invalid argument, even without this mod's injected code, before initializing the logon controller DWORD result = WaitForSingleObject(pi.hProcess, 2000); if (result != WAIT_OBJECT_0) { // Should exit immediately but just to be safe Wh_Log(L"LogonUI wait timed out or failed"); - TerminateProcess(pi.hProcess, 0); + // Let it exit automatically } CloseHandle(pi.hThread); @@ -3679,10 +3774,6 @@ bool IsLogonUiInjectionEnabled() { return false; } -bool g_isWinlogon = false; -int* p_g_fShutdownResolverDisabled = nullptr; -int g_origResolverDisabledState = 0; - WindhawkUtils::SYMBOL_HOOK winlogonExeHooks[] = { { { @@ -3715,6 +3806,7 @@ BOOL Wh_ModInit() { return FALSE; } } + g_noSafetyChecks = Wh_GetIntSetting(L"noSafetyChecks"); return TRUE; } @@ -3876,12 +3968,23 @@ void Wh_ModUninit() { BOOL Wh_ModSettingsChanged(BOOL* bReload) { if (g_isWinlogon) { - // Handle winlogon hook status change - *bReload = TRUE; - return TRUE; - } else { - // resDllPath: only used by LogonUI during shutdown sequence which is unlikely timing for a settings change - // and reloading already loaded resources/dialog etc. is tedious so just ignore it - return TRUE; + if (!Wh_GetIntSetting(L"disableAsyncLogoff")) { + // The global injection is enabled or disabled, need to unload the mod + // (Reloading unloaded mod on mod setting change is automatically handled by Windhawk) + return FALSE; + } else { + bool newNoSafetyChecks = Wh_GetIntSetting(L"noSafetyChecks"); + if (g_noSafetyChecks != newNoSafetyChecks) { + // Only update the winlogon variable instead of reloading the whole mod + if (p_g_fShutdownResolverDisabled) { + *p_g_fShutdownResolverDisabled = g_origResolverDisabledState; + } + g_noSafetyChecks = newNoSafetyChecks; + Wh_ModAfterInit(); + } + } } + // resDllPath: only used by LogonUI during shutdown sequence which is unlikely timing for a settings change + // and reloading already loaded resources/dialog etc. is tedious so just ignore it + return TRUE; } From b9619c006a75999274eb5b4340c700759a862143 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Sun, 30 Aug 2026 22:14:42 +0900 Subject: [PATCH 24/51] Allow decreasing dialog size on resolution change --- mods/win7-bsdr.wh.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 5886e86868..a60b335658 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -2566,13 +2566,10 @@ void CustomBSDR::UpdateAppListLayout() { int minHeight = visibleHeight; int maxHeight = screenHeight - MulDiv(335, dpi, 96); - if (maxHeight < minHeight) - maxHeight = minHeight; - int newHeight = totalContentHeight; if (newHeight < minHeight) newHeight = minHeight; - if (newHeight > maxHeight) + if (newHeight > maxHeight) // Allow decreasing on resolution decrease newHeight = maxHeight; int heightDiff = newHeight - minHeight; @@ -3757,7 +3754,7 @@ bool IsLogonUiInjectionEnabled() { return false; } - // LogonUI normally auto exits when ran with invalid argument, even without this mod's injected code, before initializing the logon controller + // LogonUI normally auto exits when ran with invalid argument, even without this mod's injected code, without initializing the logon controller DWORD result = WaitForSingleObject(pi.hProcess, 2000); if (result != WAIT_OBJECT_0) { // Should exit immediately but just to be safe @@ -3767,7 +3764,7 @@ bool IsLogonUiInjectionEnabled() { CloseHandle(pi.hThread); CloseHandle(pi.hProcess); - + if ((DWORD)Wh_GetIntValue(L"LogonUiLoadCheck", -1) == myPid + pi.dwProcessId) { return true; } From 3ab405667c94f1514566ae8bbab41c8b94c45b16 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Sun, 30 Aug 2026 22:39:10 +0900 Subject: [PATCH 25/51] Deduplicate app tiles on CreateAppTileControls --- mods/win7-bsdr.wh.cpp | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index a60b335658..997aa0e0d1 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -1777,7 +1777,7 @@ namespace CustomBSDR { void DrawSeparator(LPDRAWITEMSTRUCT pDIS); void DrawButton(HDC hdc, LPDRAWITEMSTRUCT pDIS); void CreateAppTileControls(IShutdownBlockingApp* blockingApp, bool noUpdateLayout = false); - void RemoveAppTileControls(UINT appId); + void RemoveAppTileControls(UINT appId, bool noUpdateLayout = false); void UpdateAppListLayout(); LRESULT CALLBACK ButtonSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData); LRESULT CALLBACK AppListSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData); @@ -1802,6 +1802,8 @@ namespace CustomBSDR { static int scrollPos = 0; static unsigned int scrollLines = 3; static int totalContentHeight = 0; + static int minHeight = 0; + static bool paintedFirstFrame = false; static bool isOnSecureDesktop = true; // app list data stuff @@ -2411,6 +2413,11 @@ void CustomBSDR::CreateAppTileControls(IShutdownBlockingApp* blockingApp, bool n blockingApp->get_IsBlocking(&tile.isBlocking); tile.hIconBitmap = nullptr; + // When the blocking app changes (previous blocking app closes and there's a new blocking app), + // the BSDR backend sends the FULL list of blocking apps as AddApplication again, without calling RemoveApplication + // so deduplicate it + RemoveAppTileControls(tile.appId, true); + HSTRING caption, blockReason; std::wstring titleText; std::wstring blockReasonText; @@ -2500,7 +2507,7 @@ void CustomBSDR::CreateAppTileControls(IShutdownBlockingApp* blockingApp, bool n } } -void CustomBSDR::RemoveAppTileControls(UINT appId) { +void CustomBSDR::RemoveAppTileControls(UINT appId, bool noUpdateLayout) { for (auto it = appTiles.begin(); it != appTiles.end(); ++it) { if (it->appId == appId) { if (it->hIcon) { @@ -2518,7 +2525,9 @@ void CustomBSDR::RemoveAppTileControls(UINT appId) { appTiles.erase(it); - UpdateAppListLayout(); + if (!noUpdateLayout) { + UpdateAppListLayout(); + } return; } } @@ -2563,15 +2572,18 @@ void CustomBSDR::UpdateAppListLayout() { int scrollBarWidth = rcScrollBar.right - rcScrollBar.left; int screenHeight = GetSystemMetrics(SM_CYSCREEN); - int minHeight = visibleHeight; int maxHeight = screenHeight - MulDiv(335, dpi, 96); int newHeight = totalContentHeight; - if (newHeight < minHeight) + if (paintedFirstFrame && newHeight < visibleHeight) + newHeight = visibleHeight; + // Allow decreasing before the first hBgWnd WM_PAINT + else if (!paintedFirstFrame && newHeight < minHeight) newHeight = minHeight; - if (newHeight > maxHeight) // Allow decreasing on resolution decrease + // Allow decreasing on resolution decrease + if (newHeight > maxHeight) newHeight = maxHeight; - int heightDiff = newHeight - minHeight; + int heightDiff = newHeight - visibleHeight; if (heightDiff) { visibleHeight = newHeight; @@ -2756,7 +2768,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA int scrollBarWidth = rcScrollBar.right - rcScrollBar.left; int screenHeight = GetSystemMetrics(SM_CYSCREEN); - int minHeight = rcAppList.bottom - rcAppList.top; + minHeight = rcAppList.bottom - rcAppList.top; int maxHeight = screenHeight - MulDiv(335, dpi, 96); if (maxHeight < minHeight) @@ -3126,6 +3138,7 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA DeleteDC(memDC); } EndPaint(hWnd, &ps); + paintedFirstFrame = true; return 0; } case WM_WINDOWPOSCHANGING: { From de616421feca1dff1ba0bf4b548aedb0e36f3db7 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Sun, 30 Aug 2026 23:26:37 +0900 Subject: [PATCH 26/51] Misc fixes --- mods/win7-bsdr.wh.cpp | 52 +++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 997aa0e0d1..ba8fd499b7 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -93,6 +93,7 @@ settings. If you do not do this, it will silently fail to inject. #define WM_REMOVE_APP (WM_APP + 2) #define BSDR_CLASSNAME L"BlockedShutdownResolver_WH" +#define BSDR_CLOSE 1337 #define RETURN_IF_FAILED(x) do { \ HRESULT hr = (x); \ @@ -2025,8 +2026,7 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { pHeader->biHeight == LONG_MIN || pHeader->biPlanes != 1 || pHeader->biBitCount != 32 || - (pHeader->biCompression != BI_RGB && - pHeader->biCompression != BI_BITFIELDS)) { + (pHeader->biCompression != BI_RGB && pHeader->biCompression != BI_BITFIELDS)) { Wh_Log(L"Image load error: invalid bitmap header"); return LoadAlphaBitmap(resourceId, true); } @@ -2034,9 +2034,7 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { uint32_t width = static_cast(pHeader->biWidth); uint32_t height = static_cast( - pHeader->biHeight < 0 - ? -static_cast(pHeader->biHeight) - : pHeader->biHeight + pHeader->biHeight < 0 ? -static_cast(pHeader->biHeight) : pHeader->biHeight ); uint64_t bitsOffset = pHeader->biSize; @@ -2046,8 +2044,7 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { bitsOffset += 3 * sizeof(DWORD); } - uint64_t requiredSize = - static_cast(width) * height * 4; + uint64_t requiredSize = static_cast(width) * height * 4; if (hResource && (bitsOffset > dwSize || @@ -2070,18 +2067,7 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { HBITMAP hBitmap = CreateDIBSection(nullptr, &bmi, DIB_RGB_COLORS, &pBits, nullptr, 0); if (hBitmap && pBits) { - DWORD dwBitsOffset = pHeader->biSize; - if (pHeader->biCompression == BI_BITFIELDS) { - dwBitsOffset += 12; - } - - if (hResource && (dwSize < dwBitsOffset || (DWORD)width * height * 4 > dwSize - dwBitsOffset)) { - Wh_Log(L"Image load error: invalid size"); - DeleteObject(hBitmap); - return LoadAlphaBitmap(resourceId, true); - } - - const unsigned char* pSrc = static_cast(pResourceData) + dwBitsOffset; + const unsigned char* pSrc = static_cast(pResourceData) + bitsOffset; if (isTopDown) { memcpy(pBits, pSrc, width * height * 4); @@ -2961,7 +2947,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA ExitProcess(0); } } - PostMessageW(hBgWnd, WM_CLOSE, 0, 1337); + PostMessageW(hBgWnd, WM_CLOSE, 0, BSDR_CLOSE); return TRUE; case IDC_BSDR_FORCE_BTN: ShowWindow(hWarningText, SW_SHOW); @@ -2973,7 +2959,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA return TRUE; case IDYES: Resolve(BlockedShutdownResolution_Force); - PostMessageW(hBgWnd, WM_CLOSE, 0, 1377); + PostMessageW(hBgWnd, WM_CLOSE, 0, BSDR_CLOSE); return TRUE; case IDNO: ShowWindow(hWarningText, SW_HIDE); @@ -3012,7 +2998,11 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA int delta = GET_WHEEL_DELTA_WPARAM(wParam); int oldPos = scrollPos; int lineHeight = MulDiv(20, dpi, 96); - scrollPos -= MulDiv(delta * lineHeight, scrollLines, WHEEL_DELTA); + if (scrollLines == WHEEL_PAGESCROLL) { + scrollPos -= visibleHeight * (delta > 0 ? 1 : -1); + } else { + scrollPos -= MulDiv(delta * lineHeight, scrollLines, WHEEL_DELTA); + } if (scrollPos < 0) scrollPos = 0; int maxScroll = totalContentHeight - visibleHeight; @@ -3047,7 +3037,15 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA case SB_PAGEUP: scrollPos -= MulDiv(100, dpi, 96); break; case SB_PAGEDOWN: scrollPos += MulDiv(100, dpi, 96); break; case SB_THUMBTRACK: - case SB_THUMBPOSITION: scrollPos = HIWORD(wParam); break; + case SB_THUMBPOSITION: { + SCROLLINFO si = {0}; + si.cbSize = sizeof(si); + si.fMask = SIF_TRACKPOS; + if (GetScrollInfo(hScrollBar, SB_CTL, &si)) { + scrollPos = si.nTrackPos; + } + break; + } } if (scrollPos < 0) scrollPos = 0; @@ -3060,7 +3058,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_POS; si.nPos = scrollPos; - SetScrollInfo(hScrollBar, SB_CTL, &si, FALSE); + SetScrollInfo(hScrollBar, SB_CTL, &si, TRUE); SetWindowPos(hAppListScroll, nullptr, 0, -scrollPos, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOREDRAW); RedrawWindow(hAppList, nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_ALLCHILDREN); } @@ -3162,7 +3160,7 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA return 0; } case WM_CLOSE: { - if (lParam != 1337) { + if (lParam != BSDR_CLOSE) { // Deny Alt+F4 or other external window closes (like Windows 7) return 0; } @@ -3294,7 +3292,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { if (result == WAIT_OBJECT_0) { // hStopEvent if (hBgWnd && IsWindow(hBgWnd)) { - SendMessageW(hBgWnd, WM_CLOSE, 0, 1337); + SendMessageW(hBgWnd, WM_CLOSE, 0, BSDR_CLOSE); } else { break; } @@ -3334,7 +3332,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { } if (hBgWnd && IsWindow(hBgWnd)) { - SendMessageW(hBgWnd, WM_CLOSE, 0, 1337); + SendMessageW(hBgWnd, WM_CLOSE, 0, BSDR_CLOSE); } break; } From e867bcd30c1950912d2978bd84c5865f8d4c4ade Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Sun, 30 Aug 2026 23:31:47 +0900 Subject: [PATCH 27/51] Remove stray spaces and semicolon --- mods/win7-bsdr.wh.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index ba8fd499b7..5bcc4950c9 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -1730,7 +1730,7 @@ void Resolve(BlockedShutdownResolution resolution) { { std::lock_guard lock(g_resolvedMutex); if (g_resolvedValue != BlockedShutdownResolution_None) { - return; // already resolved + return; // already resolved } g_wasClicked = true; g_resolvedValue = resolution; @@ -1819,7 +1819,7 @@ namespace CustomBSDR { static std::vector appTiles; [[clang::no_destroy]] static std::optional>> pendingApps{std::in_place}; std::mutex pendingAppsMutex; -}; +} #pragma endregion logoncontroller.h and CustomBSDR.h #pragma region wicutil.cpp @@ -2905,7 +2905,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA GetClientRect(hWndDlg, &rcDlg); HBRUSH hBrush = (HBRUSH)GetStockObject(BLACK_BRUSH); FillRect(hdc, &rcDlg, hBrush); - SetWindowLongPtrW(hWndDlg, DWLP_MSGRESULT, TRUE); + SetWindowLongPtrW(hWndDlg, DWLP_MSGRESULT, TRUE); return TRUE; } break; @@ -2913,11 +2913,11 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA LPDRAWITEMSTRUCT pDIS = (LPDRAWITEMSTRUCT)lParam; if (pDIS->CtlID == IDC_BSDR_SEPARATOR_TOP || pDIS->CtlID == IDC_BSDR_SEPARATOR_BOTTOM) { DrawSeparator(pDIS); - SetWindowLongPtrW(hWndDlg, DWLP_MSGRESULT, TRUE); + SetWindowLongPtrW(hWndDlg, DWLP_MSGRESULT, TRUE); return TRUE; } else if (pDIS->CtlType == ODT_BUTTON) { DrawButton(pDIS->hDC, pDIS); - SetWindowLongPtrW(hWndDlg, DWLP_MSGRESULT, TRUE); + SetWindowLongPtrW(hWndDlg, DWLP_MSGRESULT, TRUE); return TRUE; } break; @@ -3117,7 +3117,7 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA ReleaseDC(nullptr, hDC); separatorBitmap = LoadAlphaBitmap(IDB_BSDR_SEPARATOR); - btnNormalBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_NORMAL); + btnNormalBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_NORMAL); btnHoverBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_HOVER); btnPressedBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_PRESSED); btnSelectedBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_SELECTED); From e012334dd6596006dc6efad95e4c99a8353f2170 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Mon, 31 Aug 2026 01:38:44 +0900 Subject: [PATCH 28/51] Apply fixes --- mods/win7-bsdr.wh.cpp | 96 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 79 insertions(+), 17 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 5bcc4950c9..cedd7f826e 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -106,7 +106,7 @@ EXTERN_C IMAGE_DOS_HEADER __ImageBase; HINSTANCE g_hLogonControllerDll; HINSTANCE g_hBlockedShutdownDll; -std::atomic g_hDoModalExitEvent = nullptr; +std::atomic g_hDoModalExitEventDup = nullptr; #pragma region resources // Resource IDs @@ -1940,8 +1940,12 @@ void CustomBSDR::CenterWindow(HWND hWnd) { // Center on primary monitor RECT rcWindow, rcWorkArea; - GetWindowRect(hWnd, &rcWindow); - SystemParametersInfoW(SPI_GETWORKAREA, 0, &rcWorkArea, 0); + if (!GetWindowRect(hWnd, &rcWindow)) { + return; + } + if (!SystemParametersInfoW(SPI_GETWORKAREA, 0, &rcWorkArea, 0)) { + return; + } int windowWidth = rcWindow.right - rcWindow.left; int windowHeight = rcWindow.bottom - rcWindow.top; int xPos = rcWorkArea.left + (rcWorkArea.right - rcWorkArea.left - windowWidth) / 2 - GetSystemMetrics(SM_XVIRTUALSCREEN); @@ -2395,7 +2399,10 @@ void CustomBSDR::CreateAppTileControls(IShutdownBlockingApp* blockingApp, bool n int dpi = GetDpiForWindow(hDlg); AppTile tile = {}; - blockingApp->get_Id(&tile.appId); + if (FAILED(blockingApp->get_Id(&tile.appId))) { + Wh_Log(L"get_Id failed"); + return; + } blockingApp->get_IsBlocking(&tile.isBlocking); tile.hIconBitmap = nullptr; @@ -2553,13 +2560,21 @@ void CustomBSDR::UpdateAppListLayout() { // Windows 7 decided the dialog height only on the initial load (and when the screen resolution was decreased), and never resized on late item add/remove // On Windows 10+, the BSDR backend sends the initial list of apps with random delays (whether pendingApps or WM_ADD_APP is used), // and never sends newly created windows after initialization. So consider all additions as initial item and always increase the dialog height - RECT rcScrollBar; - GetWindowRect(hScrollBar, &rcScrollBar); - int scrollBarWidth = rcScrollBar.right - rcScrollBar.left; + int scrollBarWidth = 0; + if (hScrollBar) { + RECT rcScrollBar; + GetWindowRect(hScrollBar, &rcScrollBar); + scrollBarWidth = rcScrollBar.right - rcScrollBar.left; + } else { + scrollBarWidth = GetSystemMetrics(SM_CXVSCROLL); + } int screenHeight = GetSystemMetrics(SM_CYSCREEN); int maxHeight = screenHeight - MulDiv(335, dpi, 96); + if (maxHeight < minHeight) + maxHeight = minHeight; + int newHeight = totalContentHeight; if (paintedFirstFrame && newHeight < visibleHeight) newHeight = visibleHeight; @@ -2693,7 +2708,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA SetWindowLongW(hWndDlg, GWL_STYLE, GetWindowLongW(hWndDlg, GWL_STYLE) & ~WS_CAPTION); // Use composited style on dialog, not bg window, to prevent the scrollbar control from flickering on drag SetWindowLongW(hWndDlg, GWL_EXSTYLE, GetWindowLongW(hWndDlg, GWL_EXSTYLE) | WS_EX_COMPOSITED); - + hTitleText = GetDlgItem(hWndDlg, IDC_BSDR_TITLE); hAppList = GetDlgItem(hWndDlg, IDC_BSDR_APPLIST); hScrollBar = GetDlgItem(hWndDlg, IDC_BSDR_SCROLLBAR); @@ -2934,9 +2949,11 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA // and makes the thread stuck in WaitForSingleObject // So force signal the event as a not so clean workaround (still better than previous ExitProcess workaround) // (I still haven't found neither the culprint nor which code signals that event) - HANDLE hDoModalExitEvent = g_hDoModalExitEvent.load(); + HANDLE hDoModalExitEvent = g_hDoModalExitEventDup.load(); if (hDoModalExitEvent) { - SetEvent(hDoModalExitEvent); + if (!SetEvent(hDoModalExitEvent)) { + Wh_Log(L"SetEvent(DoModal exit) failed, GLE=%u", GetLastError()); + } } else { // Event capture failed, oh noes! // Here comes the old terrible workaround @@ -3437,7 +3454,17 @@ long __fastcall BlockedShutdownUXImpl_Start_hook(void* thisPtr, void* userSettin long __fastcall BlockedShutdownUXImpl_get_ScaleFactor_hook(void* thisPtr, unsigned int* scaleFactor) { Wh_Log(L"BlockedShutdownUXImpl::get_ScaleFactor"); - *scaleFactor = 100; + + using namespace CustomBSDR; + + int dpi = 96; + if (hDlg) { + dpi = GetDpiForWindow(hDlg); + } else { + dpi = GetDpiForSystem(); + } + *scaleFactor = MulDiv(dpi, 100, 96); + return S_OK; } @@ -3537,7 +3564,7 @@ WindhawkUtils::SYMBOL_HOOK blockedShutdownHooks[] = { { L"public: virtual long __cdecl BlockedShutdownUXImpl::Start(struct Windows::Internal::UI::Logon::Controller::IUserSettingManager *,struct Windows::Internal::UI::Logon::Controller::ILogonUIStateInfo *)", }, - (void**)nullptr, // Cast is not needed on WH 1.7+ + (void**)nullptr, // Cast is not needed on WH 1.7+; only needed for WH 1.6.1 and below (void*)BlockedShutdownUXImpl_Start_hook, FALSE }, @@ -3616,7 +3643,6 @@ long __cdecl CLogonController__DoModal_hook(void* pThis, unsigned long a2, unsig Wh_Log(L"DoModal"); int result = CLogonController__DoModal_orig(pThis, a2, a3, a4, a5); g_enteringDoModal = false; - g_hDoModalExitEvent.store(nullptr); return result; } @@ -3636,12 +3662,27 @@ CreateEventW_t CreateEventW_orig; HANDLE WINAPI CreateEventW_hook(LPSECURITY_ATTRIBUTES lpEventAttributes, WINBOOL bManualReset, WINBOOL bInitialState, LPCWSTR lpName) { HANDLE result = CreateEventW_orig(lpEventAttributes, bManualReset, bInitialState, lpName); if (lpEventAttributes == 0 && bManualReset == 0 && bInitialState == 0 && lpName == 0 && g_enteringDoModal) { - if (g_hDoModalExitEvent.load()) { - Wh_Log(L"Warning: DoModal event already captured; ignoring the subsequent one..."); - } else { + HANDLE duplicated = nullptr; + if (!DuplicateHandle( + GetCurrentProcess(), + result, + GetCurrentProcess(), + &duplicated, + 0, + FALSE, + DUPLICATE_SAME_ACCESS + )) { + Wh_Log(L"DuplicateHandle failed, GLE=%d", GetLastError()); + return result; + } + + HANDLE expected = nullptr; + if (g_hDoModalExitEventDup.compare_exchange_strong(expected, duplicated, std::memory_order_release, std::memory_order_relaxed)) { // Works cleanly on LTSC 2021 and 11 25H2 - g_hDoModalExitEvent.store(result); Wh_Log(L"Caught DoModal event"); + } else { + CloseHandle(duplicated); + Wh_Log(L"Warning: DoModal event already captured; ignoring the subsequent one..."); } } return result; @@ -3651,6 +3692,7 @@ HANDLE WINAPI CreateEventW_hook(LPSECURITY_ATTRIBUTES lpEventAttributes, WINBOOL #pragma region Winlogon hooks (disable async logoff) bool g_isWinlogon = false; bool g_noSafetyChecks = false; +thread_local bool g_isInitialThread = false; int* p_g_fShutdownResolverDisabled = nullptr; int g_origResolverDisabledState = 0; @@ -3690,6 +3732,13 @@ bool IsLogonUiInjectionEnabled() { return true; } + // Win10+ winlogon injection is always asynchronous because smss.exe, the parent of winlogon, is a protected process (PPL), so the probe shouldn't block its startup routine + // But if synchronous injection somehow happens, just check for existing LogonUiLoadCheck value (from previous probe) and call it a day + if (g_isInitialThread) { + Wh_Log(L"Synchronously injected into winlogon! Wow!"); + return Wh_GetIntValue(L"LogonUiLoadCheck", -1) != -1; + } + // Skip if AuthUX is installed if (IsAuthUxInstalled()) { return true; @@ -3794,6 +3843,8 @@ WindhawkUtils::SYMBOL_HOOK winlogonExeHooks[] = { }; #pragma endregion Winlogon hooks (disable async logoff) +static constexpr size_t OFFSET_SAME_TEB_FLAGS = 0x17EE; // Win64 only + // The mod is being initialized, load settings, hook functions, and do other // initialization stuff if required. BOOL Wh_ModInit() { @@ -3807,6 +3858,7 @@ BOOL Wh_ModInit() { Wh_Log(L"Running in winlogon.exe, but winlogon hooks are disabled"); return FALSE; } + g_isInitialThread = *(USHORT*)((BYTE*)NtCurrentTeb() + OFFSET_SAME_TEB_FLAGS) & 0x0400; HMODULE winlogon = GetModuleHandleW(NULL); if (winlogon) { if (!WindhawkUtils::HookSymbols(winlogon, winlogonExeHooks, ARRAYSIZE(winlogonExeHooks))) { @@ -3873,6 +3925,7 @@ BOOL Wh_ModInit() { if (g_hBlockedShutdownDll) { if (!WindhawkUtils::HookSymbols(g_hBlockedShutdownDll, blockedShutdownHooks, ARRAYSIZE(blockedShutdownHooks))) { Wh_Log(L"Failed to hook symbols in Windows.UI.BlockedShutdown.dll"); + FreeLibrary(g_hBlockedShutdownDll); if (g_hLogonControllerDll) { FreeLibrary(g_hLogonControllerDll); } @@ -3880,6 +3933,9 @@ BOOL Wh_ModInit() { } } else { Wh_Log(L"Failed to load Windows.UI.BlockedShutdown.dll"); + if (g_hLogonControllerDll) { + FreeLibrary(g_hLogonControllerDll); + } return FALSE; } @@ -3950,11 +4006,17 @@ void Wh_ModUninit() { } WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); + hThread = nullptr; } if (hDesktop) { CloseDesktop(hDesktop); } + HANDLE hDoModalExitEventDup = g_hDoModalExitEventDup.exchange(nullptr, std::memory_order_acq_rel); + if (hDoModalExitEventDup) { + CloseHandle(hDoModalExitEventDup); + } + pendingApps.reset(); if (hStopEvent) { From 70c00766b5a156dd560f237a7036e1c416fad7bf Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Mon, 31 Aug 2026 03:25:52 +0900 Subject: [PATCH 29/51] Apply fixes --- mods/win7-bsdr.wh.cpp | 123 ++++++++++++++++++++++++++---------------- 1 file changed, 78 insertions(+), 45 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index cedd7f826e..36d721d417 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -64,7 +64,7 @@ settings. If you do not do this, it will silently fail to inject. $name: (Advanced) Skip safety checks before restoring the classic logoff sequence $name:ko-KR: (고급) 고전 로그오프 절차를 복원하기 전 안전 검사 생략 $description: This mod verifies that LogonUI injection was successful before enabling the old sequence, to prevent the modern BSDR from showing on the invisible secure desktop, thus making the logoff sequence stuck. Before enabling this option, remember to press Ctrl+Alt+Del if logoff gets stuck. - $description:ko-KR: 이 모드는 신형 BSDR이 보이지 않는 보안 데스크톱에 표시되어 로그오프 절차가 중단되지 않도록 고전 로그오프 절차를 복원하기 전에 LogonUI 인젝션이 성공적인지 검사힙니다. 이 옵션을 활성화하기 전, 로그오프 절차가 멈출 경우 Ctrl+Alt+Del을 누르는 것을 기억하십시오. + $description:ko-KR: 이 모드는 신형 BSDR이 보이지 않는 보안 데스크톱에 표시되어 로그오프 절차가 중단되지 않도록 고전 로그오프 절차를 복원하기 전에 LogonUI 인젝션이 성공적인지 검사합니다. 이 옵션을 활성화하기 전, 로그오프 절차가 멈출 경우 Ctrl+Alt+Del을 누르는 것을 기억하십시오. */ // ==/WindhawkModSettings== @@ -73,7 +73,6 @@ settings. If you do not do this, it will silently fail to inject. #include #include #include -#include #include #include #include @@ -1748,6 +1747,7 @@ namespace CustomBSDR { void Stop(); void AddApplication(IShutdownBlockingApp* app); void RemoveApplication(UINT appId); + int GetScaleFactor(); static LogonUIState _logonUIState = LogonUIState::LogonUIState_LoggingOff; @@ -1819,6 +1819,7 @@ namespace CustomBSDR { static std::vector appTiles; [[clang::no_destroy]] static std::optional>> pendingApps{std::in_place}; std::mutex pendingAppsMutex; + [[clang::no_destroy]] static Microsoft::WRL::ComPtr spWICFactory; } #pragma endregion logoncontroller.h and CustomBSDR.h @@ -1903,7 +1904,7 @@ HRESULT ConvertWICBitmapToHBITMAP(IWICImagingFactory* pWICImagingFactory, IWICBi Microsoft::WRL::ComPtr spWICImagingFactory = pWICImagingFactory; if (!spWICImagingFactory.Get()) { - hr = CoCreateInstance(CLSID_WICImagingFactory2, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spWICImagingFactory)); + hr = E_ABORT; } if (SUCCEEDED(hr)) { Microsoft::WRL::ComPtr spBitmapSourceConverted; @@ -1917,17 +1918,18 @@ HRESULT ConvertWICBitmapToHBITMAP(IWICImagingFactory* pWICImagingFactory, IWICBi return hr; } -HRESULT GetBitmapFromRandomStream(Microsoft::WRL::ComPtr stream, HBITMAP* outBitmap) { +HRESULT GetBitmapFromRandomStream(IWICImagingFactory* pWICImagingFactory, Microsoft::WRL::ComPtr stream, HBITMAP* outBitmap) { + if (!pWICImagingFactory) { + return E_ABORT; + } + Microsoft::WRL::ComPtr spStream; RETURN_IF_FAILED(CreateStreamOverRandomAccessStream(stream.Get(), IID_PPV_ARGS(&spStream))); - Microsoft::WRL::ComPtr spWICFactory; - RETURN_IF_FAILED(CoCreateInstance(CLSID_WICImagingFactory2, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spWICFactory))); - Microsoft::WRL::ComPtr spWICBitmapSource; - RETURN_IF_FAILED(LoadImageWithWIC(spWICFactory.Get(), spStream.Get(), &spWICBitmapSource)); + RETURN_IF_FAILED(LoadImageWithWIC(pWICImagingFactory, spStream.Get(), &spWICBitmapSource)); - RETURN_IF_FAILED(ConvertWICBitmapToHBITMAP(spWICFactory.Get(), spWICBitmapSource.Get(), outBitmap)); + RETURN_IF_FAILED(ConvertWICBitmapToHBITMAP(pWICImagingFactory, spWICBitmapSource.Get(), outBitmap)); return S_OK; } #pragma endregion wicutil.cpp @@ -2482,7 +2484,7 @@ void CustomBSDR::CreateAppTileControls(IShutdownBlockingApp* blockingApp, bool n ABI::Windows::Storage::Streams::IRandomAccessStream* iconStream = nullptr; if (SUCCEEDED(blockingApp->get_Icon(&iconStream)) && iconStream) { - GetBitmapFromRandomStream(iconStream, &tile.hIconBitmap); + GetBitmapFromRandomStream(spWICFactory.Get(), iconStream, &tile.hIconBitmap); if (tile.hIcon && tile.hIconBitmap) { SendMessageW(tile.hIcon, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)tile.hIconBitmap); } @@ -2560,13 +2562,12 @@ void CustomBSDR::UpdateAppListLayout() { // Windows 7 decided the dialog height only on the initial load (and when the screen resolution was decreased), and never resized on late item add/remove // On Windows 10+, the BSDR backend sends the initial list of apps with random delays (whether pendingApps or WM_ADD_APP is used), // and never sends newly created windows after initialization. So consider all additions as initial item and always increase the dialog height - int scrollBarWidth = 0; + int scrollBarWidth = GetSystemMetrics(SM_CXVSCROLL); if (hScrollBar) { RECT rcScrollBar; - GetWindowRect(hScrollBar, &rcScrollBar); - scrollBarWidth = rcScrollBar.right - rcScrollBar.left; - } else { - scrollBarWidth = GetSystemMetrics(SM_CXVSCROLL); + if (GetWindowRect(hScrollBar, &rcScrollBar)) { + scrollBarWidth = rcScrollBar.right - rcScrollBar.left; + } } int screenHeight = GetSystemMetrics(SM_CYSCREEN); @@ -2687,7 +2688,11 @@ void CustomBSDR::UpdateAppListLayout() { GetString(IDS_BSDR_BLOCKINGAPPCOUNT_MULTI, titleFormat, _countof(titleFormat)); } // Avoid swprintf with format string from user supplied dll - std::wstring titleText = regex_replace(titleFormat, std::wregex(L"%d"), std::to_wstring(appTiles.size())); + auto titleText = std::wstring(titleFormat); + size_t pos = titleText.find(L"%d"); + if (pos != std::wstring::npos) { + titleText.replace(pos, 2, std::to_wstring(appTiles.size())); + } SetWindowTextW(hTitleText, titleText.c_str()); } @@ -2697,6 +2702,23 @@ void CustomBSDR::UpdateAppListLayout() { INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_INITDIALOG: { + // Verify if all expected controls are there + hTitleText = GetDlgItem(hWndDlg, IDC_BSDR_TITLE); + hAppList = GetDlgItem(hWndDlg, IDC_BSDR_APPLIST); + hScrollBar = GetDlgItem(hWndDlg, IDC_BSDR_SCROLLBAR); + hWarningText = GetDlgItem(hWndDlg, IDC_BSDR_WARNING); + hForceButton = GetDlgItem(hWndDlg, IDC_BSDR_FORCE_BTN); + hCancelButton = GetDlgItem(hWndDlg, IDCANCEL); + hDescText = GetDlgItem(hWndDlg, IDC_BSDR_DESC); + hYesButton = GetDlgItem(hWndDlg, IDYES); + hNoButton = GetDlgItem(hWndDlg, IDNO); + + if (!hTitleText || !hAppList || !hScrollBar || !hWarningText || !hForceButton || !hCancelButton || !hDescText || !hYesButton || !hNoButton) { + Wh_Log(L"Dialog is missing necessary controls"); + DestroyWindow(hWndDlg); + return FALSE; + } + std::vector> pendingAppsLocal; { std::lock_guard lock(pendingAppsMutex); @@ -2709,16 +2731,6 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA // Use composited style on dialog, not bg window, to prevent the scrollbar control from flickering on drag SetWindowLongW(hWndDlg, GWL_EXSTYLE, GetWindowLongW(hWndDlg, GWL_EXSTYLE) | WS_EX_COMPOSITED); - hTitleText = GetDlgItem(hWndDlg, IDC_BSDR_TITLE); - hAppList = GetDlgItem(hWndDlg, IDC_BSDR_APPLIST); - hScrollBar = GetDlgItem(hWndDlg, IDC_BSDR_SCROLLBAR); - hWarningText = GetDlgItem(hWndDlg, IDC_BSDR_WARNING); - hForceButton = GetDlgItem(hWndDlg, IDC_BSDR_FORCE_BTN); - hCancelButton = GetDlgItem(hWndDlg, IDCANCEL); - hDescText = GetDlgItem(hWndDlg, IDC_BSDR_DESC); - hYesButton = GetDlgItem(hWndDlg, IDYES); - hNoButton = GetDlgItem(hWndDlg, IDNO); - // Hide the warning message controls ShowWindow(hWarningText, SW_HIDE); ShowWindow(hYesButton, SW_HIDE); @@ -3033,7 +3045,8 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA si.nPos = scrollPos; SetScrollInfo(hScrollBar, SB_CTL, &si, TRUE); SetWindowPos(hAppListScroll, nullptr, 0, -scrollPos, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOREDRAW); - RedrawWindow(hAppList, nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_ALLCHILDREN); + if (hAppList) + RedrawWindow(hAppList, nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_ALLCHILDREN); } return TRUE; } @@ -3077,7 +3090,8 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA si.nPos = scrollPos; SetScrollInfo(hScrollBar, SB_CTL, &si, TRUE); SetWindowPos(hAppListScroll, nullptr, 0, -scrollPos, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOREDRAW); - RedrawWindow(hAppList, nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_ALLCHILDREN); + if (hAppList) + RedrawWindow(hAppList, nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_ALLCHILDREN); } return TRUE; } @@ -3097,7 +3111,7 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA HWND hDlgLocal = nullptr; if (!g_isUsingHardcodedRes && g_hResDll) { hDlgLocal = CreateDialogParamW(g_hResDll, MAKEINTRESOURCEW(IDD_BSDR_DLG), hWnd, DlgProc, lParam); - if (!hDlgLocal) { + if (!hDlgLocal || !IsWindow(hDlgLocal)) { Wh_Log(L"CreateDialogParamW failed: %d", GetLastError()); // Retry with hardcoded one hDlgLocal = CreateDialogIndirectParamW(nullptr, reinterpret_cast(&RES_DIALOG), hWnd, DlgProc, lParam); @@ -3169,9 +3183,11 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA int cx = GetSystemMetrics(SM_CXVIRTUALSCREEN); int cy = GetSystemMetrics(SM_CYVIRTUALSCREEN); SetWindowPos(hWnd, nullptr, x, y, cx, cy, SWP_NOSENDCHANGING); - CenterWindow(hDlg); - UpdateAppListLayout(); - RedrawWindow(hDlg, nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_UPDATENOW); + if (hDlg) { + CenterWindow(hDlg); + UpdateAppListLayout(); + RedrawWindow(hDlg, nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_UPDATENOW); + } // Retaking screenshot requires temporarily hiding the window // Windows 7 doesn't do that either so skip that return 0; @@ -3182,6 +3198,12 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA return 0; } + // Drain the add app queue + MSG msg; + while (PeekMessageW(&msg, hDlg, WM_ADD_APP, WM_ADD_APP, PM_REMOVE)) { + reinterpret_cast(msg.lParam)->Release(); + } + for (auto& tile : appTiles) { if (tile.hIconBitmap) DeleteObject(tile.hIconBitmap); } @@ -3268,6 +3290,12 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { HRESULT hrCo = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + HRESULT hrIf = CoCreateInstance(CLSID_WICImagingFactory2, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spWICFactory)); + if (FAILED(hrIf)) { + Wh_Log(L"CoCreateInstance(CLSID_WICImagingFactory2) failed, HR=%d", hrIf); + // Continue without icons + } + WNDCLASSEXW wndClass = {}; wndClass.cbSize = sizeof(WNDCLASSEXW); @@ -3320,7 +3348,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { bQuit = TRUE; break; } - if (msg.message == WM_SYSKEYDOWN && msg.wParam == VK_MENU) { + if (hDlg && msg.message == WM_SYSKEYDOWN && msg.wParam == VK_MENU) { SetPropW(hForceButton, L"CustomBSDR_HideAccel", (HANDLE)FALSE); SetPropW(hCancelButton, L"CustomBSDR_HideAccel", (HANDLE)FALSE); SetPropW(hYesButton, L"CustomBSDR_HideAccel", (HANDLE)FALSE); @@ -3367,6 +3395,8 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { Wh_Log(L"RegisterClassExW failed, GLE=%d", ret); } + spWICFactory.Reset(); + if (SUCCEEDED(hrCo)) { CoUninitialize(); } @@ -3428,7 +3458,17 @@ void CustomBSDR::RemoveApplication(UINT appid) { } } +int CustomBSDR::GetScaleFactor() { + std::lock_guard lock(pendingAppsMutex); + if (hDlg) { + return GetDpiForWindow(hDlg); + } else { + return GetDpiForSystem(); + } +} + void CustomBSDR::Hide() { + std::lock_guard lock(pendingAppsMutex); if (hBgWnd) { ShowWindow(hBgWnd, SW_HIDE); } @@ -3454,17 +3494,7 @@ long __fastcall BlockedShutdownUXImpl_Start_hook(void* thisPtr, void* userSettin long __fastcall BlockedShutdownUXImpl_get_ScaleFactor_hook(void* thisPtr, unsigned int* scaleFactor) { Wh_Log(L"BlockedShutdownUXImpl::get_ScaleFactor"); - - using namespace CustomBSDR; - - int dpi = 96; - if (hDlg) { - dpi = GetDpiForWindow(hDlg); - } else { - dpi = GetDpiForSystem(); - } - *scaleFactor = MulDiv(dpi, 100, 96); - + *scaleFactor = CustomBSDR::GetScaleFactor(); return S_OK; } @@ -3692,10 +3722,12 @@ HANDLE WINAPI CreateEventW_hook(LPSECURITY_ATTRIBUTES lpEventAttributes, WINBOOL #pragma region Winlogon hooks (disable async logoff) bool g_isWinlogon = false; bool g_noSafetyChecks = false; +// It's thread local to allow probing on further setting change thread_local bool g_isInitialThread = false; int* p_g_fShutdownResolverDisabled = nullptr; int g_origResolverDisabledState = 0; +// Check if non-default BSDR (e.g. AuthUX BSDR) is installed bool IsAuthUxInstalled() { wchar_t dllPath[MAX_PATH]; DWORD size = sizeof(dllPath); @@ -3819,7 +3851,7 @@ bool IsLogonUiInjectionEnabled() { if (result != WAIT_OBJECT_0) { // Should exit immediately but just to be safe Wh_Log(L"LogonUI wait timed out or failed"); - // Let it exit automatically + TerminateProcess(pi.hProcess, 0); } CloseHandle(pi.hThread); @@ -3843,6 +3875,7 @@ WindhawkUtils::SYMBOL_HOOK winlogonExeHooks[] = { }; #pragma endregion Winlogon hooks (disable async logoff) +// https://github.com/ramensoftware/windhawk/wiki/Development-tips static constexpr size_t OFFSET_SAME_TEB_FLAGS = 0x17EE; // Win64 only // The mod is being initialized, load settings, hook functions, and do other From 917a688684a0b8653e77fa7a4e40dafdac8c84d2 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Mon, 31 Aug 2026 03:35:04 +0900 Subject: [PATCH 30/51] Misc fix --- mods/win7-bsdr.wh.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 36d721d417..4a598c7666 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -3119,7 +3119,7 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA } else { hDlgLocal = CreateDialogIndirectParamW(nullptr, reinterpret_cast(&RES_DIALOG), hWnd, DlgProc, lParam); } - if (hDlgLocal) { + if (hDlgLocal && IsWindow(hDlgLocal)) { ShowWindow(hDlgLocal, SW_SHOW); } else { // Bail out @@ -3198,23 +3198,25 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA return 0; } - // Drain the add app queue - MSG msg; - while (PeekMessageW(&msg, hDlg, WM_ADD_APP, WM_ADD_APP, PM_REMOVE)) { - reinterpret_cast(msg.lParam)->Release(); - } - for (auto& tile : appTiles) { if (tile.hIconBitmap) DeleteObject(tile.hIconBitmap); } appTiles.clear(); - if (hDlg) { - HWND hDlgLocal = hDlg; - { - std::lock_guard lock(pendingAppsMutex); - hDlg = nullptr; + HWND hDlgLocal = nullptr; + { + std::lock_guard lock(pendingAppsMutex); + hDlgLocal = hDlg; + hDlg = nullptr; + } + + if (hDlgLocal) { + // Drain the add app queue after preventing AddApplication from posting new add app messages + MSG msg; + while (PeekMessageW(&msg, hDlgLocal, WM_ADD_APP, WM_ADD_APP, PM_REMOVE)) { + reinterpret_cast(msg.lParam)->Release(); } + DestroyWindow(hDlgLocal); } From 65cc45009c6cb4c64f71a9a53ae425a6154bfdf0 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Mon, 31 Aug 2026 16:22:28 +0900 Subject: [PATCH 31/51] Apply fixes, improve thread safety, and revert LUI hook probe to reg checks Added a noInvoke parameter to the Resolve function to control invocation behavior. Updated various static variables to non-static for better thread safety and mutex handling. Enhanced dialog initialization checks and improved resource management. --- mods/win7-bsdr.wh.cpp | 389 ++++++++++++++++++++++++------------------ 1 file changed, 223 insertions(+), 166 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 4a598c7666..bb34c85c93 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -39,6 +39,9 @@ In order to use this mod, you must allow Windhawk to inject into the **LogonUI.e system process. To do so, add it to the process inclusion list in the advanced settings. If you do not do this, it will silently fail to inject. +You must add it to the inclusion list even if you have disabled the critical system process +exclusion option. Otherwise the logoff sequence portion of the mod will not function. + ![Advanced settings screenshot](https://i.imgur.com/LRhREtJ.png) */ // ==/WindhawkModReadme== @@ -107,6 +110,8 @@ HINSTANCE g_hBlockedShutdownDll; std::atomic g_hDoModalExitEventDup = nullptr; +std::atomic g_isExiting = false; + #pragma region resources // Resource IDs #define IDD_BSDR_DLG 20 @@ -1724,7 +1729,7 @@ std::mutex g_resolvedMutex; BlockedShutdownResolution g_resolvedValue = BlockedShutdownResolution_None; bool g_wasClicked = false; -void Resolve(BlockedShutdownResolution resolution) { +void Resolve(BlockedShutdownResolution resolution, bool noInvoke = false) { Microsoft::WRL::ComPtr resolvedLocal; { std::lock_guard lock(g_resolvedMutex); @@ -1736,12 +1741,13 @@ void Resolve(BlockedShutdownResolution resolution) { resolvedLocal = g_Resolved; } - if (resolvedLocal) { + if (resolvedLocal && !noInvoke) { resolvedLocal->Invoke(nullptr, resolution); } } namespace CustomBSDR { + // interface functions void Start(LogonUIState state); void Hide(); void Stop(); @@ -1749,27 +1755,32 @@ namespace CustomBSDR { void RemoveApplication(UINT appId); int GetScaleFactor(); - static LogonUIState _logonUIState = LogonUIState::LogonUIState_LoggingOff; + LogonUIState _logonUIState = LogonUIState::LogonUIState_LoggingOff; // handles - static HANDLE hThread = nullptr; - static HANDLE hStopEvent = nullptr; - static HDESK hDesktop = nullptr; - static HWND hDlg = nullptr; - static HWND hBgWnd = nullptr; - static HWND hTitleText = nullptr; - static HWND hAppList = nullptr; - static HWND hAppListScroll = nullptr; - static HWND hScrollBar = nullptr; - static HWND hWarningText = nullptr; - static HWND hForceButton = nullptr; - static HWND hCancelButton = nullptr; - static HWND hDescText = nullptr; - static HWND hYesButton = nullptr; - static HWND hNoButton = nullptr; - static HWND hHoverButton = nullptr; - static HFONT hTitleFont = nullptr; - static HFONT hDescFont = nullptr; + HANDLE hThread = nullptr; + HANDLE hStopEvent = nullptr; + HDESK hDesktop = nullptr; + HWND hDlg = nullptr; + HWND hBgWnd = nullptr; + HWND hTitleText = nullptr; + HWND hAppList = nullptr; + HWND hAppListScroll = nullptr; + HWND hScrollBar = nullptr; + HWND hWarningText = nullptr; + HWND hForceButton = nullptr; + HWND hCancelButton = nullptr; + HWND hDescText = nullptr; + HWND hYesButton = nullptr; + HWND hNoButton = nullptr; + HWND hHoverButton = nullptr; + HFONT hTitleFont = nullptr; + HFONT hDescFont = nullptr; + + // mutexes + std::mutex workerMutex; + std::mutex hBgWndMutex; + std::mutex pendingAppsMutex; // functions void CenterWindow(HWND hWnd); @@ -1787,25 +1798,26 @@ namespace CustomBSDR { DWORD WINAPI ThreadProc(LPVOID lpParameter); // bitmaps - static HBITMAP bgBitmap = nullptr; - static HBITMAP separatorBitmap = nullptr; - static HBITMAP btnNormalBitmap = nullptr; - static HBITMAP btnHoverBitmap = nullptr; - static HBITMAP btnPressedBitmap = nullptr; - static HBITMAP btnSelectedBitmap = nullptr; - static HBITMAP btnSelectedHoverBitmap = nullptr; + HBITMAP bgBitmap = nullptr; + HBITMAP separatorBitmap = nullptr; + HBITMAP btnNormalBitmap = nullptr; + HBITMAP btnHoverBitmap = nullptr; + HBITMAP btnPressedBitmap = nullptr; + HBITMAP btnSelectedBitmap = nullptr; + HBITMAP btnSelectedHoverBitmap = nullptr; // variables - static int bgOffsetX = 0; - static int bgOffsetY = 0; - static int bgWidth = 0; - static int bgHeight = 0; - static int scrollPos = 0; - static unsigned int scrollLines = 3; - static int totalContentHeight = 0; - static int minHeight = 0; - static bool paintedFirstFrame = false; - static bool isOnSecureDesktop = true; + bool dlgInitFailed = false; + int bgOffsetX = 0; + int bgOffsetY = 0; + int bgWidth = 0; + int bgHeight = 0; + int scrollPos = 0; + unsigned int scrollLines = 3; + int totalContentHeight = 0; + int minHeight = 0; + bool paintedFirstFrame = false; + bool isOnSecureDesktop = true; // app list data stuff struct AppTile { @@ -1816,10 +1828,9 @@ namespace CustomBSDR { HWND hBlockReason; HBITMAP hIconBitmap; }; - static std::vector appTiles; - [[clang::no_destroy]] static std::optional>> pendingApps{std::in_place}; - std::mutex pendingAppsMutex; - [[clang::no_destroy]] static Microsoft::WRL::ComPtr spWICFactory; + std::vector appTiles; + [[clang::no_destroy]] std::optional>> pendingApps{std::in_place}; + [[clang::no_destroy]] Microsoft::WRL::ComPtr spWICFactory; } #pragma endregion logoncontroller.h and CustomBSDR.h @@ -2098,6 +2109,7 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { } } else { Wh_Log(L"Image load error: no hBitmap/pBits"); + return LoadAlphaBitmap(resourceId, true); } return hBitmap; @@ -2286,6 +2298,11 @@ void CustomBSDR::DrawButton(HDC hdc, LPDRAWITEMSTRUCT pDIS) { DeleteDC(hdcOffscreen); DeleteObject(hOffscreenBmp); } + } else { + UINT uState = DFCS_BUTTONPUSH; + if (isPressed) + uState |= DFCS_PUSHED; + DrawFrameControl(hdc, &rcButton, DFC_BUTTON, uState); } } @@ -2715,9 +2732,10 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA if (!hTitleText || !hAppList || !hScrollBar || !hWarningText || !hForceButton || !hCancelButton || !hDescText || !hYesButton || !hNoButton) { Wh_Log(L"Dialog is missing necessary controls"); - DestroyWindow(hWndDlg); + dlgInitFailed = true; return FALSE; } + dlgInitFailed = false; std::vector> pendingAppsLocal; { @@ -3111,15 +3129,18 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA HWND hDlgLocal = nullptr; if (!g_isUsingHardcodedRes && g_hResDll) { hDlgLocal = CreateDialogParamW(g_hResDll, MAKEINTRESOURCEW(IDD_BSDR_DLG), hWnd, DlgProc, lParam); - if (!hDlgLocal || !IsWindow(hDlgLocal)) { + if (!hDlgLocal || !IsWindow(hDlgLocal) || dlgInitFailed) { Wh_Log(L"CreateDialogParamW failed: %d", GetLastError()); + if (hDlgLocal && IsWindow(hDlgLocal)) { + DestroyWindow(hDlgLocal); + } // Retry with hardcoded one hDlgLocal = CreateDialogIndirectParamW(nullptr, reinterpret_cast(&RES_DIALOG), hWnd, DlgProc, lParam); } } else { hDlgLocal = CreateDialogIndirectParamW(nullptr, reinterpret_cast(&RES_DIALOG), hWnd, DlgProc, lParam); } - if (hDlgLocal && IsWindow(hDlgLocal)) { + if (hDlgLocal && IsWindow(hDlgLocal) && !dlgInitFailed) { ShowWindow(hDlgLocal, SW_SHOW); } else { // Bail out @@ -3214,15 +3235,22 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA // Drain the add app queue after preventing AddApplication from posting new add app messages MSG msg; while (PeekMessageW(&msg, hDlgLocal, WM_ADD_APP, WM_ADD_APP, PM_REMOVE)) { - reinterpret_cast(msg.lParam)->Release(); + if (msg.lParam) { + reinterpret_cast(msg.lParam)->Release(); + } } DestroyWindow(hDlgLocal); } - if (hBgWnd) { - HWND hBgWndLocal = hBgWnd; + HWND hBgWndLocal = nullptr; + { + std::lock_guard lock(hBgWndMutex); + hBgWndLocal = hBgWnd; hBgWnd = nullptr; + } + + if (hBgWndLocal) { DestroyWindow(hBgWndLocal); } @@ -3262,6 +3290,32 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA DeleteObject(hDescFont); hDescFont = nullptr; } + + hTitleText = nullptr; + hAppList = nullptr; + hAppListScroll = nullptr; + hScrollBar = nullptr; + hWarningText = nullptr; + hForceButton = nullptr; + hCancelButton = nullptr; + hDescText = nullptr; + hYesButton = nullptr; + hNoButton = nullptr; + hHoverButton = nullptr; + hTitleFont = nullptr; + hDescFont = nullptr; + + dlgInitFailed = false; + bgOffsetX = 0; + bgOffsetY = 0; + bgWidth = 0; + bgHeight = 0; + scrollPos = 0; + totalContentHeight = 0; + minHeight = 0; + paintedFirstFrame = false; + isOnSecureDesktop = true; + return 0; } case WM_DESTROY: @@ -3313,7 +3367,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { bgWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN); bgHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN); - hBgWnd = CreateWindowExW( + HWND hBgWndLocal = CreateWindowExW( WS_EX_TOPMOST, BSDR_CLASSNAME, nullptr, @@ -3325,7 +3379,12 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { nullptr ); - if (hBgWnd) { + if (hBgWndLocal) { + { + std::lock_guard lock(hBgWndMutex); + CustomBSDR::hBgWnd = hBgWndLocal; + } + HANDLE waitHandles[1] = { hStopEvent }; BOOL bQuit = FALSE; while (TRUE) { @@ -3338,8 +3397,8 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { ); if (result == WAIT_OBJECT_0) { // hStopEvent - if (hBgWnd && IsWindow(hBgWnd)) { - SendMessageW(hBgWnd, WM_CLOSE, 0, BSDR_CLOSE); + if (hBgWndLocal && IsWindow(hBgWndLocal)) { + SendMessageW(hBgWndLocal, WM_CLOSE, 0, BSDR_CLOSE); } else { break; } @@ -3378,8 +3437,8 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { Resolve(BlockedShutdownResolution_Cancel); } - if (hBgWnd && IsWindow(hBgWnd)) { - SendMessageW(hBgWnd, WM_CLOSE, 0, BSDR_CLOSE); + if (hBgWndLocal && IsWindow(hBgWndLocal)) { + SendMessageW(hBgWndLocal, WM_CLOSE, 0, BSDR_CLOSE); } break; } @@ -3411,20 +3470,56 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { return 0; } +// LogonUI calls the BSDR interface in the following order: +// (Winlogon) ShutdownWindowsWorkerThread -> LogonUI launch -> BSDR::Start -> add_Resolved -> AddApplication * n (either before or after dlg open) -> +// (Force logoff chosen) -> BSDR::Hide -> get_WasClicked -> BSDR::Stop -> LogonUI exit -> Session teardown (Winlogon exit) void CustomBSDR::Start(LogonUIState state) { + std::unique_lock lock(workerMutex); + if (g_isExiting.load()) { + Wh_Log(L"Mod is unloading!"); + return; + } + if (hThread) { - // This is one-shot, as this mod highly relies on the fact that LogonUI calls the BSDR interface in the following order: - // (Winlogon) ShutdownWindowsWorkerThread -> LogonUI launch -> BSDR::Start -> add_Resolved -> AddApplication * n (either before or after dlg open) -> - // (Force logoff chosen) -> BSDR::Hide -> get_WasClicked -> BSDR::Stop -> LogonUI exit -> Session teardown (Winlogon exit) - Wh_Log(L"CustomBSDR has already started once!"); - Resolve(BlockedShutdownResolution_Force); + DWORD result = WaitForSingleObject(hThread, 0); + if (result != WAIT_OBJECT_0) { + if (result != WAIT_TIMEOUT) { + Wh_Log(L"WaitForSingleObject failed, GLE=%d", GetLastError()); + } else { + Wh_Log(L"CustomBSDR thread is still running"); + } + + lock.unlock(); + Resolve(BlockedShutdownResolution_Force); + return; + } + + CloseHandle(hThread); + hThread = nullptr; + + if (hDesktop) { + CloseDesktop(hDesktop); + hDesktop = nullptr; + } + } + + { + std::lock_guard lock(g_resolvedMutex); + g_resolvedValue = BlockedShutdownResolution_None; + g_wasClicked = false; + } + + if (!ResetEvent(hStopEvent)) { + Wh_Log(L"ResetEvent failed, GLE=%u", GetLastError()); + Resolve(BlockedShutdownResolution_Force, true); return; } + _logonUIState = state; hThread = CreateThread(nullptr, 0, ThreadProc, nullptr, 0, nullptr); if (!hThread) { Wh_Log(L"Failed to create CustomBSDR thread, GLE=%d", GetLastError()); - Resolve(BlockedShutdownResolution_Force); + Resolve(BlockedShutdownResolution_Force, true); return; } Wh_Log(L"Created CustomBSDR thread"); @@ -3462,17 +3557,14 @@ void CustomBSDR::RemoveApplication(UINT appid) { int CustomBSDR::GetScaleFactor() { std::lock_guard lock(pendingAppsMutex); - if (hDlg) { - return GetDpiForWindow(hDlg); - } else { - return GetDpiForSystem(); - } + int dpi = hDlg ? GetDpiForWindow(hDlg) : GetDpiForSystem(); + return MulDiv(dpi, 100, 96); } void CustomBSDR::Hide() { - std::lock_guard lock(pendingAppsMutex); + std::lock_guard lock(hBgWndMutex); if (hBgWnd) { - ShowWindow(hBgWnd, SW_HIDE); + ShowWindowAsync(hBgWnd, SW_HIDE); } } @@ -3756,28 +3848,54 @@ bool IsAuthUxInstalled() { // // Can't think of better appraoch because of the execution sequence mentioned in CustomBSDR::Start // ShutdownWindowsWorkerThread (which uses g_fShutdownResolverDisabled) runs before LogonUI exec so checking live LUI injection status is tricky -// Relying on natural execution will result in detection being one step behind -// (e.g. will detect as LUI not injected on first logoff after mod install) -// -// This approach, derived from the WH tool mod code, also doesn't mess with internal Windhawk configuration +// and will be always one step behind (e.g. will detect as LUI not injected on first logoff after mod install) +// I know the global inclusion key is not everything that affects that injection but this is minimal safeguard anyway +// Let's just hope users don't mess with mod specific advanced settings to force exclude LogonUI.exe lol bool IsLogonUiInjectionEnabled() { // Skip by user choice if (g_noSafetyChecks) { return true; } - // Win10+ winlogon injection is always asynchronous because smss.exe, the parent of winlogon, is a protected process (PPL), so the probe shouldn't block its startup routine - // But if synchronous injection somehow happens, just check for existing LogonUiLoadCheck value (from previous probe) and call it a day - if (g_isInitialThread) { - Wh_Log(L"Synchronously injected into winlogon! Wow!"); - return Wh_GetIntValue(L"LogonUiLoadCheck", -1) != -1; - } - // Skip if AuthUX is installed if (IsAuthUxInstalled()) { return true; } + // Check for the global WH inclusion key (only for non portable) + HKEY hKey; + int res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Windhawk\\Engine\\Settings", 0, KEY_READ, &hKey); + if (res != ERROR_SUCCESS) { + Wh_Log(L"WH inclusion check failed (RegOpenKeyExW), error=%d", res); + return false; + } + + DWORD size = 0; + // WH inclusion key: pipe separated REG_SZ + res = RegQueryValueExW(hKey, L"Include", nullptr, nullptr, nullptr, &size); + if (res != ERROR_SUCCESS && res != ERROR_MORE_DATA) { + Wh_Log(L"WH inclusion check failed (size query), GLE=%d", res); + RegCloseKey(hKey); + return false; + } + + wchar_t* data = new wchar_t[size / sizeof(wchar_t) + 1]; + res = RegQueryValueExW(hKey, L"Include", nullptr, nullptr, (LPBYTE)data, &size); + RegCloseKey(hKey); + if (res != ERROR_SUCCESS) { + Wh_Log(L"WH inclusion check failed, error=%d", res); + delete[] data; + return false; + } + + data[size / sizeof(wchar_t)] = L'\0'; + int isLogonUiThere = wcsstr(_wcsupr(data), L"LOGONUI.EXE") != NULL; + delete[] data; + + if (!isLogonUiThere) { + return false; + } + // Check if Windhawk service is running, to deny the portable version // One of these happens on portable (depending on the Windhawk.exe exit timing): // The active probe below succeeds as WH is still running, but it dies early during the logoff sequence, so actual LUI hook fails @@ -3813,56 +3931,7 @@ bool IsLogonUiInjectionEnabled() { CloseServiceHandle(hService); CloseServiceHandle(hSCM); - Wh_Log(L"Windhawk service running"); - - // Active LogonUI injection probe - STARTUPINFO si = {0}; - si.cb = sizeof(si); - PROCESS_INFORMATION pi = {0}; - - DWORD myPid = GetCurrentProcessId(); - std::wstring cmdline = L"LogonUI.exe /wh-load-check " + std::to_wstring(myPid); - - wchar_t logonUiPath[MAX_PATH]; - UINT len = GetSystemDirectoryW(logonUiPath, MAX_PATH); - if (!len || len >= MAX_PATH - ARRAYSIZE(L"\\LogonUI.exe")) { - return false; - } - wcscat_s(logonUiPath, L"\\LogonUI.exe"); - - Wh_SetIntValue(L"LogonUiLoadCheck", -1); - - if (!CreateProcessW( - logonUiPath, - cmdline.data(), - nullptr, - nullptr, - false, - 0, - nullptr, - nullptr, - &si, - &pi - )) { - Wh_Log(L"LogonUI load check exec failed"); - return false; - } - - // LogonUI normally auto exits when ran with invalid argument, even without this mod's injected code, without initializing the logon controller - DWORD result = WaitForSingleObject(pi.hProcess, 2000); - if (result != WAIT_OBJECT_0) { - // Should exit immediately but just to be safe - Wh_Log(L"LogonUI wait timed out or failed"); - TerminateProcess(pi.hProcess, 0); - } - - CloseHandle(pi.hThread); - CloseHandle(pi.hProcess); - - if ((DWORD)Wh_GetIntValue(L"LogonUiLoadCheck", -1) == myPid + pi.dwProcessId) { - return true; - } - return false; + return true; } WindhawkUtils::SYMBOL_HOOK winlogonExeHooks[] = { @@ -3877,9 +3946,6 @@ WindhawkUtils::SYMBOL_HOOK winlogonExeHooks[] = { }; #pragma endregion Winlogon hooks (disable async logoff) -// https://github.com/ramensoftware/windhawk/wiki/Development-tips -static constexpr size_t OFFSET_SAME_TEB_FLAGS = 0x17EE; // Win64 only - // The mod is being initialized, load settings, hook functions, and do other // initialization stuff if required. BOOL Wh_ModInit() { @@ -3893,7 +3959,6 @@ BOOL Wh_ModInit() { Wh_Log(L"Running in winlogon.exe, but winlogon hooks are disabled"); return FALSE; } - g_isInitialThread = *(USHORT*)((BYTE*)NtCurrentTeb() + OFFSET_SAME_TEB_FLAGS) & 0x0400; HMODULE winlogon = GetModuleHandleW(NULL); if (winlogon) { if (!WindhawkUtils::HookSymbols(winlogon, winlogonExeHooks, ARRAYSIZE(winlogonExeHooks))) { @@ -3917,35 +3982,15 @@ BOOL Wh_ModInit() { return FALSE; } - int checkValue = -1; - int argc; - LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); - if (!argv) { - Wh_Log(L"CommandLineToArgvW failed"); - return FALSE; - } - - for (int i = 1; i < argc - 1; i++) { - if (wcscmp(argv[i], L"/wh-load-check") == 0) { - checkValue = _wtoi(argv[i + 1]); - DWORD pid = GetCurrentProcessId(); - if (checkValue != -1) { - Wh_Log(L"Writing load check value %d+%d=%d...", checkValue, pid, checkValue + pid); - Wh_SetIntValue(L"LogonUiLoadCheck", checkValue + pid); - LocalFree(argv); - return FALSE; // Let LogonUI automatically exit immediately (because it lacks proper args) - } - break; - } - } - - LocalFree(argv); - HMODULE kernelBase = GetModuleHandleW(L"kernelbase.dll"); - CreateEventW_t pCreateEventW = (CreateEventW_t)GetProcAddress(kernelBase, "CreateEventW"); - if (!WindhawkUtils::SetFunctionHook(pCreateEventW, CreateEventW_hook, &CreateEventW_orig)) { - Wh_Log(L"CreateEventW hook failed"); - // not critical, well, there still is a terrrible workaround path + if (kernelBase) { + CreateEventW_t pCreateEventW = (CreateEventW_t)GetProcAddress(kernelBase, "CreateEventW"); + if (!WindhawkUtils::SetFunctionHook(pCreateEventW, CreateEventW_hook, &CreateEventW_orig)) { + Wh_Log(L"CreateEventW hook failed"); + // not critical, well, there still is a terrrible workaround path + } + } else { + Wh_Log(L"GetModuleHandle kernelbase failed??"); } g_hLogonControllerDll = LoadLibraryExW(L"LogonController.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); @@ -3954,6 +3999,8 @@ BOOL Wh_ModInit() { Wh_Log(L"Failed to hook symbols in LogonController.dll"); // Ditto } + } else { + Wh_Log(L"Failed to load LogonController.dll"); } g_hBlockedShutdownDll = LoadLibraryExW(L"Windows.UI.BlockedShutdown.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); @@ -4019,14 +4066,25 @@ void Wh_ModAfterInit() { void Wh_ModUninit() { Wh_Log(L"Uninit"); - if (g_isWinlogon && p_g_fShutdownResolverDisabled) { - *p_g_fShutdownResolverDisabled = g_origResolverDisabledState; + if (g_isWinlogon) { + if (p_g_fShutdownResolverDisabled) { + *p_g_fShutdownResolverDisabled = g_origResolverDisabledState; + } return; } + g_isExiting.store(true); + using namespace CustomBSDR; - if (hThread) { + HANDLE hThreadLocal = nullptr; + { + std::lock_guard lock(workerMutex); + hThreadLocal = hThread; + hThread = nullptr; + } + + if (hThreadLocal) { bool needsResolve; { std::lock_guard lock(g_resolvedMutex); @@ -4039,9 +4097,8 @@ void Wh_ModUninit() { if (hStopEvent) { SetEvent(hStopEvent); } - WaitForSingleObject(hThread, INFINITE); - CloseHandle(hThread); - hThread = nullptr; + WaitForSingleObject(hThreadLocal, INFINITE); + CloseHandle(hThreadLocal); } if (hDesktop) { CloseDesktop(hDesktop); From ca4f073d8d6d1475c08fc6099e4d0c04d7aa6628 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Mon, 31 Aug 2026 16:53:14 +0900 Subject: [PATCH 32/51] Force focus the BSDR window --- mods/win7-bsdr.wh.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index bb34c85c93..897949fed6 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -1783,6 +1783,7 @@ namespace CustomBSDR { std::mutex pendingAppsMutex; // functions + void ActivateWindow(); void CenterWindow(HWND hWnd); bool IsHighContrast(); HBITMAP LoadAlphaBitmap(UINT resourceId, bool forceHardcoded = false); @@ -3123,6 +3124,26 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA return FALSE; } +void CustomBSDR::ActivateWindow() +{ + HWND fgWnd = GetForegroundWindow(); + DWORD myTid = GetCurrentThreadId(); + DWORD fgTid = fgWnd ? GetWindowThreadProcessId(fgWnd, nullptr) : 0; + + BOOL attached = FALSE; + + if (fgTid && fgTid != myTid) { + attached = AttachThreadInput(myTid, fgTid, TRUE); + } + + SetForegroundWindow(hBgWnd); + SetActiveWindow(hBgWnd); + + if (attached) { + AttachThreadInput(myTid, fgTid, FALSE); + } +} + LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: { @@ -3175,6 +3196,10 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA btnSelectedBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_SELECTED); btnSelectedHoverBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_SELECTED_HOVER); } + + if (!isOnSecureDesktop) { + ActivateWindow(); + } return 0; } case WM_PAINT: { From b7f94a4a84e3275a50e3a34900282a8797d74503 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Mon, 31 Aug 2026 17:08:13 +0900 Subject: [PATCH 33/51] Misc change --- mods/win7-bsdr.wh.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 897949fed6..a25dceb069 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -3196,10 +3196,6 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA btnSelectedBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_SELECTED); btnSelectedHoverBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_SELECTED_HOVER); } - - if (!isOnSecureDesktop) { - ActivateWindow(); - } return 0; } case WM_PAINT: { @@ -3410,6 +3406,10 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { CustomBSDR::hBgWnd = hBgWndLocal; } + if (!isOnSecureDesktop) { + ActivateWindow(); + } + HANDLE waitHandles[1] = { hStopEvent }; BOOL bQuit = FALSE; while (TRUE) { From 7ac3a77e5f22ad23db7e505a43f7e8e8842e35f4 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Mon, 31 Aug 2026 17:45:12 +0900 Subject: [PATCH 34/51] Stabilize focus stealing --- mods/win7-bsdr.wh.cpp | 57 +++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index a25dceb069..0838b07ec2 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -93,6 +93,7 @@ exclusion option. Otherwise the logoff sequence portion of the mod will not func #define WM_ADD_APP (WM_APP + 1) #define WM_REMOVE_APP (WM_APP + 2) +#define WM_BSDR_SETFOCUS (WM_APP + 3) #define BSDR_CLASSNAME L"BlockedShutdownResolver_WH" #define BSDR_CLOSE 1337 @@ -1783,7 +1784,6 @@ namespace CustomBSDR { std::mutex pendingAppsMutex; // functions - void ActivateWindow(); void CenterWindow(HWND hWnd); bool IsHighContrast(); HBITMAP LoadAlphaBitmap(UINT resourceId, bool forceHardcoded = false); @@ -3124,26 +3124,6 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA return FALSE; } -void CustomBSDR::ActivateWindow() -{ - HWND fgWnd = GetForegroundWindow(); - DWORD myTid = GetCurrentThreadId(); - DWORD fgTid = fgWnd ? GetWindowThreadProcessId(fgWnd, nullptr) : 0; - - BOOL attached = FALSE; - - if (fgTid && fgTid != myTid) { - attached = AttachThreadInput(myTid, fgTid, TRUE); - } - - SetForegroundWindow(hBgWnd); - SetActiveWindow(hBgWnd); - - if (attached) { - AttachThreadInput(myTid, fgTid, FALSE); - } -} - LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: { @@ -3234,6 +3214,34 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA // Windows 7 doesn't do that either so skip that return 0; } + case WM_BSDR_SETFOCUS: { + HWND fgWnd = GetForegroundWindow(); + DWORD myTid = GetCurrentThreadId(); + DWORD fgTid = fgWnd ? GetWindowThreadProcessId(fgWnd, nullptr) : 0; + bool attached = false; + + // Creating window on the default desktop doesn't allow it to get focus + // So do this workaround + if (!isOnSecureDesktop) { + if (fgTid && fgTid != myTid) { + attached = AttachThreadInput(myTid, fgTid, TRUE); + if (!attached) { + Wh_Log(L"AttachThreadInput failed, GLE=%u", GetLastError()); + } + } + } + + ShowWindow(hWnd, SW_SHOW); + SetForegroundWindow(hWnd); + SetActiveWindow(hWnd); + + if (attached) { + if (!AttachThreadInput(myTid, fgTid, FALSE)) { + Wh_Log(L"AttachThreadInput restore failed, GLE=%u", GetLastError()); + } + } + return 0; + } case WM_CLOSE: { if (lParam != BSDR_CLOSE) { // Deny Alt+F4 or other external window closes (like Windows 7) @@ -3392,7 +3400,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { WS_EX_TOPMOST, BSDR_CLASSNAME, nullptr, - WS_POPUP | WS_VISIBLE, + WS_POPUP, bgOffsetX, bgOffsetY, bgWidth, bgHeight, nullptr, nullptr, @@ -3406,8 +3414,9 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { CustomBSDR::hBgWnd = hBgWndLocal; } - if (!isOnSecureDesktop) { - ActivateWindow(); + if (!PostMessageW(hBgWndLocal, WM_BSDR_SETFOCUS, 0, 0)) { + Wh_Log(L"PostMessageW(WM_BSDR_SETFOCUS) failed, GLE=%u", GetLastError()); + ShowWindow(hBgWndLocal, SW_SHOW); } HANDLE waitHandles[1] = { hStopEvent }; From ddfee4194d8aa8705eaeccb6c26767009c9f5835 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Mon, 31 Aug 2026 18:28:59 +0900 Subject: [PATCH 35/51] Wait before resolving cancel --- mods/win7-bsdr.wh.cpp | 54 ++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 0838b07ec2..c349a20dca 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -2973,29 +2973,9 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA case IDCANCEL: Resolve(BlockedShutdownResolution_Cancel); ShowWindow(hBgWnd, SW_HIDE); - if (!isOnSecureDesktop) { - // If BSDR is forced to show on the default desktop with the Windhawk mod, LogonUI.exe won't exit for some reason on cancel, - // causing issues with subsequent session ends, unless it's killed manually or Ctrl+Alt+Del is pressed once - // It happens because the event created in CLogonController::DoModal never gets fired in this state somehow - // and makes the thread stuck in WaitForSingleObject - // So force signal the event as a not so clean workaround (still better than previous ExitProcess workaround) - // (I still haven't found neither the culprint nor which code signals that event) - HANDLE hDoModalExitEvent = g_hDoModalExitEventDup.load(); - if (hDoModalExitEvent) { - if (!SetEvent(hDoModalExitEvent)) { - Wh_Log(L"SetEvent(DoModal exit) failed, GLE=%u", GetLastError()); - } - } else { - // Event capture failed, oh noes! - // Here comes the old terrible workaround - Sleep(1000); // Make sure the resolve request reaches winlogon - // Note: this does not cause any user-facing issues because there is no visible LogonUI window in the default desktop at this point - // and winlogon has already got out of the mid-logoff state. Subsequent LogonUI launches (lock/C-A-D) are fine too. - Wh_Log(L"Force exiting LogonUI!"); - ExitProcess(0); - } - } - PostMessageW(hBgWnd, WM_CLOSE, 0, BSDR_CLOSE); + // Make sure the resolve request reaches winlogon + // Otherwise, winlogon might just decide to force resolve after LogonUI has fully closed + SetTimer(hWndDlg, 1, 500, nullptr); return TRUE; case IDC_BSDR_FORCE_BTN: ShowWindow(hWarningText, SW_SHOW); @@ -3020,6 +3000,32 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA } break; } + case WM_TIMER: { + KillTimer(hWndDlg, 1); + if (!isOnSecureDesktop) { + // If BSDR is forced to show on the default desktop with the Windhawk mod, LogonUI.exe won't exit for some reason on cancel, + // causing issues with subsequent session ends, unless it's killed manually or Ctrl+Alt+Del is pressed once + // It happens because the event created in CLogonController::DoModal never gets fired in this state somehow + // and makes the thread stuck in WaitForSingleObject + // So force signal the event as a not so clean workaround (still better than previous ExitProcess workaround) + // (I still haven't found neither the culprint nor which code signals that event) + HANDLE hDoModalExitEvent = g_hDoModalExitEventDup.load(); + if (hDoModalExitEvent) { + if (!SetEvent(hDoModalExitEvent)) { + Wh_Log(L"SetEvent(DoModal exit) failed, GLE=%u", GetLastError()); + } + } else { + // Event capture failed, oh noes! + // Here comes the old terrible workaround + // Note: this does not cause any user-facing issues because there is no visible LogonUI window in the default desktop at this point + // and winlogon has already got out of the mid-logoff state. Subsequent LogonUI launches (lock/C-A-D) are fine too. + Wh_Log(L"Force exiting LogonUI!"); + ExitProcess(0); + } + } + PostMessageW(hBgWnd, WM_CLOSE, 0, BSDR_CLOSE); + return TRUE; + } case WM_ADD_APP: { Microsoft::WRL::ComPtr app; app.Attach((IShutdownBlockingApp*)lParam); @@ -3397,7 +3403,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { bgHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN); HWND hBgWndLocal = CreateWindowExW( - WS_EX_TOPMOST, + 0,//WS_EX_TOPMOST, BSDR_CLASSNAME, nullptr, WS_POPUP, From ba12cc1c582ef308e9ad4c6f896128e52fd6c9d2 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Mon, 31 Aug 2026 18:29:39 +0900 Subject: [PATCH 36/51] Revert debug leftover --- mods/win7-bsdr.wh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index c349a20dca..3853222df1 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -3403,7 +3403,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { bgHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN); HWND hBgWndLocal = CreateWindowExW( - 0,//WS_EX_TOPMOST, + WS_EX_TOPMOST, BSDR_CLASSNAME, nullptr, WS_POPUP, From b28574a026fb48a9089813b18dffd41ea7be5f73 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Tue, 1 Sep 2026 00:56:38 +0900 Subject: [PATCH 37/51] Misc fix --- mods/win7-bsdr.wh.cpp | 80 +++++++++++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 25 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 3853222df1..fa2c5d31b4 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -97,6 +97,7 @@ exclusion option. Otherwise the logoff sequence portion of the mod will not func #define BSDR_CLASSNAME L"BlockedShutdownResolver_WH" #define BSDR_CLOSE 1337 +#define BSDR_CANCEL_TIMER 1 #define RETURN_IF_FAILED(x) do { \ HRESULT hr = (x); \ @@ -1782,6 +1783,7 @@ namespace CustomBSDR { std::mutex workerMutex; std::mutex hBgWndMutex; std::mutex pendingAppsMutex; + std::mutex cancelMutex; // functions void CenterWindow(HWND hWnd); @@ -1792,6 +1794,7 @@ namespace CustomBSDR { void CreateAppTileControls(IShutdownBlockingApp* blockingApp, bool noUpdateLayout = false); void RemoveAppTileControls(UINT appId, bool noUpdateLayout = false); void UpdateAppListLayout(); + void Cancel(); LRESULT CALLBACK ButtonSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData); LRESULT CALLBACK AppListSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData); INT_PTR CALLBACK DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); @@ -1819,6 +1822,7 @@ namespace CustomBSDR { int minHeight = 0; bool paintedFirstFrame = false; bool isOnSecureDesktop = true; + bool isCanceling = false; // app list data stuff struct AppTile { @@ -2717,6 +2721,35 @@ void CustomBSDR::UpdateAppListLayout() { RedrawWindow(hDlg, nullptr, nullptr, RDW_FRAME | RDW_INVALIDATE); } +void CustomBSDR::Cancel() { + if (!isOnSecureDesktop) { + // If BSDR is forced to show on the default desktop with the Windhawk mod, LogonUI.exe won't exit for some reason on cancel, + // causing issues with subsequent session ends, unless it's killed manually or Ctrl+Alt+Del is pressed once + // It happens because the event created in CLogonController::DoModal never gets fired in this state somehow + // and makes the thread stuck in WaitForSingleObject + // So force signal the event as a not so clean workaround (still better than previous ExitProcess workaround) + // (I still haven't found neither the culprint nor which code signals that event) + HANDLE hDoModalExitEvent = g_hDoModalExitEventDup.load(); + if (hDoModalExitEvent) { + if (!SetEvent(hDoModalExitEvent)) { + Wh_Log(L"SetEvent(DoModal exit) failed, GLE=%u", GetLastError()); + } + } else { + // Event capture failed, oh noes! + // Here comes the old terrible workaround + // Note: this does not cause any user-facing issues because there is no visible LogonUI window in the default desktop at this point + // and winlogon has already got out of the mid-logoff state. Subsequent LogonUI launches (lock/C-A-D) are fine too. + Wh_Log(L"Force exiting LogonUI!"); + ExitProcess(0); + } + } + { + std::lock_guard lock(cancelMutex); + isCanceling = false; + } + PostMessageW(hBgWnd, WM_CLOSE, 0, BSDR_CLOSE); +} + INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_INITDIALOG: { @@ -2769,6 +2802,10 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA // so handling per-monitor DPI is not much trouble. Screenshotting works fine // Runtime DPI change might be problematic but the BSDR window isn't movable and the Settings app isn't accessible // during logoff phase either so it's low priority + // Note 2: when a window exactly covers the whole virtual screen, it uses the primary monitor's DPI regardless of proportions, + // which is good for us because the dialog is only shown on the primary monitor + // Even a 1px offset disables that behavior and makes the window use DPI of the monitor the window is primarily in + // This behavior is the same for per-monitor V1, V2, and also MonitorFromWindow/MonitorFromRect int dpi = GetDpiForWindow(hWndDlg); int itemHeight = MulDiv(83, dpi, 96); @@ -2971,11 +3008,20 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA case WM_COMMAND: { switch (LOWORD(wParam)) { case IDCANCEL: + { + std::lock_guard lock(cancelMutex); + isCanceling = true; + } Resolve(BlockedShutdownResolution_Cancel); ShowWindow(hBgWnd, SW_HIDE); // Make sure the resolve request reaches winlogon // Otherwise, winlogon might just decide to force resolve after LogonUI has fully closed - SetTimer(hWndDlg, 1, 500, nullptr); + if (!SetTimer(hWndDlg, BSDR_CANCEL_TIMER, 500, nullptr)) { + Wh_Log(L"SetTimer failed, GLE=%d", GetLastError()); + // Fallback to sleep + Sleep(500); + Cancel(); + } return TRUE; case IDC_BSDR_FORCE_BTN: ShowWindow(hWarningText, SW_SHOW); @@ -3001,29 +3047,11 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA break; } case WM_TIMER: { - KillTimer(hWndDlg, 1); - if (!isOnSecureDesktop) { - // If BSDR is forced to show on the default desktop with the Windhawk mod, LogonUI.exe won't exit for some reason on cancel, - // causing issues with subsequent session ends, unless it's killed manually or Ctrl+Alt+Del is pressed once - // It happens because the event created in CLogonController::DoModal never gets fired in this state somehow - // and makes the thread stuck in WaitForSingleObject - // So force signal the event as a not so clean workaround (still better than previous ExitProcess workaround) - // (I still haven't found neither the culprint nor which code signals that event) - HANDLE hDoModalExitEvent = g_hDoModalExitEventDup.load(); - if (hDoModalExitEvent) { - if (!SetEvent(hDoModalExitEvent)) { - Wh_Log(L"SetEvent(DoModal exit) failed, GLE=%u", GetLastError()); - } - } else { - // Event capture failed, oh noes! - // Here comes the old terrible workaround - // Note: this does not cause any user-facing issues because there is no visible LogonUI window in the default desktop at this point - // and winlogon has already got out of the mid-logoff state. Subsequent LogonUI launches (lock/C-A-D) are fine too. - Wh_Log(L"Force exiting LogonUI!"); - ExitProcess(0); - } - } - PostMessageW(hBgWnd, WM_CLOSE, 0, BSDR_CLOSE); + if (wParam != BSDR_CANCEL_TIMER) + break; + + KillTimer(hWndDlg, BSDR_CANCEL_TIMER); + Cancel(); return TRUE; } case WM_ADD_APP: { @@ -3609,7 +3637,9 @@ void CustomBSDR::Hide() { } void CustomBSDR::Stop() { - if (hStopEvent) { + std::lock_guard lock(cancelMutex); + + if (!isCanceling && hStopEvent) { SetEvent(hStopEvent); } } From 8f286394734fbed97f487534d76516e7a04a459a Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Tue, 1 Sep 2026 02:46:28 +0900 Subject: [PATCH 38/51] Apply fixes --- mods/win7-bsdr.wh.cpp | 141 ++++++++++++++++++++++-------------------- 1 file changed, 75 insertions(+), 66 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index fa2c5d31b4..72ae153d9f 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -78,7 +78,6 @@ exclusion option. Otherwise the logoff sequence portion of the mod will not func #include #include #include -#include #include #include #include @@ -86,7 +85,6 @@ exclusion option. Otherwise the logoff sequence portion of the mod will not func #include #include #include -#include #include #include #include @@ -1730,6 +1728,7 @@ using ResolvedHandler = ABI::Windows::Foundation::ITypedEventHandler resolvedLocal; @@ -1845,13 +1844,12 @@ HRESULT LoadImageWithWIC(IWICImagingFactory* pWICImagingFactory, IStream* pStrea HRESULT hr = S_OK; - Microsoft::WRL::ComPtr spWICImagingFactory(pWICImagingFactory); - if (!spWICImagingFactory.Get()) { - hr = E_ABORT; + if (!pWICImagingFactory) { + hr = E_POINTER; } if (SUCCEEDED(hr)) { Microsoft::WRL::ComPtr spDecoder; - hr = spWICImagingFactory->CreateDecoderFromStream( + hr = pWICImagingFactory->CreateDecoderFromStream( pStream, &GUID_VendorMicrosoftBuiltIn, WICDecodeMetadataCacheOnDemand, &spDecoder); if (SUCCEEDED(hr)) { Microsoft::WRL::ComPtr spBitmapFrameDecode; @@ -1918,14 +1916,13 @@ HRESULT ConvertWICBitmapToHBITMAP(IWICImagingFactory* pWICImagingFactory, IWICBi *phbmImage = nullptr; HRESULT hr = S_OK; - Microsoft::WRL::ComPtr spWICImagingFactory = pWICImagingFactory; - if (!spWICImagingFactory.Get()) { - hr = E_ABORT; + if (!pWICImagingFactory) { + hr = E_POINTER; } if (SUCCEEDED(hr)) { Microsoft::WRL::ComPtr spBitmapSourceConverted; hr = ConvertWICBitmapPixelFormat( - spWICImagingFactory.Get(), pWICBitmapSource, GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone, &spBitmapSourceConverted); + pWICImagingFactory, pWICBitmapSource, GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone, &spBitmapSourceConverted); if (SUCCEEDED(hr)) { hr = Convert32bppWICBitmapSourceToHBITMAP(spBitmapSourceConverted.Get(), phbmImage); } @@ -1934,13 +1931,13 @@ HRESULT ConvertWICBitmapToHBITMAP(IWICImagingFactory* pWICImagingFactory, IWICBi return hr; } -HRESULT GetBitmapFromRandomStream(IWICImagingFactory* pWICImagingFactory, Microsoft::WRL::ComPtr stream, HBITMAP* outBitmap) { +HRESULT GetBitmapFromRandomStream(IWICImagingFactory* pWICImagingFactory, ABI::Windows::Storage::Streams::IRandomAccessStream* stream, HBITMAP* outBitmap) { if (!pWICImagingFactory) { - return E_ABORT; + return E_POINTER; } Microsoft::WRL::ComPtr spStream; - RETURN_IF_FAILED(CreateStreamOverRandomAccessStream(stream.Get(), IID_PPV_ARGS(&spStream))); + RETURN_IF_FAILED(CreateStreamOverRandomAccessStream(stream, IID_PPV_ARGS(&spStream))); Microsoft::WRL::ComPtr spWICBitmapSource; RETURN_IF_FAILED(LoadImageWithWIC(pWICImagingFactory, spStream.Get(), &spWICBitmapSource)); @@ -3016,10 +3013,10 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA ShowWindow(hBgWnd, SW_HIDE); // Make sure the resolve request reaches winlogon // Otherwise, winlogon might just decide to force resolve after LogonUI has fully closed - if (!SetTimer(hWndDlg, BSDR_CANCEL_TIMER, 500, nullptr)) { + if (!SetTimer(hWndDlg, BSDR_CANCEL_TIMER, 700, nullptr)) { Wh_Log(L"SetTimer failed, GLE=%d", GetLastError()); // Fallback to sleep - Sleep(500); + Sleep(700); Cancel(); } return TRUE; @@ -3163,27 +3160,25 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA case WM_CREATE: { HWND hDlgLocal = nullptr; if (!g_isUsingHardcodedRes && g_hResDll) { - hDlgLocal = CreateDialogParamW(g_hResDll, MAKEINTRESOURCEW(IDD_BSDR_DLG), hWnd, DlgProc, lParam); + hDlgLocal = CreateDialogParamW(g_hResDll, MAKEINTRESOURCEW(IDD_BSDR_DLG), hWnd, DlgProc, 0); if (!hDlgLocal || !IsWindow(hDlgLocal) || dlgInitFailed) { Wh_Log(L"CreateDialogParamW failed: %d", GetLastError()); if (hDlgLocal && IsWindow(hDlgLocal)) { DestroyWindow(hDlgLocal); } // Retry with hardcoded one - hDlgLocal = CreateDialogIndirectParamW(nullptr, reinterpret_cast(&RES_DIALOG), hWnd, DlgProc, lParam); + hDlgLocal = CreateDialogIndirectParamW(nullptr, reinterpret_cast(&RES_DIALOG), hWnd, DlgProc, 0); } } else { - hDlgLocal = CreateDialogIndirectParamW(nullptr, reinterpret_cast(&RES_DIALOG), hWnd, DlgProc, lParam); + hDlgLocal = CreateDialogIndirectParamW(nullptr, reinterpret_cast(&RES_DIALOG), hWnd, DlgProc, 0); } if (hDlgLocal && IsWindow(hDlgLocal) && !dlgInitFailed) { ShowWindow(hDlgLocal, SW_SHOW); } else { // Bail out Wh_Log(L"Dialog creation failed!!"); - // Note: Windows also does force logoff when the BSDR is not available, e.g. the BSDR DLL is missing, on Server Core, etc. - // Probably this is better than leaving a system that never shuts down (if the mod is repeatedly failing) - Resolve(BlockedShutdownResolution_Force); - return 0; + // Make CreateWindowExW fail + return -1; } if (!IsHighContrast()) { @@ -3282,11 +3277,6 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA return 0; } - for (auto& tile : appTiles) { - if (tile.hIconBitmap) DeleteObject(tile.hIconBitmap); - } - appTiles.clear(); - HWND hDlgLocal = nullptr; { std::lock_guard lock(pendingAppsMutex); @@ -3299,7 +3289,7 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA MSG msg; while (PeekMessageW(&msg, hDlgLocal, WM_ADD_APP, WM_ADD_APP, PM_REMOVE)) { if (msg.lParam) { - reinterpret_cast(msg.lParam)->Release(); + reinterpret_cast(msg.lParam)->Release(); } } @@ -3317,6 +3307,16 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA DestroyWindow(hBgWndLocal); } + for (auto& tile : appTiles) { + if (tile.hIconBitmap) DeleteObject(tile.hIconBitmap); + } + appTiles.clear(); + + { + std::lock_guard lock(pendingAppsMutex); + pendingApps->clear(); + } + if (bgBitmap) { DeleteObject(bgBitmap); bgBitmap = nullptr; @@ -3365,8 +3365,6 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA hYesButton = nullptr; hNoButton = nullptr; hHoverButton = nullptr; - hTitleFont = nullptr; - hDescFont = nullptr; dlgInitFailed = false; bgOffsetX = 0; @@ -3411,7 +3409,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { HRESULT hrIf = CoCreateInstance(CLSID_WICImagingFactory2, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spWICFactory)); if (FAILED(hrIf)) { - Wh_Log(L"CoCreateInstance(CLSID_WICImagingFactory2) failed, HR=%d", hrIf); + Wh_Log(L"CoCreateInstance(CLSID_WICImagingFactory2) failed, HR=%08X", hrIf); // Continue without icons } @@ -3532,6 +3530,8 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { if (ret != 0) { Wh_Log(L"CustomBSDR thread init failed"); + // Note: Windows also does force logoff when the BSDR is not available, e.g. the BSDR DLL is missing, on Server Core, etc. + // Probably this is better than leaving a system that never shuts down (if the mod is repeatedly failing) Resolve(BlockedShutdownResolution_Force); } @@ -3542,7 +3542,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { // (Winlogon) ShutdownWindowsWorkerThread -> LogonUI launch -> BSDR::Start -> add_Resolved -> AddApplication * n (either before or after dlg open) -> // (Force logoff chosen) -> BSDR::Hide -> get_WasClicked -> BSDR::Stop -> LogonUI exit -> Session teardown (Winlogon exit) void CustomBSDR::Start(LogonUIState state) { - std::unique_lock lock(workerMutex); + std::lock_guard lock(workerMutex); if (g_isExiting.load()) { Wh_Log(L"Mod is unloading!"); return; @@ -3557,8 +3557,11 @@ void CustomBSDR::Start(LogonUIState state) { Wh_Log(L"CustomBSDR thread is still running"); } - lock.unlock(); - Resolve(BlockedShutdownResolution_Force); + { + std::lock_guard resolvedLock(g_resolvedMutex); + // Don't touch the existing session's g_Resolve, and just reject (force resolve) the add_Resolved call that comes after this call + g_rejectNextResolved = true; + } return; } @@ -3666,33 +3669,26 @@ long __fastcall BlockedShutdownUXImpl_get_WasClicked_hook(void* thisPtr, unsigne Wh_Log(L"BlockedShutdownUXImpl::get_WasClicked"); { std::lock_guard lock(g_resolvedMutex); + // If this is false, resolving with cancel somehow makes winlogon lock the session after stopping BSDR + // which is not wanted in any cases (no version of stock BSDR locks the screen!) *wasClicked = g_wasClicked; } return S_OK; } long __fastcall BlockedShutdownUXImpl_AddApplication_hook(void* thisPtr, IShutdownBlockingApp* blockingApp) { - std::wstring logMessage = L"BlockedShutdownUXImpl::AddApplication, AppId="; UINT appId = 0; - if (SUCCEEDED(blockingApp->get_Id(&appId))) { - logMessage += std::to_wstring(appId); - } BOOLEAN isBlocking = FALSE; - if (SUCCEEDED(blockingApp->get_IsBlocking(&isBlocking))) { - logMessage += L", IsBlocking=" + std::wstring(isBlocking ? L"true" : L"false"); - } HSTRING caption, blockReason; - if (SUCCEEDED(blockingApp->get_Caption(&caption))) { - // Log the caption of the blocking app - logMessage += L", Caption=" + std::wstring(WindowsGetStringRawBuffer(caption, NULL)); - WindowsDeleteString(caption); - } - if (SUCCEEDED(blockingApp->get_BlockReason(&blockReason))) { - // Log the block reason of the blocking app - logMessage += L", BlockReason=" + std::wstring(WindowsGetStringRawBuffer(blockReason, NULL)); - WindowsDeleteString(blockReason); - } - Wh_Log(L"%s", logMessage.c_str()); + blockingApp->get_Id(&appId); + blockingApp->get_IsBlocking(&isBlocking); + blockingApp->get_Caption(&caption); + blockingApp->get_BlockReason(&blockReason); + const wchar_t* captionStr = WindowsGetStringRawBuffer(caption, NULL); + const wchar_t* blockReasonStr = WindowsGetStringRawBuffer(blockReason, NULL); + Wh_Log(L"BlockedShutdownUXImpl::AddApplication, AppId=%u, IsBlocking=%d, Caption=%s, BlockReason=%s", appId, isBlocking, captionStr, blockReasonStr); + WindowsDeleteString(blockReason); + WindowsDeleteString(caption); CustomBSDR::AddApplication(blockingApp); return S_OK; @@ -3709,15 +3705,20 @@ long __fastcall BlockedShutdownUXImpl_add_Resolved_hook(void* thisPtr, ABI::Wind if (!eventHandler || !eventToken) return E_POINTER; - eventToken->value = 0; - BlockedShutdownResolution resolvedValueLocal; { std::lock_guard lock(g_resolvedMutex); - resolvedValueLocal = g_resolvedValue; + if (g_rejectNextResolved) { + g_rejectNextResolved = false; + resolvedValueLocal = BlockedShutdownResolution_Force; + eventToken->value = 1; + } else { + resolvedValueLocal = g_resolvedValue; + eventToken->value = 0; - if (resolvedValueLocal == BlockedShutdownResolution_None) { - g_Resolved = eventHandler; + if (resolvedValueLocal == BlockedShutdownResolution_None) { + g_Resolved = eventHandler; + } } } @@ -3758,7 +3759,7 @@ WindhawkUtils::SYMBOL_HOOK blockedShutdownHooks[] = { { L"public: virtual long __cdecl BlockedShutdownUXImpl::Start(struct Windows::Internal::UI::Logon::Controller::IUserSettingManager *,struct Windows::Internal::UI::Logon::Controller::ILogonUIStateInfo *)", }, - (void**)nullptr, // Cast is not needed on WH 1.7+; only needed for WH 1.6.1 and below + (void**)nullptr, // Cast is not needed on WH 1.7+; only needed for WH 1.6.1 (which the CI still checks at the time of submitting this) and below (void*)BlockedShutdownUXImpl_Start_hook, FALSE }, @@ -3886,8 +3887,6 @@ HANDLE WINAPI CreateEventW_hook(LPSECURITY_ATTRIBUTES lpEventAttributes, WINBOOL #pragma region Winlogon hooks (disable async logoff) bool g_isWinlogon = false; bool g_noSafetyChecks = false; -// It's thread local to allow probing on further setting change -thread_local bool g_isInitialThread = false; int* p_g_fShutdownResolverDisabled = nullptr; int g_origResolverDisabledState = 0; @@ -3920,7 +3919,8 @@ bool IsAuthUxInstalled() { // ShutdownWindowsWorkerThread (which uses g_fShutdownResolverDisabled) runs before LogonUI exec so checking live LUI injection status is tricky // and will be always one step behind (e.g. will detect as LUI not injected on first logoff after mod install) // I know the global inclusion key is not everything that affects that injection but this is minimal safeguard anyway -// Let's just hope users don't mess with mod specific advanced settings to force exclude LogonUI.exe lol +// Note: the global include key is honored over the global exclusions. Mod specific exclusions have more priority but checking it overcomplicates stuff +// So let's just hope users don't mess with mod specific advanced settings to force exclude LogonUI.exe lol bool IsLogonUiInjectionEnabled() { // Skip by user choice if (g_noSafetyChecks) { @@ -4055,9 +4055,13 @@ BOOL Wh_ModInit() { HMODULE kernelBase = GetModuleHandleW(L"kernelbase.dll"); if (kernelBase) { CreateEventW_t pCreateEventW = (CreateEventW_t)GetProcAddress(kernelBase, "CreateEventW"); - if (!WindhawkUtils::SetFunctionHook(pCreateEventW, CreateEventW_hook, &CreateEventW_orig)) { - Wh_Log(L"CreateEventW hook failed"); - // not critical, well, there still is a terrrible workaround path + if (pCreateEventW) { + if (!WindhawkUtils::SetFunctionHook(pCreateEventW, CreateEventW_hook, &CreateEventW_orig)) { + Wh_Log(L"CreateEventW hook failed"); + // not critical, well, there still is a terrrible workaround path + } + } else { + Wh_Log(L"GetProcAddress CreateEventW failed??"); } } else { Wh_Log(L"GetModuleHandle kernelbase failed??"); @@ -4118,7 +4122,7 @@ BOOL Wh_ModInit() { return TRUE; } -void Wh_ModAfterInit() { +void ApplyResolverDisabledState() { if (g_isWinlogon && p_g_fShutdownResolverDisabled) { g_origResolverDisabledState = *p_g_fShutdownResolverDisabled; if (g_origResolverDisabledState) { // Maybe the allowblockingappsatshutdown registry is set @@ -4133,6 +4137,10 @@ void Wh_ModAfterInit() { } } +void Wh_ModAfterInit() { + ApplyResolverDisabledState(); +} + void Wh_ModUninit() { Wh_Log(L"Uninit"); @@ -4199,6 +4207,7 @@ void Wh_ModUninit() { } BOOL Wh_ModSettingsChanged(BOOL* bReload) { + *bReload = FALSE; if (g_isWinlogon) { if (!Wh_GetIntSetting(L"disableAsyncLogoff")) { // The global injection is enabled or disabled, need to unload the mod @@ -4212,7 +4221,7 @@ BOOL Wh_ModSettingsChanged(BOOL* bReload) { *p_g_fShutdownResolverDisabled = g_origResolverDisabledState; } g_noSafetyChecks = newNoSafetyChecks; - Wh_ModAfterInit(); + ApplyResolverDisabledState(); } } } From 60659ab4643aa639c4eda9832fc5669473adda3b Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Tue, 1 Sep 2026 11:18:47 +0900 Subject: [PATCH 39/51] Apply fixes --- mods/win7-bsdr.wh.cpp | 63 +++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 72ae153d9f..e69777515e 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -1820,6 +1820,7 @@ namespace CustomBSDR { int totalContentHeight = 0; int minHeight = 0; bool paintedFirstFrame = false; + bool isHighContrast = false; bool isOnSecureDesktop = true; bool isCanceling = false; @@ -2118,7 +2119,7 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { } void CustomBSDR::DrawSeparator(LPDRAWITEMSTRUCT pDIS) { - if (separatorBitmap && !IsHighContrast()) { + if (separatorBitmap && !isHighContrast) { int width = pDIS->rcItem.right - pDIS->rcItem.left; int height = pDIS->rcItem.bottom - pDIS->rcItem.top; @@ -2199,7 +2200,6 @@ void CustomBSDR::DrawSeparator(LPDRAWITEMSTRUCT pDIS) { void CustomBSDR::DrawButton(HDC hdc, LPDRAWITEMSTRUCT pDIS) { bool isPressed = (pDIS->itemState & ODS_SELECTED); bool isFocused = (pDIS->itemState & ODS_FOCUS); - bool isHighContrast = IsHighContrast(); RECT rcButton = pDIS->rcItem; @@ -2381,7 +2381,7 @@ LRESULT CALLBACK CustomBSDR::ButtonSubclassProc(HWND hWnd, UINT uMsg, WPARAM wPa LRESULT CALLBACK CustomBSDR::AppListSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) { switch (uMsg) { case WM_CTLCOLORSTATIC: { - if (!IsHighContrast()) { + if (!isHighContrast) { SetBkMode((HDC)wParam, TRANSPARENT); SetStretchBltMode((HDC)wParam, HALFTONE); @@ -2872,6 +2872,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA } SystemParametersInfoW(SPI_GETWHEELSCROLLLINES, 0, &scrollLines, 0); + isHighContrast = IsHighContrast(); SetWindowSubclass(hForceButton, ButtonSubclassProc, 0, 0); LRESULT forceButtonUIState = SendMessageW(hForceButton, WM_QUERYUISTATE, 0, 0); @@ -2949,12 +2950,12 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA return TRUE; } case WM_CTLCOLORDLG: // make dialog transparent - if (!IsHighContrast()) { + if (!isHighContrast) { return (INT_PTR)GetStockObject(NULL_BRUSH); } break; case WM_CTLCOLORSTATIC: // make (direct) children bg transparent and text white - if (!IsHighContrast()) { + if (!isHighContrast) { SetBkMode((HDC)wParam, TRANSPARENT); SetStretchBltMode((HDC)wParam, HALFTONE); SetTextColor((HDC)wParam, RGB(255, 255, 255)); @@ -2979,7 +2980,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA return TRUE; } case WM_ERASEBKGND: - if (!IsHighContrast()) { + if (!isHighContrast) { HDC hdc = (HDC)wParam; RECT rcDlg; GetClientRect(hWndDlg, &rcDlg); @@ -3015,8 +3016,6 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA // Otherwise, winlogon might just decide to force resolve after LogonUI has fully closed if (!SetTimer(hWndDlg, BSDR_CANCEL_TIMER, 700, nullptr)) { Wh_Log(L"SetTimer failed, GLE=%d", GetLastError()); - // Fallback to sleep - Sleep(700); Cancel(); } return TRUE; @@ -3148,8 +3147,13 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA case WM_SETTINGCHANGE: { if (wParam == SPI_SETWHEELSCROLLLINES) { SystemParametersInfoW(SPI_GETWHEELSCROLLLINES, 0, &scrollLines, 0); + return TRUE; } - return TRUE; + if (wParam == SPI_SETHIGHCONTRAST) { + isHighContrast = IsHighContrast(); + return TRUE; + } + break; } } return FALSE; @@ -3181,7 +3185,7 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA return -1; } - if (!IsHighContrast()) { + if (!isHighContrast) { // Screenshot desktop and dim it // No DPI virtualization involved because LogonUI is per-monitor scaled by manifest HDC hDC = GetDC(nullptr); @@ -3679,14 +3683,15 @@ long __fastcall BlockedShutdownUXImpl_get_WasClicked_hook(void* thisPtr, unsigne long __fastcall BlockedShutdownUXImpl_AddApplication_hook(void* thisPtr, IShutdownBlockingApp* blockingApp) { UINT appId = 0; BOOLEAN isBlocking = FALSE; - HSTRING caption, blockReason; + HSTRING caption = nullptr, blockReason = nullptr; blockingApp->get_Id(&appId); blockingApp->get_IsBlocking(&isBlocking); - blockingApp->get_Caption(&caption); - blockingApp->get_BlockReason(&blockReason); - const wchar_t* captionStr = WindowsGetStringRawBuffer(caption, NULL); - const wchar_t* blockReasonStr = WindowsGetStringRawBuffer(blockReason, NULL); - Wh_Log(L"BlockedShutdownUXImpl::AddApplication, AppId=%u, IsBlocking=%d, Caption=%s, BlockReason=%s", appId, isBlocking, captionStr, blockReasonStr); + HRESULT hrCaption = blockingApp->get_Caption(&caption); + HRESULT hrReason = blockingApp->get_BlockReason(&blockReason); + Wh_Log(L"BlockedShutdownUXImpl::AddApplication, AppId=%u, IsBlocking=%d, Caption=%s, BlockReason=%s", + appId, isBlocking, + SUCCEEDED(hrCaption) ? WindowsGetStringRawBuffer(caption, nullptr) : L"", + SUCCEEDED(hrReason) ? WindowsGetStringRawBuffer(blockReason, nullptr) : L""); WindowsDeleteString(blockReason); WindowsDeleteString(caption); @@ -3920,7 +3925,9 @@ bool IsAuthUxInstalled() { // and will be always one step behind (e.g. will detect as LUI not injected on first logoff after mod install) // I know the global inclusion key is not everything that affects that injection but this is minimal safeguard anyway // Note: the global include key is honored over the global exclusions. Mod specific exclusions have more priority but checking it overcomplicates stuff +// (Checking for mod specific exclusion needs checking two keys, and asking users to add LogonUI to the mod specific inclusion is unnecessary and too much burden) // So let's just hope users don't mess with mod specific advanced settings to force exclude LogonUI.exe lol +// Note 2: applying the global inclusion setting fully restarts Windhawk and reloads every mods; there's no need to tell users to disable and reenable the mod bool IsLogonUiInjectionEnabled() { // Skip by user choice if (g_noSafetyChecks) { @@ -4141,13 +4148,8 @@ void Wh_ModAfterInit() { ApplyResolverDisabledState(); } -void Wh_ModUninit() { - Wh_Log(L"Uninit"); - +void Wh_ModBeforeUninit() { if (g_isWinlogon) { - if (p_g_fShutdownResolverDisabled) { - *p_g_fShutdownResolverDisabled = g_origResolverDisabledState; - } return; } @@ -4171,16 +4173,29 @@ void Wh_ModUninit() { if (needsResolve) { Resolve(BlockedShutdownResolution_Cancel); } - if (hStopEvent) { SetEvent(hStopEvent); } + WaitForSingleObject(hThreadLocal, INFINITE); CloseHandle(hThreadLocal); } if (hDesktop) { CloseDesktop(hDesktop); } +} + +void Wh_ModUninit() { + Wh_Log(L"Uninit"); + + if (g_isWinlogon) { + if (p_g_fShutdownResolverDisabled) { + *p_g_fShutdownResolverDisabled = g_origResolverDisabledState; + } + return; + } + + using namespace CustomBSDR; HANDLE hDoModalExitEventDup = g_hDoModalExitEventDup.exchange(nullptr, std::memory_order_acq_rel); if (hDoModalExitEventDup) { @@ -4227,5 +4242,7 @@ BOOL Wh_ModSettingsChanged(BOOL* bReload) { } // resDllPath: only used by LogonUI during shutdown sequence which is unlikely timing for a settings change // and reloading already loaded resources/dialog etc. is tedious so just ignore it + // Note: LogonUI just exits when idle. It isn't even running most of the time; + // it's more likely to be not running when the user changes the mod settings from the WH UI return TRUE; } From 4bf78399341c9fd2693a8059f1ae98f1fdf025f8 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Wed, 2 Sep 2026 03:06:24 +0900 Subject: [PATCH 40/51] Make high contrast more accurate, and fallback to HC when bitmaps are missing --- mods/win7-bsdr.wh.cpp | 94 ++++++++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 38 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index e69777515e..7d80ee45d4 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -2203,6 +2203,8 @@ void CustomBSDR::DrawButton(HDC hdc, LPDRAWITEMSTRUCT pDIS) { RECT rcButton = pDIS->rcItem; + bool drewAsHighContrast = isHighContrast; + if (isHighContrast) { UINT uState = DFCS_BUTTONPUSH; if (isPressed) @@ -2305,6 +2307,7 @@ void CustomBSDR::DrawButton(HDC hdc, LPDRAWITEMSTRUCT pDIS) { if (isPressed) uState |= DFCS_PUSHED; DrawFrameControl(hdc, &rcButton, DFC_BUTTON, uState); + drewAsHighContrast = true; } } @@ -2312,7 +2315,7 @@ void CustomBSDR::DrawButton(HDC hdc, LPDRAWITEMSTRUCT pDIS) { GetWindowTextW(pDIS->hwndItem, buttonText, _countof(buttonText)); if (wcslen(buttonText) > 0) { - if (!isHighContrast) { + if (!drewAsHighContrast) { SetBkMode(hdc, TRANSPARENT); SetTextColor(hdc, RGB(255, 255, 255)); } else { @@ -2326,7 +2329,7 @@ void CustomBSDR::DrawButton(HDC hdc, LPDRAWITEMSTRUCT pDIS) { DrawTextW(hdc, buttonText, -1, &rcButton, format); } - if (isHighContrast && isFocused && !GetPropW(pDIS->hwndItem, L"CustomBSDR_HideFocus")) { + if (drewAsHighContrast && isFocused && !GetPropW(pDIS->hwndItem, L"CustomBSDR_HideFocus")) { int cxEdge = 2 * GetSystemMetrics(SM_CXEDGE); int cxBorder = GetSystemMetrics(SM_CXBORDER) + cxEdge; int cyEdge = 2 * GetSystemMetrics(SM_CYEDGE); @@ -2399,9 +2402,11 @@ LRESULT CALLBACK CustomBSDR::AppListSubclassProc(HWND hWnd, UINT uMsg, WPARAM wP } else { SetTextColor((HDC)wParam, RGB(255, 255, 255)); } - - return (INT_PTR)GetStockObject(NULL_BRUSH); + } else { + SetBkMode((HDC)wParam, OPAQUE); + SetBkColor((HDC)wParam, GetSysColor(COLOR_BTNFACE)); } + return (INT_PTR)GetStockObject(NULL_BRUSH); break; } case WM_NCDESTROY: { @@ -2744,7 +2749,6 @@ void CustomBSDR::Cancel() { std::lock_guard lock(cancelMutex); isCanceling = false; } - PostMessageW(hBgWnd, WM_CLOSE, 0, BSDR_CLOSE); } INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { @@ -2872,7 +2876,6 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA } SystemParametersInfoW(SPI_GETWHEELSCROLLLINES, 0, &scrollLines, 0); - isHighContrast = IsHighContrast(); SetWindowSubclass(hForceButton, ButtonSubclassProc, 0, 0); LRESULT forceButtonUIState = SendMessageW(hForceButton, WM_QUERYUISTATE, 0, 0); @@ -2949,11 +2952,6 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA CenterWindow(hWndDlg); return TRUE; } - case WM_CTLCOLORDLG: // make dialog transparent - if (!isHighContrast) { - return (INT_PTR)GetStockObject(NULL_BRUSH); - } - break; case WM_CTLCOLORSTATIC: // make (direct) children bg transparent and text white if (!isHighContrast) { SetBkMode((HDC)wParam, TRANSPARENT); @@ -2965,7 +2963,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWndDlg, &ps); - if (bgBitmap) { + if (bgBitmap && !isHighContrast) { RECT rcDlg; GetClientRect(hWndDlg, &rcDlg); SIZE szDlg = { rcDlg.right, rcDlg.bottom }; @@ -2979,17 +2977,15 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA EndPaint(hWndDlg, &ps); return TRUE; } - case WM_ERASEBKGND: - if (!isHighContrast) { - HDC hdc = (HDC)wParam; - RECT rcDlg; - GetClientRect(hWndDlg, &rcDlg); - HBRUSH hBrush = (HBRUSH)GetStockObject(BLACK_BRUSH); - FillRect(hdc, &rcDlg, hBrush); - SetWindowLongPtrW(hWndDlg, DWLP_MSGRESULT, TRUE); - return TRUE; - } - break; + case WM_ERASEBKGND: { + HDC hdc = (HDC)wParam; + RECT rcDlg; + GetClientRect(hWndDlg, &rcDlg); + HBRUSH hBrush = !isHighContrast ? (HBRUSH)GetStockObject(BLACK_BRUSH) : GetSysColorBrush(COLOR_WINDOW); + FillRect(hdc, &rcDlg, hBrush); + SetWindowLongPtrW(hWndDlg, DWLP_MSGRESULT, TRUE); + return TRUE; + } case WM_DRAWITEM: { LPDRAWITEMSTRUCT pDIS = (LPDRAWITEMSTRUCT)lParam; if (pDIS->CtlID == IDC_BSDR_SEPARATOR_TOP || pDIS->CtlID == IDC_BSDR_SEPARATOR_BOTTOM) { @@ -3017,6 +3013,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA if (!SetTimer(hWndDlg, BSDR_CANCEL_TIMER, 700, nullptr)) { Wh_Log(L"SetTimer failed, GLE=%d", GetLastError()); Cancel(); + PostMessageW(hBgWnd, WM_CLOSE, 0, BSDR_CLOSE); } return TRUE; case IDC_BSDR_FORCE_BTN: @@ -3048,6 +3045,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA KillTimer(hWndDlg, BSDR_CANCEL_TIMER); Cancel(); + PostMessageW(hBgWnd, WM_CLOSE, 0, BSDR_CLOSE); return TRUE; } case WM_ADD_APP: { @@ -3149,10 +3147,8 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA SystemParametersInfoW(SPI_GETWHEELSCROLLLINES, 0, &scrollLines, 0); return TRUE; } - if (wParam == SPI_SETHIGHCONTRAST) { - isHighContrast = IsHighContrast(); - return TRUE; - } + // SPI_SETHIGHCONTRAST: Windows 7 BSDR never updated the high-contrast status on runtime, so don't update it here either + // This event as well as WM_THEMECHANGED does not even reliably fire in mid-logoff state anyway break; } } @@ -3185,6 +3181,22 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA return -1; } + isHighContrast = IsHighContrast(); + + if (!isHighContrast) { + separatorBitmap = LoadAlphaBitmap(IDB_BSDR_SEPARATOR); + btnNormalBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_NORMAL); + btnHoverBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_HOVER); + btnPressedBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_PRESSED); + btnSelectedBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_SELECTED); + btnSelectedHoverBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_SELECTED_HOVER); + + if (!separatorBitmap || !btnNormalBitmap || !btnHoverBitmap || !btnPressedBitmap || !btnSelectedBitmap || !btnSelectedHoverBitmap) { + Wh_Log(L"One or more images are missing; falling back to high-contrast rendering..."); + isHighContrast = true; + } + } + if (!isHighContrast) { // Screenshot desktop and dim it // No DPI virtualization involved because LogonUI is per-monitor scaled by manifest @@ -3201,20 +3213,13 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA SelectObject(hMemDC, oldBitmap); DeleteDC(hMemDC); ReleaseDC(nullptr, hDC); - - separatorBitmap = LoadAlphaBitmap(IDB_BSDR_SEPARATOR); - btnNormalBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_NORMAL); - btnHoverBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_HOVER); - btnPressedBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_PRESSED); - btnSelectedBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_SELECTED); - btnSelectedHoverBitmap = LoadAlphaBitmap(IDB_BSDR_BTN_SELECTED_HOVER); } return 0; } case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); - if (bgBitmap) { + if (bgBitmap && !isHighContrast) { HDC memDC = CreateCompatibleDC(hdc); HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, bgBitmap); BitBlt(hdc, 0, 0, bgWidth, bgHeight, memDC, 0, 0, SRCCOPY); @@ -3225,6 +3230,18 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA paintedFirstFrame = true; return 0; } + case WM_ERASEBKGND: { + if (isHighContrast) { + HDC hdc = (HDC)wParam; + RECT rcDlg; + GetClientRect(hWnd, &rcDlg); + HBRUSH hBrush = GetSysColorBrush(COLOR_WINDOW); + FillRect(hdc, &rcDlg, hBrush); + SetWindowLongPtrW(hWnd, DWLP_MSGRESULT, TRUE); + return TRUE; + } + break; + } case WM_WINDOWPOSCHANGING: { // Deny window movement/resize from workarea resize, etc. WINDOWPOS* pPos = (WINDOWPOS*)lParam; @@ -3505,6 +3522,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { } if (needsResolve) { Resolve(BlockedShutdownResolution_Cancel); + Cancel(); } if (hBgWndLocal && IsWindow(hBgWndLocal)) { @@ -4172,10 +4190,10 @@ void Wh_ModBeforeUninit() { } if (needsResolve) { Resolve(BlockedShutdownResolution_Cancel); + Sleep(700); + Cancel(); } - if (hStopEvent) { - SetEvent(hStopEvent); - } + Stop(); WaitForSingleObject(hThreadLocal, INFINITE); CloseHandle(hThreadLocal); From 03df578d2d12a7afd7cf294edc119852b3842020 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Wed, 2 Sep 2026 16:58:34 +0900 Subject: [PATCH 41/51] Apply fixes --- mods/win7-bsdr.wh.cpp | 105 ++++++++++++++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 25 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 7d80ee45d4..5b3d3ca156 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -80,6 +80,7 @@ exclusion option. Otherwise the logoff sequence portion of the mod will not func #include #include #include +#include #include #include #include @@ -96,6 +97,7 @@ exclusion option. Otherwise the logoff sequence portion of the mod will not func #define BSDR_CLASSNAME L"BlockedShutdownResolver_WH" #define BSDR_CLOSE 1337 #define BSDR_CANCEL_TIMER 1 +#define BSDR_CANCEL_TIMER_MS 700 #define RETURN_IF_FAILED(x) do { \ HRESULT hr = (x); \ @@ -108,7 +110,8 @@ EXTERN_C IMAGE_DOS_HEADER __ImageBase; HINSTANCE g_hLogonControllerDll; HINSTANCE g_hBlockedShutdownDll; -std::atomic g_hDoModalExitEventDup = nullptr; +std::mutex g_doModalExitEventMutex; +HANDLE g_hDoModalExitEventDup = nullptr; std::atomic g_isExiting = false; @@ -1793,7 +1796,7 @@ namespace CustomBSDR { void CreateAppTileControls(IShutdownBlockingApp* blockingApp, bool noUpdateLayout = false); void RemoveAppTileControls(UINT appId, bool noUpdateLayout = false); void UpdateAppListLayout(); - void Cancel(); + void Cancel(bool noExitProcess = false); LRESULT CALLBACK ButtonSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData); LRESULT CALLBACK AppListSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData); INT_PTR CALLBACK DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); @@ -2407,7 +2410,6 @@ LRESULT CALLBACK CustomBSDR::AppListSubclassProc(HWND hWnd, UINT uMsg, WPARAM wP SetBkColor((HDC)wParam, GetSysColor(COLOR_BTNFACE)); } return (INT_PTR)GetStockObject(NULL_BRUSH); - break; } case WM_NCDESTROY: { RemoveWindowSubclass(hWnd, AppListSubclassProc, uIdSubclass); @@ -2723,7 +2725,7 @@ void CustomBSDR::UpdateAppListLayout() { RedrawWindow(hDlg, nullptr, nullptr, RDW_FRAME | RDW_INVALIDATE); } -void CustomBSDR::Cancel() { +void CustomBSDR::Cancel(bool noExitProcess) { if (!isOnSecureDesktop) { // If BSDR is forced to show on the default desktop with the Windhawk mod, LogonUI.exe won't exit for some reason on cancel, // causing issues with subsequent session ends, unless it's killed manually or Ctrl+Alt+Del is pressed once @@ -2731,12 +2733,22 @@ void CustomBSDR::Cancel() { // and makes the thread stuck in WaitForSingleObject // So force signal the event as a not so clean workaround (still better than previous ExitProcess workaround) // (I still haven't found neither the culprint nor which code signals that event) - HANDLE hDoModalExitEvent = g_hDoModalExitEventDup.load(); - if (hDoModalExitEvent) { - if (!SetEvent(hDoModalExitEvent)) { - Wh_Log(L"SetEvent(DoModal exit) failed, GLE=%u", GetLastError()); + bool signaled = false; + { + std::lock_guard lock(g_doModalExitEventMutex); + + if (g_hDoModalExitEventDup) { + signaled = SetEvent(g_hDoModalExitEventDup); + if (!signaled) { + Wh_Log( + L"SetEvent(DoModal exit) failed, GLE=%u", + GetLastError() + ); + } } - } else { + } + + if (!signaled && !noExitProcess) { // Event capture failed, oh noes! // Here comes the old terrible workaround // Note: this does not cause any user-facing issues because there is no visible LogonUI window in the default desktop at this point @@ -2745,6 +2757,7 @@ void CustomBSDR::Cancel() { ExitProcess(0); } } + { std::lock_guard lock(cancelMutex); isCanceling = false; @@ -3010,7 +3023,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA ShowWindow(hBgWnd, SW_HIDE); // Make sure the resolve request reaches winlogon // Otherwise, winlogon might just decide to force resolve after LogonUI has fully closed - if (!SetTimer(hWndDlg, BSDR_CANCEL_TIMER, 700, nullptr)) { + if (!SetTimer(hWndDlg, BSDR_CANCEL_TIMER, BSDR_CANCEL_TIMER_MS, nullptr)) { Wh_Log(L"SetTimer failed, GLE=%d", GetLastError()); Cancel(); PostMessageW(hBgWnd, WM_CLOSE, 0, BSDR_CLOSE); @@ -3418,9 +3431,20 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { wchar_t desktopName[256] = {}; if (GetUserObjectInformationW(hDesktop, UOI_NAME, desktopName, sizeof(desktopName), nullptr)) { isOnSecureDesktop = (_wcsicmp(desktopName, L"winlogon") == 0); - } - if (!SetThreadDesktop(hDesktop)) { - Wh_Log(L"SetThreadDesktop failed, GLE=%d", GetLastError()); + if (!isOnSecureDesktop) { + if (!SetThreadDesktop(hDesktop)) { + Wh_Log(L"SetThreadDesktop failed, GLE=%d", GetLastError()); + isOnSecureDesktop = true; + CloseDesktop(hDesktop); + hDesktop = nullptr; + } + } else { + // no need to switch to secure desktop because we're alr in there + CloseDesktop(hDesktop); + hDesktop = nullptr; + } + } else { + Wh_Log(L"GetUserObjectInformationW failed, GLE=%d", GetLastError()); } } else { Wh_Log(L"OpenInputDesktop failed, GLE=%d", GetLastError()); @@ -3859,8 +3883,20 @@ CLogonController__DoModal_t CLogonController__DoModal_orig; long __cdecl CLogonController__DoModal_hook(void* pThis, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5) { g_enteringDoModal = true; Wh_Log(L"DoModal"); - int result = CLogonController__DoModal_orig(pThis, a2, a3, a4, a5); + + long result = CLogonController__DoModal_orig(pThis, a2, a3, a4, a5); g_enteringDoModal = false; + + HANDLE eventToClose = nullptr; + { + std::lock_guard lock(g_doModalExitEventMutex); + eventToClose = g_hDoModalExitEventDup; + g_hDoModalExitEventDup = nullptr; + } + + if (eventToClose) { + CloseHandle(eventToClose); + } return result; } @@ -3894,13 +3930,21 @@ HANDLE WINAPI CreateEventW_hook(LPSECURITY_ATTRIBUTES lpEventAttributes, WINBOOL return result; } - HANDLE expected = nullptr; - if (g_hDoModalExitEventDup.compare_exchange_strong(expected, duplicated, std::memory_order_release, std::memory_order_relaxed)) { - // Works cleanly on LTSC 2021 and 11 25H2 - Wh_Log(L"Caught DoModal event"); - } else { + { + std::lock_guard lock(g_doModalExitEventMutex); + + if (!g_hDoModalExitEventDup) { + g_hDoModalExitEventDup = duplicated; + duplicated = nullptr; + // Works cleanly on LTSC 2021 and 11 25H2 + Wh_Log(L"Caught DoModal event"); + } else { + Wh_Log(L"Warning: DoModal event already captured; ignoring the subsequent one..."); + } + } + + if (duplicated) { CloseHandle(duplicated); - Wh_Log(L"Warning: DoModal event already captured; ignoring the subsequent one..."); } } return result; @@ -4189,9 +4233,14 @@ void Wh_ModBeforeUninit() { needsResolve = g_resolvedValue == BlockedShutdownResolution_None; } if (needsResolve) { + { + std::lock_guard lock(cancelMutex); + isCanceling = true; + } + Resolve(BlockedShutdownResolution_Cancel); - Sleep(700); - Cancel(); + Sleep(BSDR_CANCEL_TIMER_MS); + Cancel(true); } Stop(); @@ -4215,9 +4264,15 @@ void Wh_ModUninit() { using namespace CustomBSDR; - HANDLE hDoModalExitEventDup = g_hDoModalExitEventDup.exchange(nullptr, std::memory_order_acq_rel); - if (hDoModalExitEventDup) { - CloseHandle(hDoModalExitEventDup); + HANDLE eventToClose = nullptr; + { + std::lock_guard lock(g_doModalExitEventMutex); + eventToClose = g_hDoModalExitEventDup; + g_hDoModalExitEventDup = nullptr; + } + + if (eventToClose) { + CloseHandle(eventToClose); } pendingApps.reset(); From a540d1106f78228e23bcc4d925ee2bf23291a475 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Wed, 2 Sep 2026 17:45:47 +0900 Subject: [PATCH 42/51] Apply fixes --- mods/win7-bsdr.wh.cpp | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 5b3d3ca156..19887552b3 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -2230,7 +2230,7 @@ void CustomBSDR::DrawButton(HDC hdc, LPDRAWITEMSTRUCT pDIS) { } if (hBitmap) { - BITMAP bm; + BITMAP bm = {}; GetObject(hBitmap, sizeof(bm), &bm); int width = rcButton.right - rcButton.left; @@ -2510,9 +2510,13 @@ void CustomBSDR::CreateAppTileControls(IShutdownBlockingApp* blockingApp, bool n ABI::Windows::Storage::Streams::IRandomAccessStream* iconStream = nullptr; if (SUCCEEDED(blockingApp->get_Icon(&iconStream)) && iconStream) { - GetBitmapFromRandomStream(spWICFactory.Get(), iconStream, &tile.hIconBitmap); - if (tile.hIcon && tile.hIconBitmap) { - SendMessageW(tile.hIcon, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)tile.hIconBitmap); + HRESULT hr = GetBitmapFromRandomStream(spWICFactory.Get(), iconStream, &tile.hIconBitmap); + if (SUCCEEDED(hr)) { + if (tile.hIcon && tile.hIconBitmap) { + SendMessageW(tile.hIcon, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)tile.hIconBitmap); + } + } else { + Wh_Log(L"GetBitmapFromRandomStream failed, HR=%08X", hr); } iconStream->Release(); } @@ -2748,7 +2752,7 @@ void CustomBSDR::Cancel(bool noExitProcess) { } } - if (!signaled && !noExitProcess) { + if (!signaled && !noExitProcess && !g_isExiting.load()) { // Event capture failed, oh noes! // Here comes the old terrible workaround // Note: this does not cause any user-facing issues because there is no visible LogonUI window in the default desktop at this point @@ -3330,16 +3334,11 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA DestroyWindow(hDlgLocal); } - HWND hBgWndLocal = nullptr; { std::lock_guard lock(hBgWndMutex); - hBgWndLocal = hBgWnd; hBgWnd = nullptr; } - - if (hBgWndLocal) { - DestroyWindow(hBgWndLocal); - } + DestroyWindow(hWnd); for (auto& tile : appTiles) { if (tile.hIconBitmap) DeleteObject(tile.hIconBitmap); @@ -3421,6 +3420,7 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA } DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { + bool failed = false; int ret = 0; // Attempt to create window on the input desktop, as the thread is always running in secure desktop at this point, @@ -3536,6 +3536,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { break; } } else { + failed = true; ret = GetLastError(); Wh_Log(L"MsgWaitForMultipleObjects returned %d, GLE=%d", result, ret); @@ -3556,6 +3557,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { } } } else { + failed = true; ret = GetLastError(); Wh_Log(L"CreateWindowExW failed, GLE=%d", ret); } @@ -3564,6 +3566,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { Wh_Log(L"UnregisterClassW failed, GLE=%d", GetLastError()); } } else { + failed = true; ret = GetLastError(); Wh_Log(L"RegisterClassExW failed, GLE=%d", ret); } @@ -3574,7 +3577,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { CoUninitialize(); } - if (ret != 0) { + if (failed || ret != 0) { Wh_Log(L"CustomBSDR thread init failed"); // Note: Windows also does force logoff when the BSDR is not available, e.g. the BSDR DLL is missing, on Server Core, etc. // Probably this is better than leaving a system that never shuts down (if the mod is repeatedly failing) @@ -3707,7 +3710,9 @@ long __fastcall BlockedShutdownUXImpl_Start_hook(void* thisPtr, void* userSettin long __fastcall BlockedShutdownUXImpl_get_ScaleFactor_hook(void* thisPtr, unsigned int* scaleFactor) { Wh_Log(L"BlockedShutdownUXImpl::get_ScaleFactor"); - *scaleFactor = CustomBSDR::GetScaleFactor(); + if (scaleFactor) { + *scaleFactor = CustomBSDR::GetScaleFactor(); + } return S_OK; } @@ -3915,7 +3920,7 @@ using CreateEventW_t = decltype(&CreateEventW); CreateEventW_t CreateEventW_orig; HANDLE WINAPI CreateEventW_hook(LPSECURITY_ATTRIBUTES lpEventAttributes, WINBOOL bManualReset, WINBOOL bInitialState, LPCWSTR lpName) { HANDLE result = CreateEventW_orig(lpEventAttributes, bManualReset, bInitialState, lpName); - if (lpEventAttributes == 0 && bManualReset == 0 && bInitialState == 0 && lpName == 0 && g_enteringDoModal) { + if (result && lpEventAttributes == 0 && bManualReset == 0 && bInitialState == 0 && lpName == 0 && g_enteringDoModal) { HANDLE duplicated = nullptr; if (!DuplicateHandle( GetCurrentProcess(), From f12d2e616502d39e3c85f775aa6729a248e74d64 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Thu, 3 Sep 2026 03:37:53 +0900 Subject: [PATCH 43/51] Apply fixes --- mods/win7-bsdr.wh.cpp | 85 ++++++++++++++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 22 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 19887552b3..d4ea6de3ac 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -28,6 +28,7 @@ * Set the DLL path in the mod settings afterwards. You may also point this directly to `winsrv.dll.mui` instead of setting up the directory structure mentioned above. * Warning: **never, ever replace `C:\Windows\System32\winsrv.dll` (or mui) with the Windows 7 version**; this will brick your Windows installation! * This is not necessary. Hardcoded resources will be used instead if the path is not set or the file is missing. +* This mod is confirmed to work on Windows 10 LTSC 2021 (22H2) and 11 25H2. * Known issues * If your user account has no password, enabling the logoff sequence option may make the system automatically log on again after logging off. * Not compatible with a portable Windhawk installation, even when run as admin, because it doesn't survive a logoff long enough to handle BSDR. @@ -35,12 +36,12 @@ ![Screenshot](https://raw.githubusercontent.com/Ingan121/files/refs/heads/master/vmware_gj0gqnHj8e.png) ## ⚠ Important usage note ⚠ -In order to use this mod, you must allow Windhawk to inject into the **LogonUI.exe** -system process. To do so, add it to the process inclusion list in the advanced -settings. If you do not do this, it will silently fail to inject. +This mod needs to hook into `LogonUI.exe` to work. Please navigate to Windhawk's +Settings > Advanced settings > More advanced settings > Process inclusion list, +and make sure that `LogonUI.exe` is in the list. -You must add it to the inclusion list even if you have disabled the critical system process -exclusion option. Otherwise the logoff sequence portion of the mod will not function. +Please make sure `LogonUI.exe` isn't excluded by any means, such as a wildcard entry +in the global exclusion list or inclusion options in this mod's advanced settings page. ![Advanced settings screenshot](https://i.imgur.com/LRhREtJ.png) */ @@ -1792,7 +1793,7 @@ namespace CustomBSDR { bool IsHighContrast(); HBITMAP LoadAlphaBitmap(UINT resourceId, bool forceHardcoded = false); void DrawSeparator(LPDRAWITEMSTRUCT pDIS); - void DrawButton(HDC hdc, LPDRAWITEMSTRUCT pDIS); + void DrawButton(LPDRAWITEMSTRUCT pDIS); void CreateAppTileControls(IShutdownBlockingApp* blockingApp, bool noUpdateLayout = false); void RemoveAppTileControls(UINT appId, bool noUpdateLayout = false); void UpdateAppListLayout(); @@ -2200,7 +2201,9 @@ void CustomBSDR::DrawSeparator(LPDRAWITEMSTRUCT pDIS) { } } -void CustomBSDR::DrawButton(HDC hdc, LPDRAWITEMSTRUCT pDIS) { +void CustomBSDR::DrawButton(LPDRAWITEMSTRUCT pDIS) { + HDC hdc = pDIS->hDC; + bool isPressed = (pDIS->itemState & ODS_SELECTED); bool isFocused = (pDIS->itemState & ODS_FOCUS); @@ -3010,7 +3013,7 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA SetWindowLongPtrW(hWndDlg, DWLP_MSGRESULT, TRUE); return TRUE; } else if (pDIS->CtlType == ODT_BUTTON) { - DrawButton(pDIS->hDC, pDIS); + DrawButton(pDIS); SetWindowLongPtrW(hWndDlg, DWLP_MSGRESULT, TRUE); return TRUE; } @@ -3350,6 +3353,11 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA pendingApps->clear(); } + { + std::lock_guard lock(cancelMutex); + isCanceling = false; + } + if (bgBitmap) { DeleteObject(bgBitmap); bgBitmap = nullptr; @@ -3577,7 +3585,7 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { CoUninitialize(); } - if (failed || ret != 0) { + if (failed) { Wh_Log(L"CustomBSDR thread init failed"); // Note: Windows also does force logoff when the BSDR is not available, e.g. the BSDR DLL is missing, on Server Core, etc. // Probably this is better than leaving a system that never shuts down (if the mod is repeatedly failing) @@ -3594,6 +3602,12 @@ void CustomBSDR::Start(LogonUIState state) { std::lock_guard lock(workerMutex); if (g_isExiting.load()) { Wh_Log(L"Mod is unloading!"); + + { + std::lock_guard resolvedLock(g_resolvedMutex); + g_rejectNextResolved = true; + } + return; } @@ -3710,14 +3724,18 @@ long __fastcall BlockedShutdownUXImpl_Start_hook(void* thisPtr, void* userSettin long __fastcall BlockedShutdownUXImpl_get_ScaleFactor_hook(void* thisPtr, unsigned int* scaleFactor) { Wh_Log(L"BlockedShutdownUXImpl::get_ScaleFactor"); - if (scaleFactor) { - *scaleFactor = CustomBSDR::GetScaleFactor(); - } + if (!scaleFactor) + return E_POINTER; + + *scaleFactor = CustomBSDR::GetScaleFactor(); return S_OK; } long __fastcall BlockedShutdownUXImpl_get_WasClicked_hook(void* thisPtr, unsigned char* wasClicked) { Wh_Log(L"BlockedShutdownUXImpl::get_WasClicked"); + if (!wasClicked) + return E_POINTER; + { std::lock_guard lock(g_resolvedMutex); // If this is false, resolving with cancel somehow makes winlogon lock the session after stopping BSDR @@ -3728,6 +3746,9 @@ long __fastcall BlockedShutdownUXImpl_get_WasClicked_hook(void* thisPtr, unsigne } long __fastcall BlockedShutdownUXImpl_AddApplication_hook(void* thisPtr, IShutdownBlockingApp* blockingApp) { + if (!blockingApp) + return E_POINTER; + UINT appId = 0; BOOLEAN isBlocking = FALSE; HSTRING caption = nullptr, blockReason = nullptr; @@ -4006,7 +4027,7 @@ bool IsLogonUiInjectionEnabled() { return true; } - // Check for the global WH inclusion key (only for non portable) + // Check for the global WH inclusion settings (only for non portable) HKEY hKey; int res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Windhawk\\Engine\\Settings", 0, KEY_READ, &hKey); if (res != ERROR_SUCCESS) { @@ -4023,21 +4044,41 @@ bool IsLogonUiInjectionEnabled() { return false; } - wchar_t* data = new wchar_t[size / sizeof(wchar_t) + 1]; - res = RegQueryValueExW(hKey, L"Include", nullptr, nullptr, (LPBYTE)data, &size); - RegCloseKey(hKey); + wchar_t* inclData = new wchar_t[size / sizeof(wchar_t) + 1]; + res = RegQueryValueExW(hKey, L"Include", nullptr, nullptr, (LPBYTE)inclData, &size); if (res != ERROR_SUCCESS) { Wh_Log(L"WH inclusion check failed, error=%d", res); - delete[] data; + delete[] inclData; + RegCloseKey(hKey); return false; } - data[size / sizeof(wchar_t)] = L'\0'; - int isLogonUiThere = wcsstr(_wcsupr(data), L"LOGONUI.EXE") != NULL; - delete[] data; + inclData[size / sizeof(wchar_t)] = L'\0'; + int isLogonUiInInclusion = wcsstr(_wcsupr(inclData), L"LOGONUI.EXE") != NULL; + delete[] inclData; - if (!isLogonUiThere) { - return false; + Wh_Log(L"WH inclusion check: isLogonUiInInclusion=%d", isLogonUiInInclusion); + + if (!isLogonUiInInclusion) { + size = sizeof(DWORD); + DWORD type = 0; + + DWORD inclCritSysData; + res = RegQueryValueExW(hKey, L"InjectIntoCriticalProcesses", nullptr, &type, (LPBYTE)&inclCritSysData, &size); + RegCloseKey(hKey); + if (res != ERROR_SUCCESS || type != REG_DWORD || size != sizeof(DWORD)) { + Wh_Log(L"WH inclusion check failed, error=%d, type=%lu, size=%lu", res, type, size); + return false; + } + + Wh_Log(L"WH inclusion check: inclCritSysData=%d", inclCritSysData); + + // Deny if LogonUI isn't explicitly included and critical-process injection is disabled. + if (inclCritSysData == 0) { + return false; + } + } else { + RegCloseKey(hKey); } // Check if Windhawk service is running, to deny the portable version From 50748b2843c360f383f353b9c3945587983597f8 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Thu, 3 Sep 2026 04:46:35 +0900 Subject: [PATCH 44/51] Apply fixes --- mods/win7-bsdr.wh.cpp | 80 ++++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 28 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index d4ea6de3ac..3eee0182ce 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -29,6 +29,7 @@ * Warning: **never, ever replace `C:\Windows\System32\winsrv.dll` (or mui) with the Windows 7 version**; this will brick your Windows installation! * This is not necessary. Hardcoded resources will be used instead if the path is not set or the file is missing. * This mod is confirmed to work on Windows 10 LTSC 2021 (22H2) and 11 25H2. +* This mod does not modify any system files. The above optional DLL can be saved anywhere in the file system. * Known issues * If your user account has no password, enabling the logoff sequence option may make the system automatically log on again after logging off. * Not compatible with a portable Windhawk installation, even when run as admin, because it doesn't survive a logoff long enough to handle BSDR. @@ -621,7 +622,7 @@ static constexpr unsigned char RES_BSDR_BTN_NORMAL[] = { 0x00, 0x00, 0x00, 0x1b, 0xff, 0xff, 0xff, 0x00 }; alignas(BITMAPINFOHEADER) -static constexpr const unsigned char RES_BSDR_BTN_HOVER[] = { +static constexpr unsigned char RES_BSDR_BTN_HOVER[] = { 0x28, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc4, 0x0e, 0x00, 0x00, 0xc4, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, @@ -1963,6 +1964,7 @@ void CustomBSDR::CenterWindow(HWND hWnd) { if (!GetWindowRect(hWnd, &rcWindow)) { return; } + // Honor work area like Windows 7 (verified pixel perfectness) if (!SystemParametersInfoW(SPI_GETWORKAREA, 0, &rcWorkArea, 0)) { return; } @@ -2116,6 +2118,13 @@ HBITMAP CustomBSDR::LoadAlphaBitmap(UINT resourceId, bool forceHardcoded) { } } else { Wh_Log(L"Image load error: no hBitmap/pBits"); + if (hBitmap) { + DeleteObject(hBitmap); + } + if (forceHardcoded) { + Wh_Log(L"Failed to load hardcoded image!"); + return nullptr; + } return LoadAlphaBitmap(resourceId, true); } @@ -2480,7 +2489,7 @@ void CustomBSDR::CreateAppTileControls(IShutdownBlockingApp* blockingApp, bool n int iconSize = MulDiv(32, dpi, 96); - tile.hIcon = CreateWindowExW(0, L"Static", nullptr, WS_CHILD | WS_VISIBLE | SS_BITMAP, -iconSize, -iconSize, iconSize, iconSize, hAppListScroll, nullptr, nullptr, nullptr); + tile.hIcon = CreateWindowExW(0, L"Static", nullptr, WS_CHILD | WS_VISIBLE | SS_BITMAP | SS_REALSIZECONTROL, -iconSize, -iconSize, iconSize, iconSize, hAppListScroll, nullptr, nullptr, nullptr); tile.hTitle = CreateWindowExW(0, L"Static", titleText.c_str(), WS_CHILD | WS_VISIBLE | SS_ENDELLIPSIS, -100, -20, 100, 20, hAppListScroll, nullptr, nullptr, nullptr); if (!blockReasonText.empty()) { @@ -2760,6 +2769,7 @@ void CustomBSDR::Cancel(bool noExitProcess) { // Here comes the old terrible workaround // Note: this does not cause any user-facing issues because there is no visible LogonUI window in the default desktop at this point // and winlogon has already got out of the mid-logoff state. Subsequent LogonUI launches (lock/C-A-D) are fine too. + // Has been using this approach from my AuthUX BSDR for months and have not seen any issues with it Wh_Log(L"Force exiting LogonUI!"); ExitProcess(0); } @@ -3282,6 +3292,7 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA } // Retaking screenshot requires temporarily hiding the window // Windows 7 doesn't do that either so skip that + // (Win7 also leaves the empty area from resolution increase black) return 0; } case WM_BSDR_SETFOCUS: { @@ -3942,35 +3953,46 @@ CreateEventW_t CreateEventW_orig; HANDLE WINAPI CreateEventW_hook(LPSECURITY_ATTRIBUTES lpEventAttributes, WINBOOL bManualReset, WINBOOL bInitialState, LPCWSTR lpName) { HANDLE result = CreateEventW_orig(lpEventAttributes, bManualReset, bInitialState, lpName); if (result && lpEventAttributes == 0 && bManualReset == 0 && bInitialState == 0 && lpName == 0 && g_enteringDoModal) { - HANDLE duplicated = nullptr; - if (!DuplicateHandle( - GetCurrentProcess(), - result, - GetCurrentProcess(), - &duplicated, - 0, - FALSE, - DUPLICATE_SAME_ACCESS - )) { - Wh_Log(L"DuplicateHandle failed, GLE=%d", GetLastError()); - return result; - } + void* returnAddress = __builtin_return_address(0); + HMODULE callerModule; + DWORD dwFlags = GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT; + if (GetModuleHandleExW(dwFlags, (LPCWSTR)returnAddress, &callerModule)) { + if (callerModule == g_hLogonControllerDll) { + HANDLE duplicated = nullptr; + if (!DuplicateHandle( + GetCurrentProcess(), + result, + GetCurrentProcess(), + &duplicated, + 0, + FALSE, + DUPLICATE_SAME_ACCESS + )) { + Wh_Log(L"DuplicateHandle failed, GLE=%d", GetLastError()); + return result; + } - { - std::lock_guard lock(g_doModalExitEventMutex); + { + std::lock_guard lock(g_doModalExitEventMutex); + + if (!g_hDoModalExitEventDup) { + g_hDoModalExitEventDup = duplicated; + duplicated = nullptr; + // Works cleanly on LTSC 2021 and 11 25H2 + Wh_Log(L"Caught DoModal event"); + } else { + Wh_Log(L"Warning: DoModal event already captured; ignoring the subsequent one..."); + } + } - if (!g_hDoModalExitEventDup) { - g_hDoModalExitEventDup = duplicated; - duplicated = nullptr; - // Works cleanly on LTSC 2021 and 11 25H2 - Wh_Log(L"Caught DoModal event"); + if (duplicated) { + CloseHandle(duplicated); + } } else { - Wh_Log(L"Warning: DoModal event already captured; ignoring the subsequent one..."); + Wh_Log(L"Caught matching event from non-LogonController module; ignoring..."); } - } - - if (duplicated) { - CloseHandle(duplicated); + } else { + Wh_Log(L"GetModuleHandleExW failed, GLE=%d", GetLastError()); } } return result; @@ -4150,6 +4172,9 @@ BOOL Wh_ModInit() { Wh_Log(L"HookSymbols winlogon failed"); return FALSE; } + } else { + Wh_Log(L"GetModuleHandleW(NULL) == NULL??"); + return FALSE; } g_noSafetyChecks = Wh_GetIntSetting(L"noSafetyChecks"); return TRUE; @@ -4285,7 +4310,6 @@ void Wh_ModBeforeUninit() { } Resolve(BlockedShutdownResolution_Cancel); - Sleep(BSDR_CANCEL_TIMER_MS); Cancel(true); } Stop(); From fc7167ac1de75790e7b8aba56e8156634cc68db3 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Thu, 3 Sep 2026 05:10:11 +0900 Subject: [PATCH 45/51] Apply preemptive fixes --- mods/win7-bsdr.wh.cpp | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 3eee0182ce..6f6b740876 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -2501,23 +2501,33 @@ void CustomBSDR::CreateAppTileControls(IShutdownBlockingApp* blockingApp, bool n if (tile.hTitle) { SendMessageW(tile.hTitle, WM_SETFONT, (WPARAM)hDescFont, FALSE); HDC hdc = GetDC(tile.hTitle); - HFONT hOldFont = (HFONT)SelectObject(hdc, hDescFont); - SIZE textSize; - GetTextExtentPoint32W(hdc, titleText.c_str(), (int)titleText.length(), &textSize); - SelectObject(hdc, hOldFont); - ReleaseDC(tile.hTitle, hdc); - SetWindowPos(tile.hTitle, nullptr, 0, 0, textSize.cx, textSize.cy, SWP_NOMOVE | SWP_NOZORDER); // Width is corrected later + if (hdc) { + HFONT hOldFont = hDescFont ? (HFONT)SelectObject(hdc, hDescFont) : nullptr; + SIZE textSize = {}; + if (GetTextExtentPoint32W(hdc, titleText.c_str(), (int)titleText.length(), &textSize)) { + SetWindowPos(tile.hTitle, nullptr, 0, 0, textSize.cx, textSize.cy, SWP_NOMOVE | SWP_NOZORDER); // Width is corrected later + } + if (hOldFont && hOldFont != HGDI_ERROR) { + SelectObject(hdc, hOldFont); + } + ReleaseDC(tile.hTitle, hdc); + } } HFONT hFont = (HFONT)SendMessageW(hDlg, WM_GETFONT, 0, 0); if (tile.hBlockReason) { SendMessageW(tile.hBlockReason, WM_SETFONT, (WPARAM)hFont, FALSE); HDC hdc = GetDC(tile.hBlockReason); - HFONT hOldFont = (HFONT)SelectObject(hdc, hFont); - SIZE textSize; - GetTextExtentPoint32W(hdc, blockReasonText.c_str(), (int)blockReasonText.length(), &textSize); - SelectObject(hdc, hOldFont); - ReleaseDC(tile.hBlockReason, hdc); - SetWindowPos(tile.hBlockReason, nullptr, 0, 0, textSize.cx, textSize.cy * 2, SWP_NOMOVE | SWP_NOZORDER); // Always two lines + if (hdc) { + HFONT hOldFont = hFont ? (HFONT)SelectObject(hdc, hFont) : nullptr; + SIZE textSize = {}; + if (GetTextExtentPoint32W(hdc, blockReasonText.c_str(), (int)blockReasonText.length(), &textSize)) { + SetWindowPos(tile.hBlockReason, nullptr, 0, 0, textSize.cx, textSize.cy * 2, SWP_NOMOVE | SWP_NOZORDER); // Always two lines + } + if (hOldFont && hOldFont != HGDI_ERROR) { + SelectObject(hdc, hOldFont); + } + ReleaseDC(tile.hBlockReason, hdc); + } } ABI::Windows::Storage::Streams::IRandomAccessStream* iconStream = nullptr; From 527bd1f1c6385bd9469b6fedc60acf9c4b1f5f22 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Thu, 3 Sep 2026 11:48:22 +0900 Subject: [PATCH 46/51] Apply preemptive fixes 2 --- mods/win7-bsdr.wh.cpp | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 6f6b740876..42633a6df4 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -1756,7 +1756,7 @@ namespace CustomBSDR { // interface functions void Start(LogonUIState state); void Hide(); - void Stop(); + void Stop(bool ignoreIsCanceling = false); void AddApplication(IShutdownBlockingApp* app); void RemoveApplication(UINT appId); int GetScaleFactor(); @@ -1828,6 +1828,7 @@ namespace CustomBSDR { bool isHighContrast = false; bool isOnSecureDesktop = true; bool isCanceling = false; + bool unloadCancelHandled = false; // app list data stuff struct AppTile { @@ -3537,6 +3538,19 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { ); if (result == WAIT_OBJECT_0) { // hStopEvent + if (g_isExiting.load()) { + bool wasCanceled; + { + std::lock_guard lock(g_resolvedMutex); + wasCanceled = g_resolvedValue == BlockedShutdownResolution_Cancel; + } + + if (wasCanceled) { + Cancel(true); + unloadCancelHandled = true; + } + } + if (hBgWndLocal && IsWindow(hBgWndLocal)) { SendMessageW(hBgWndLocal, WM_CLOSE, 0, BSDR_CLOSE); } else { @@ -3600,6 +3614,19 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { Wh_Log(L"RegisterClassExW failed, GLE=%d", ret); } + if (g_isExiting.load() && !unloadCancelHandled) { + bool wasCanceled; + { + std::lock_guard lock(g_resolvedMutex); + wasCanceled = g_resolvedValue == BlockedShutdownResolution_Cancel; + } + + if (wasCanceled) { + Cancel(true); + unloadCancelHandled = true; + } + } + spWICFactory.Reset(); if (SUCCEEDED(hrCo)) { @@ -3723,10 +3750,10 @@ void CustomBSDR::Hide() { } } -void CustomBSDR::Stop() { +void CustomBSDR::Stop(bool ignoreIsCanceling) { std::lock_guard lock(cancelMutex); - if (!isCanceling && hStopEvent) { + if ((ignoreIsCanceling || !isCanceling) && hStopEvent) { SetEvent(hStopEvent); } } @@ -4320,9 +4347,8 @@ void Wh_ModBeforeUninit() { } Resolve(BlockedShutdownResolution_Cancel); - Cancel(true); } - Stop(); + Stop(true); WaitForSingleObject(hThreadLocal, INFINITE); CloseHandle(hThreadLocal); From 61f7668e1c5e2e3c6afacc4b56683f492f2a18d3 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Thu, 3 Sep 2026 11:49:05 +0900 Subject: [PATCH 47/51] Misc change --- mods/win7-bsdr.wh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 42633a6df4..58d8877759 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -1828,7 +1828,6 @@ namespace CustomBSDR { bool isHighContrast = false; bool isOnSecureDesktop = true; bool isCanceling = false; - bool unloadCancelHandled = false; // app list data stuff struct AppTile { @@ -3451,6 +3450,7 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { bool failed = false; + bool unloadCancelHandled = false; int ret = 0; // Attempt to create window on the input desktop, as the thread is always running in secure desktop at this point, From 7485e050b75d277c0c12db21ac4a264716a4c73b Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Thu, 3 Sep 2026 16:30:54 +0900 Subject: [PATCH 48/51] Update readme and add license info --- mods/win7-bsdr.wh.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 58d8877759..61712c8298 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -28,8 +28,8 @@ * Set the DLL path in the mod settings afterwards. You may also point this directly to `winsrv.dll.mui` instead of setting up the directory structure mentioned above. * Warning: **never, ever replace `C:\Windows\System32\winsrv.dll` (or mui) with the Windows 7 version**; this will brick your Windows installation! * This is not necessary. Hardcoded resources will be used instead if the path is not set or the file is missing. -* This mod is confirmed to work on Windows 10 LTSC 2021 (22H2) and 11 25H2. -* This mod does not modify any system files. The above optional DLL can be saved anywhere in the file system. +* This mod is confirmed to work on Windows 10 LTSC 2021 (22H2) and 11 25H2. It may not work on older Windows 10 versions, and it will not work on versions older than Windows 10 build 1607. +* This mod does not modify any system files. The above optional DLL can be placed anywhere in the file system. * Known issues * If your user account has no password, enabling the logoff sequence option may make the system automatically log on again after logging off. * Not compatible with a portable Windhawk installation, even when run as admin, because it doesn't survive a logoff long enough to handle BSDR. @@ -41,10 +41,11 @@ This mod needs to hook into `LogonUI.exe` to work. Please navigate to Windhawk's Settings > Advanced settings > More advanced settings > Process inclusion list, and make sure that `LogonUI.exe` is in the list. -Please make sure `LogonUI.exe` isn't excluded by any means, such as a wildcard entry -in the global exclusion list or inclusion options in this mod's advanced settings page. - ![Advanced settings screenshot](https://i.imgur.com/LRhREtJ.png) +### Notes for advanced users +* Please make sure `LogonUI.exe` isn't excluded by any means, such as a wildcard entry in the global exclusion list or process inclusion options in this mod's advanced settings page. +* This mod has safety checks before enabling the classic logoff behavior, such as checking if `LogonUI.exe` is added to the global inclusion list properly as stated above, to prevent the logoff sequence from appearing stuck when misconfigured. +* You may disable the safety checks by enabling the last option on the mod settings page, but before doing so, please remember to press Ctrl+Alt+Del if logoff gets stuck. This will help you get out of such a state. */ // ==/WindhawkModReadme== @@ -73,6 +74,11 @@ in the global exclusion list or inclusion options in this mod's advanced setting */ // ==/WindhawkModSettings== +// Source code is published under The GNU General Public License v3.0. +// Fork: https://github.com/Ingan121/AuthUX/blob/bsdr/LICENSE +// Upstream: https://github.com/world-windows-federation/AuthUX/blob/main/LICENSE +// Codes from upstream: logoncontroller.h and wicutil.cpp + #include #include #include From ddc75964647eccf89324fbd2fdc5cb019c974379 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Thu, 3 Sep 2026 16:54:25 +0900 Subject: [PATCH 49/51] Apply preemptive fixes 3 + misc change --- mods/win7-bsdr.wh.cpp | 206 +++++++++++++++++++++++------------------- 1 file changed, 111 insertions(+), 95 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index 61712c8298..f89059ea16 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -211,6 +211,10 @@ typedef struct { DLGITEMTEMPLATEEX<5> item11; } DLGTEMPLATEEX; +#define CTL_STATIC { 0xffff, 0x82 } +#define CTL_SCROLLBAR { 0xffff, 0x84 } +#define CTL_BUTTON { 0xffff, 0x80 } + static constexpr DLGTEMPLATEEX RES_DIALOG = { .dlgVer = 1, .signature = 0xffff, @@ -236,7 +240,7 @@ static constexpr DLGTEMPLATEEX RES_DIALOG = { SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 0, 20, 266, 14, IDC_BSDR_TITLE, - { 0xffff, 0x82 }, // STATIC + CTL_STATIC, L"The following programs need to close:", 0 }, @@ -245,7 +249,7 @@ static constexpr DLGTEMPLATEEX RES_DIALOG = { SS_OWNERDRAW | WS_CHILD | WS_VISIBLE, 0, 46, 266, 1, IDC_BSDR_SEPARATOR_TOP, - { 0xffff, 0x82 }, // STATIC + CTL_STATIC, L"", 0 }, @@ -254,7 +258,7 @@ static constexpr DLGTEMPLATEEX RES_DIALOG = { SS_USERITEM | WS_CHILD | WS_VISIBLE, 0, 52, 256, 85, IDC_BSDR_APPLIST, - { 0xffff, 0x82 }, // STATIC + CTL_STATIC, L"", 0 }, @@ -263,7 +267,7 @@ static constexpr DLGTEMPLATEEX RES_DIALOG = { SBS_VERT | WS_CHILD | WS_VISIBLE, 256, 52, 10, 85, IDC_BSDR_SCROLLBAR, - { 0xffff, 0x84 }, // SCROLLBAR + CTL_SCROLLBAR, L"", 0 }, @@ -272,7 +276,7 @@ static constexpr DLGTEMPLATEEX RES_DIALOG = { SS_OWNERDRAW | WS_CHILD | WS_VISIBLE, 0, 143, 266, 1, IDC_BSDR_SEPARATOR_BOTTOM, - { 0xffff, 0x82 }, // STATIC + CTL_STATIC, L"", 0 }, @@ -281,7 +285,7 @@ static constexpr DLGTEMPLATEEX RES_DIALOG = { SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 0, 152, 266, 30, IDC_BSDR_DESC, - { 0xffff, 0x82 }, // STATIC + CTL_STATIC, L"To close the program that is preventing Windows from\nshutting down, click Cancel, and then close the program.", 0 }, @@ -290,7 +294,7 @@ static constexpr DLGTEMPLATEEX RES_DIALOG = { BS_OWNERDRAW | WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP, 193, 191, 70, 13, IDCANCEL, - { 0xffff, 0x80 }, // BUTTON + CTL_BUTTON, L"&Cancel", 0 }, @@ -299,7 +303,7 @@ static constexpr DLGTEMPLATEEX RES_DIALOG = { BS_OWNERDRAW | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 115, 191, 70, 13, IDC_BSDR_FORCE_BTN, - { 0xffff, 0x80 }, // BUTTON + CTL_BUTTON, L"&Force shut down", 0 }, @@ -308,7 +312,7 @@ static constexpr DLGTEMPLATEEX RES_DIALOG = { SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 0, 152, 266, 30, IDC_BSDR_WARNING, - { 0xffff, 0x82 }, // STATIC + CTL_STATIC, L"If you force shut down you may lose work that you haven't saved.\nDo you still want to force shutdown?", 0 }, @@ -317,7 +321,7 @@ static constexpr DLGTEMPLATEEX RES_DIALOG = { BS_OWNERDRAW | WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP, 193, 191, 70, 13, IDNO, - { 0xffff, 0x80 }, // BUTTON + CTL_BUTTON, L"&No", 0 }, @@ -326,7 +330,7 @@ static constexpr DLGTEMPLATEEX RES_DIALOG = { BS_OWNERDRAW | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 115, 191, 70, 13, IDYES, - { 0xffff, 0x80 }, // BUTTON + CTL_BUTTON, L"&Yes", 0 } @@ -2457,7 +2461,8 @@ void CustomBSDR::CreateAppTileControls(IShutdownBlockingApp* blockingApp, bool n // so deduplicate it RemoveAppTileControls(tile.appId, true); - HSTRING caption, blockReason; + HSTRING caption = nullptr; + HSTRING blockReason = nullptr; std::wstring titleText; std::wstring blockReasonText; @@ -2816,14 +2821,6 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA dlgInitFailed = true; return FALSE; } - dlgInitFailed = false; - - std::vector> pendingAppsLocal; - { - std::lock_guard lock(pendingAppsMutex); - CustomBSDR::hDlg = hWndDlg; - pendingAppsLocal.swap(*pendingApps); - } // Hide title bar SetWindowLongW(hWndDlg, GWL_STYLE, GetWindowLongW(hWndDlg, GWL_STYLE) & ~WS_CAPTION); @@ -2839,88 +2836,101 @@ INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPA SetWindowTheme(hScrollBar, L" ", L""); } - if (hAppList) { - hAppListScroll = CreateWindowExW(0, L"STATIC", nullptr, WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN, 0, 0, 100, 100, hAppList, nullptr, nullptr, nullptr); - SetWindowSubclass(hAppList, AppListSubclassProc, 0, 0); - SetWindowSubclass(hAppListScroll, AppListSubclassProc, 0, 0); - - // Calculate the height of the app list container then resize it, also moving the controls below it - // Note: Win10+ LogonUI is per-monitor scaled by manifest, and we're only showing the dialog on the primary monitor, - // so handling per-monitor DPI is not much trouble. Screenshotting works fine - // Runtime DPI change might be problematic but the BSDR window isn't movable and the Settings app isn't accessible - // during logoff phase either so it's low priority - // Note 2: when a window exactly covers the whole virtual screen, it uses the primary monitor's DPI regardless of proportions, - // which is good for us because the dialog is only shown on the primary monitor - // Even a 1px offset disables that behavior and makes the window use DPI of the monitor the window is primarily in - // This behavior is the same for per-monitor V1, V2, and also MonitorFromWindow/MonitorFromRect - int dpi = GetDpiForWindow(hWndDlg); - - int itemHeight = MulDiv(83, dpi, 96); - int itemHeightNoReason = MulDiv(62, dpi, 96); - - int totalItemsHeight = 0; - for (auto& app : pendingAppsLocal) { - BOOLEAN isBlocking = FALSE; - HSTRING blockReason; - if (SUCCEEDED(app->get_IsBlocking(&isBlocking)) && isBlocking) { + hAppListScroll = CreateWindowExW(0, L"STATIC", nullptr, WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN, 0, 0, 100, 100, hAppList, nullptr, nullptr, nullptr); + if (!hAppListScroll) { + Wh_Log(L"CreateWindowExW hAppListScroll failed, GLE=%d", GetLastError()); + dlgInitFailed = true; + return FALSE; + } + dlgInitFailed = false; + + SetWindowSubclass(hAppList, AppListSubclassProc, 0, 0); + SetWindowSubclass(hAppListScroll, AppListSubclassProc, 0, 0); + + // Set CustomBSDR::hDlg and clear pendingApps after possible dialog init fail point + std::vector> pendingAppsLocal; + { + std::lock_guard lock(pendingAppsMutex); + CustomBSDR::hDlg = hWndDlg; + pendingAppsLocal.swap(*pendingApps); + } + + // Calculate the height of the app list container then resize it, also moving the controls below it + // Note: Win10+ LogonUI is per-monitor scaled by manifest, and we're only showing the dialog on the primary monitor, + // so handling per-monitor DPI is not much trouble. Screenshotting works fine + // Runtime DPI change might be problematic but the BSDR window isn't movable and the Settings app isn't accessible + // during logoff phase either so it's low priority + // Note 2: when a window exactly covers the whole virtual screen, it uses the primary monitor's DPI regardless of proportions, + // which is good for us because the dialog is only shown on the primary monitor + // Even a 1px offset disables that behavior and makes the window use DPI of the monitor the window is primarily in + // This behavior is the same for per-monitor V1, V2, and also MonitorFromWindow/MonitorFromRect + int dpi = GetDpiForWindow(hWndDlg); + + int itemHeight = MulDiv(83, dpi, 96); + int itemHeightNoReason = MulDiv(62, dpi, 96); + + int totalItemsHeight = 0; + for (auto& app : pendingAppsLocal) { + BOOLEAN isBlocking = FALSE; + HSTRING blockReason = nullptr; + if (SUCCEEDED(app->get_IsBlocking(&isBlocking)) && isBlocking) { + totalItemsHeight += itemHeight; + } else if (SUCCEEDED(app->get_BlockReason(&blockReason))) { + const wchar_t* reasonStr = WindowsGetStringRawBuffer(blockReason, nullptr); + if (reasonStr && wcslen(reasonStr) > 0) { totalItemsHeight += itemHeight; - } else if (SUCCEEDED(app->get_BlockReason(&blockReason))) { - const wchar_t* reasonStr = WindowsGetStringRawBuffer(blockReason, nullptr); - if (reasonStr && wcslen(reasonStr) > 0) { - totalItemsHeight += itemHeight; - } else { - totalItemsHeight += itemHeightNoReason; - } - WindowsDeleteString(blockReason); } else { totalItemsHeight += itemHeightNoReason; } + WindowsDeleteString(blockReason); + } else { + totalItemsHeight += itemHeightNoReason; } + } - RECT rcAppList, rcScrollBar; - GetWindowRect(hAppList, &rcAppList); - GetWindowRect(hScrollBar, &rcScrollBar); - int currentWidth = rcAppList.right - rcAppList.left; - int scrollBarWidth = rcScrollBar.right - rcScrollBar.left; - - int screenHeight = GetSystemMetrics(SM_CYSCREEN); - minHeight = rcAppList.bottom - rcAppList.top; - int maxHeight = screenHeight - MulDiv(335, dpi, 96); - - if (maxHeight < minHeight) - maxHeight = minHeight; - - int newHeight = totalItemsHeight; - if (newHeight < minHeight) - newHeight = minHeight; - if (newHeight > maxHeight) - newHeight = maxHeight; - - int heightDiff = newHeight - minHeight; - - SetWindowPos(hAppList, nullptr, 0, 0, currentWidth, newHeight, SWP_NOMOVE | SWP_NOZORDER); - SetWindowPos(hAppListScroll, nullptr, 0, 0, currentWidth, totalItemsHeight, SWP_NOMOVE | SWP_NOZORDER); - SetWindowPos(hScrollBar, nullptr, 0, 0, scrollBarWidth, newHeight, SWP_NOMOVE | SWP_NOZORDER); - - for (HWND hwndSibling = GetWindow(hScrollBar, GW_HWNDNEXT); hwndSibling; hwndSibling = GetWindow(hwndSibling, GW_HWNDNEXT)) { - RECT rcSibling; - GetWindowRect(hwndSibling, &rcSibling); - MapWindowPoints(HWND_DESKTOP, hWndDlg, (LPPOINT)&rcSibling, 2); - OffsetRect(&rcSibling, 0, heightDiff); - SetWindowPos(hwndSibling, nullptr, rcSibling.left, rcSibling.top, 0, 0, SWP_NOSIZE | SWP_NOZORDER); - } + RECT rcAppList, rcScrollBar; + GetWindowRect(hAppList, &rcAppList); + GetWindowRect(hScrollBar, &rcScrollBar); + int currentWidth = rcAppList.right - rcAppList.left; + int scrollBarWidth = rcScrollBar.right - rcScrollBar.left; + + int screenHeight = GetSystemMetrics(SM_CYSCREEN); + minHeight = rcAppList.bottom - rcAppList.top; + int maxHeight = screenHeight - MulDiv(335, dpi, 96); + + if (maxHeight < minHeight) + maxHeight = minHeight; + + int newHeight = totalItemsHeight; + if (newHeight < minHeight) + newHeight = minHeight; + if (newHeight > maxHeight) + newHeight = maxHeight; - RECT rcTitle, rcNo; - GetWindowRect(hTitleText, &rcTitle); - MapWindowPoints(HWND_DESKTOP, hWndDlg, (LPPOINT)&rcTitle, 2); - GetWindowRect(hNoButton, &rcNo); - MapWindowPoints(HWND_DESKTOP, hWndDlg, (LPPOINT)&rcNo, 2); - int topPadding = rcTitle.top; - int dialogWidth = rcTitle.right; - int newDialogHeight = rcNo.bottom + topPadding - 1; - SetWindowPos(hWndDlg, nullptr, 0, 0, dialogWidth, newDialogHeight, SWP_NOMOVE | SWP_NOZORDER); + int heightDiff = newHeight - minHeight; + + SetWindowPos(hAppList, nullptr, 0, 0, currentWidth, newHeight, SWP_NOMOVE | SWP_NOZORDER); + SetWindowPos(hAppListScroll, nullptr, 0, 0, currentWidth, totalItemsHeight, SWP_NOMOVE | SWP_NOZORDER); + SetWindowPos(hScrollBar, nullptr, 0, 0, scrollBarWidth, newHeight, SWP_NOMOVE | SWP_NOZORDER); + + for (HWND hwndSibling = GetWindow(hScrollBar, GW_HWNDNEXT); hwndSibling; hwndSibling = GetWindow(hwndSibling, GW_HWNDNEXT)) { + RECT rcSibling; + GetWindowRect(hwndSibling, &rcSibling); + MapWindowPoints(HWND_DESKTOP, hWndDlg, (LPPOINT)&rcSibling, 2); + OffsetRect(&rcSibling, 0, heightDiff); + SetWindowPos(hwndSibling, nullptr, rcSibling.left, rcSibling.top, 0, 0, SWP_NOSIZE | SWP_NOZORDER); } + RECT rcTitle, rcNo; + GetWindowRect(hTitleText, &rcTitle); + MapWindowPoints(HWND_DESKTOP, hWndDlg, (LPPOINT)&rcTitle, 2); + GetWindowRect(hNoButton, &rcNo); + MapWindowPoints(HWND_DESKTOP, hWndDlg, (LPPOINT)&rcNo, 2); + int topPadding = rcTitle.top; + int dialogWidth = rcTitle.right; + int newDialogHeight = rcNo.bottom + topPadding - 1; + SetWindowPos(hWndDlg, nullptr, 0, 0, dialogWidth, newDialogHeight, SWP_NOMOVE | SWP_NOZORDER); + SystemParametersInfoW(SPI_GETWHEELSCROLLLINES, 0, &scrollLines, 0); SetWindowSubclass(hForceButton, ButtonSubclassProc, 0, 0); @@ -3760,7 +3770,9 @@ void CustomBSDR::Stop(bool ignoreIsCanceling) { std::lock_guard lock(cancelMutex); if ((ignoreIsCanceling || !isCanceling) && hStopEvent) { - SetEvent(hStopEvent); + if (!SetEvent(hStopEvent)) { + Wh_Log(L"SetEvent(hStopEvent) failed, GLE=%u", GetLastError()); + } } } #pragma endregion CustomBSDR.cpp @@ -3805,15 +3817,19 @@ long __fastcall BlockedShutdownUXImpl_AddApplication_hook(void* thisPtr, IShutdo UINT appId = 0; BOOLEAN isBlocking = FALSE; - HSTRING caption = nullptr, blockReason = nullptr; + HSTRING caption = nullptr; + HSTRING blockReason = nullptr; + blockingApp->get_Id(&appId); blockingApp->get_IsBlocking(&isBlocking); HRESULT hrCaption = blockingApp->get_Caption(&caption); HRESULT hrReason = blockingApp->get_BlockReason(&blockReason); + Wh_Log(L"BlockedShutdownUXImpl::AddApplication, AppId=%u, IsBlocking=%d, Caption=%s, BlockReason=%s", appId, isBlocking, SUCCEEDED(hrCaption) ? WindowsGetStringRawBuffer(caption, nullptr) : L"", SUCCEEDED(hrReason) ? WindowsGetStringRawBuffer(blockReason, nullptr) : L""); + WindowsDeleteString(blockReason); WindowsDeleteString(caption); From 2e9728f0da0059cb35322b595eb152b8ccd8433b Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Thu, 3 Sep 2026 17:22:53 +0900 Subject: [PATCH 50/51] Add more logging --- mods/win7-bsdr.wh.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index f89059ea16..e84d7c47ca 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -1746,6 +1746,7 @@ bool g_wasClicked = false; bool g_rejectNextResolved = false; void Resolve(BlockedShutdownResolution resolution, bool noInvoke = false) { + Wh_Log(L"Resolving with %d, noInvoke=%d", resolution, noInvoke); Microsoft::WRL::ComPtr resolvedLocal; { std::lock_guard lock(g_resolvedMutex); @@ -2763,6 +2764,7 @@ void CustomBSDR::UpdateAppListLayout() { } void CustomBSDR::Cancel(bool noExitProcess) { + Wh_Log(L"Canceling... noExitProcess=%d, isOnSecureDesktop=%d", noExitProcess, isOnSecureDesktop); if (!isOnSecureDesktop) { // If BSDR is forced to show on the default desktop with the Windhawk mod, LogonUI.exe won't exit for some reason on cancel, // causing issues with subsequent session ends, unless it's killed manually or Ctrl+Alt+Del is pressed once @@ -3355,6 +3357,8 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA return 0; } + Wh_Log(L"BSDR window closing..."); + HWND hDlgLocal = nullptr; { std::lock_guard lock(pendingAppsMutex); From 8b1b7888b7ba3a6f6810b9ac19ecaf4c89e53029 Mon Sep 17 00:00:00 2001 From: Ingan121 Date: Thu, 3 Sep 2026 17:50:36 +0900 Subject: [PATCH 51/51] Apply preemptive fixes 4 Move some variable initialization from WM_CLOSE to more appropriate places, to deal with window/dialog creation failures --- mods/win7-bsdr.wh.cpp | 56 +++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/mods/win7-bsdr.wh.cpp b/mods/win7-bsdr.wh.cpp index e84d7c47ca..c1473584b9 100644 --- a/mods/win7-bsdr.wh.cpp +++ b/mods/win7-bsdr.wh.cpp @@ -2807,6 +2807,18 @@ void CustomBSDR::Cancel(bool noExitProcess) { INT_PTR CALLBACK CustomBSDR::DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_INITDIALOG: { + hTitleText = nullptr; + hAppList = nullptr; + hAppListScroll = nullptr; + hScrollBar = nullptr; + hWarningText = nullptr; + hForceButton = nullptr; + hCancelButton = nullptr; + hDescText = nullptr; + hYesButton = nullptr; + hNoButton = nullptr; + hHoverButton = nullptr; + // Verify if all expected controls are there hTitleText = GetDlgItem(hWndDlg, IDC_BSDR_TITLE); hAppList = GetDlgItem(hWndDlg, IDC_BSDR_APPLIST); @@ -3394,11 +3406,6 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA pendingApps->clear(); } - { - std::lock_guard lock(cancelMutex); - isCanceling = false; - } - if (bgBitmap) { DeleteObject(bgBitmap); bgBitmap = nullptr; @@ -3436,29 +3443,6 @@ LRESULT CALLBACK CustomBSDR::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPA hDescFont = nullptr; } - hTitleText = nullptr; - hAppList = nullptr; - hAppListScroll = nullptr; - hScrollBar = nullptr; - hWarningText = nullptr; - hForceButton = nullptr; - hCancelButton = nullptr; - hDescText = nullptr; - hYesButton = nullptr; - hNoButton = nullptr; - hHoverButton = nullptr; - - dlgInitFailed = false; - bgOffsetX = 0; - bgOffsetY = 0; - bgWidth = 0; - bgHeight = 0; - scrollPos = 0; - totalContentHeight = 0; - minHeight = 0; - paintedFirstFrame = false; - isOnSecureDesktop = true; - return 0; } case WM_DESTROY: @@ -3473,6 +3457,17 @@ DWORD WINAPI CustomBSDR::ThreadProc(LPVOID lpParameter) { bool unloadCancelHandled = false; int ret = 0; + dlgInitFailed = false; + bgOffsetX = 0; + bgOffsetY = 0; + bgWidth = 0; + bgHeight = 0; + scrollPos = 0; + totalContentHeight = 0; + minHeight = 0; + paintedFirstFrame = false; + isOnSecureDesktop = true; + // Attempt to create window on the input desktop, as the thread is always running in secure desktop at this point, // but the Windhawk mod can force this phase of session end to run in the default desktop // (This is only an attempt and not critical if failed) @@ -3711,6 +3706,11 @@ void CustomBSDR::Start(LogonUIState state) { g_wasClicked = false; } + { + std::lock_guard lock(cancelMutex); + isCanceling = false; + } + if (!ResetEvent(hStopEvent)) { Wh_Log(L"ResetEvent failed, GLE=%u", GetLastError()); Resolve(BlockedShutdownResolution_Force, true);