--- /dev/null
+/******************************************************************************\r
+* setupmon.c\r
+*\r
+* Copyright (c) 2022, longpanda <admin@ventoy.net>\r
+*\r
+* This program is free software; you can redistribute it and/or\r
+* modify it under the terms of the GNU General Public License as\r
+* published by the Free Software Foundation; either version 3 of the\r
+* License, or (at your option) any later version.\r
+*\r
+* This program is distributed in the hope that it will be useful, but\r
+* WITHOUT ANY WARRANTY; without even the implied warranty of\r
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
+* General Public License for more details.\r
+*\r
+* You should have received a copy of the GNU General Public License\r
+* along with this program; if not, see <http://www.gnu.org/licenses/>.\r
+*\r
+*/\r
+\r
+#include <stdio.h>\r
+#include <stdlib.h>\r
+#include <string.h>\r
+#include <Windows.h>\r
+#include <virtdisk.h>\r
+#include <winioctl.h>\r
+#include <VersionHelpers.h>\r
+#include "vtoyjump.h"\r
+\r
+\r
+#define PROMPT_MAX 1024\r
+typedef struct VTOY_MUI_DATA\r
+{\r
+ WCHAR Prompt[PROMPT_MAX];\r
+ struct VTOY_MUI_DATA* pNext;\r
+}VTOY_MUI_DATA;\r
+\r
+static VTOY_MUI_DATA* g_MuiDataHead = NULL;\r
+static FILETIME g_SetupMonStartTime;\r
+\r
+#pragma pack(1)\r
+typedef struct {\r
+ WORD dlgVer;\r
+ WORD signature;\r
+ DWORD helpID;\r
+ DWORD exStyle;\r
+ DWORD style;\r
+ WORD cDlgItems;\r
+ short x;\r
+ short y;\r
+ short cx;\r
+ short cy;\r
+ //sz_Or_Ord menu;\r
+ //sz_Or_Ord windowClass;\r
+ //WCHAR title[titleLen];\r
+ //WORD pointsize;\r
+ //WORD weight;\r
+ //BYTE italic;\r
+ //BYTE charset;\r
+ //WCHAR typeface[stringLen];\r
+} DLGTEMPLATEEX;\r
+\r
+typedef struct {\r
+ DWORD helpID;\r
+ DWORD exStyle;\r
+ DWORD style;\r
+ short x;\r
+ short y;\r
+ short cx;\r
+ short cy;\r
+ DWORD id;\r
+ // sz_Or_Ord windowClass;\r
+ // sz_Or_Ord title;\r
+ // WORD extraCount;\r
+} DLGITEMTEMPLATEEX;\r
+\r
+\r
+#pragma pack()\r
+\r
+#define wchar_to_utf8_no_alloc(wsrc, dest, dest_size) \\r
+ WideCharToMultiByte(CP_UTF8, 0, wsrc, -1, dest, dest_size, NULL, NULL)\r
+#define utf8_to_wchar_no_alloc(src, wdest, wdest_size) \\r
+ MultiByteToWideChar(CP_UTF8, 0, src, -1, wdest, wdest_size)\r
+\r
+#define VTOY_DLG_SKIP_SZ_ORD(pWord) \\r
+{\\r
+ if (*pWord == 0x0000)\\r
+ {\\r
+ pWordData += 1;\\r
+ }\\r
+ else if (*pWordData == 0xFFFF)\\r
+ {\\r
+ pWordData += 2;\\r
+ }\\r
+ else\\r
+ {\\r
+ while (*pWordData++)\\r
+ {\\r
+ ;\\r
+ }\\r
+ }\\r
+}\r
+\r
+#define VTOY_DLG_SKIP_SZ(pWord) \\r
+{\\r
+ while (*pWord)\\r
+ {\\r
+ pWord++;\\r
+ }\\r
+ pWord++;\\r
+}\r
+\r
+\r
+static BOOL LoadSetupRebootDialogPrompt(HMODULE hMui, DWORD ID, WCHAR* Prompt, DWORD Len)\r
+{\r
+ DWORD i = 0;\r
+ UINT64 Addr = 0;\r
+ WORD* pWordData = NULL;\r
+ HRSRC hDlg = NULL;\r
+ HGLOBAL hTemplate = NULL;\r
+ DLGTEMPLATEEX* pDlgTempEx = NULL;\r
+ DLGITEMTEMPLATEEX* pDlgItemTempEx = NULL;\r
+\r
+ hDlg = FindResource(hMui, MAKEINTRESOURCE(ID), RT_DIALOG);\r
+ if (!hDlg)\r
+ {\r
+ return FALSE;\r
+ }\r
+\r
+ hTemplate = LoadResource(hMui, hDlg);\r
+ if (!hTemplate)\r
+ {\r
+ return FALSE;\r
+ }\r
+\r
+ pDlgTempEx = (DLGTEMPLATEEX*)LockResource(hTemplate);\r
+ if (!pDlgTempEx)\r
+ {\r
+ return FALSE;\r
+ }\r
+\r
+ if (pDlgTempEx->signature != 0xFFFF)\r
+ {\r
+ return FALSE;\r
+ }\r
+\r
+ pWordData = (WORD*)(pDlgTempEx + 1);\r
+\r
+ //skip menu\r
+ VTOY_DLG_SKIP_SZ_ORD(pWordData);\r
+\r
+ //skip windowClass\r
+ VTOY_DLG_SKIP_SZ_ORD(pWordData);\r
+\r
+ //skip title\r
+ VTOY_DLG_SKIP_SZ(pWordData);\r
+\r
+ //skip pointsize/weight/italic + charset\r
+ pWordData += 3;\r
+\r
+ //skip typeface\r
+ VTOY_DLG_SKIP_SZ(pWordData);\r
+\r
+ //align to DWORD\r
+ Addr = (UINT64)pWordData;\r
+ if ((Addr % 4) > 0)\r
+ {\r
+ Addr += 4 - (Addr % 4);\r
+ pWordData = (WORD*)Addr;\r
+ }\r
+\r
+ //First Dlg Item \r
+ pDlgItemTempEx = (DLGITEMTEMPLATEEX*)pWordData;\r
+\r
+ if (pDlgItemTempEx->id != 1023)\r
+ {\r
+ return FALSE;\r
+ }\r
+\r
+ pWordData = (WORD*)(pDlgItemTempEx + 1);\r
+\r
+ //skip windowClass\r
+ VTOY_DLG_SKIP_SZ_ORD(pWordData);\r
+\r
+ for (i = 0; i < Len; i++)\r
+ {\r
+ Prompt[i] = pWordData[i];\r
+ if (Prompt[i] == 0)\r
+ {\r
+ break;\r
+ }\r
+ }\r
+\r
+ return TRUE;\r
+}\r
+\r
+static int LoadPromptString(const char* szDir)\r
+{\r
+ BOOL bRet;\r
+ HMODULE hMui;\r
+ CHAR MuiFile[MAX_PATH];\r
+ WCHAR Prompt[PROMPT_MAX];\r
+ VTOY_MUI_DATA* Node = NULL;\r
+\r
+ sprintf_s(MuiFile, sizeof(MuiFile), "X:\\Sources\\%s\\w32uires.dll.mui", szDir);\r
+\r
+ hMui = LoadLibraryA(MuiFile);\r
+ if (!hMui)\r
+ {\r
+ Log("Failed to loadlibrary <%s> %u", MuiFile, LASTERR);\r
+ return 1;\r
+ }\r
+\r
+ bRet = LoadSetupRebootDialogPrompt(hMui, 103, Prompt, PROMPT_MAX);\r
+ if (bRet)\r
+ {\r
+ Node = malloc(sizeof(VTOY_MUI_DATA));\r
+ if (Node)\r
+ {\r
+ memset(Node, 0, sizeof(VTOY_MUI_DATA));\r
+ memcpy(Node->Prompt, Prompt, sizeof(Prompt));\r
+ if (g_MuiDataHead == NULL)\r
+ {\r
+ g_MuiDataHead = Node;\r
+ }\r
+ else\r
+ {\r
+ Node->pNext = g_MuiDataHead;\r
+ g_MuiDataHead = Node;\r
+ }\r
+\r
+ Log("Successfully add prompt string for <%s>", szDir);\r
+ }\r
+ }\r
+ else\r
+ {\r
+ Log("Failed to load prompt string from %s", szDir);\r
+ }\r
+\r
+ FreeLibrary(hMui);\r
+ return 0;\r
+}\r
+\r
+static int FindAllPromptStrings(void)\r
+{\r
+ HANDLE hFind;\r
+ WIN32_FIND_DATAA FindData;\r
+\r
+ hFind = FindFirstFileA("X:\\Sources\\*", &FindData);\r
+ if (hFind == INVALID_HANDLE_VALUE)\r
+ {\r
+ Log("FindFirstFileA failed %u", LASTERR);\r
+ return 0;\r
+ }\r
+\r
+ do\r
+ {\r
+ if ((FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (strlen(FindData.cFileName) == 5))\r
+ {\r
+ if (IsFileExist("X:\\Sources\\%s\\w32uires.dll.mui", FindData.cFileName))\r
+ {\r
+ LoadPromptString(FindData.cFileName);\r
+ }\r
+ } \r
+ } while (FindNextFileA(hFind, &FindData));\r
+\r
+ return 0;\r
+}\r
+\r
+static BOOL CALLBACK SetupPromptCallback(HWND hWnd, LPARAM lParam)\r
+{\r
+ VTOY_MUI_DATA* Node = NULL;\r
+ WCHAR Prompt[PROMPT_MAX] = { 0 };\r
+ BOOL* found = (BOOL*)lParam;\r
+\r
+ if (GetWindowTextW(hWnd, Prompt, PROMPT_MAX - 1) > 0)\r
+ {\r
+ for (Node = g_MuiDataHead; Node; Node = Node->pNext)\r
+ {\r
+ if (wcscmp(Node->Prompt, Prompt) == 0)\r
+ {\r
+ *found = TRUE;\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ \r
+ return TRUE;\r
+}\r
+\r
+static BOOL CALLBACK EnumWindowsProcFunc(HWND hWnd, LPARAM lParam)\r
+{\r
+ EnumChildWindows(hWnd, SetupPromptCallback, lParam);\r
+ return TRUE;\r
+}\r
+\r
+\r
+static CHAR FindWindowsInstallDstDrive(void)\r
+{\r
+ CHAR ret = 0;\r
+ CHAR Drv = 'A';\r
+ DWORD Drives;\r
+ CHAR ChkFilePath[MAX_PATH];\r
+ HANDLE hFile;\r
+ UINT64 Time1, Time2, Time3;\r
+ FILETIME fTime;\r
+ FILETIME sysftime;\r
+ SYSTEMTIME cur_systime;\r
+\r
+ GetSystemTime(&cur_systime);\r
+ SystemTimeToFileTime(&cur_systime, &sysftime);\r
+\r
+ memcpy(&Time1, &g_SetupMonStartTime, 8);\r
+ memcpy(&Time2, &sysftime, 8);\r
+\r
+ Drives = GetLogicalDrives();\r
+ Log("Find Windows: Logical Drives: 0x%x", Drives);\r
+\r
+ while ((Drives > 0) && (ret == 0))\r
+ {\r
+ if ((Drives & 1) && Drv != 'X')\r
+ {\r
+ sprintf_s(ChkFilePath, sizeof(ChkFilePath), "%C:\\$WINDOWS.~BT\\Sources\\Panther\\setupact.log", Drv);\r
+ hFile = CreateFileA(ChkFilePath, FILE_READ_EA, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);\r
+ if (hFile != INVALID_HANDLE_VALUE)\r
+ {\r
+ Log("%s Exist", ChkFilePath);\r
+\r
+ if (GetFileTime(hFile, NULL, NULL, &fTime))\r
+ {\r
+ memcpy(&Time3, &fTime, 8);\r
+\r
+ Log("### %s %llu %llu %llu", ChkFilePath, Time1, Time2, Time3);\r
+\r
+ if (Time3 > Time1 && Time3 < Time2)\r
+ {\r
+ Log("Detect Windows11 install drive:<%C>", Drv);\r
+ ret = Drv;\r
+ }\r
+ }\r
+ CloseHandle(hFile);\r
+ }\r
+ else\r
+ {\r
+ Log("%s NOT Exist %u", ChkFilePath, LASTERR);\r
+ }\r
+ }\r
+\r
+ Drives >>= 1;\r
+ Drv++;\r
+ }\r
+\r
+ return ret;\r
+}\r
+\r
+static int AddBypassNROReg(const CHAR* OfflineRegPath)\r
+{\r
+ int ret = 1;\r
+ BOOL Unload = FALSE;\r
+ LSTATUS Status;\r
+ HKEY hKey = NULL;\r
+ HKEY hSubKey = NULL; \r
+ DWORD dwValue = 1;\r
+ DWORD dwSize;\r
+ HANDLE htoken;\r
+ DWORD s = 0;\r
+ TOKEN_PRIVILEGES* p = NULL;\r
+\r
+ Log("AddBypassNROReg<%s>", OfflineRegPath);\r
+\r
+ if (!IsFileExist(OfflineRegPath))\r
+ {\r
+ return ret;\r
+ }\r
+ \r
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &htoken))\r
+ {\r
+ Log("Open process token failed! %u", LASTERR);\r
+ return ret;\r
+ }\r
+\r
+ s = sizeof(TOKEN_PRIVILEGES) + 2 * sizeof(LUID_AND_ATTRIBUTES);\r
+ p = (PTOKEN_PRIVILEGES)malloc(s);\r
+ if (!p)\r
+ {\r
+ Log("Failed to alloc privileges memory");\r
+ return ret;\r
+ }\r
+\r
+ if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &(p->Privileges[0].Luid)) ||\r
+ !LookupPrivilegeValue(NULL, SE_BACKUP_NAME, &(p->Privileges[1].Luid)) ||\r
+ !LookupPrivilegeValue(NULL, SE_RESTORE_NAME, &(p->Privileges[2].Luid))) \r
+ {\r
+ Log("LookupPrivilegeValue failed!");\r
+ goto End;\r
+ }\r
+\r
+ p->PrivilegeCount = 3; \r
+ p->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\r
+ p->Privileges[1].Attributes = SE_PRIVILEGE_ENABLED;\r
+ p->Privileges[2].Attributes = SE_PRIVILEGE_ENABLED;\r
+\r
+ if (!AdjustTokenPrivileges(htoken, FALSE, p, s, NULL, NULL) || GetLastError() != ERROR_SUCCESS) \r
+ {\r
+ Log("AdjustTokenPrivileges failed!");\r
+ goto End;\r
+ }\r
+\r
+ Log("AdjustTokenPrivileges success");\r
+\r
+ Status = RegLoadKeyA(HKEY_LOCAL_MACHINE, "VTOYNEWSW", OfflineRegPath);\r
+ if (Status != ERROR_SUCCESS)\r
+ {\r
+ Log("RegLoadKey Failed 0x%x", Status);\r
+ goto End;\r
+ }\r
+ Unload = TRUE;\r
+\r
+ Status = RegCreateKeyExA(HKEY_LOCAL_MACHINE, "VTOYNEWSW\\Microsoft\\Windows\\CurrentVersion", 0, NULL, REG_OPTION_BACKUP_RESTORE, KEY_ALL_ACCESS, NULL, &hKey, &dwSize);\r
+ if (ERROR_SUCCESS != Status)\r
+ {\r
+ Log("Failed to create reg key VTOYNEWSW\\Microsoft\\Windows\\CurrentVersion %u %u", LASTERR, Status);\r
+ goto End;\r
+ }\r
+\r
+ Status = RegCreateKeyExA(hKey, "OOBE", 0, NULL, 0, KEY_SET_VALUE | KEY_QUERY_VALUE | KEY_CREATE_SUB_KEY, NULL, &hSubKey, &dwSize);\r
+ if (ERROR_SUCCESS != Status)\r
+ {\r
+ Log("Failed to create OOBE reg %u %u", LASTERR, Status);\r
+ goto End;\r
+ }\r
+\r
+ Status = RegSetValueExA(hSubKey, "BypassNRO", 0, REG_DWORD, (LPBYTE)&dwValue, sizeof(DWORD));\r
+ Log("Create BypassNRO registry %s %u", (Status == ERROR_SUCCESS) ? "SUCCESS" : "FAILED", Status);\r
+\r
+ Status = RegFlushKey(hSubKey);\r
+ Status += RegCloseKey(hSubKey);\r
+ Log("Flush and close subkey %s %u", (Status == ERROR_SUCCESS) ? "SUCCESS" : "FAILED", Status);\r
+\r
+ Status = RegFlushKey(hKey);\r
+ Status += RegCloseKey(hKey);\r
+ Log("Flush and close key %s %u", (Status == ERROR_SUCCESS) ? "SUCCESS" : "FAILED", Status);\r
+\r
+ ret = 0;\r
+ \r
+End:\r
+ if (Unload)\r
+ {\r
+ Status = RegUnLoadKeyA(HKEY_LOCAL_MACHINE, "VTOYNEWSW");\r
+ Log("RegUnLoadKey %s %u", (Status == ERROR_SUCCESS) ? "SUCCESS" : "FAILED", Status);\r
+ }\r
+ \r
+ if (p)\r
+ {\r
+ free(p);\r
+ }\r
+\r
+ return ret;\r
+}\r
+\r
+static DWORD WINAPI WaitSetupFinishThread(void* Param)\r
+{\r
+ CHAR Drv;\r
+ BOOL Found = FALSE;\r
+ CHAR OfflineRegPath[MAX_PATH];\r
+\r
+ while (!Found)\r
+ {\r
+ Sleep(300);\r
+ EnumWindows(EnumWindowsProcFunc, (LPARAM)&Found);\r
+ }\r
+\r
+ Log("### Setup finish detected");\r
+\r
+ // Add HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE\BypassNRO\r
+ Drv = FindWindowsInstallDstDrive();\r
+ Log("Find Windows install drive %d", Drv);\r
+ if (Drv)\r
+ {\r
+ sprintf_s(OfflineRegPath, sizeof(OfflineRegPath), "%C:\\Windows\\system32\\config\\SOFTWARE", Drv);\r
+ AddBypassNROReg(OfflineRegPath);\r
+ }\r
+\r
+ return 0;\r
+}\r
+\r
+int SetupMonNroStart(const char *isopath)\r
+{\r
+ SYSTEMTIME systime;\r
+\r
+ Log("SetupMonNroStart ...");\r
+\r
+ FindAllPromptStrings();\r
+\r
+ if (!g_MuiDataHead)\r
+ {\r
+ Log("Prompt not found, add default");\r
+ g_MuiDataHead = malloc(sizeof(VTOY_MUI_DATA));\r
+ if (g_MuiDataHead)\r
+ {\r
+ wcscpy_s(g_MuiDataHead->Prompt, PROMPT_MAX, L"Windows needs to restart to continue");\r
+ g_MuiDataHead->pNext = NULL;\r
+ }\r
+ }\r
+\r
+ Log("Wait for setup finish...");\r
+ GetSystemTime(&systime);\r
+ SystemTimeToFileTime(&systime, &g_SetupMonStartTime);\r
+\r
+ CreateThread(NULL, 0, WaitSetupFinishThread, NULL, 0, NULL);\r
+\r
+ return 0;\r
+}\r