Skip to content

Instantly share code, notes, and snippets.

@fredemmott
Created August 27, 2024 22:43
Show Gist options
  • Save fredemmott/4651df860b0ae0cc062020cdd503e5cf to your computer and use it in GitHub Desktop.
Save fredemmott/4651df860b0ae0cc062020cdd503e5cf to your computer and use it in GitHub Desktop.
diff --git a/.clang-format b/.clang-format
index 63d4edf..1cd1c88 100644
--- a/.clang-format
+++ b/.clang-format
@@ -23,7 +23,10 @@ IncludeCategories:
- Regex: '^<Windows\.h>$'
Priority: 30
- Regex: '^<Unknwn\.h>'
- Priority: 31
+ Priority: 30
+ - Regex: '^<Psapi\.h>'
+ Priority: 30
+ SortPriority: 31
- Regex: '^<winrt/.+'
Priority: 32
SortPriority: 32
diff --git a/src/windows/wWinMain.cpp b/src/windows/wWinMain.cpp
index 2b28c09..27e824f 100644
--- a/src/windows/wWinMain.cpp
+++ b/src/windows/wWinMain.cpp
@@ -2,9 +2,136 @@
// SPDX-License-Identifier: ISC
#include <Windows.h>
+#include <Psapi.h>
+
+#include <winrt/base.h>
+
+#include <filesystem>
+#include <format>
+#include <memory>
#include "GUI.hpp"
+static void CheckForUpdates() {
+ wchar_t exePathStr[32767];
+ const auto exePathStrLength = GetModuleFileNameExW(
+ GetCurrentProcess(), nullptr, exePathStr, std::size(exePathStr));
+ const std::filesystem::path thisExe {
+ std::wstring_view {exePathStr, exePathStrLength}};
+ const auto directory = thisExe.parent_path();
+
+ constexpr auto base = L"fredemmott_OpenXR-API-Layers-GUI_Updater";
+ const auto config = directory / std::format(L"{}.json", base);
+ const auto updater = directory / std::format(L"{}.exe", base);
+
+ if (!std::filesystem::exists(config)) {
+ OutputDebugStringW(
+ std::format(
+ L"Skipping auto-update because `{}` does not exist", config.wstring())
+ .c_str());
+ return;
+ }
+ if (!std::filesystem::exists(updater)) {
+ OutputDebugStringW(
+ std::format(
+ L"Skipping auto-update because `{}` does not exist", updater.wstring())
+ .c_str());
+ return;
+ }
+
+ // - We run elevated as the entire point of this program is to write to HKLM
+ // - We don't want to elevate the installer unnecessarily
+ //
+ // So, we get the shell window/process so we can use
+ // PROC_THREAD_ATTRIBUTE_PARENT_PROCESS to de-elevate the updater
+
+ const auto shellWindow = GetShellWindow();
+ DWORD shellPid {};
+ GetWindowThreadProcessId(shellWindow, &shellPid);
+ winrt::handle shellProcess {
+ OpenProcess(PROCESS_CREATE_PROCESS | PROCESS_DUP_HANDLE, FALSE, shellPid)};
+
+ SIZE_T threadAttributesSize {};
+ InitializeProcThreadAttributeList(nullptr, 1, 0, &threadAttributesSize);
+ auto threadAttributesBuffer = std::make_unique<char[]>(threadAttributesSize);
+ auto threadAttributes = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(
+ threadAttributesBuffer.get());
+ winrt::check_bool(InitializeProcThreadAttributeList(
+ threadAttributes, 1, 0, &threadAttributesSize));
+
+ // Need a pointer to the value, not just the value
+ HANDLE shellProcessRaw {shellProcess.get()};
+ winrt::check_bool(UpdateProcThreadAttribute(
+ threadAttributes,
+ 0,
+ PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
+ &shellProcessRaw,
+ sizeof(HANDLE),
+ nullptr,
+ nullptr));
+
+ // As we're specifying PROC_THREAD_ATTRIBUTE_PARENT_PROCESS as the shell, we
+ // need to duplicate the handle to the shell too, rather than the current
+ // process.
+ HANDLE thisProcessAsShell {};
+ winrt::check_bool(DuplicateHandle(
+ GetCurrentProcess(),
+ GetCurrentProcess(),
+ shellProcessRaw,
+ &thisProcessAsShell,
+ PROCESS_TERMINATE | PROCESS_QUERY_LIMITED_INFORMATION,
+ /* inherit = */ true,
+ 0));
+
+ auto launch = [&]<class... Args>(
+ std::wformat_string<Args...> commandLineFmt,
+ Args&&... commandLineFmtArgs) {
+ auto commandLine
+ = std::format(commandLineFmt, std::forward<Args>(commandLineFmtArgs)...);
+
+ STARTUPINFOEXW startupInfo {
+ .StartupInfo = {sizeof(STARTUPINFOEXW)},
+ .lpAttributeList = threadAttributes,
+ };
+ PROCESS_INFORMATION processInfo {};
+
+ winrt::check_bool(CreateProcessW(
+ updater.wstring().c_str(),
+ commandLine.data(),
+ nullptr,
+ nullptr,
+ /* inherit handles = */ true,
+ EXTENDED_STARTUPINFO_PRESENT,
+ nullptr,
+ directory.wstring().c_str(),
+ &startupInfo.StartupInfo,
+ &processInfo));
+ WaitForSingleObject(processInfo.hProcess, INFINITE);
+ CloseHandle(processInfo.hProcess);
+ CloseHandle(processInfo.hThread);
+ };
+
+ // launch(L"{} --install --no-scheduled-task --no-autostart",
+ // updater.wstring());
+
+ launch(
+ L"{} --channel=live --terminate-process-before-update={}",
+ updater.wstring(),
+ reinterpret_cast<uintptr_t>(thisProcessAsShell));
+
+ // `thisProcessAsShell` is owned by the shell, so we need to duplicate it back
+ // with DUPLICATE_CLOSE_SOURCE, then close it again :D
+ winrt::handle thisProcess;
+ winrt::check_bool(DuplicateHandle(
+ shellProcessRaw,
+ thisProcessAsShell,
+ GetCurrentProcess(),
+ thisProcess.put(),
+ 0,
+ FALSE,
+ DUPLICATE_CLOSE_SOURCE));
+}
+
// Entrypoint for Windows
//
// See main.cpp for Linux and MacOS
@@ -13,6 +140,8 @@ int WINAPI wWinMain(
[[maybe_unused]] HINSTANCE hPrevInstance,
[[maybe_unused]] PWSTR pCmdLine,
[[maybe_unused]] int nCmdShow) {
+ CheckForUpdates();
+
FredEmmott::OpenXRLayers::GUI().Run();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment