LegacyUpdate/launcher/InitRunOnce.c
2024-10-24 17:20:36 +10:30

300 lines
8.2 KiB
C

#include <windows.h>
#include <commctrl.h>
#include "MsgBox.h"
#include "VersionInfo.h"
#include "LoadImage.h"
#include "Gdip.h"
#define HK_RUNCMD 1
typedef DWORD (__fastcall *_ThemeWaitForServiceReady)(DWORD timeout);
typedef DWORD (__fastcall *_ThemeWatchForStart)();
static const COLORREF WallpaperColorWin2k = RGB(58, 110, 165); // #3a6ea5
static const COLORREF WallpaperColorWinXP = RGB( 0, 78, 152); // #004e98
static const COLORREF WallpaperColorWin8 = RGB(32, 103, 178); // #2067b2
static const COLORREF WallpaperColorWin10 = RGB(24, 0, 82); // #180052
static const WCHAR RunOnceClassName[] = L"LegacyUpdateRunOnce";
static HANDLE g_cmdHandle;
static void StartThemes() {
// Ask UxInit.dll to ask the Themes service to start a session for this desktop. Themes doesn't
// automatically start a session for the SYSTEM desktop, so we need to ask it to. This matches
// what msoobe.exe does on first boot.
// Windows 7 moves this to UxInit.dll
HMODULE shsvcs = LoadLibrary(L"UxInit.dll");
if (!shsvcs) {
shsvcs = LoadLibrary(L"shsvcs.dll");
if (!shsvcs) {
return;
}
}
// Get functions by ordinals
_ThemeWaitForServiceReady $ThemeWaitForServiceReady = (_ThemeWaitForServiceReady)GetProcAddress(shsvcs, MAKEINTRESOURCEA(2));
_ThemeWatchForStart $ThemeWatchForStart = (_ThemeWatchForStart)GetProcAddress(shsvcs, MAKEINTRESOURCEA(1));
// 1. Wait up to 1000ms for Themes to start
if ($ThemeWaitForServiceReady) {
$ThemeWaitForServiceReady(1000);
}
// 2. Prompt Themes to start a session for the SYSTEM desktop
if ($ThemeWatchForStart) {
$ThemeWatchForStart();
}
FreeLibrary(shsvcs);
}
static BOOL RunCmd(LPPROCESS_INFORMATION processInfo) {
WCHAR cmd[MAX_PATH];
ExpandEnvironmentStrings(L"%SystemRoot%\\System32\\cmd.exe", cmd, ARRAYSIZE(cmd));
STARTUPINFO startupInfo = {0};
startupInfo.cb = sizeof(startupInfo);
if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &startupInfo, processInfo)) {
MsgBox(NULL, L"Launching cmd.exe failed", NULL, MB_OK | MB_ICONERROR);
return FALSE;
}
CloseHandle(processInfo->hThread);
g_cmdHandle = processInfo->hProcess;
return TRUE;
}
static LRESULT CALLBACK RunOnceWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WM_NCHITTEST:
// Don't accept any mouse input
return HTNOWHERE;
case WM_HOTKEY: {
// Shift-F10 to run cmd
if (wParam == HK_RUNCMD) {
DWORD exitCode;
if (!g_cmdHandle || (GetExitCodeProcess(g_cmdHandle, &exitCode) && exitCode != STILL_ACTIVE)) {
PROCESS_INFORMATION processInfo;
RunCmd(&processInfo);
}
}
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, message, wParam, lParam);;
}
static void CreateRunOnceWindow() {
// Init COM
CoInitialize(NULL);
// Init common controls
INITCOMMONCONTROLSEX initComctl = {0};
initComctl.dwSize = sizeof(initComctl);
initComctl.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&initComctl);
// Create window
WNDCLASS wndClass = {0};
wndClass.style = CS_VREDRAW | CS_HREDRAW | CS_OWNDC | CS_NOCLOSE;
wndClass.lpfnWndProc = RunOnceWndProc;
wndClass.hInstance = GetModuleHandle(NULL);
wndClass.lpszClassName = RunOnceClassName;
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
if (!RegisterClass(&wndClass)) {
TRACE(L"RegisterClass failed: %d", GetLastError());
return;
}
HWND hwnd = CreateWindowEx(
WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE,
wndClass.lpszClassName,
L"Legacy Update",
WS_POPUP,
0, 0, 0, 0,
NULL, NULL,
wndClass.hInstance,
NULL
);
if (!hwnd) {
TRACE(L"CreateWindow failed: %d", GetLastError());
return;
}
// Register hotkey
RegisterHotKey(hwnd, HK_RUNCMD, MOD_SHIFT, VK_F10);
// Set the wallpaper color
COLORREF color = GetSysColor(COLOR_DESKTOP);
if (AtLeastWin10()) {
color = WallpaperColorWin10;
} else if (AtLeastWin8()) {
color = WallpaperColorWin8;
} else if ((IsWinXP2002() || IsWinXP2003()) && color == RGB(0, 0, 0)) {
// XP uses a black wallpaper in fast user switching mode. Override to the default blue.
color = WallpaperColorWinXP;
}
SetSysColors(1, (const INT[1]){COLOR_DESKTOP}, (const COLORREF[1]){color});
DWORD width = GetSystemMetrics(SM_CXSCREEN);
DWORD height = GetSystemMetrics(SM_CYSCREEN);
GpBitmap *wallpaper;
if (IsWin7()) {
// 7: Bitmap in oobe dir
WCHAR bmpPath[MAX_PATH];
ExpandEnvironmentStrings(L"%SystemRoot%\\System32\\oobe\\background.bmp", bmpPath, ARRAYSIZE(bmpPath));
_GdipLoadImageFromFile $GdipLoadImageFromFile = (_GdipLoadImageFromFile)GetGdiplusSymbol("GdipLoadImageFromFile");
if ($GdipLoadImageFromFile) {
$GdipLoadImageFromFile(bmpPath, &wallpaper);
}
} else if (IsWinVista()) {
// Vista: Resources in ooberesources.dll
WCHAR ooberesPath[MAX_PATH];
ExpandEnvironmentStrings(L"%SystemRoot%\\System32\\oobe\\ooberesources.dll", ooberesPath, ARRAYSIZE(ooberesPath));
HMODULE ooberes = LoadLibrary(ooberesPath);
if (ooberes) {
// Width logic is the same used by Vista msoobe.dll
LPWSTR resource = GetSystemMetrics(SM_CXSCREEN) < 1200 ? L"OOBE_BACKGROUND_0" : L"OOBE_BACKGROUND_LARGE_0";
wallpaper = LoadPNGResource(ooberes, resource, RT_RCDATA);
}
FreeLibrary(ooberes);
}
if (wallpaper) {
// Write to disk
WCHAR tempPath[MAX_PATH];
ExpandEnvironmentStrings(L"%ProgramData%\\Legacy Update\\background.bmp", tempPath, ARRAYSIZE(tempPath));
if (GetFileAttributes(tempPath) != INVALID_FILE_ATTRIBUTES || ScaleAndWriteToBMP(wallpaper, width, height, tempPath)) {
SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, (PVOID)tempPath, SPIF_SENDWININICHANGE);
}
DeleteObject(wallpaper);
}
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
}
static BOOL IsSystemUser() {
BOOL result = FALSE;
PTOKEN_USER tokenInfo;
PSID systemSid;
HANDLE token;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
goto end;
}
DWORD tokenInfoLen;
GetTokenInformation(token, TokenUser, NULL, 0, &tokenInfoLen);
tokenInfo = (PTOKEN_USER)LocalAlloc(LPTR, tokenInfoLen);
if (!GetTokenInformation(token, TokenUser, tokenInfo, tokenInfoLen, &tokenInfoLen)) {
goto end;
}
DWORD sidSize = SECURITY_MAX_SID_SIZE;
systemSid = LocalAlloc(LPTR, sidSize);
if (!CreateWellKnownSid(WinLocalSystemSid, NULL, systemSid, &sidSize)) {
goto end;
}
result = EqualSid(tokenInfo->User.Sid, systemSid);
end:
if (tokenInfo) {
LocalFree(tokenInfo);
}
if (systemSid) {
LocalFree(systemSid);
}
if (token) {
CloseHandle(token);
}
return result;
}
void RunOnce() {
#ifndef _DEBUG
// Only relevant if we're SYSTEM
if (!IsSystemUser()) {
PostQuitMessage(1);
return;
}
#endif
// Start Themes on this desktop
StartThemes();
// Find and hide the FirstUxWnd window, if it exists (Windows 7+)
HWND firstUxWnd = FindWindow(L"FirstUxWndClass", NULL);
if (firstUxWnd) {
ShowWindow(firstUxWnd, SW_HIDE);
}
// Set up our window
CreateRunOnceWindow();
// Construct path to LegacyUpdateSetup.exe
WCHAR setupPath[MAX_PATH];
GetModuleFileName(NULL, setupPath, ARRAYSIZE(setupPath));
wcsrchr(setupPath, L'\\')[1] = L'\0';
wcsncat(setupPath, L"LegacyUpdateSetup.exe", ARRAYSIZE(setupPath) - wcslen(setupPath) - 1);
// Execute and wait for completion
STARTUPINFO startupInfo = {0};
startupInfo.cb = sizeof(startupInfo);
PROCESS_INFORMATION processInfo = {0};
LPWSTR cmdLine = (LPWSTR)LocalAlloc(LPTR, 4096 * sizeof(WCHAR));
wsprintf(cmdLine, L"\"%ls\" /runonce", setupPath);
if (!CreateProcess(setupPath, cmdLine, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &startupInfo, &processInfo)) {
#ifdef _DEBUG
// Run cmd.exe instead
if (!RunCmd(&processInfo)) {
PostQuitMessage(0);
return;
}
#else
MsgBox(NULL, L"Continuing Legacy Update setup failed", NULL, MB_OK | MB_ICONERROR);
PostQuitMessage(0);
return;
#endif
}
CloseHandle(processInfo.hThread);
// Wait for it to finish, while running a message loop
MSG msg;
while (WaitForSingleObject(processInfo.hProcess, 100) == WAIT_TIMEOUT) {
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
CloseHandle(processInfo.hProcess);
// Don't let SYSTEM cmd keep running beyond runonce
if (g_cmdHandle) {
TerminateProcess(g_cmdHandle, 0);
CloseHandle(g_cmdHandle);
}
// Show FirstUxWnd again
if (firstUxWnd) {
ShowWindow(firstUxWnd, SW_SHOW);
}
PostQuitMessage(0);
}