From d0e3f96364925c2ea36943a2fcc00c1b2f856c07 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Sun, 26 Jul 2026 09:41:16 +0000 Subject: [PATCH] feat: ship validated HyperTwist Windows installer --- .../alpha_test_20260723_release19/README.md | 22 +- .../alpha_test_20260726_installer/README.md | 119 +++ .../installed-first-run-launch.log | 15 + .../installed-readiness-report.json | 180 +++++ .../installed-runtime.hyperdiagnostics.log | 9 + .../makensis.log | 557 +++++++++++++ .../manifest-hardening-positive.json | 180 +++++ .../post-task-hygiene-report.json | 132 ++++ .../pre-release-shortcut-failure-report.json | 67 ++ .../windows-installer-build-report.json | 34 + .../windows-installer-lifecycle-report.json | 287 +++++++ .../windows-installer-payload-manifest.json | 729 ++++++++++++++++++ ...ows-installer-source-readiness-report.json | 180 +++++ ...SCREEN_REPAIR_AND_VALIDATION_2026-07-19.md | 53 +- ...NSIVE_RECONSTRUCTION_ROADMAP_2026-06-10.md | 30 +- .../HyperTwist/DEVELOPMENT.md | 57 ++ .../HyperTwist/FEATURE_REGISTRY.md | 1 + docs/v6_5_deep_manual_pack/HyperTwist/PRD.md | 8 + .../HyperTwist/ROADMAP.md | 14 +- packaging/windows/HyperTwistInstaller.nsi | 331 ++++++++ scripts/Build-HyperTwistWindowsInstaller.ps1 | 464 +++++++++++ scripts/Test-HyperTwistWindowsInstaller.ps1 | 707 +++++++++++++++++ scripts/Test-HyperTwistWindowsReadiness.ps1 | 617 +++++++++++++++ 23 files changed, 4777 insertions(+), 16 deletions(-) create mode 100644 docs/generated/alpha_test_20260726_installer/README.md create mode 100644 docs/generated/alpha_test_20260726_installer/installed-first-run-launch.log create mode 100644 docs/generated/alpha_test_20260726_installer/installed-readiness-report.json create mode 100644 docs/generated/alpha_test_20260726_installer/installed-runtime.hyperdiagnostics.log create mode 100644 docs/generated/alpha_test_20260726_installer/makensis.log create mode 100644 docs/generated/alpha_test_20260726_installer/manifest-hardening-positive.json create mode 100644 docs/generated/alpha_test_20260726_installer/post-task-hygiene-report.json create mode 100644 docs/generated/alpha_test_20260726_installer/pre-release-shortcut-failure-report.json create mode 100644 docs/generated/alpha_test_20260726_installer/windows-installer-build-report.json create mode 100644 docs/generated/alpha_test_20260726_installer/windows-installer-lifecycle-report.json create mode 100644 docs/generated/alpha_test_20260726_installer/windows-installer-payload-manifest.json create mode 100644 docs/generated/alpha_test_20260726_installer/windows-installer-source-readiness-report.json create mode 100644 packaging/windows/HyperTwistInstaller.nsi create mode 100644 scripts/Build-HyperTwistWindowsInstaller.ps1 create mode 100644 scripts/Test-HyperTwistWindowsInstaller.ps1 create mode 100644 scripts/Test-HyperTwistWindowsReadiness.ps1 diff --git a/docs/generated/alpha_test_20260723_release19/README.md b/docs/generated/alpha_test_20260723_release19/README.md index 81aea59..13bcc3c 100644 --- a/docs/generated/alpha_test_20260723_release19/README.md +++ b/docs/generated/alpha_test_20260723_release19/README.md @@ -18,11 +18,21 @@ Current operator delivery: - ZIP size: `1,093,537,260` bytes - ZIP SHA-256: `630963f96c681fc49130cbc56c6c850008d4cddda7e83f2ee9c077c52d7709a9` +- Validated installer: + `C:\Users\Anthracite Ace\Downloads\HyperTwist-Alpha-Test-20260723-R19-Setup.exe` +- Installer size: `851049086` bytes +- Installer SHA-256: + `c31ed6b0b2ae1feb449c27e484d72dc2e07ba220e3cdafa4c3605429d638e0ea` The delivered folder was compared against the validated archive source by hashing all `173` files. The comparison found `0` mismatches. The Downloads ZIP size and SHA-256 also match the validation-source ZIP. +The installer is separately grounded by +`docs/generated/alpha_test_20260726_installer/README.md`. It installs the same +Release 19 runtime payload, bundles the signed Microsoft Visual C++ runtime, +and has green install, launch, in-place upgrade, and uninstall lifecycle proof. + ## Product Scope This package contains the repaired first-run and player-facing shell, reliable @@ -63,6 +73,11 @@ where the visible global assistant could reject dictation on 4D, 5D, or - Browser production build: `1,872` modules transformed. - Exact solver table: `676,207,080` bytes, SHA-256 `dc6de3d909a37afe2ba7f1f978543a13eae215bd20d954d66813524a8ca20034`. +- Windows installer build: `passed`, `143` payload files, + `1,585,341,236` payload bytes, and `0` NSIS compiler warnings. +- Installer lifecycle: first install, complete hash parity, readiness, Shipping + launch, in-place upgrade, second parity, uninstall, user-data preservation, + and owned-state cleanup all `passed`. The automation authority is `player_experience_automation_index.json`. Package, staged-browser, @@ -130,6 +145,7 @@ conditions: real audio hardware. - Live XR headset-session and controller-input observation still requires a connected headset and controllers. -- Installer/uninstaller, code signing, clean-machine prerequisites, - entitlement, and update-channel rollout remain release-engineering gates - above this portable Alpha package. +- Installer/uninstaller ownership and isolated-current-host prerequisite proof + are now closed. Trusted code signing, genuinely fresh-machine proof, + entitlement-backed public delivery, and update-channel rollout remain + release-engineering gates above this Alpha package. diff --git a/docs/generated/alpha_test_20260726_installer/README.md b/docs/generated/alpha_test_20260726_installer/README.md new file mode 100644 index 0000000..5cd0b81 --- /dev/null +++ b/docs/generated/alpha_test_20260726_installer/README.md @@ -0,0 +1,119 @@ +# HyperTwist Alpha R19 Windows Installer Evidence + +## Authority + +This directory is the durable evidence index for the first validated +HyperTwist Windows installer above the Release 19 Shipping Alpha payload. + +Current operator delivery: + +- installer: + `C:\Users\Anthracite Ace\Downloads\HyperTwist-Alpha-Test-20260723-R19-Setup.exe` +- size: `851049086` bytes +- SHA-256: + `c31ed6b0b2ae1feb449c27e484d72dc2e07ba220e3cdafa4c3605429d638e0ea` +- Authenticode: `NotSigned` + +The unsigned status is an explicit Alpha limitation. Production distribution +still requires a trusted Authenticode certificate and signed-artifact proof. + +## Installer Ownership + +The NSIS installer owns a per-machine, 64-bit installation with: + +- Windows 10 22H2 / Windows 11 and x64 preflight +- a valid Microsoft-signed Visual C++ 2015-2022 x64 redistributable +- the complete `143`-file Release 19 runtime payload +- PDB and debug-manifest exclusion +- a complete SHA-256 payload manifest +- Add/Remove Programs registration +- Start Menu app, readiness, and uninstall shortcuts +- an optional all-users desktop shortcut +- marker-and-registry-gated upgrade and uninstall ownership +- a hard `HyperTwist` install-directory leaf contract +- refusal of unowned non-empty install directories +- user-visible, manifest-aware readiness reporting +- preservation of settings, saves, replays, and diagnostics during uninstall + +The prior payload is not cleaned for upgrade until the bundled prerequisite +has succeeded. Failed builds remove incomplete installer output rather than +leaving a corrupt executable at the release destination. + +## Validation + +The final build passed with: + +- NSIS `3.12` +- `0` compiler warnings +- payload files: `143` +- payload bytes: `1585341236` +- exact solver table: `676207080` bytes +- exact solver SHA-256: + `dc6de3d909a37afe2ba7f1f978543a13eae215bd20d954d66813524a8ca20034` +- source readiness: `ready-with-warnings` +- required readiness failures: `0` +- optional warnings: unsigned desktop code and no active OpenXR runtime + +Bounded contract proof also passed: + +- all `3` PowerShell scripts parsed on Linux PowerShell and Windows PowerShell +- disposable NSIS contract compilation completed with `0` warnings +- an unsafe install-directory leaf was refused with exit code `13` +- an unowned non-empty `HyperTwist` directory was refused with exit code `14` +- the unrelated sentinel survived and no product registration was created +- manifest traversal input was blocked as `invalid-path:../escape.txt` +- manifest file-count and total-size tampering were blocked +- the interactive readiness wait completed under redirected-input proof + +The final lifecycle used a path containing spaces and passed: + +- unowned-directory refusal +- first silent install, exit `0` +- first `143`-entry payload hash parity +- Add/Remove Programs and product-registration ownership +- resolved app, readiness, uninstall, and desktop shortcut contracts +- installed readiness with `0` required failures +- stock-Shipping launch diagnostics with runtime initialization +- `0` fatal lines and `0` package-owned listeners +- in-place upgrade, exit `0` +- second `143`-entry payload hash parity +- silent uninstall, exit `0` +- user-data sentinel preservation +- install-root, registration, and shortcut removal +- final owned-process count `0` + +The validation class is `isolated-current-host`. No Windows Sandbox, clean +Hyper-V VM, or base VHD was available, so this evidence does not masquerade as +a fresh-machine proof. + +## Failure Lineage + +`pre-release-shortcut-failure-report.json` records the intentionally rejected +candidate whose readiness shortcut embedded the installing administrator's +temporary path. That report proved first-install payload parity and successful +failure cleanup. The final installer supersedes it with a per-user report path, +manifest-aware arguments, an interactive wait, and resolved shortcut-target +validation. + +## Evidence Map + +- `windows-installer-build-report.json`: final build and installer identity +- `windows-installer-payload-manifest.json`: complete payload hashes and sizes +- `windows-installer-source-readiness-report.json`: source-host readiness +- `windows-installer-lifecycle-report.json`: final install-to-uninstall proof +- `installed-readiness-report.json`: installed-host and manifest proof +- `installed-runtime.hyperdiagnostics.log`: Shipping runtime initialization +- `installed-first-run-launch.log`: first-run menu launch authority +- `manifest-hardening-positive.json`: full manifest reconciliation proof +- `makensis.log`: final compiler transcript +- `pre-release-shortcut-failure-report.json`: superseded failure lineage +- `post-task-hygiene-report.json`: doctrine-ordered artifact disposition + +## Remaining External Gates + +- trusted Authenticode code signing +- a genuinely fresh Windows VM install and first-launch proof +- entitlement-backed public release URL wiring +- update-channel, rollback, and staged-rollout proof +- real headset/controller observation +- configured-provider, microphone, and audible-TTS observation diff --git a/docs/generated/alpha_test_20260726_installer/installed-first-run-launch.log b/docs/generated/alpha_test_20260726_installer/installed-first-run-launch.log new file mode 100644 index 0000000..6a2d47d --- /dev/null +++ b/docs/generated/alpha_test_20260726_installer/installed-first-run-launch.log @@ -0,0 +1,15 @@ +session=20260726T092358Z +world=L_HyperTwist_ClassicTraining +runtime_log=C:/HyperTwist Installer Validation/20260726T091926Z/evidence/isolated-user/Saved/Logs/HyperTwistRuntime-latest.log +latest_diagnostics_log=C:/HyperTwist Installer Validation/20260726T091926Z/evidence/isolated-user/Saved/Logs/HyperTwistFirstRunLaunch-latest.log +[2026-07-26T09:23:58Z] [BeginPlay] First-run controller booted on world 'L_HyperTwist_ClassicTraining'. +[2026-07-26T09:23:58Z] [ApplyFirstRunInputMode] Applied game-and-UI input mode with unlocked cursor capture. +[2026-07-26T09:23:58Z] [ShowFirstRunLaunchMenu] Fell back to the native first-run widget class. +[2026-07-26T09:23:58Z] [ShowFirstRunLaunchMenu] Added the first-run launch widget to the viewport at z-order 100. +[2026-07-26T09:23:58Z] [ApplyFirstRunInputMode] Applied game-and-UI input mode with unlocked cursor capture. +[2026-07-26T09:24:00Z] [StartupRecovery] The first-run launch widget never produced a visible non-zero layout after 20 attempts (last size 0x0). +[2026-07-26T09:24:00Z] [StartupRecovery] Attempting fallback route 'classic-cube-training'. +[2026-07-26T09:24:00Z] [OpenFirstRunRoute] Opening route 'classic-cube-training' (dedicated-map). +[2026-07-26T09:24:00Z] [OpenMapRoute] Opening map '/Game/HyperTwistTraining/Maps/L_HyperTwist_ClassicTraining' with options 'game=/Script/UnrealHyperTwist.HyperTwistClassicCubeGameMode'. +result=startup-recovered +last_failure=The first-run launch widget never produced a visible non-zero layout after 20 attempts (last size 0x0). diff --git a/docs/generated/alpha_test_20260726_installer/installed-readiness-report.json b/docs/generated/alpha_test_20260726_installer/installed-readiness-report.json new file mode 100644 index 0000000..093ae96 --- /dev/null +++ b/docs/generated/alpha_test_20260726_installer/installed-readiness-report.json @@ -0,0 +1,180 @@ +{ + "reportVersion": "ht-windows-readiness/v1", + "generatedAtUtc": "2026-07-26T09:23:56.9501545Z", + "result": "ready-with-warnings", + "payloadRoot": "C:\\HyperTwist Installer Validation\\20260726T091926Z\\HyperTwist", + "targetInstallRoot": "C:\\HyperTwist Installer Validation\\20260726T091926Z\\HyperTwist", + "requiredFailureCount": 0, + "optionalWarningCount": 2, + "checks": [ + { + "id": "windows-x64", + "class": "required", + "passed": true, + "summary": "HyperTwist requires a 64-bit Windows operating system.", + "evidence": { + "architecture": "64-bit", + "processArchitecture": "AMD64" + } + }, + { + "id": "windows-supported-build", + "class": "required", + "passed": true, + "summary": "HyperTwist requires Windows build 19045 or newer.", + "evidence": { + "caption": "Microsoft Windows 11 Home", + "version": "10.0.22631", + "build": 22631, + "minimumBuild": 19045 + } + }, + { + "id": "system-memory", + "class": "required", + "passed": true, + "summary": "At least 8 GiB of physical memory is required for the desktop Alpha.", + "evidence": { + "totalBytes": 16893206528, + "minimumBytes": 8589934592 + } + }, + { + "id": "install-volume-free-space", + "class": "required", + "passed": true, + "summary": "The target volume must have enough free space for installation and first-run caches.", + "evidence": { + "targetInstallRoot": "C:\\HyperTwist Installer Validation\\20260726T091926Z\\HyperTwist", + "availableBytes": 167681560576, + "minimumBytes": 4294967296 + } + }, + { + "id": "vc-runtime-x64", + "class": "required", + "passed": true, + "summary": "Microsoft Visual C++ 2015-2022 x64 runtime 14.44.35211.0 or newer is required and is bundled by the installer.", + "evidence": { + "registryPath": "HKLM:\\SOFTWARE\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x64", + "installed": true, + "version": "14.44.35211.0" + } + }, + { + "id": "windows-media-and-graphics-libraries", + "class": "required", + "passed": true, + "summary": "Windows DirectX, XInput, and Media Foundation runtime libraries must be available.", + "evidence": { + "systemDirectory": "C:\\Windows\\system32", + "requiredLibraries": [ + "d3d11.dll", + "d3d12.dll", + "dxgi.dll", + "mf.dll", + "mfplat.dll", + "mfreadwrite.dll", + "xinput1_4.dll" + ], + "missingLibraries": [ + + ] + } + }, + { + "id": "graphics-adapter", + "class": "required", + "passed": true, + "summary": "At least one Windows graphics adapter must be visible.", + "evidence": [ + { + "name": "Intel(R) Iris(R) Xe Graphics", + "driverVersion": "31.0.101.3358", + "driverDate": "2022-08-04T02:00:00.0000000+02:00", + "adapterRamBytes": 1073741824, + "status": "OK" + } + ] + }, + { + "id": "audio-device", + "class": "optional", + "passed": true, + "summary": "An audio device is optional for puzzle play and recommended for narration and speech features.", + "evidence": [ + { + "name": "Intel® Smart Sound Technology for Digital Microphones", + "status": "OK" + }, + { + "name": "Intel® Smart Sound Technology for USB Audio", + "status": "OK" + }, + { + "name": "Intel® Smart Sound Technology for Bluetooth® Audio", + "status": "OK" + }, + { + "name": "Audio Device", + "status": "OK" + }, + { + "name": "Virtual Audio Cable", + "status": "OK" + } + ] + }, + { + "id": "openxr-runtime", + "class": "optional", + "passed": false, + "summary": "An active OpenXR runtime is optional for keyboard/mouse play and required only for live headset sessions.", + "evidence": null + }, + { + "id": "required-payload-files", + "class": "required", + "passed": true, + "summary": "The installed payload must contain every required executable, runtime, cooked-data, browser, solver, and OpenXR file.", + "evidence": { + "payloadRoot": "C:\\HyperTwist Installer Validation\\20260726T091926Z\\HyperTwist", + "requiredFileCount": 15, + "cefRuntimeFiles": [ + "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/libcef.dll" + ], + "failures": [ + + ] + } + }, + { + "id": "desktop-code-signature", + "class": "optional", + "passed": false, + "summary": "The Alpha payload is currently unsigned; production distribution requires Authenticode signing.", + "evidence": { + "status": "NotSigned", + "signer": null + } + }, + { + "id": "payload-manifest-parity", + "class": "required", + "passed": true, + "summary": "The installed payload must match the release manifest without missing, truncated, or altered files.", + "evidence": { + "manifestPath": "C:\\HyperTwist Installer Validation\\20260726T091926Z\\HyperTwist\\Installer\\payload-manifest.json", + "manifestVersion": "ht-windows-installer-payload/v1", + "manifestEntryCount": 143, + "declaredFileCount": 143, + "declaredTotalSizeBytes": 1585341236, + "computedTotalSizeBytes": 1585341236, + "hashesVerified": true, + "failures": [ + + ] + } + } + ] +} \ No newline at end of file diff --git a/docs/generated/alpha_test_20260726_installer/installed-runtime.hyperdiagnostics.log b/docs/generated/alpha_test_20260726_installer/installed-runtime.hyperdiagnostics.log new file mode 100644 index 0000000..46039e3 --- /dev/null +++ b/docs/generated/alpha_test_20260726_installer/installed-runtime.hyperdiagnostics.log @@ -0,0 +1,9 @@ +format=hypertwist-runtime-diagnostics/v1 +session=20260726T092358Z +process_id=14248 +command_line=-notraceserver -traceautostart=0 -NullRHI -nosound -windowed -ResX=1280 -ResY=720 -log -FORCELOGFLUSH -abslog=C:\HyperTwist Installer Validation\20260726T091926Z\evidence\installed-runtime.log -HyperTwistDiagnosticsLog="C:\HyperTwist Installer Validation\20260726T091926Z\evidence\installed-runtime.hyperdiagnostics.log" -UserDir="C:\HyperTwist Installer Validation\20260726T091926Z\evidence\isolated-user" +diagnostics_path=C:/HyperTwist Installer Validation/20260726T091926Z/evidence/installed-runtime.hyperdiagnostics.log +[2026-07-26T09:23:58Z] [Process] HyperTwist runtime module initialized. +[2026-07-26T09:24:00Z] [MapReady] Runtime map ready: /Game/HyperTwistTraining/Maps/L_HyperTwist_ClassicTraining.L_HyperTwist_ClassicTraining. +[2026-07-26T09:24:00Z] [ClassicPresentation] Classic cube presentation initialized with 26 renderable pieces and orbit pawn ready. +[2026-07-26T09:24:07Z] [ClassicScrambleSettlement] Classic cube scramble settled with 11 completed quarter turns, 0 queued rotations, and 26 renderable pieces; renderable state valid. diff --git a/docs/generated/alpha_test_20260726_installer/makensis.log b/docs/generated/alpha_test_20260726_installer/makensis.log new file mode 100644 index 0000000..dec2c2d --- /dev/null +++ b/docs/generated/alpha_test_20260726_installer/makensis.log @@ -0,0 +1,557 @@ +MakeNSIS v3.12 - Copyright 1999-2026 Contributors +See the file COPYING for license details. +Credits can be found in the Users Manual. + +Command line defined: "PRODUCT_VERSION=0.19.0.0" +Command line defined: "DISPLAY_VERSION=Alpha R19" +Command line defined: "RELEASE_ID=20260723-R19" +Command line defined: "OUTPUT_FILE=C:\HyperTwist_worktrees\installer-release19-20260726\HyperTwist-Alpha-Test-20260723-R19-Setup.exe" +Command line defined: "PAYLOAD_ROOT=C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows" +Command line defined: "PAYLOAD_MANIFEST=C:\HyperTwist_worktrees\installer-release19-20260726\validation\windows-installer-payload-manifest.json" +Command line defined: "READINESS_SCRIPT=C:\HyperTwist_worktrees\phase10validate\scripts\Test-HyperTwistWindowsReadiness.ps1" +Command line defined: "VC_REDIST_PATH=C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Redist\MSVC\v143\vc_redist.x64.exe" +Command line defined: "INSTALLER_ICON=C:\HyperTwist_worktrees\installer-release19-20260726\build-context\HyperTwist.ico" +Command line defined: "PAYLOAD_SIZE_KB=1548185" +Processing config: C:\Program Files (x86)\NSIS\nsisconf.nsh +Processing default plugins: "C:\Program Files (x86)\NSIS\Plugins\x86-unicode\*.dll" + + AdvSplash::show + + Banner::destroy + + Banner::getWindow + + Banner::show + + BgImage::AddImage + + BgImage::AddText + + BgImage::Clear + + BgImage::Destroy + + BgImage::Redraw + + BgImage::SetBg + + BgImage::SetReturn + + BgImage::Sound + + Dialer::AttemptConnect + + Dialer::AutodialHangup + + Dialer::AutodialOnline + + Dialer::AutodialUnattended + + Dialer::GetConnectedState + + InstallOptions::dialog + + InstallOptions::initDialog + + InstallOptions::make_unicode + + InstallOptions::show + + LangDLL::LangDialog + + Math::Script + + NSISdl::download + + NSISdl::download_quiet + + Splash::show + + StartMenu::Init + + StartMenu::Select + + StartMenu::Show + + System::Alloc + + System::Call + + System::Copy + + System::Free + + System::Get + + System::Int64Op + + System::Store + + System::StrAlloc + + TypeLib::GetLibVersion + + TypeLib::Register + + TypeLib::UnRegister + + UserInfo::GetAccountType + + UserInfo::GetName + + UserInfo::GetOriginalAccountType + + VPatch::GetFileCRC32 + + VPatch::GetFileMD5 + + VPatch::vpatchfile + + nsDialogs::Create + + nsDialogs::CreateControl + + nsDialogs::CreateItem + + nsDialogs::CreateTimer + + nsDialogs::GetUserData + + nsDialogs::KillTimer + + nsDialogs::OnBack + + nsDialogs::OnChange + + nsDialogs::OnClick + + nsDialogs::OnNotify + + nsDialogs::SelectFileDialog + + nsDialogs::SelectFolderDialog + + nsDialogs::SetRTL + + nsDialogs::SetUserData + + nsDialogs::Show + + nsExec::Exec + + nsExec::ExecToLog + + nsExec::ExecToStack + +!define: "MUI_INSERT_NSISCONF"="" + +Changing directory to: "C:\HyperTwist_worktrees\phase10validate\packaging\windows" + +Processing script file: "C:\HyperTwist_worktrees\phase10validate\packaging\windows\HyperTwistInstaller.nsi" (ACP) +Unicode: true +Processing default plugins: "C:\Program Files (x86)\NSIS\Plugins\x86-unicode\*.dll" + +!include: "C:\Program Files (x86)\NSIS\Include\MUI2.nsh" (UTF8) +!include: "C:\Program Files (x86)\NSIS\Contrib\Modern UI 2\MUI2.nsh" (UTF8) +NSIS Modern User Interface version 2.1 - Copyright 2002-2026 Joost Verburg (C:\Program Files (x86)\NSIS\Contrib\Modern UI 2\MUI2.nsh:14) +!include: closed: "C:\Program Files (x86)\NSIS\Contrib\Modern UI 2\MUI2.nsh" +!include: closed: "C:\Program Files (x86)\NSIS\Include\MUI2.nsh" +!include: "C:\Program Files (x86)\NSIS\Include\LogicLib.nsh" (UTF8) +!include: closed: "C:\Program Files (x86)\NSIS\Include\LogicLib.nsh" +!include: "C:\Program Files (x86)\NSIS\Include\WinVer.nsh" (UTF8) +!include: closed: "C:\Program Files (x86)\NSIS\Include\WinVer.nsh" +!include: "C:\Program Files (x86)\NSIS\Include\x64.nsh" (UTF8) +!define: "___X64__NSH___"="" +!include: "C:\Program Files (x86)\NSIS\Include\LogicLib.nsh" (UTF8) +!include: closed: "C:\Program Files (x86)\NSIS\Include\LogicLib.nsh" +!define: "IsWow64"=""" IsWow64 """ +!define: "RunningX64"=""" RunningX64 """ +!define: "GetNativeMachineArchitecture"="!insertmacro GetNativeMachineArchitecture " +!define: "IsNativeMachineArchitecture"=""" IsNativeMachineArchitecture " +!define: "IsNativeIA32"=""" IsNativeMachineArchitecture 332" +!define: "IsNativeAMD64"=""" IsNativeMachineArchitecture 34404" +!define: "IsNativeARM64"=""" IsNativeMachineArchitecture 43620" +!define: "DisableX64FSRedirection"="!insertmacro DisableX64FSRedirection" +!define: "EnableX64FSRedirection"="!insertmacro EnableX64FSRedirection" +!include: closed: "C:\Program Files (x86)\NSIS\Include\x64.nsh" +!include: "C:\Program Files (x86)\NSIS\Include\FileFunc.nsh" (UTF8) +!include: closed: "C:\Program Files (x86)\NSIS\Include\FileFunc.nsh" +!define: "PRODUCT_NAME"="HyperTwist" +!define: "PRODUCT_CHANNEL"="Alpha" +!define: "PRODUCT_PUBLISHER"="HyperTwist" +!define: "PRODUCT_WEBSITE"="https://hypertwist.app" +!define: "PRODUCT_SUPPORT"="https://hypertwist.app/support" +!define: "PRODUCT_REG_KEY"="Software\HyperTwist\HyperTwist" +!define: "PRODUCT_UNINSTALL_KEY"="Software\Microsoft\Windows\CurrentVersion\Uninstall\HyperTwist" +!define: "INSTALL_MARKER"="HyperTwist.install-marker" +Name: "HyperTwist Alpha" +Caption: "HyperTwist Alpha R19 Setup" +BrandingText: "HyperTwist | Explore dimensions beyond the ordinary" +OutFile: "C:\HyperTwist_worktrees\installer-release19-20260726\HyperTwist-Alpha-Test-20260723-R19-Setup.exe" +InstallDir: "$PROGRAMFILES64\HyperTwist" +CRCCheck: force +SetDatablockOptimize: on +SetCompressor: /SOLID lzma +SetCompressorDictSize: 64 mb +ShowInstDetails: show +ShowUninstDetails: show +Icon: "C:\HyperTwist_worktrees\installer-release19-20260726\build-context\HyperTwist.ico" +UninstallIcon: "C:\HyperTwist_worktrees\installer-release19-20260726\build-context\HyperTwist.ico" +VIAddVersionKey: "ProductName" "HyperTwist" +VIAddVersionKey: "ProductVersion" "Alpha R19" +VIAddVersionKey: "CompanyName" "HyperTwist" +VIAddVersionKey: "FileDescription" "HyperTwist Alpha Installer" +VIAddVersionKey: "FileVersion" "0.19.0.0" +VIAddVersionKey: "LegalCopyright" "Copyright (c) HyperTwist" +VIAddVersionKey: "OriginalFilename" "HyperTwist-Setup.exe" +!define: "MUI_ABORTWARNING"="" +!define: "MUI_ICON"="C:\HyperTwist_worktrees\installer-release19-20260726\build-context\HyperTwist.ico" +!define: "MUI_UNICON"="C:\HyperTwist_worktrees\installer-release19-20260726\build-context\HyperTwist.ico" +!define: "MUI_FINISHPAGE_LINK"="Visit hypertwist.app" +!define: "MUI_FINISHPAGE_LINK_LOCATION"="https://hypertwist.app" +!insertmacro: MUI_PAGE_WELCOME +!insertmacro: end of MUI_PAGE_WELCOME +!insertmacro: MUI_PAGE_COMPONENTS +!insertmacro: end of MUI_PAGE_COMPONENTS +!insertmacro: MUI_PAGE_DIRECTORY +!insertmacro: end of MUI_PAGE_DIRECTORY +!insertmacro: MUI_PAGE_INSTFILES +!insertmacro: end of MUI_PAGE_INSTFILES +!insertmacro: MUI_PAGE_FINISH +!insertmacro: end of MUI_PAGE_FINISH +!insertmacro: MUI_UNPAGE_CONFIRM +!insertmacro: end of MUI_UNPAGE_CONFIRM +!insertmacro: MUI_UNPAGE_INSTFILES +!insertmacro: end of MUI_UNPAGE_INSTFILES +!insertmacro: MUI_UNPAGE_FINISH +!insertmacro: end of MUI_UNPAGE_FINISH +!insertmacro: MUI_LANGUAGE +!insertmacro: end of MUI_LANGUAGE +Function: ".onInit" +SetRegView: 64 +!insertmacro: _If +!insertmacro: end of _If +MessageBox: 16: "HyperTwist requires 64-bit Windows." +SetErrorLevel: 10 +Quit +!insertmacro: _EndIf +!insertmacro: end of _EndIf +!insertmacro: _If +!insertmacro: end of _If +MessageBox: 16: "HyperTwist requires Windows 10 version 22H2 or Windows 11." +SetErrorLevel: 11 +Quit +!insertmacro: _EndIf +!insertmacro: end of _EndIf +ReadRegStr $1 HKLM\Software\HyperTwist\HyperTwist\InstallLocation +StrCmp "$1" "" equal=install_location_ready, nonequal= +StrCpy $INSTDIR "$1" () () +ReadRegStr $0 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\CurrentBuildNumber +IntCmp $0:19045 equal=windows_build_ok, < windows_build_too_old, > windows_build_ok +MessageBox: 16: "This Windows build is too old for the supported HyperTwist Alpha baseline. Update to Windows 10 22H2 or Windows 11 and try again." +SetErrorLevel: 12 +Quit +FunctionEnd +Function: "CleanOwnedPayloadForUpgrade" +ReadRegStr $0 HKLM\Software\HyperTwist\HyperTwist\InstallLocation +StrCmp "$0" "$INSTDIR" equal=registry_owner_confirmed, nonequal=cleanup_done +IfFileExists: "$INSTDIR\Installer\HyperTwist.install-marker" ? 0 : cleanup_done +DetailPrint: "Removing the previous HyperTwist payload before upgrade." +RMDir: /r "$INSTDIR\Content" +RMDir: /r "$INSTDIR\Engine" +RMDir: /r "$INSTDIR\UnrealHyperTwist" +RMDir: /r "$INSTDIR\Installer" +Delete: "$INSTDIR\Manifest_DebugFiles_Win64.txt" +Delete: "$INSTDIR\Manifest_NonUFSFiles_Win64.txt" +Delete: "$INSTDIR\Manifest_UFSFiles_Win64.txt" +Delete: "$INSTDIR\UnrealHyperTwist.exe" +Delete: "$INSTDIR\Uninstall HyperTwist.exe" +FunctionEnd +Function: "AssertSafeInstallLocation" +!insertmacro: GetFileNameCall +!insertmacro: end of GetFileNameCall +StrCmp "$0" "HyperTwist" equal=install_leaf_safe, nonequal=install_location_refused +ReadRegStr $0 HKLM\Software\HyperTwist\HyperTwist\InstallLocation +StrCmp "$0" "$INSTDIR" equal=install_registry_owner_candidate, nonequal=install_unowned_scan +IfFileExists: "$INSTDIR\Installer\HyperTwist.install-marker" ? install_location_safe : install_unowned_scan +FindFirst: spec="$INSTDIR\*" handle=$0 output=$1 +StrCmp "$1" "" equal=install_unowned_empty, nonequal= +StrCmp "$1" "." equal=install_unowned_continue, nonequal= +StrCmp "$1" ".." equal=install_unowned_continue, nonequal= +FindClose: $0 +MessageBox: 16: "The selected HyperTwist folder is not empty and is not owned by an existing registered HyperTwist installation. Choose an empty folder named HyperTwist." +SetErrorLevel: 14 +Abort: "" +FindNext: handle=$0 output=$1 +Goto: install_unowned_scan_next +FindClose: $0 +Goto: install_location_safe +MessageBox: 16: "The installation folder itself must be named HyperTwist, for example C:\Program Files\HyperTwist or D:\Games\HyperTwist." +SetErrorLevel: 13 +Abort: "" +FunctionEnd +Section: "-ValidateInstallLocation" +Call "AssertSafeInstallLocation" +SectionEnd +Section: "-MicrosoftRuntime" +SetOutPath: "$PLUGINSDIR" +File: "vc_redist.x64.exe"->"vc_redist.x64.exe" 25635768 bytes +DetailPrint: "Installing or repairing the Microsoft Visual C++ 2015-2022 x64 runtime." +ExecWait: ""$PLUGINSDIR\vc_redist.x64.exe" /install /quiet /norestart" (->$0) +!insertmacro: _If +!insertmacro: end of _If +DetailPrint: "The Microsoft runtime requested a restart." +SetRebootFlag: true +!insertmacro: _ElseIf +!insertmacro: end of _ElseIf +DetailPrint: "A compatible or newer Microsoft runtime is already installed." +!insertmacro: _ElseIf +!insertmacro: end of _ElseIf +MessageBox: 16: "Microsoft Visual C++ runtime setup failed with exit code $0. HyperTwist was not installed." +SetErrorLevel: 20 +Abort: "" +!insertmacro: _EndIf +!insertmacro: end of _EndIf +SectionEnd +Section: "!HyperTwist application" ->(SEC_CORE) +SectionIn: [RO] +AddSize: 1548185 kb +Call "CleanOwnedPayloadForUpgrade" +SetOutPath: "$INSTDIR" +File: "Manifest_NonUFSFiles_Win64.txt" 10686 bytes +File: "Manifest_UFSFiles_Win64.txt" 255758 bytes +File: "UnrealHyperTwist.exe" 165376 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Content\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Content\Browser\" +File: "index.html" 7473 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Content\Browser\dist\" +File: "browser-spatial-runtime.js" 30651 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Content\Browser\dist\assets\" +File: "MathUtils.js" 185 bytes +File: "MeshoptDecoder.js" 595800 bytes +File: "Painter.js" 255062 bytes +File: "client.js" 875934 bytes +File: "decode.js" 26052 bytes +File: "easing-0f4db1c0.esm.js" 29269 bytes +File: "events-b389eeca.esm.js" 608404 bytes +File: "gltf-viewer.module.js" 371169 bytes +File: "index.js" 101 bytes +File: "index10.js" 463085 bytes +File: "index11.js" 1010478 bytes +File: "index12.js" 8792 bytes +File: "index13.js" 867555 bytes +File: "index2.js" 16990 bytes +File: "index3.js" 541053 bytes +File: "index4.js" 44323 bytes +File: "index5.js" 65696 bytes +File: "index6.js" 1608912 bytes +File: "index7.js" 200923 bytes +File: "index8.js" 2925000 bytes +File: "index9.js" 536 bytes +File: "install.js" 396204 bytes +File: "inter.js" 440193 bytes +File: "jsx-runtime.js" 22632 bytes +File: "leva.esm.js" 282310 bytes +File: "maath.esm.js" 13221 bytes +File: "model-viewer-effects.js" 28163 bytes +File: "model-viewer.js" 569271 bytes +File: "morphPath.js" 59852 bytes +File: "property.js" 20774 bytes +File: "react-spring_core.modern.js" 69814 bytes +File: "react-spring_three.modern.js" 1577 bytes +File: "react-spring_web.modern.js" 6888 bytes +File: "react-three-fiber.esm.js" 7829 bytes +File: "three.module.js" 1292285 bytes +File: "tslib.es6.js" 3434 bytes +File: "use-gesture-react.esm.js" 42626 bytes +File: "vanilla.js" 541 bytes +File: "vision_bundle.js" 169647 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Content\Browser\src\" +File: "browser-runtime-bootstrap.js" 3422 bytes +File: "browser-spatial-runtime-fallback.js" 30201 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\CEF3\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\CEF3\Win64\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\CEF3\Win64\128.4.13+ge76af7e+chromium-128.0.6613.138\" +File: "chrome_100_percent.pak" 695026 bytes +File: "chrome_200_percent.pak" 1094579 bytes +File: "chrome_elf.dll" 1370552 bytes +File: "d3dcompiler_47.dll" 4927552 bytes +File: "dxcompiler.dll" 20817848 bytes +File: "dxil.dll" 1519176 bytes +File: "icudtl.dat" 10468208 bytes +File: "libEGL.dll" 536504 bytes +File: "libGLESv2.dll" 8959416 bytes +File: "libcef.dll" 239992248 bytes +File: "resources.pak" 8683856 bytes +File: "snapshot_blob.bin" 313526 bytes +File: "v8_context_snapshot.bin" 665981 bytes +File: "vk_swiftshader.dll" 5572536 bytes +File: "vulkan-1.dll" 930744 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\CEF3\Win64\128.4.13+ge76af7e+chromium-128.0.6613.138\Resources\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\CEF3\Win64\128.4.13+ge76af7e+chromium-128.0.6613.138\Resources\locales\" +File: "af.pak" 538998 bytes +File: "am.pak" 866444 bytes +File: "ar.pak" 953918 bytes +File: "bg.pak" 987762 bytes +File: "bn.pak" 1269605 bytes +File: "ca.pak" 612377 bytes +File: "cs.pak" 622919 bytes +File: "da.pak" 567958 bytes +File: "de.pak" 609935 bytes +File: "el.pak" 1080416 bytes +File: "en-GB.pak" 492124 bytes +File: "en-US.pak" 497015 bytes +File: "es-419.pak" 598355 bytes +File: "es.pak" 596806 bytes +File: "et.pak" 544221 bytes +File: "fa.pak" 879421 bytes +File: "fi.pak" 552657 bytes +File: "fil.pak" 624599 bytes +File: "fr.pak" 646732 bytes +File: "gu.pak" 1255599 bytes +File: "he.pak" 777593 bytes +File: "hi.pak" 1330999 bytes +File: "hr.pak" 600581 bytes +File: "hu.pak" 646489 bytes +File: "id.pak" 535422 bytes +File: "it.pak" 594371 bytes +File: "ja.pak" 719063 bytes +File: "kn.pak" 1424332 bytes +File: "ko.pak" 606589 bytes +File: "lt.pak" 652194 bytes +File: "lv.pak" 651016 bytes +File: "ml.pak" 1479560 bytes +File: "mr.pak" 1223735 bytes +File: "ms.pak" 560048 bytes +File: "nb.pak" 543051 bytes +File: "nl.pak" 562204 bytes +File: "pl.pak" 627081 bytes +File: "pt-BR.pak" 587941 bytes +File: "pt-PT.pak" 592511 bytes +File: "ro.pak" 612678 bytes +File: "ru.pak" 1003077 bytes +File: "sk.pak" 632482 bytes +File: "sl.pak" 607050 bytes +File: "sr.pak" 929699 bytes +File: "sv.pak" 547471 bytes +File: "sw.pak" 576117 bytes +File: "ta.pak" 1475609 bytes +File: "te.pak" 1361935 bytes +File: "th.pak" 1144623 bytes +File: "tr.pak" 587129 bytes +File: "uk.pak" 1001189 bytes +File: "ur.pak" 872334 bytes +File: "vi.pak" 695768 bytes +File: "zh-CN.pak" 503346 bytes +File: "zh-TW.pak" 500142 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\DbgHelp\" +File: "dbghelp.dll" 2232688 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\MsQuic\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\MsQuic\v220\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\MsQuic\v220\win64\" +File: "msquic.dll" 2547080 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\NVIDIA\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\NVIDIA\NVaftermath\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\NVIDIA\NVaftermath\Win64\" +File: "GFSDK_Aftermath_Lib.x64.dll" 5442592 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\Ogg\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\Ogg\Win64\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\Ogg\Win64\VS2015\" +File: "libogg_64.dll" 59832 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\OpenXR\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\OpenXR\win64\" +File: "openxr_loader.dll" 1971640 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\Vorbis\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\Vorbis\Win64\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\Vorbis\Win64\VS2015\" +File: "libvorbis_64.dll" 1724856 bytes +File: "libvorbisfile_64.dll" 48568 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\Windows\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\Windows\XAudio2_9\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\ThirdParty\Windows\XAudio2_9\x64\" +File: "xaudio2_9redist.dll" 858144 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Binaries\Win64\" +File: "EpicWebHelper.exe" 4751288 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Config\" +File: "StagedBuild_UnrealHyperTwist.ini" 3 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Content\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Content\Renderer\" +File: "TessellationTable.bin" 259568 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Content\Slate\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Content\Slate\Cursor\" +File: "invisible.cur" 326 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Content\SlateDebug\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Content\SlateDebug\Fonts\" +File: "LastResort.tps" 930 bytes +File: "LastResort.ttf" 5395052 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Plugins\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Plugins\NNE\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Plugins\NNE\NNERuntimeORT\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Plugins\NNE\NNERuntimeORT\Binaries\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Plugins\NNE\NNERuntimeORT\Binaries\ThirdParty\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Plugins\NNE\NNERuntimeORT\Binaries\ThirdParty\Onnxruntime\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\Engine\Plugins\NNE\NNERuntimeORT\Binaries\ThirdParty\Onnxruntime\Win64\" +File: "onnxruntime.dll" 14692872 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\UnrealHyperTwist\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\UnrealHyperTwist\Binaries\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\UnrealHyperTwist\Binaries\Win64\" +File: "UnrealHyperTwist-Win64-Shipping.exe" 181121024 bytes +File: "tbb12.dll" 331704 bytes +File: "tbbmalloc.dll" 117688 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\UnrealHyperTwist\Binaries\Win64\D3D12\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\UnrealHyperTwist\Binaries\Win64\D3D12\x64\" +File: "D3D12Core.dll" 4544000 bytes +File: "d3d12SDKLayers.dll" 5104632 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\UnrealHyperTwist\Binaries\Win64\DML\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\UnrealHyperTwist\Binaries\Win64\DML\x64\" +File: "DirectML.dll" 18537968 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\UnrealHyperTwist\Content\" +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\UnrealHyperTwist\Content\Paks\" +File: "UnrealHyperTwist-Windows.pak" 11069853 bytes +File: "UnrealHyperTwist-Windows.ucas" 281222144 bytes +File: "UnrealHyperTwist-Windows.utoc" 216013 bytes +File: "global.ucas" 3325696 bytes +File: "global.utoc" 806 bytes +File: Descending to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows\UnrealHyperTwist\Saved\" +File: "twophase-ht.tbl" 676207080 bytes +File: Returning to: "C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723\Windows" +SetOutPath: "$INSTDIR\Installer" +File: "windows-installer-payload-manifest.json"->"payload-manifest.json" 44356 bytes +File: "Test-HyperTwistWindowsReadiness.ps1"->"Test-HyperTwistWindowsReadiness.ps1" 21840 bytes +FileOpen: $INSTDIR\Installer\HyperTwist.install-marker as w -> $0 +FileWrite: product=HyperTwist +->$0 +FileWrite: release=20260723-R19 +->$0 +FileWrite: version=Alpha R19 +->$0 +FileClose: $0 +WriteUninstaller: "$INSTDIR\Uninstall HyperTwist.exe" +WriteRegStr: HKLM\Software\HyperTwist\HyperTwist\InstallLocation=$INSTDIR +WriteRegStr: HKLM\Software\HyperTwist\HyperTwist\ReleaseId=20260723-R19 +WriteRegStr: HKLM\Software\HyperTwist\HyperTwist\DisplayVersion=Alpha R19 +WriteRegStr: HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\HyperTwist\DisplayName=HyperTwist Alpha +WriteRegStr: HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\HyperTwist\DisplayVersion=Alpha R19 +WriteRegStr: HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\HyperTwist\Publisher=HyperTwist +WriteRegStr: HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\HyperTwist\InstallLocation=$INSTDIR +WriteRegStr: HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\HyperTwist\DisplayIcon=$INSTDIR\UnrealHyperTwist.exe,0 +WriteRegStr: HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\HyperTwist\URLInfoAbout=https://hypertwist.app +WriteRegStr: HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\HyperTwist\HelpLink=https://hypertwist.app/support +WriteRegStr: HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\HyperTwist\UninstallString="$INSTDIR\Uninstall HyperTwist.exe" +WriteRegStr: HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\HyperTwist\QuietUninstallString="$INSTDIR\Uninstall HyperTwist.exe" /S +WriteRegDWORD: HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\HyperTwist\EstimatedSize=1548185 +WriteRegDWORD: HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\HyperTwist\NoModify=1 +WriteRegDWORD: HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\HyperTwist\NoRepair=1 +SetShellVarContext: all +CreateDirectory: "$SMPROGRAMS\HyperTwist" +CreateShortcut: "$SMPROGRAMS\HyperTwist\HyperTwist.lnk"->"$INSTDIR\UnrealHyperTwist.exe" icon:$INSTDIR\UnrealHyperTwist.exe,0, nwd=0, showmode=0x0, hotkey=0x0, comment= +CreateShortcut: "$SMPROGRAMS\HyperTwist\System Readiness Report.lnk"->"$SYSDIR\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File "$INSTDIR\Installer\Test-HyperTwistWindowsReadiness.ps1" -PayloadRoot "$INSTDIR" -TargetInstallRoot "$INSTDIR" -ExpectedPayloadManifestPath "$INSTDIR\Installer\payload-manifest.json" -WaitForUser icon:$INSTDIR\UnrealHyperTwist.exe,0, nwd=0, showmode=0x0, hotkey=0x0, comment= +CreateShortcut: "$SMPROGRAMS\HyperTwist\Uninstall HyperTwist.lnk"->"$INSTDIR\Uninstall HyperTwist.exe" icon:,0, nwd=0, showmode=0x0, hotkey=0x0, comment= +SectionEnd +Section: "Desktop shortcut" ->(SEC_DESKTOP) +SetShellVarContext: all +CreateShortcut: "$DESKTOP\HyperTwist.lnk"->"$INSTDIR\UnrealHyperTwist.exe" icon:$INSTDIR\UnrealHyperTwist.exe,0, nwd=0, showmode=0x0, hotkey=0x0, comment= +SectionEnd +LangString: "DESC_SEC_CORE" 1033 "The complete HyperTwist desktop Alpha, puzzle runtimes, learning studio, browser shell, and local solver data." +LangString: "DESC_SEC_DESKTOP" 1033 "Create an all-users HyperTwist shortcut on the desktop." +!insertmacro: MUI_FUNCTION_DESCRIPTION_BEGIN +!insertmacro: end of MUI_FUNCTION_DESCRIPTION_BEGIN +!insertmacro: MUI_DESCRIPTION_TEXT +!insertmacro: end of MUI_DESCRIPTION_TEXT +!insertmacro: MUI_DESCRIPTION_TEXT +!insertmacro: end of MUI_DESCRIPTION_TEXT +!insertmacro: MUI_FUNCTION_DESCRIPTION_END +!insertmacro: end of MUI_FUNCTION_DESCRIPTION_END +Section: "Uninstall" +SetRegView: 64 +!insertmacro: GetFileNameCall +!insertmacro: end of GetFileNameCall +StrCmp "$1" "HyperTwist" equal=uninstall_leaf_safe, nonequal=uninstall_refused +ReadRegStr $0 HKLM\Software\HyperTwist\HyperTwist\InstallLocation +StrCmp "$0" "$INSTDIR" equal=uninstall_registry_owned, nonequal=uninstall_refused +IfFileExists: "$INSTDIR\Installer\HyperTwist.install-marker" ? uninstall_owned : uninstall_refused +MessageBox: 16: "HyperTwist registry ownership or the install marker is missing. The uninstaller will not delete an unverified directory." +SetErrorLevel: 30 +Abort: "" +SetShellVarContext: all +Delete: "$DESKTOP\HyperTwist.lnk" +RMDir: /r "$SMPROGRAMS\HyperTwist" +DeleteRegKey: HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\HyperTwist +DeleteRegKey: HKLM\Software\HyperTwist\HyperTwist +DeleteRegKey: HKLM\Software\HyperTwist +RMDir: /r "$INSTDIR\Content" +RMDir: /r "$INSTDIR\Engine" +RMDir: /r "$INSTDIR\UnrealHyperTwist" +Delete: "$INSTDIR\Manifest_DebugFiles_Win64.txt" +Delete: "$INSTDIR\Manifest_NonUFSFiles_Win64.txt" +Delete: "$INSTDIR\Manifest_UFSFiles_Win64.txt" +Delete: "$INSTDIR\UnrealHyperTwist.exe" +Delete: "$INSTDIR\Installer\payload-manifest.json" +Delete: "$INSTDIR\Installer\Test-HyperTwistWindowsReadiness.ps1" +Delete: "$INSTDIR\Installer\HyperTwist.install-marker" +RMDir: "$INSTDIR\Installer" +Delete: "$INSTDIR\Uninstall HyperTwist.exe" +RMDir: "$INSTDIR" +SectionEnd + +Processed 1 file, writing output (x86-unicode): +Adding plug-ins initializing function... Done! +Processing pages... Done! +Removing unused resources... Done! +Generating language tables... Done! +Generating uninstaller... Done! + +Output: "C:\HyperTwist_worktrees\installer-release19-20260726\HyperTwist-Alpha-Test-20260723-R19-Setup.exe" +Install: 5 pages (320 bytes), 4 sections (3 required) (8288 bytes), 796 instructions (22288 bytes), 489 strings (19966 bytes), 1 language table (342 bytes). +Uninstall: 3 pages (256 bytes), 1 section (2072 bytes), 326 instructions (9128 bytes), 183 strings (5968 bytes), 1 language table (282 bytes). +Datablock optimizer saved 26498 bytes (~0.0%). + +Using lzma (compress whole) compression. + +EXE header size: 41472 / 38912 bytes +Install code: (51692 bytes) +Install data: (1611092306 bytes) +Uninstall code+data: (67420 bytes) +Compressed data: 851007610 / 1611211418 bytes +CRC (0x2B3F13D2): 4 / 4 bytes + +Total size: 851049086 / 1611250334 bytes (52.8%) diff --git a/docs/generated/alpha_test_20260726_installer/manifest-hardening-positive.json b/docs/generated/alpha_test_20260726_installer/manifest-hardening-positive.json new file mode 100644 index 0000000..e658591 --- /dev/null +++ b/docs/generated/alpha_test_20260726_installer/manifest-hardening-positive.json @@ -0,0 +1,180 @@ +{ + "reportVersion": "ht-windows-readiness/v1", + "generatedAtUtc": "2026-07-26T08:30:49.1663635Z", + "result": "ready-with-warnings", + "payloadRoot": "C:\\HyperTwist_worktrees\\phase10validate_packaged_alpha_release19_shipping_20260723\\Windows", + "targetInstallRoot": "C:\\Program Files\\HyperTwist", + "requiredFailureCount": 0, + "optionalWarningCount": 2, + "checks": [ + { + "id": "windows-x64", + "class": "required", + "passed": true, + "summary": "HyperTwist requires a 64-bit Windows operating system.", + "evidence": { + "architecture": "64-bit", + "processArchitecture": "AMD64" + } + }, + { + "id": "windows-supported-build", + "class": "required", + "passed": true, + "summary": "HyperTwist requires Windows build 19045 or newer.", + "evidence": { + "caption": "Microsoft Windows 11 Home", + "version": "10.0.22631", + "build": 22631, + "minimumBuild": 19045 + } + }, + { + "id": "system-memory", + "class": "required", + "passed": true, + "summary": "At least 8 GiB of physical memory is required for the desktop Alpha.", + "evidence": { + "totalBytes": 16893206528, + "minimumBytes": 8589934592 + } + }, + { + "id": "install-volume-free-space", + "class": "required", + "passed": true, + "summary": "The target volume must have enough free space for installation and first-run caches.", + "evidence": { + "targetInstallRoot": "C:\\Program Files\\HyperTwist", + "availableBytes": 171947429888, + "minimumBytes": 4294967296 + } + }, + { + "id": "vc-runtime-x64", + "class": "required", + "passed": true, + "summary": "Microsoft Visual C++ 2015-2022 x64 runtime 14.44.35211.0 or newer is required and is bundled by the installer.", + "evidence": { + "registryPath": "HKLM:\\SOFTWARE\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x64", + "installed": true, + "version": "14.44.35211.0" + } + }, + { + "id": "windows-media-and-graphics-libraries", + "class": "required", + "passed": true, + "summary": "Windows DirectX, XInput, and Media Foundation runtime libraries must be available.", + "evidence": { + "systemDirectory": "C:\\Windows\\system32", + "requiredLibraries": [ + "d3d11.dll", + "d3d12.dll", + "dxgi.dll", + "mf.dll", + "mfplat.dll", + "mfreadwrite.dll", + "xinput1_4.dll" + ], + "missingLibraries": [ + + ] + } + }, + { + "id": "graphics-adapter", + "class": "required", + "passed": true, + "summary": "At least one Windows graphics adapter must be visible.", + "evidence": [ + { + "name": "Intel(R) Iris(R) Xe Graphics", + "driverVersion": "31.0.101.3358", + "driverDate": "2022-08-04T02:00:00.0000000+02:00", + "adapterRamBytes": 1073741824, + "status": "OK" + } + ] + }, + { + "id": "audio-device", + "class": "optional", + "passed": true, + "summary": "An audio device is optional for puzzle play and recommended for narration and speech features.", + "evidence": [ + { + "name": "Intel® Smart Sound Technology for Digital Microphones", + "status": "OK" + }, + { + "name": "Intel® Smart Sound Technology for USB Audio", + "status": "OK" + }, + { + "name": "Intel® Smart Sound Technology for Bluetooth® Audio", + "status": "OK" + }, + { + "name": "Audio Device", + "status": "OK" + }, + { + "name": "Virtual Audio Cable", + "status": "OK" + } + ] + }, + { + "id": "openxr-runtime", + "class": "optional", + "passed": false, + "summary": "An active OpenXR runtime is optional for keyboard/mouse play and required only for live headset sessions.", + "evidence": null + }, + { + "id": "required-payload-files", + "class": "required", + "passed": true, + "summary": "The installed payload must contain every required executable, runtime, cooked-data, browser, solver, and OpenXR file.", + "evidence": { + "payloadRoot": "C:\\HyperTwist_worktrees\\phase10validate_packaged_alpha_release19_shipping_20260723\\Windows", + "requiredFileCount": 15, + "cefRuntimeFiles": [ + "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/libcef.dll" + ], + "failures": [ + + ] + } + }, + { + "id": "desktop-code-signature", + "class": "optional", + "passed": false, + "summary": "The Alpha payload is currently unsigned; production distribution requires Authenticode signing.", + "evidence": { + "status": "NotSigned", + "signer": null + } + }, + { + "id": "payload-manifest-parity", + "class": "required", + "passed": true, + "summary": "The installed payload must match the release manifest without missing, truncated, or altered files.", + "evidence": { + "manifestPath": "C:\\HyperTwist_worktrees\\installer-release19-20260726\\validation\\windows-installer-payload-manifest.json", + "manifestVersion": "ht-windows-installer-payload/v1", + "manifestEntryCount": 143, + "declaredFileCount": 143, + "declaredTotalSizeBytes": 1585341236, + "computedTotalSizeBytes": 1585341236, + "hashesVerified": true, + "failures": [ + + ] + } + } + ] +} \ No newline at end of file diff --git a/docs/generated/alpha_test_20260726_installer/post-task-hygiene-report.json b/docs/generated/alpha_test_20260726_installer/post-task-hygiene-report.json new file mode 100644 index 0000000..aab01b2 --- /dev/null +++ b/docs/generated/alpha_test_20260726_installer/post-task-hygiene-report.json @@ -0,0 +1,132 @@ +{ + "reportVersion": "ht-post-task-hygiene/v1", + "generatedAtUtc": "2026-07-26T09:38:34Z", + "doctrine": "docs/ops/HYPERTWIST_PACKET_CLEANUP_AND_REBUILDABLE_ARTIFACT_HYGIENE_2026-05-20.md", + "result": "passed-with-protected-retention", + "inventory": { + "initialPhase10ValidateFileCount": 53308, + "initialPhase10ValidateSizeBytes": 29264296595, + "postCleanupPhase10ValidateFileCount": 19793, + "postCleanupPhase10ValidateSizeBytes": 2514767231, + "finalWindowsFreeBytes": 167725654016 + }, + "safeDeletion": { + "measuredTargetCount": 52, + "measuredFileCount": 37793, + "measuredSizeBytes": 54474200710, + "passes": [ + { + "class": "superseded-top-level-package-and-probe-roots", + "targetCount": 23, + "fileCount": 4264, + "sizeBytes": 27475757341 + }, + { + "class": "generated-subtrees-inside-phase10validate", + "targetCount": 24, + "fileCount": 33519, + "sizeBytes": 26749605009 + }, + { + "class": "intentionally-interrupted-installer-partials", + "targetCount": 2, + "fileCount": 2, + "sizeBytes": 248464450 + }, + { + "class": "incomplete-source-comparison-manifest", + "targetCount": 1, + "fileCount": 1, + "sizeBytes": 334999 + }, + { + "class": "evidence-harvested-installer-lifecycle-roots", + "targetCount": 2, + "fileCount": 7, + "sizeBytes": 38911 + } + ], + "additionalUnmeasuredDisposableArtifactsRemoved": [ + "tiny-payload NSIS contract roots", + "readiness interactive-contract files", + "malicious-manifest contract files", + "hash-staged Downloads incoming file" + ] + }, + "protectedRetention": [ + { + "path": "C:\\HyperTwist_worktrees\\phase10validate", + "fileCount": 19793, + "sizeBytes": 2514767231, + "reason": "Source/custody envelope rather than rebuildable-only output; complete duplicate proof was blocked by four relative source-tree reparse points." + }, + { + "path": "C:\\HyperTwist_worktrees\\phase10validate_packaged_alpha_release17_20260723", + "fileCount": 30, + "sizeBytes": 2138251, + "reason": "Release 17 rollback provenance." + }, + { + "path": "C:\\HyperTwist_worktrees\\phase10validate_packaged_alpha_release18_20260723", + "fileCount": 172, + "sizeBytes": 2288143046, + "reason": "Release 18 historical package evidence." + }, + { + "path": "C:\\HyperTwist_worktrees\\phase10validate_packaged_alpha_release18_shipping_20260723", + "fileCount": 175, + "sizeBytes": 1920603184, + "reason": "Release 18 Shipping rollback evidence." + }, + { + "path": "C:\\HyperTwist_worktrees\\phase10validate_packaged_alpha_release19_shipping_20260723", + "fileCount": 173, + "sizeBytes": 1920589115, + "reason": "Current Release 19 installer source authority." + }, + { + "path": "C:\\HyperTwist_worktrees\\installer-release19-20260726", + "fileCount": 7, + "sizeBytes": 851156084, + "reason": "Current validated installer and machine-generated build evidence." + }, + { + "path": "C:\\Users\\Anthracite Ace\\Downloads\\HyperTwist-Alpha-Test-20260723-R19", + "fileCount": 178, + "sizeBytes": 1920681761, + "reason": "Current portable operator delivery." + }, + { + "path": "C:\\Users\\Anthracite Ace\\Downloads\\HyperTwist-Alpha-Test-20260723-R19.zip", + "fileCount": 1, + "sizeBytes": 1093537260, + "reason": "Current verified portable archive delivery." + }, + { + "path": "C:\\Users\\Anthracite Ace\\Downloads\\HyperTwist-Alpha-Test-20260723-R19-Setup.exe", + "fileCount": 1, + "sizeBytes": 851049086, + "reason": "Current validated installer delivery." + }, + { + "path": "C:\\Users\\Anthracite Ace\\AppData\\Local\\UnrealHyperTwist\\Saved", + "fileCount": 275, + "sizeBytes": 736756671, + "reason": "Protected operator settings, saves, replays, logs, and diagnostics." + }, + { + "path": "/home/dev/src/HyperTwist", + "reason": "VPS main-worktree dependencies, mirrors, custody, source, and pre-existing ambiguous generated roots were not packet-created and were retained." + } + ], + "finalVerification": { + "temporaryLifecycleRootsAbsent": true, + "partialInstallerOutputsAbsent": true, + "disposableContractRootsAbsent": true, + "downloadsStagingAbsent": true, + "hyperTwistInstallerRegistrationAbsent": true, + "makensisProcessCount": 0, + "todoJsonPolicy": "ignored", + "protectedPathsPresent": true + } +} diff --git a/docs/generated/alpha_test_20260726_installer/pre-release-shortcut-failure-report.json b/docs/generated/alpha_test_20260726_installer/pre-release-shortcut-failure-report.json new file mode 100644 index 0000000..ead4242 --- /dev/null +++ b/docs/generated/alpha_test_20260726_installer/pre-release-shortcut-failure-report.json @@ -0,0 +1,67 @@ +{ + "reportVersion": "ht-windows-installer-lifecycle/v1", + "generatedAtUtc": "2026-07-26T07:56:13.8198132Z", + "result": "failed", + "validationClass": "isolated-current-host", + "cleanMachineAvailable": false, + "cleanMachineBoundary": "No Windows Sandbox, clean Hyper-V VM, or base VHD was available on the validation host.", + "installerPath": "C:\\HyperTwist_worktrees\\installer-release19-20260726\\HyperTwist-Alpha-Test-20260723-R19-Setup.exe", + "installerSizeBytes": 851127022, + "installerSha256": "42b9c7ce17b40cbce9adcef15cbc904e239bac673615447909d7a21419ab5ac0", + "installerSignatureStatus": "NotSigned", + "installRoot": "C:\\HyperTwistInstallerValidation\\20260726T075613Z\\HyperTwist", + "firstInstallExitCode": 0, + "upgradeInstallExitCode": null, + "payloadParityAfterFirstInstall": { + "passed": true, + "manifestEntryCount": 143, + "failureCount": 0, + "failures": [ + + ], + "unexpectedFiles": [ + + ], + "unexpectedPdbFiles": [ + + ] + }, + "payloadParityAfterUpgrade": null, + "readiness": null, + "launchSmoke": null, + "uninstallExitCode": null, + "registrationVerified": true, + "shortcutsVerified": false, + "shortcutDetails": { + "application": { + "path": "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\HyperTwist\\HyperTwist.lnk", + "exists": true, + "targetPath": "C:\\HyperTwistInstallerValidation\\20260726T075613Z\\HyperTwist\\UnrealHyperTwist.exe", + "arguments": "" + }, + "readiness": { + "path": "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\HyperTwist\\System Readiness Report.lnk", + "exists": true, + "targetPath": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + "arguments": "-NoProfile -ExecutionPolicy Bypass -File \"C:\\HyperTwistInstallerValidation\\20260726T075613Z\\HyperTwist\\Installer\\Test-HyperTwistWindowsReadiness.ps1\" -PayloadRoot \"C:\\HyperTwistInstallerValidation\\20260726T075613Z\\HyperTwist\" -ReportPath \"C:\\Users\\Anthracite Ace\\AppData\\Local\\Temp\\HyperTwist-readiness.json\"" + }, + "uninstall": { + "path": "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\HyperTwist\\Uninstall HyperTwist.lnk", + "exists": true, + "targetPath": "C:\\HyperTwistInstallerValidation\\20260726T075613Z\\HyperTwist\\Uninstall HyperTwist.exe", + "arguments": "" + }, + "desktop": { + "path": "C:\\Users\\Public\\Desktop\\HyperTwist.lnk", + "exists": true, + "targetPath": "C:\\HyperTwistInstallerValidation\\20260726T075613Z\\HyperTwist\\UnrealHyperTwist.exe", + "arguments": "" + } + }, + "userDataPreserved": false, + "installRootRemoved": false, + "registrationRemoved": false, + "shortcutsRemoved": false, + "cleanupCompleted": false, + "error": "One or more installer-managed shortcuts are missing." +} \ No newline at end of file diff --git a/docs/generated/alpha_test_20260726_installer/windows-installer-build-report.json b/docs/generated/alpha_test_20260726_installer/windows-installer-build-report.json new file mode 100644 index 0000000..02969e7 --- /dev/null +++ b/docs/generated/alpha_test_20260726_installer/windows-installer-build-report.json @@ -0,0 +1,34 @@ +{ + "reportVersion": "ht-windows-installer-build/v1", + "generatedAtUtc": "2026-07-26T08:31:34.7586689Z", + "result": "passed", + "releaseId": "20260723-R19", + "displayVersion": "Alpha R19", + "productVersion": "0.19.0.0", + "packageRoot": "C:\\HyperTwist_worktrees\\phase10validate_packaged_alpha_release19_shipping_20260723", + "payloadRoot": "C:\\HyperTwist_worktrees\\phase10validate_packaged_alpha_release19_shipping_20260723\\Windows", + "installerPath": "C:\\HyperTwist_worktrees\\installer-release19-20260726\\HyperTwist-Alpha-Test-20260723-R19-Setup.exe", + "payloadManifestPath": "C:\\HyperTwist_worktrees\\installer-release19-20260726\\validation\\windows-installer-payload-manifest.json", + "sourceReadinessReportPath": "C:\\HyperTwist_worktrees\\installer-release19-20260726\\validation\\windows-installer-source-readiness-report.json", + "makensisPath": "C:\\Program Files (x86)\\NSIS\\makensis.exe", + "makensisLogPath": "C:\\HyperTwist_worktrees\\installer-release19-20260726\\validation\\makensis.log", + "vcRedist": { + "path": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Redist\\MSVC\\v143\\vc_redist.x64.exe", + "version": "14.44.35211.0", + "sizeBytes": 25635768, + "sha256": "cc0ff0eb1dc3f5188ae6300faef32bf5beeba4bdd6e8e445a9184072096b713b", + "signatureStatus": "Valid", + "signer": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" + }, + "excludedPatterns": [ + "*.pdb", + "Manifest_DebugFiles_Win64.txt" + ], + "payloadFileCount": 143, + "payloadSizeBytes": 1585341236, + "installerSizeBytes": 851049086, + "installerSha256": "c31ed6b0b2ae1feb449c27e484d72dc2e07ba220e3cdafa4c3605429d638e0ea", + "installerSignatureStatus": "NotSigned", + "incompleteInstallerRemoved": false, + "error": null +} \ No newline at end of file diff --git a/docs/generated/alpha_test_20260726_installer/windows-installer-lifecycle-report.json b/docs/generated/alpha_test_20260726_installer/windows-installer-lifecycle-report.json new file mode 100644 index 0000000..36b1c9c --- /dev/null +++ b/docs/generated/alpha_test_20260726_installer/windows-installer-lifecycle-report.json @@ -0,0 +1,287 @@ +{ + "reportVersion": "ht-windows-installer-lifecycle/v1", + "generatedAtUtc": "2026-07-26T09:19:26.8973913Z", + "result": "passed", + "validationClass": "isolated-current-host", + "cleanMachineAvailable": false, + "cleanMachineBoundary": "No Windows Sandbox, clean Hyper-V VM, or base VHD was available on the validation host.", + "installerPath": "C:\\HyperTwist_worktrees\\installer-release19-20260726\\HyperTwist-Alpha-Test-20260723-R19-Setup.exe", + "installerSizeBytes": 851049086, + "installerSha256": "c31ed6b0b2ae1feb449c27e484d72dc2e07ba220e3cdafa4c3605429d638e0ea", + "installerSignatureStatus": "NotSigned", + "installRoot": "C:\\HyperTwist Installer Validation\\20260726T091926Z\\HyperTwist", + "unownedNonEmptyRefusal": { + "passed": true, + "exitCode": 14, + "expectedExitCode": 14, + "sentinelPreserved": true, + "registrationAbsent": true + }, + "firstInstallExitCode": 0, + "upgradeInstallExitCode": 0, + "payloadParityAfterFirstInstall": { + "passed": true, + "manifestEntryCount": 143, + "failureCount": 0, + "failures": [ + + ], + "unexpectedFiles": [ + + ], + "unexpectedPdbFiles": [ + + ] + }, + "payloadParityAfterUpgrade": { + "passed": true, + "manifestEntryCount": 143, + "failureCount": 0, + "failures": [ + + ], + "unexpectedFiles": [ + + ], + "unexpectedPdbFiles": [ + + ] + }, + "readiness": { + "reportVersion": "ht-windows-readiness/v1", + "generatedAtUtc": "2026-07-26T09:23:56.9501545Z", + "result": "ready-with-warnings", + "payloadRoot": "C:\\HyperTwist Installer Validation\\20260726T091926Z\\HyperTwist", + "targetInstallRoot": "C:\\HyperTwist Installer Validation\\20260726T091926Z\\HyperTwist", + "requiredFailureCount": 0, + "optionalWarningCount": 2, + "checks": [ + { + "id": "windows-x64", + "class": "required", + "passed": true, + "summary": "HyperTwist requires a 64-bit Windows operating system.", + "evidence": { + "architecture": "64-bit", + "processArchitecture": "AMD64" + } + }, + { + "id": "windows-supported-build", + "class": "required", + "passed": true, + "summary": "HyperTwist requires Windows build 19045 or newer.", + "evidence": { + "caption": "Microsoft Windows 11 Home", + "version": "10.0.22631", + "build": 22631, + "minimumBuild": 19045 + } + }, + { + "id": "system-memory", + "class": "required", + "passed": true, + "summary": "At least 8 GiB of physical memory is required for the desktop Alpha.", + "evidence": { + "totalBytes": 16893206528, + "minimumBytes": 8589934592 + } + }, + { + "id": "install-volume-free-space", + "class": "required", + "passed": true, + "summary": "The target volume must have enough free space for installation and first-run caches.", + "evidence": { + "targetInstallRoot": "C:\\HyperTwist Installer Validation\\20260726T091926Z\\HyperTwist", + "availableBytes": 167681560576, + "minimumBytes": 4294967296 + } + }, + { + "id": "vc-runtime-x64", + "class": "required", + "passed": true, + "summary": "Microsoft Visual C++ 2015-2022 x64 runtime 14.44.35211.0 or newer is required and is bundled by the installer.", + "evidence": { + "registryPath": "HKLM:\\SOFTWARE\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x64", + "installed": true, + "version": "14.44.35211.0" + } + }, + { + "id": "windows-media-and-graphics-libraries", + "class": "required", + "passed": true, + "summary": "Windows DirectX, XInput, and Media Foundation runtime libraries must be available.", + "evidence": { + "systemDirectory": "C:\\Windows\\system32", + "requiredLibraries": [ + "d3d11.dll", + "d3d12.dll", + "dxgi.dll", + "mf.dll", + "mfplat.dll", + "mfreadwrite.dll", + "xinput1_4.dll" + ], + "missingLibraries": [ + + ] + } + }, + { + "id": "graphics-adapter", + "class": "required", + "passed": true, + "summary": "At least one Windows graphics adapter must be visible.", + "evidence": [ + { + "name": "Intel(R) Iris(R) Xe Graphics", + "driverVersion": "31.0.101.3358", + "driverDate": "2022-08-04T02:00:00.0000000+02:00", + "adapterRamBytes": 1073741824, + "status": "OK" + } + ] + }, + { + "id": "audio-device", + "class": "optional", + "passed": true, + "summary": "An audio device is optional for puzzle play and recommended for narration and speech features.", + "evidence": [ + { + "name": "Intel® Smart Sound Technology for Digital Microphones", + "status": "OK" + }, + { + "name": "Intel® Smart Sound Technology for USB Audio", + "status": "OK" + }, + { + "name": "Intel® Smart Sound Technology for Bluetooth® Audio", + "status": "OK" + }, + { + "name": "Audio Device", + "status": "OK" + }, + { + "name": "Virtual Audio Cable", + "status": "OK" + } + ] + }, + { + "id": "openxr-runtime", + "class": "optional", + "passed": false, + "summary": "An active OpenXR runtime is optional for keyboard/mouse play and required only for live headset sessions.", + "evidence": null + }, + { + "id": "required-payload-files", + "class": "required", + "passed": true, + "summary": "The installed payload must contain every required executable, runtime, cooked-data, browser, solver, and OpenXR file.", + "evidence": { + "payloadRoot": "C:\\HyperTwist Installer Validation\\20260726T091926Z\\HyperTwist", + "requiredFileCount": 15, + "cefRuntimeFiles": [ + "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/libcef.dll" + ], + "failures": [ + + ] + } + }, + { + "id": "desktop-code-signature", + "class": "optional", + "passed": false, + "summary": "The Alpha payload is currently unsigned; production distribution requires Authenticode signing.", + "evidence": { + "status": "NotSigned", + "signer": null + } + }, + { + "id": "payload-manifest-parity", + "class": "required", + "passed": true, + "summary": "The installed payload must match the release manifest without missing, truncated, or altered files.", + "evidence": { + "manifestPath": "C:\\HyperTwist Installer Validation\\20260726T091926Z\\HyperTwist\\Installer\\payload-manifest.json", + "manifestVersion": "ht-windows-installer-payload/v1", + "manifestEntryCount": 143, + "declaredFileCount": 143, + "declaredTotalSizeBytes": 1585341236, + "computedTotalSizeBytes": 1585341236, + "hashesVerified": true, + "failures": [ + + ] + } + } + ] + }, + "launchSmoke": { + "passed": true, + "startedAtUtc": "2026-07-26T09:23:57.0508749Z", + "launcherProcessId": 3756, + "ownedProcessIds": [ + 3756, + 14248 + ], + "stoppedProcessIds": [ + 3756, + 14248 + ], + "runtimeLogPath": "C:\\HyperTwist Installer Validation\\20260726T091926Z\\evidence\\installed-runtime.log", + "diagnosticsPath": "C:\\HyperTwist Installer Validation\\20260726T091926Z\\evidence\\installed-runtime.hyperdiagnostics.log", + "diagnosticsInitialized": true, + "fatalLines": [ + + ], + "listeningEndpoints": [ + + ] + }, + "uninstallExitCode": 0, + "registrationVerified": true, + "shortcutsVerified": true, + "shortcutDetails": { + "application": { + "path": "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\HyperTwist\\HyperTwist.lnk", + "exists": true, + "targetPath": "C:\\HyperTwist Installer Validation\\20260726T091926Z\\HyperTwist\\UnrealHyperTwist.exe", + "arguments": "" + }, + "readiness": { + "path": "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\HyperTwist\\System Readiness Report.lnk", + "exists": true, + "targetPath": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + "arguments": "-NoProfile -ExecutionPolicy Bypass -File \"C:\\HyperTwist Installer Validation\\20260726T091926Z\\HyperTwist\\Installer\\Test-HyperTwistWindowsReadiness.ps1\" -PayloadRoot \"C:\\HyperTwist Installer Validation\\20260726T091926Z\\HyperTwist\" -TargetInstallRoot \"C:\\HyperTwist Installer Validation\\20260726T091926Z\\HyperTwist\" -ExpectedPayloadManifestPath \"C:\\HyperTwist Installer Validation\\20260726T091926Z\\HyperTwist\\Installer\\payload-manifest.json\" -WaitForUser" + }, + "uninstall": { + "path": "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\HyperTwist\\Uninstall HyperTwist.lnk", + "exists": true, + "targetPath": "C:\\HyperTwist Installer Validation\\20260726T091926Z\\HyperTwist\\Uninstall HyperTwist.exe", + "arguments": "" + }, + "desktop": { + "path": "C:\\Users\\Public\\Desktop\\HyperTwist.lnk", + "exists": true, + "targetPath": "C:\\HyperTwist Installer Validation\\20260726T091926Z\\HyperTwist\\UnrealHyperTwist.exe", + "arguments": "" + } + }, + "userDataPreserved": true, + "installRootRemoved": true, + "registrationRemoved": true, + "shortcutsRemoved": true, + "cleanupCompleted": true, + "error": null +} \ No newline at end of file diff --git a/docs/generated/alpha_test_20260726_installer/windows-installer-payload-manifest.json b/docs/generated/alpha_test_20260726_installer/windows-installer-payload-manifest.json new file mode 100644 index 0000000..a049b61 --- /dev/null +++ b/docs/generated/alpha_test_20260726_installer/windows-installer-payload-manifest.json @@ -0,0 +1,729 @@ +{ + "manifestVersion": "ht-windows-installer-payload/v1", + "generatedAtUtc": "2026-07-26T08:31:40.6337955Z", + "releaseId": "20260723-R19", + "sourcePayloadRoot": "C:\\HyperTwist_worktrees\\phase10validate_packaged_alpha_release19_shipping_20260723\\Windows", + "excludedPatterns": [ + "*.pdb", + "Manifest_DebugFiles_Win64.txt" + ], + "fileCount": 143, + "totalSizeBytes": 1585341236, + "files": [ + { + "relativePath": "Content/Browser/dist/assets/client.js", + "sizeBytes": 875934, + "sha256": "524383f29f700b5907b999ec80fe1d4f472ac14af8ea5580a906696eba0955d9" + }, + { + "relativePath": "Content/Browser/dist/assets/decode.js", + "sizeBytes": 26052, + "sha256": "f6b754561563af8aeda1e6aa9f503496a45f90cf079ce74119cdba7087058c9b" + }, + { + "relativePath": "Content/Browser/dist/assets/easing-0f4db1c0.esm.js", + "sizeBytes": 29269, + "sha256": "38ddf7add1b0274ecb8e422332452e1cc2661758d576324c0267f1893cc96704" + }, + { + "relativePath": "Content/Browser/dist/assets/events-b389eeca.esm.js", + "sizeBytes": 608404, + "sha256": "415f094c85d96883fe17136b2ccaf7c0599c74c1fec1d50fbfe77c5aa49e8a78" + }, + { + "relativePath": "Content/Browser/dist/assets/gltf-viewer.module.js", + "sizeBytes": 371169, + "sha256": "803cf04b8bb52f4540459a6b755215ace8e3f9ca06ed6e269c2de58b1791084d" + }, + { + "relativePath": "Content/Browser/dist/assets/index.js", + "sizeBytes": 101, + "sha256": "c17a644a35874aec14eb885b7570e76742265aab419e26bf5ec3aef3d0c33004" + }, + { + "relativePath": "Content/Browser/dist/assets/index10.js", + "sizeBytes": 463085, + "sha256": "5eb1d53e1f4ad93014c9db5e7053a284ee75d12f67922a5342d5babaaf62f88f" + }, + { + "relativePath": "Content/Browser/dist/assets/index11.js", + "sizeBytes": 1010478, + "sha256": "943fa33589d184eb7c2580122f44baf69426d720fc7459aad7b3b452666cdcbd" + }, + { + "relativePath": "Content/Browser/dist/assets/index12.js", + "sizeBytes": 8792, + "sha256": "4ff02630aa51698e0e571bdd1615df1e47908ccb94c27fcf30abf886677c2dbd" + }, + { + "relativePath": "Content/Browser/dist/assets/index13.js", + "sizeBytes": 867555, + "sha256": "dd8d1a29141fa69a55d38f196aee7ea64b02354bbeadfd5578f98533cdd8959f" + }, + { + "relativePath": "Content/Browser/dist/assets/index2.js", + "sizeBytes": 16990, + "sha256": "00f5a4f3a3d11486be6feebae2511963dd06903eed45e1a1dc44c1b3be610d18" + }, + { + "relativePath": "Content/Browser/dist/assets/index3.js", + "sizeBytes": 541053, + "sha256": "0fcda6a7676782f695f3e713f721b7dd998cb12740ab81814af7ca263547c69b" + }, + { + "relativePath": "Content/Browser/dist/assets/index4.js", + "sizeBytes": 44323, + "sha256": "cfec865d65f73d1ef3102352554f5fb10a0d6c7b3572241de9077fbd89f11d26" + }, + { + "relativePath": "Content/Browser/dist/assets/index5.js", + "sizeBytes": 65696, + "sha256": "e83b58f0daa45e909992294461784bc49ca4fd4d857227767719b2eb43d390d9" + }, + { + "relativePath": "Content/Browser/dist/assets/index6.js", + "sizeBytes": 1608912, + "sha256": "bac438344a5a247cfbeb8b4025903b7c0fec959a04c80e2375f1bd53a89b27d0" + }, + { + "relativePath": "Content/Browser/dist/assets/index7.js", + "sizeBytes": 200923, + "sha256": "493590d87a9126e4e74489acd71981ccae2cfef540956fbc41b1220ff4e6a117" + }, + { + "relativePath": "Content/Browser/dist/assets/index8.js", + "sizeBytes": 2925000, + "sha256": "b94712cac9e7b384811054142511b411e9e50ea84e4244be57296d934a2b3ab8" + }, + { + "relativePath": "Content/Browser/dist/assets/index9.js", + "sizeBytes": 536, + "sha256": "1a22f6a2f3f1694a068d86a10cdbb0612007a82609dad95d661de48924f47395" + }, + { + "relativePath": "Content/Browser/dist/assets/install.js", + "sizeBytes": 396204, + "sha256": "43d1b725ff895424f5d6067851ff0b23a6822f4ab00e7e40877ae90925c985d2" + }, + { + "relativePath": "Content/Browser/dist/assets/inter.js", + "sizeBytes": 440193, + "sha256": "24b0d2388654b0e94d12a73a0ab262c18155cd90b4ddefc048ac8be673f5a9ce" + }, + { + "relativePath": "Content/Browser/dist/assets/jsx-runtime.js", + "sizeBytes": 22632, + "sha256": "a994feec9287fe3f3f3afd143dbf386e6ce68921d2573df3778fb1594dfe0954" + }, + { + "relativePath": "Content/Browser/dist/assets/leva.esm.js", + "sizeBytes": 282310, + "sha256": "8b8e8048af8e722e886919ef3fc42e17201aa4fdc972979f40980bb324a24783" + }, + { + "relativePath": "Content/Browser/dist/assets/maath.esm.js", + "sizeBytes": 13221, + "sha256": "9e0bb39344397d519cc0601ac2d9409e891b4ad627a509ca16a9d1a7c047959b" + }, + { + "relativePath": "Content/Browser/dist/assets/MathUtils.js", + "sizeBytes": 185, + "sha256": "04ead5c1ece921b0dbb5c3a8c7b1b9f59698f43fe5f2c2ba7390307d35fe275d" + }, + { + "relativePath": "Content/Browser/dist/assets/MeshoptDecoder.js", + "sizeBytes": 595800, + "sha256": "902a1fbb9e9dadb8aaac060e2f38f51bb9afc0f3f526c66d8f78339079af527d" + }, + { + "relativePath": "Content/Browser/dist/assets/model-viewer.js", + "sizeBytes": 569271, + "sha256": "78b871924c1fe17e3907e221a0488ccb74a237622363917400c08e81300ddff2" + }, + { + "relativePath": "Content/Browser/dist/assets/model-viewer-effects.js", + "sizeBytes": 28163, + "sha256": "1e8577509a823663aae78e29dd8f7a499a24ac30eb0df5874fac12d5a8d2ed45" + }, + { + "relativePath": "Content/Browser/dist/assets/morphPath.js", + "sizeBytes": 59852, + "sha256": "47274c7da895da1dd209982343f7e0b96e69c8849785c370ab23b4bb909e9cb6" + }, + { + "relativePath": "Content/Browser/dist/assets/Painter.js", + "sizeBytes": 255062, + "sha256": "482c8b4179b89e56847e02b9175d4d1b554a269d09b9f7c3b89a191c55f9ac87" + }, + { + "relativePath": "Content/Browser/dist/assets/property.js", + "sizeBytes": 20774, + "sha256": "7d5b7c2bdf95125984b778c7b1ad8db105d85cec36d3f0a74205c6cc6f3fa267" + }, + { + "relativePath": "Content/Browser/dist/assets/react-spring_core.modern.js", + "sizeBytes": 69814, + "sha256": "cbf81e7fec6557b7baef30cd7f180ac9cc3ade3ddb5bada3e4382ab7a63e9511" + }, + { + "relativePath": "Content/Browser/dist/assets/react-spring_three.modern.js", + "sizeBytes": 1577, + "sha256": "ee4a91412c95a90241efff06bb7c80475e252250b8795eb2abd1265a18c0254b" + }, + { + "relativePath": "Content/Browser/dist/assets/react-spring_web.modern.js", + "sizeBytes": 6888, + "sha256": "ca824b8b650778235149947119e4e37e5e9c997870cd9bc248d9bcd1efe9d911" + }, + { + "relativePath": "Content/Browser/dist/assets/react-three-fiber.esm.js", + "sizeBytes": 7829, + "sha256": "3ca075d4805914a117e2496a0b3c5d49aedb99ed0fc5df9cfb19aa59700d3b25" + }, + { + "relativePath": "Content/Browser/dist/assets/three.module.js", + "sizeBytes": 1292285, + "sha256": "31e68a7aa67d4d54c90d64ed6b1a86040965e1d619c2229154dc5ff63be67a1b" + }, + { + "relativePath": "Content/Browser/dist/assets/tslib.es6.js", + "sizeBytes": 3434, + "sha256": "e525bc57e4875c563602b26f5ff4ce5c8cabc69f8ca5107cef68ce182bf6b81f" + }, + { + "relativePath": "Content/Browser/dist/assets/use-gesture-react.esm.js", + "sizeBytes": 42626, + "sha256": "f3ff4be03a822f20e3275013068617c96ef4e2f147988806f7620bfbad4b8b11" + }, + { + "relativePath": "Content/Browser/dist/assets/vanilla.js", + "sizeBytes": 541, + "sha256": "264fdf8ce2d8c33fef425b00082363420a4c1c3d5b737a44b3f64ad6ea62292a" + }, + { + "relativePath": "Content/Browser/dist/assets/vision_bundle.js", + "sizeBytes": 169647, + "sha256": "268be65875c0724747ceae21789108e4f40f6402bc18baf469362a90003f9af5" + }, + { + "relativePath": "Content/Browser/dist/browser-spatial-runtime.js", + "sizeBytes": 30651, + "sha256": "0feb7fc24108b4804e4b801518ce91a0a5a9eb8f806edaedb7506d8a82bb283e" + }, + { + "relativePath": "Content/Browser/index.html", + "sizeBytes": 7473, + "sha256": "00a730f4d8ee099d0d0d63a171b94194f9fe9c9c92de810a411050c0b260a562" + }, + { + "relativePath": "Content/Browser/src/browser-runtime-bootstrap.js", + "sizeBytes": 3422, + "sha256": "e35963e1888deac61578141579c64e686c15cb964b2b4d8d8c080ed35b4ed00e" + }, + { + "relativePath": "Content/Browser/src/browser-spatial-runtime-fallback.js", + "sizeBytes": 30201, + "sha256": "0089004d3aa86de1edefcc7c95ea0fc80c9352e49778bc384f6d676e8a9ad1a4" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/chrome_100_percent.pak", + "sizeBytes": 695026, + "sha256": "3f665b02bbe58d6423c2e86211bd8f074d8a36726438e639541ad695f8430b9b" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/chrome_200_percent.pak", + "sizeBytes": 1094579, + "sha256": "8152796c92c3481a546564be8f0b2ca90bf50d1bf4a141db166c90377239794d" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/chrome_elf.dll", + "sizeBytes": 1370552, + "sha256": "6cb248829b22689691fcfeb93258e12d8b19204d62dafc75bfa84bd21b142679" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/d3dcompiler_47.dll", + "sizeBytes": 4927552, + "sha256": "7b52ad25b00b6c67e721ec0105601dcb462fefe233a55e5a93927ed69650829f" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/dxcompiler.dll", + "sizeBytes": 20817848, + "sha256": "7e8d14f9c13cf9d7344a865503cbe91e8cbe8e3b023525e5f208c155a1a28075" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/dxil.dll", + "sizeBytes": 1519176, + "sha256": "3b8648bd722480535753c28d34ee9de98ddb69f7e01bef96068303ffbd80ccc4" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/icudtl.dat", + "sizeBytes": 10468208, + "sha256": "9ae98c06cbb0ea43c5cd6b5725310c008c65e46072421a1118cb88e1de9a8b92" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/libcef.dll", + "sizeBytes": 239992248, + "sha256": "e89ec24b1abac5c77260c49c7bd33d010ddd67b752d22d87dc142d37d2e8d966" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/libEGL.dll", + "sizeBytes": 536504, + "sha256": "f3f527ac94b3e81e36c123d1a0b04c7e5cc6e079ae1fbd689540ab85108797d6" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/libGLESv2.dll", + "sizeBytes": 8959416, + "sha256": "251bbd3d72cbe381857401ad62e42b5627d2c4c05fbcdd4f68e774720ba01c44" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/resources.pak", + "sizeBytes": 8683856, + "sha256": "aa8d203d593ed170696eec09137e2421882f46273337a00d16c6b23e91de675e" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/af.pak", + "sizeBytes": 538998, + "sha256": "c514fe813e56c4767e9b190acca7a44d6f0f6d7c03dec6c4afb6586f0c89ebb7" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/am.pak", + "sizeBytes": 866444, + "sha256": "fd641fb8513ea2b93a5f34f45a9a8af3596e73dd738207bcc07ecfbd398f4285" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/ar.pak", + "sizeBytes": 953918, + "sha256": "1d11e3b7cae4762d0c35dc83c331813f49a7f9bc492eb63cada01954af8a7d2a" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/bg.pak", + "sizeBytes": 987762, + "sha256": "d2fa3f3e66113fbd7efe891eef6f66915e04dfa8aedbb162cebe27ee7db4d248" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/bn.pak", + "sizeBytes": 1269605, + "sha256": "a3f81cf087eb69c505243db32d36fce0f4266d074f22dd6e1742a1311cc8d5c5" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/ca.pak", + "sizeBytes": 612377, + "sha256": "bc32492cbaa82d9de625548c79f03173ec4db4d3e25d2e5d3099a7d7231f8ff1" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/cs.pak", + "sizeBytes": 622919, + "sha256": "3223737e45b3f06acd90d2ea6b923c99ef3a988f072234328f5cd337d428a219" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/da.pak", + "sizeBytes": 567958, + "sha256": "a2960a7b635fc8a100e325b5ebe0bd696cccaa0af5d77ec1ea2ef3a7ad77471c" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/de.pak", + "sizeBytes": 609935, + "sha256": "d7a8385e3e1bedd61d102be6b19d5e8cf09c814528cfd782c1d3bf62812690b8" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/el.pak", + "sizeBytes": 1080416, + "sha256": "889501de74431bc33ca326df8711c46e3def70cd82b1124a44524ee2829167c2" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/en-GB.pak", + "sizeBytes": 492124, + "sha256": "86c1766ba2e7b86f111445a1f57c5120e1be78a4b6e7b2e124dcd31116c7ccdb" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/en-US.pak", + "sizeBytes": 497015, + "sha256": "fc44badc0f1ee3daa57d905a9e39ae3ee6dbd38049cbd944905630409038ff2b" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/es.pak", + "sizeBytes": 596806, + "sha256": "270147cc81547477f181b5c6b75dd8cf46eb124de802de8eb2bc6855d2ef8fe8" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/es-419.pak", + "sizeBytes": 598355, + "sha256": "d7088cd07d220af16275befc09c56c2524d4b63a424876ce2f7f5442f56dcaa3" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/et.pak", + "sizeBytes": 544221, + "sha256": "21046ded9f9aab82bbc7ff1579fbc80b94bd6da09a3e4817188391d4af1c24be" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/fa.pak", + "sizeBytes": 879421, + "sha256": "af1dd21b0aa302cbebc56d39abd8bcfda981734e04c7ab8040e905cbbbaa193c" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/fi.pak", + "sizeBytes": 552657, + "sha256": "48a5e49251723e3e191c8502ff1b6dedf39e168ac252fcf535b36ad52e9ea8fd" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/fil.pak", + "sizeBytes": 624599, + "sha256": "7117986303e4954ac39bb144ac76bacb450f7be68ae1fde970ee639ad39bd9d8" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/fr.pak", + "sizeBytes": 646732, + "sha256": "4dee926b878d106a3599c1c884bb84dcdd7785836032384420f5fd0c76cea640" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/gu.pak", + "sizeBytes": 1255599, + "sha256": "9af5364f7cbe7ef972c0fb9b62bf2c13cb1a9ff4dde1d6fd2ffcacd088adb987" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/he.pak", + "sizeBytes": 777593, + "sha256": "ad956cf0f663b25c19907bb8737b2342704223ac79f8a85b7a3038d52e9afa7a" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/hi.pak", + "sizeBytes": 1330999, + "sha256": "6b2629ccdc38a3c14ee5de4eba558527bbc4c590c87cc9393f9b7e337b8e5efa" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/hr.pak", + "sizeBytes": 600581, + "sha256": "d2d55568886ef5bb2a073e619f8dbf98a3ff341ce5ba6f3f362224081cc0cdc8" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/hu.pak", + "sizeBytes": 646489, + "sha256": "d3f6a1560e893042b92f40a56b5761790e94a78efda73a26a640dd21997443ae" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/id.pak", + "sizeBytes": 535422, + "sha256": "8f84ac460c88e44518e2c5165d0758e8d0f2c6223cfb5ca6ad5507aae79f67bd" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/it.pak", + "sizeBytes": 594371, + "sha256": "fd236c934edfae7fe10176f9d7a2293838d2cd59f54beb7af689cd61014a51c9" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/ja.pak", + "sizeBytes": 719063, + "sha256": "539e2195c7e7bbf7b2976e9b3b4950bc4a6616a590cf85678bd264387382de1c" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/kn.pak", + "sizeBytes": 1424332, + "sha256": "70d9698ad1cb9d78e1eab70e7e1161ca4216399969991922abd0edeae0ecbb01" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/ko.pak", + "sizeBytes": 606589, + "sha256": "a21b8cb55ed43abd51fb2f97d40208be637b3cb2dae2f55b3abd210722e67951" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/lt.pak", + "sizeBytes": 652194, + "sha256": "160b2da4626a0ce7570460ed48326352f25624753dcc6a6aa90c6906eca15eb5" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/lv.pak", + "sizeBytes": 651016, + "sha256": "70e1736c8d1406513ccf47ad85eec64f46f2ada1a7aab1ddddeb049170152f14" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/ml.pak", + "sizeBytes": 1479560, + "sha256": "7f42ec438ff13b97b775146033fce3ee33a73d0806e89b61f6f284a80e1c350b" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/mr.pak", + "sizeBytes": 1223735, + "sha256": "7277f688e1773d68088ce48efabf2dde4386c7cc5cfc579a8e16806eb0e37989" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/ms.pak", + "sizeBytes": 560048, + "sha256": "95d27fc7023e763c546bf19d9d214b1d1eaef1ea01dab23391fe5fba7361a674" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/nb.pak", + "sizeBytes": 543051, + "sha256": "c135a7f5f35a3533a2da5666768cb78402580ecb41e1309eee580816a8a0ac28" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/nl.pak", + "sizeBytes": 562204, + "sha256": "39bbe7e945c5cedf6a53f88fbc5c298a65e48192fe46c4e6a66c289b0869de1c" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/pl.pak", + "sizeBytes": 627081, + "sha256": "9ad6f0c5b4c3f335b155251b07261f5241a29892667fa607817e48abeae19462" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/pt-BR.pak", + "sizeBytes": 587941, + "sha256": "8e5466e2acbe6e350dc3b0752495dfa44eaf1da8e6054ffba9c320c4277c96d1" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/pt-PT.pak", + "sizeBytes": 592511, + "sha256": "f193c222cfd9cd723dbd7839cf45c14b7749cde5527b552160df819c72b442c1" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/ro.pak", + "sizeBytes": 612678, + "sha256": "31c2568ba713ce7d9b2a784b8bce09b116408a5943171b2baf84b83a2cdd2801" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/ru.pak", + "sizeBytes": 1003077, + "sha256": "8badbaff9761b5cfa26a409524c2c85f252ec2166449edb32246a2bee5f914b0" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/sk.pak", + "sizeBytes": 632482, + "sha256": "6491c4f1c9e8ad0a498cf2a5fe11467fd419fa105622977105915639341f1c27" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/sl.pak", + "sizeBytes": 607050, + "sha256": "97b5ab48cc42512a693fa7887acc7d4f3ea5cdd9e6bbf334fa2940c922e121af" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/sr.pak", + "sizeBytes": 929699, + "sha256": "d53039029b8b60ffca583896abe66e860016bf8b250df689bece5e17c2d86abc" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/sv.pak", + "sizeBytes": 547471, + "sha256": "669fbcc98ccb78259f497e965efbf2ba38e5c0e5ad6d50c588f013c018e03d7a" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/sw.pak", + "sizeBytes": 576117, + "sha256": "c8a7fe1af281b73985da58b66570150c4df27b0425f81068df6949799ada2769" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/ta.pak", + "sizeBytes": 1475609, + "sha256": "4019bcfd216e224924e34cb879b8cec6d91fde49d4a362aede9120bc9355543e" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/te.pak", + "sizeBytes": 1361935, + "sha256": "cd3a666d5f0091ac4c6f065b45ab21a30a9fe2a1073f8c6bf6192f123ad8156c" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/th.pak", + "sizeBytes": 1144623, + "sha256": "114d7a817a0c73f2d0bff438eb6a076ad76a8bfb2c561e542aeb699cbf1298ef" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/tr.pak", + "sizeBytes": 587129, + "sha256": "982728b531abdb1c2e33fc65203bb20e3abac226674b4611d8d609a82a483cb2" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/uk.pak", + "sizeBytes": 1001189, + "sha256": "54e5c7d4cb654c880c54051b35ec108dff28621afcc896a7d227d3b01a98c60b" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/ur.pak", + "sizeBytes": 872334, + "sha256": "04a50bcbedd0760bb9c6e0628d8e520d58e8f9e9655c76349bceea5a891783d5" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/vi.pak", + "sizeBytes": 695768, + "sha256": "f87b6a8530ffa5760367c62260b6bceaeb9148f58ad4041222dbd9592488299e" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/zh-CN.pak", + "sizeBytes": 503346, + "sha256": "dc5703f8267b4343a9d8e6acf5b82a4a52559f494c4555aded02f29cf20ed631" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/Resources/locales/zh-TW.pak", + "sizeBytes": 500142, + "sha256": "ccaf09ef99e6feda260aee9cd073bd7513223f770422d799d7490a6f16f44fc5" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/snapshot_blob.bin", + "sizeBytes": 313526, + "sha256": "e8a4351c04b18aef318612989c147e2878082306094e39349fb0fea8711a19a0" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/v8_context_snapshot.bin", + "sizeBytes": 665981, + "sha256": "b443db3a84c0c020e1ceeeb355018d6589eb8ef5b48e110ef6e02fd723781d78" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/vk_swiftshader.dll", + "sizeBytes": 5572536, + "sha256": "06b32c4dae03afe26c5acb821fdf19b7bdb9e3ffe8f29af93bba4e8e49722287" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/vulkan-1.dll", + "sizeBytes": 930744, + "sha256": "ede9bf15fc0a3dc6f60d76eda8a7a765b57866edc5410c91709c82c4b7fe7e5f" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/DbgHelp/dbghelp.dll", + "sizeBytes": 2232688, + "sha256": "d65dff2ebe90bf79fba62f3bb1f105a7ce8efaefaa8349379a886bb893a4ef45" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/MsQuic/v220/win64/msquic.dll", + "sizeBytes": 2547080, + "sha256": "832f4ae560b1623aa4d8f87b982e9c9414312723fbc905ddab93fd80d7f1cea2" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/NVIDIA/NVaftermath/Win64/GFSDK_Aftermath_Lib.x64.dll", + "sizeBytes": 5442592, + "sha256": "f2f1a88185345b28d05673e11111ee21845f3ebe65919cf6b3085bee7d09e30a" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/Ogg/Win64/VS2015/libogg_64.dll", + "sizeBytes": 59832, + "sha256": "1e2fe8a3dfe9616edcb3cd3a5a958c791f690d5b604eb1658b0f844cef156eee" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/OpenXR/win64/openxr_loader.dll", + "sizeBytes": 1971640, + "sha256": "36020b5170c5a653132468e7b297c5e06eaff5ba4d26d5478d723f88f699f8b5" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/Vorbis/Win64/VS2015/libvorbis_64.dll", + "sizeBytes": 1724856, + "sha256": "676bbdc5838755d7a881c847f84ca79997b525882594b66160ab800676cab9c0" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/Vorbis/Win64/VS2015/libvorbisfile_64.dll", + "sizeBytes": 48568, + "sha256": "ceb067771c03ae1a7afc125c15c85dbab027aaec72575dc101c618eba2bafea0" + }, + { + "relativePath": "Engine/Binaries/ThirdParty/Windows/XAudio2_9/x64/xaudio2_9redist.dll", + "sizeBytes": 858144, + "sha256": "013984e33e884bd0b823734ab86a599a8dceffed675039ff293d15ffcf7f2be9" + }, + { + "relativePath": "Engine/Binaries/Win64/EpicWebHelper.exe", + "sizeBytes": 4751288, + "sha256": "f727e281c69b7cbffc4343310342ff78dfccf40f94fc8ba09e9ce6b1bf842a17" + }, + { + "relativePath": "Engine/Config/StagedBuild_UnrealHyperTwist.ini", + "sizeBytes": 3, + "sha256": "f1945cd6c19e56b3c1c78943ef5ec18116907a4ca1efc40a57d48ab1db7adfc5" + }, + { + "relativePath": "Engine/Content/Renderer/TessellationTable.bin", + "sizeBytes": 259568, + "sha256": "edc4f3b628a139c5e185bbaeca661c95c1f427368d3b92892c5cff36c56dd941" + }, + { + "relativePath": "Engine/Content/Slate/Cursor/invisible.cur", + "sizeBytes": 326, + "sha256": "fef3980fa567e9c45d5d8dcaa432a2c1a3f3bb17fe67d7ab198164f56c142aa1" + }, + { + "relativePath": "Engine/Content/SlateDebug/Fonts/LastResort.tps", + "sizeBytes": 930, + "sha256": "0f775fa99d05e21d43d2d06d55982b23dcdccdb512a9a5a07aff674f212c41b8" + }, + { + "relativePath": "Engine/Content/SlateDebug/Fonts/LastResort.ttf", + "sizeBytes": 5395052, + "sha256": "fff0f8de0b868b964950fe97c9b60f893e9bc85a4f90b3629c5693db058b97b9" + }, + { + "relativePath": "Engine/Plugins/NNE/NNERuntimeORT/Binaries/ThirdParty/Onnxruntime/Win64/onnxruntime.dll", + "sizeBytes": 14692872, + "sha256": "381497f383b457180aba6e5bf56a2eb18cefd6735cfd47257539f6d87fbc8584" + }, + { + "relativePath": "Manifest_NonUFSFiles_Win64.txt", + "sizeBytes": 10686, + "sha256": "b8abaae2ce29e4fc950008d19930e2dccfbe6b6b667baa789c4b67dce60f5710" + }, + { + "relativePath": "Manifest_UFSFiles_Win64.txt", + "sizeBytes": 255758, + "sha256": "cb6e852604f98c5a63ad4c9eecaf32826522f660362b0eceb7d2a36b6531f869" + }, + { + "relativePath": "UnrealHyperTwist.exe", + "sizeBytes": 165376, + "sha256": "886f69c6a823770b3ed68db4fe3790a488124c99c98dbd41944a6bed5d634dc6" + }, + { + "relativePath": "UnrealHyperTwist/Binaries/Win64/D3D12/x64/D3D12Core.dll", + "sizeBytes": 4544000, + "sha256": "fb197903fea91aaf352d2322f342b1cec8db36ff0292a49f24173577e08f26cf" + }, + { + "relativePath": "UnrealHyperTwist/Binaries/Win64/D3D12/x64/d3d12SDKLayers.dll", + "sizeBytes": 5104632, + "sha256": "a817223446f53b3da894edb1ee7a92b94573690e689a9470b87bf42866115f91" + }, + { + "relativePath": "UnrealHyperTwist/Binaries/Win64/DML/x64/DirectML.dll", + "sizeBytes": 18537968, + "sha256": "664255a3d9a6b976e2d0e2440403537d9d0dd91b1fad866768638b4e73a2c33a" + }, + { + "relativePath": "UnrealHyperTwist/Binaries/Win64/tbb12.dll", + "sizeBytes": 331704, + "sha256": "645b681e3581c04d57f468bd85c17648172decf873653ab00382211b96cc98fb" + }, + { + "relativePath": "UnrealHyperTwist/Binaries/Win64/tbbmalloc.dll", + "sizeBytes": 117688, + "sha256": "f81a11f2e6e93036bab4e51a1daab9940fb8a9a5ec0f508c4cb76d398e27c100" + }, + { + "relativePath": "UnrealHyperTwist/Binaries/Win64/UnrealHyperTwist-Win64-Shipping.exe", + "sizeBytes": 181121024, + "sha256": "e583531cbb21d22f6c223eb12bf0cf81f82798faff9e2fbec4ebeb953bf98bf3" + }, + { + "relativePath": "UnrealHyperTwist/Content/Paks/global.ucas", + "sizeBytes": 3325696, + "sha256": "c7e20318d6b887c26a131d33bed944410469b1f331e76bbfe0e5f0168d808188" + }, + { + "relativePath": "UnrealHyperTwist/Content/Paks/global.utoc", + "sizeBytes": 806, + "sha256": "846861acb8db76d1a68a9614d89d327fb7ae4ade5a1c9aa63be79c713118d424" + }, + { + "relativePath": "UnrealHyperTwist/Content/Paks/UnrealHyperTwist-Windows.pak", + "sizeBytes": 11069853, + "sha256": "605e0b2ff7dd284762f4d4281945535f2a3ff86065904a2ce4f1c2837c72c900" + }, + { + "relativePath": "UnrealHyperTwist/Content/Paks/UnrealHyperTwist-Windows.ucas", + "sizeBytes": 281222144, + "sha256": "d52a40ad8633351078b351f4bc1321d12ee12b397ad585f7a8a43418f54cfe62" + }, + { + "relativePath": "UnrealHyperTwist/Content/Paks/UnrealHyperTwist-Windows.utoc", + "sizeBytes": 216013, + "sha256": "f317d5ac0c82871400697dc29b17925a49d55c7aa2608ddc4f91cfea896ec07e" + }, + { + "relativePath": "UnrealHyperTwist/Saved/twophase-ht.tbl", + "sizeBytes": 676207080, + "sha256": "dc6de3d909a37afe2ba7f1f978543a13eae215bd20d954d66813524a8ca20034" + } + ] +} \ No newline at end of file diff --git a/docs/generated/alpha_test_20260726_installer/windows-installer-source-readiness-report.json b/docs/generated/alpha_test_20260726_installer/windows-installer-source-readiness-report.json new file mode 100644 index 0000000..0619ecf --- /dev/null +++ b/docs/generated/alpha_test_20260726_installer/windows-installer-source-readiness-report.json @@ -0,0 +1,180 @@ +{ + "reportVersion": "ht-windows-readiness/v1", + "generatedAtUtc": "2026-07-26T08:31:51.1163747Z", + "result": "ready-with-warnings", + "payloadRoot": "C:\\HyperTwist_worktrees\\phase10validate_packaged_alpha_release19_shipping_20260723\\Windows", + "targetInstallRoot": "C:\\Program Files\\HyperTwist", + "requiredFailureCount": 0, + "optionalWarningCount": 2, + "checks": [ + { + "id": "windows-x64", + "class": "required", + "passed": true, + "summary": "HyperTwist requires a 64-bit Windows operating system.", + "evidence": { + "architecture": "64-bit", + "processArchitecture": "AMD64" + } + }, + { + "id": "windows-supported-build", + "class": "required", + "passed": true, + "summary": "HyperTwist requires Windows build 19045 or newer.", + "evidence": { + "caption": "Microsoft Windows 11 Home", + "version": "10.0.22631", + "build": 22631, + "minimumBuild": 19045 + } + }, + { + "id": "system-memory", + "class": "required", + "passed": true, + "summary": "At least 8 GiB of physical memory is required for the desktop Alpha.", + "evidence": { + "totalBytes": 16893206528, + "minimumBytes": 8589934592 + } + }, + { + "id": "install-volume-free-space", + "class": "required", + "passed": true, + "summary": "The target volume must have enough free space for installation and first-run caches.", + "evidence": { + "targetInstallRoot": "C:\\Program Files\\HyperTwist", + "availableBytes": 171945615360, + "minimumBytes": 4294967296 + } + }, + { + "id": "vc-runtime-x64", + "class": "required", + "passed": true, + "summary": "Microsoft Visual C++ 2015-2022 x64 runtime 14.44.35211.0 or newer is required and is bundled by the installer.", + "evidence": { + "registryPath": "HKLM:\\SOFTWARE\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x64", + "installed": true, + "version": "14.44.35211.0" + } + }, + { + "id": "windows-media-and-graphics-libraries", + "class": "required", + "passed": true, + "summary": "Windows DirectX, XInput, and Media Foundation runtime libraries must be available.", + "evidence": { + "systemDirectory": "C:\\Windows\\system32", + "requiredLibraries": [ + "d3d11.dll", + "d3d12.dll", + "dxgi.dll", + "mf.dll", + "mfplat.dll", + "mfreadwrite.dll", + "xinput1_4.dll" + ], + "missingLibraries": [ + + ] + } + }, + { + "id": "graphics-adapter", + "class": "required", + "passed": true, + "summary": "At least one Windows graphics adapter must be visible.", + "evidence": [ + { + "name": "Intel(R) Iris(R) Xe Graphics", + "driverVersion": "31.0.101.3358", + "driverDate": "2022-08-04T02:00:00.0000000+02:00", + "adapterRamBytes": 1073741824, + "status": "OK" + } + ] + }, + { + "id": "audio-device", + "class": "optional", + "passed": true, + "summary": "An audio device is optional for puzzle play and recommended for narration and speech features.", + "evidence": [ + { + "name": "Intel® Smart Sound Technology for Digital Microphones", + "status": "OK" + }, + { + "name": "Intel® Smart Sound Technology for USB Audio", + "status": "OK" + }, + { + "name": "Intel® Smart Sound Technology for Bluetooth® Audio", + "status": "OK" + }, + { + "name": "Audio Device", + "status": "OK" + }, + { + "name": "Virtual Audio Cable", + "status": "OK" + } + ] + }, + { + "id": "openxr-runtime", + "class": "optional", + "passed": false, + "summary": "An active OpenXR runtime is optional for keyboard/mouse play and required only for live headset sessions.", + "evidence": null + }, + { + "id": "required-payload-files", + "class": "required", + "passed": true, + "summary": "The installed payload must contain every required executable, runtime, cooked-data, browser, solver, and OpenXR file.", + "evidence": { + "payloadRoot": "C:\\HyperTwist_worktrees\\phase10validate_packaged_alpha_release19_shipping_20260723\\Windows", + "requiredFileCount": 15, + "cefRuntimeFiles": [ + "Engine/Binaries/ThirdParty/CEF3/Win64/128.4.13+ge76af7e+chromium-128.0.6613.138/libcef.dll" + ], + "failures": [ + + ] + } + }, + { + "id": "desktop-code-signature", + "class": "optional", + "passed": false, + "summary": "The Alpha payload is currently unsigned; production distribution requires Authenticode signing.", + "evidence": { + "status": "NotSigned", + "signer": null + } + }, + { + "id": "payload-manifest-parity", + "class": "required", + "passed": true, + "summary": "The installed payload must match the release manifest without missing, truncated, or altered files.", + "evidence": { + "manifestPath": "C:\\HyperTwist_worktrees\\installer-release19-20260726\\validation\\windows-installer-payload-manifest.json", + "manifestVersion": "ht-windows-installer-payload/v1", + "manifestEntryCount": 143, + "declaredFileCount": 143, + "declaredTotalSizeBytes": 1585341236, + "computedTotalSizeBytes": 1585341236, + "hashesVerified": true, + "failures": [ + + ] + } + } + ] +} \ No newline at end of file diff --git a/docs/ops/HYPERTWIST_ALPHA_TEST_TAKEOVER_BLACK_SCREEN_REPAIR_AND_VALIDATION_2026-07-19.md b/docs/ops/HYPERTWIST_ALPHA_TEST_TAKEOVER_BLACK_SCREEN_REPAIR_AND_VALIDATION_2026-07-19.md index de4af9d..e6aa6d3 100644 --- a/docs/ops/HYPERTWIST_ALPHA_TEST_TAKEOVER_BLACK_SCREEN_REPAIR_AND_VALIDATION_2026-07-19.md +++ b/docs/ops/HYPERTWIST_ALPHA_TEST_TAKEOVER_BLACK_SCREEN_REPAIR_AND_VALIDATION_2026-07-19.md @@ -357,6 +357,7 @@ Current operator delivery: - `C:\Users\Anthracite Ace\Downloads\HyperTwist-Alpha-Test-20260723-R19` - `C:\Users\Anthracite Ace\Downloads\HyperTwist-Alpha-Test-20260723-R19.zip` +- `C:\Users\Anthracite Ace\Downloads\HyperTwist-Alpha-Test-20260723-R19-Setup.exe` Durable pulled evidence: @@ -392,6 +393,12 @@ Final acceptance facts: `caa45f3d3104a8e5406f493a60c8a136d852bd35e0bd1c8dfbf79738c1343892` - no fatal signature, package-owned listener, trace-control listener, or lingering owned packaged process remained +- the later `2026-07-26` installer authority passed with size `851049086` + bytes, SHA-256 + `c31ed6b0b2ae1feb449c27e484d72dc2e07ba220e3cdafa4c3605429d638e0ea`, + `143` manifest-owned payload files, and green install, readiness, Shipping + launch, in-place upgrade, second parity, uninstall, user-data preservation, + and cleanup proof The global-assistant regression fix is part of this exact package. A standalone speech session no longer requires an active Classic training run, so the fixed @@ -400,6 +407,34 @@ provider-reachability, microphone, and audible-TTS test still requires intentionally configured services and hardware and is not inferred from structural automation. +### Release 19 Windows installer authority + +Durable evidence: + +`docs/generated/alpha_test_20260726_installer` + +The installer is now the preferred first-time Alpha delivery while the +validated folder and ZIP remain portable and rollback-friendly alternatives. +It owns: + +- x64 Windows 10 22H2 / Windows 11 preflight +- a valid Microsoft-signed Visual C++ 2015-2022 x64 prerequisite +- a complete `143`-file payload manifest with SHA-256 parity +- safe install-directory leaf and non-empty ownership refusal +- Add/Remove Programs plus resolved app/readiness/uninstall shortcuts +- marker-and-registry-gated upgrade and uninstall +- preservation of settings, saves, replays, and diagnostics + +The lifecycle harness deliberately used an install path containing spaces. It +proved an unowned non-empty target was refused before prerequisite or payload +mutation, then passed first install, full parity, installed readiness, +stock-Shipping initialization, in-place upgrade, second parity, and uninstall. +No install root, registration, shortcut, listener, or owned process remained. + +This is `isolated-current-host` evidence because no clean VM or Windows Sandbox +was available. The executable is `NotSigned`; trusted Authenticode remains a +production gate rather than an inferred Alpha capability. + ## Operator diagnostics The delivered Alpha uses stock Shipping configuration and first-party direct @@ -427,14 +462,15 @@ Never distribute a loose `UnrealHyperTwist.exe`. The executable depends on the complete staged `Windows` directory, including engine binaries, project content containers, and runtime DLLs such as `tbbmalloc.dll`. -The approved Alpha distribution shape is: +The approved Alpha distribution shapes are: +- the validated installer for normal first-time operator testing - an extracted folder for direct operator testing - a separately verified ZIP of that complete folder - matching release metadata with SHA-256, byte count, file/entry count, and validation references -Approved current delivery: +Historical Release 17 rollback delivery: - folder: `C:\Users\Anthracite Ace\Downloads\HyperTwist-Alpha-Test-20260723` @@ -444,6 +480,19 @@ Approved current delivery: - ZIP facts: `103` entries, `1082134734` bytes, SHA-256 `1ad8e1c0fc967b227ca09fb68c515ed644eca3e4bda07fdf8c378b438000fc42` +Approved current Release 19 delivery: + +- folder: + `C:\Users\Anthracite Ace\Downloads\HyperTwist-Alpha-Test-20260723-R19` +- ZIP: + `C:\Users\Anthracite Ace\Downloads\HyperTwist-Alpha-Test-20260723-R19.zip` +- ZIP facts: `167` entries, `1093537260` bytes, SHA-256 + `630963f96c681fc49130cbc56c6c850008d4cddda7e83f2ee9c077c52d7709a9` +- installer: + `C:\Users\Anthracite Ace\Downloads\HyperTwist-Alpha-Test-20260723-R19-Setup.exe` +- installer facts: `851049086` bytes, SHA-256 + `c31ed6b0b2ae1feb449c27e484d72dc2e07ba220e3cdafa4c3605429d638e0ea` + The superseded July 9 folder and matching ZIP were deleted only after the replacement folder, archive, critical-entry hashes, and final Downloads launch were verified. The retirement report is retained at diff --git a/docs/ops/HYPERTWIST_COMPREHENSIVE_RECONSTRUCTION_ROADMAP_2026-06-10.md b/docs/ops/HYPERTWIST_COMPREHENSIVE_RECONSTRUCTION_ROADMAP_2026-06-10.md index 5d7561c..bfa9d83 100644 --- a/docs/ops/HYPERTWIST_COMPREHENSIVE_RECONSTRUCTION_ROADMAP_2026-06-10.md +++ b/docs/ops/HYPERTWIST_COMPREHENSIVE_RECONSTRUCTION_ROADMAP_2026-06-10.md @@ -576,6 +576,15 @@ Routing correction: succeeded in `60.93` seconds and all `5` `HyperTwist.PlayerExperience` tests passed, including `GlobalAssistantSpeechSession`. +- [x] Release 19 Windows installer and isolated-host lifecycle gate on + `2026-07-26`: a first-party NSIS installer now owns x64/Windows preflight, + the signed Microsoft Visual C++ runtime, a complete `143`-file SHA-256 + manifest, safe install-root and marker/registry ownership, resolved + shortcuts, readiness reporting, in-place upgrade, and uninstall that + preserves user data. The final `851049086`-byte installer passed first + install, full parity, stock-Shipping launch, upgrade, second parity, + uninstall, and cleanup from a path containing spaces. Durable proof lives in + `docs/generated/alpha_test_20260726_installer`. **Estimated actions:** 20–30 **Estimated time:** 1–2 days @@ -620,15 +629,18 @@ Routing correction: ## Immediate Next Step -**Current Alpha next move (`2026-07-23`):** Release 19 is now the current -portable-Alpha authority at -`C:\Users\Anthracite Ace\Downloads\HyperTwist-Alpha-Test-20260723-R19`, while -Release 17 remains rollback authority until operator acceptance. Prioritize -installer/uninstaller, code-signing, clean-machine prerequisite, entitlement, -and update-channel rollout proof next. Separately retain the real-hardware -XR/controller observation and configured-provider AI/STT/TTS audible smoke as -external-condition gates. Do not reopen MagicTile native-renderer widening or -unrelated simulator topology to perform this release engineering. +**Current Alpha next move (`2026-07-26`):** Release 19 is now the current +portable and installed-Alpha authority at +`C:\Users\Anthracite Ace\Downloads\HyperTwist-Alpha-Test-20260723-R19` and +`C:\Users\Anthracite Ace\Downloads\HyperTwist-Alpha-Test-20260723-R19-Setup.exe`, +while Release 17 remains rollback authority until operator acceptance. +Installer/uninstaller and isolated-current-host prerequisite proof are closed. +Prioritize trusted Authenticode signing, a genuinely fresh Windows VM install, +entitlement-backed public release URLs, and update-channel/rollback rollout +proof next. Separately retain the real-hardware XR/controller observation and +configured-provider AI/STT/TTS audible smoke as external-condition gates. Do +not reopen MagicTile native-renderer widening or unrelated simulator topology +to perform this release engineering. **Phase 1 through Phase 5 are now closed on the intended simulator lane:** repo wiring, the classic-cube renderer/runtime loop, dedicated training maps and materials, solver-guided training, diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md b/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md index 30cf308..6fb5d73 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md @@ -3880,6 +3880,63 @@ does not stand in for a real microphone, reachable provider, audible TTS device, or explicit cloud consent; perform that external-service smoke only when those inputs are intentionally configured. +## Release 19 Windows installer and readiness lane (`2026-07-26`) + +The first-party Windows distribution lane is: + +```powershell +powershell -NoProfile -ExecutionPolicy Bypass -File scripts\Build-HyperTwistWindowsInstaller.ps1 +powershell -NoProfile -ExecutionPolicy Bypass -File scripts\Test-HyperTwistWindowsReadiness.ps1 +powershell -NoProfile -ExecutionPolicy Bypass -File scripts\Test-HyperTwistWindowsInstaller.ps1 -InstallerPath +``` + +`packaging\windows\HyperTwistInstaller.nsi` is the installer definition. +`Build-HyperTwistWindowsInstaller.ps1` validates required runtime, cooked, +browser, OpenXR, and exact solver files; rejects payload reparse points; +requires a valid Microsoft signature on the bundled Visual C++ runtime; +excludes PDB/debug material; writes a complete SHA-256 manifest; checks source +readiness; and deletes incomplete output on failure. + +The installer: + +- requires x64 Windows 10 22H2 or Windows 11 +- requires the selected directory leaf to be `HyperTwist` +- refuses a non-empty directory unless the 64-bit product registry and install + marker both prove existing HyperTwist ownership +- installs or repairs the bundled prerequisite before cleaning an owned prior + payload +- registers Add/Remove Programs and all-users shortcuts +- gives the readiness shortcut a per-user report path, complete manifest + validation, and an interactive close prompt +- never launches the desktop process elevated from the finish page +- removes only marker/registry-owned install payload on uninstall +- preserves `%LOCALAPPDATA%\UnrealHyperTwist\Saved` + +The lifecycle harness must run from an elevated Windows token. It refuses to +replace a pre-existing registered install, uses a unique validation run, +proves rejection of an unowned non-empty root, performs first install and full +hash parity, validates resolved shortcut targets/arguments, runs installed +readiness, launches stock Shipping with isolated user data and direct +diagnostics, performs in-place upgrade and second parity, then uninstalls and +proves user-data preservation plus install/registry/shortcut cleanup. Stop +only executables whose resolved path is inside that run's install root. + +The final Release 19 installer is: + +- delivery: + `C:\Users\Anthracite Ace\Downloads\HyperTwist-Alpha-Test-20260723-R19-Setup.exe` +- size: `851049086` bytes +- SHA-256: + `c31ed6b0b2ae1feb449c27e484d72dc2e07ba220e3cdafa4c3605429d638e0ea` +- payload: `143` files and `1585341236` bytes +- lifecycle: `passed` +- evidence: `docs/generated/alpha_test_20260726_installer` + +This is `isolated-current-host` proof, not fresh-machine proof. The host had no +Windows Sandbox, clean Hyper-V VM, or base VHD. The Alpha is also unsigned. +Trusted Authenticode signing and a genuinely fresh Windows install remain +release gates. + ### Remote-probe hygiene A locally timed-out or interrupted SSH wrapper can leave its encoded diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md index 61920a9..e46a03f 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md @@ -271,6 +271,7 @@ repo. |---|---|---|---| | Public `hypertwist.app` marketing shell | Implemented now | first-party `website/` app + feature registry/roadmap authority | HyperTwist now has a dedicated first-party public web surface for homepage, feature atlas, about, resources, pricing, download, support, legal routes, a dedicated `/getting-started` onboarding surface, and a dedicated `/launch-status` authority surface. This lane is separate from the embedded Unreal browser runtime under `Content/Browser/` and does not claim browser-simulator parity. The same public lane now also serves as a bounded operator/distribution manual through the docs/resources/download/support surfaces, the public feature-atlas route, the dedicated getting-started route, the dedicated launch-status route, and related launch/status surfaces, explaining browser-versus-desktop posture, rollout steps, package proof, simulator-use guidance, and current input/device truth without claiming browser ownership of the native runtime or overclaiming unfinished VR/controller posture. The same package now also carries a first-party external runtime-readiness verifier so deploy-time env and live health posture can be checked outside the dashboard, plus separated local-versus-production env templates whose placeholder values are intentionally rejected until real launch config is in place, bootstrap CI now validates both the frontend and auth-server website commands directly, and the auth server can now auto-serve the built `website/dist` bundle with bounded SPA fallback for same-origin public deployment. Request-level server coverage now also proves that public/app shell delivery does not shadow `/api/*`, `/auth*`, `/health`, or missing asset paths, while the pricing/download/notices routes now surface first-party preview-versus-launch posture from the same bounded launch checklist instead of relying on hidden operator-only status. The shared marketing shell now also carries a compact public-site-status banner across public pages, the homepage keeps a fuller status section, and the shared public launch-status component now consumes live auth-health webhook/runtime truth in addition to release-manifest download readiness so public marketing copy does not claim launch posture from static checkout/download config alone. The auth server now also emits an authoritative `launch` summary on `/api/auth/health`, including required blocker labels, billing product/price-map gates, and current checkout targets, so the public banner and protected dashboard no longer duplicate launch-blocker evaluation client-side. A later same-family continuation then widened that release-manifest authority again to carry public runtime commerce config for operator/studio checkout URLs and live plan-price strings, so pricing, notices, and dashboard launch-readiness surfaces no longer depend only on frontend build-time checkout config. The live website lane now also owns route-aware title/description/canonical/Open-Graph/Twitter metadata for the real `hypertwist.app` marketing surface so deployed public pages no longer remain on a single generic SPA title/description, plus first-party `robots.txt` and `sitemap.xml` assets for the public route set while keeping `/app`, `/login`, and `/register` out of crawler posture. The later shared search-authority hardening pass then moved both assets onto one generated authority seam backed by `website/src/public-route-registry.json`, `website/src/public-search-policy.json`, and first-party script/test ownership, keeping `/app`, `/api/`, `/auth`, `/health`, `/login`, and `/register` aligned without hand-maintained crawler drift. The real `check-runtime-readiness` CLI is now also exercised against the checked-in production example env files, and a spawned `website/server` bootstrap proof now verifies the live same-origin process path from production-shaped env into `/health`, `/api/auth/health`, built-shell serving, and the public anonymous release-manifest posture for the shared desktop release lane. The same verifier now also probes the deployed root-shell marker and can explicitly fail when the public origin is still serving the older placeholder rollout page instead of the first-party website/auth-server lane, while the repo now also carries first-party `website/deploy/` `nginx` plus `systemd` handoff templates, a concrete same-origin public-host cutover guide, a deployment-file renderer that emits resolved operator outputs from real checkout paths, and a manifest-driven bundle renderer that lets one authoritative input own the public origin while emitting validated env plus install artifacts together, with the shared-VPS-safe default upstream moved to `3011` after live host inspection confirmed `3001` is already occupied by FamiliarOS. The same deployment lane now also distinguishes `launch` from `preview` posture so honest missing checkout/download/webhook/release values are accepted only for non-public rehearsal while placeholder strings still fail, `runtime.mode: mixed` plus `public_origin_ready: true` counts as valid preview-host proof, and the staging helper can archive either committed `HEAD` or the live worktree through `--archive-source worktree`. An isolated VPS-local staging proof then confirmed that both the committed HyperTwist website lane and the later preview-tier worktree packet can serve green health, release-manifest, and first-party shell responses on that real shared host behind `3011`, and a later root-owned cutover then replaced the public placeholder site with the live first-party same-origin preview deployment on `https://hypertwist.app`. The package now also ships a bounded root-owned live-deploy helper that stages the committed checkout, uploads the rendered bundle, installs env, rebuilds the site, replaces the live `systemd` plus `nginx` files, and validates the public origin; that helper has already been re-proved idempotently against the live host. The repo now also ships that host-proof flow as a first-party staging helper so future sessions can rerun the temp checkout/build/boot verification path directly before or after root-owned cutover. The website lane now also owns a sanitized generated packaged-validation summary for the Windows higher-dimensional desktop proof, rendered from the checked-in authoritative package report into `website/src/shared/generated/windows-package-validation-summary.json`, and the owned web-surface validation gate now checks that generated proof for freshness before approving the current public/auth/download/browser lane. The repo now also carries checked-in preview-tier frontend/server env examples plus a matching preview same-origin bundle example for the current live `hypertwist.app` posture, while the launch-tier examples still intentionally fail until real checkout, download, source, and webhook values replace their placeholders. The remaining public conversion seams now also stop dead-ending on generic sign-in/support detours when the next lane is already known: the explorer CTA preserves `/register?next=/app`, operator fallback opens the protected downloads lane, studio fallback opens the protected browser-access rollout lane, and launch/download fallback callouts now point directly at protected release, notices, or dashboard follow-through. The narrative/help surfaces then widened again so the public About and Support pages also surface the same live launch-readiness callout and packaged desktop-proof section already used by the stronger public manual routes, keeping maturity language attached to current native release evidence instead of leaving those pages purely abstract. The same public manual continuation now also adds a direct reusable surface-choice guide across the homepage, About, Pricing, Download, and Support pages so operators no longer have to infer from the larger surface matrix alone when they should stay on the public web surface, move into the protected dashboard, or move into the packaged desktop runtime. A later same-family continuation then propagated that same guide through the remaining public feature/docs/resources/changelog/legal pages as well, so the whole public site now preserves one consistent browser-versus-protected-versus-desktop handoff story instead of leaving those lower-traffic surfaces on older implicit wording. A further same-day manual-clarity continuation then added a reusable direct comparison section across the homepage, About, Docs, and Download routes explaining why the website remains necessary, where it is intentionally narrower than the simulator, why the native Unreal runtime stays primary, and how the current keyboard or mouse versus unfinished XR/controller truth should be read. The latest same-family parity follow-up then widened that exact browser-versus-desktop reality section into Pricing and Resources as well, so commercial and reference-heavy public routes no longer rely only on the lighter surface-choice guide when explaining what stays native and where the current XR/controller boundary still begins. The next adjacent parity follow-up then extended the same direct comparison into the public Feature atlas and Support routes as well, so the remaining major public operator-facing surfaces no longer fall back to the lighter guide alone when capability review or recovery work needs the sharper website-versus-native split. A later same-family continuity follow-up then widened the shared release-decision guide into the remaining public legal/notices routes too, so open-source notices, privacy, terms, and shipping/payment now also tell the operator whether the next honest move is protected desktop access, pricing/provisioning, protected browser/account continuity, or notices/source follow-through instead of leaving those pages on the older lighter handoff only. The same browser shell now also has a first-party top-level runtime-recovery boundary, so unexpected React route failures degrade into a HyperTwist-owned retry/return surface that keeps the browser-versus-desktop product truth explicit instead of collapsing into a blank shell. A later same-family quickstart/manual continuation then added a reusable first serious-session guide across homepage, docs, and download so operators can see the real path from release-target choice through desktop-link pairing, first native verification, higher-dimensional verification, and the current XR/controller `No-Go` boundary in one place instead of reconstructing that flow from scattered adjacent sections. That same onboarding/manual packet is now also anchored at a dedicated public `/getting-started` route so the shortest complete browser-to-desktop first-session path is canonical, crawlable, and easier to hand off than the broader site sections alone. The next adjacent launch-authority continuation then did the same for rollout truth through a dedicated public `/launch-status` route, giving preview-versus-launch posture, rollout blockers, packaged proof, and release-reference follow-through one canonical public authority surface instead of leaving that lane distributed across pricing and compact status banners alone. The next same-family shared-auth manual continuation then widened the real browser-account method lineup beyond the auth entry forms and deeper docs surfaces into the homepage, pricing, download, and support routes too, so the public pages where operators actually decide to sign in, buy access, or recover access posture now expose the real provider lineup directly. The latest same-family manual continuation now also adds a shared public route atlas across homepage, docs, and resources so each major public page explains its exact job in the broader operator/distribution manual instead of leaving page purpose mostly implicit in navigation alone. The latest same-family atlas-parity follow-up then widened the public feature-atlas route again so it now also carries the shared runtime control guide directly, keeping the practical classic-control, higher-dimensional-control, native-diagnostics, and explicit XR/controller-boundary truth visible on `/features` instead of forcing advanced readers to leave the atlas for deeper manual routes. The latest same-family responsive-proof expansion then widened browser-level viewport evidence across those newer manual and authority routes too, so `/features`, `/docs`, `/getting-started`, and `/launch-status` now sit inside the owned public responsive validation lane instead of relying only on source-level or unit-level proof. The latest same-family commerce/download manual deepening follow-up then widened `/pricing` and `/download` again so the actual purchase and package routes now carry the installed-software simulator manual, the higher-dimensional family guide, and the current input/device plus runtime-control truth directly instead of leaving that practical product explanation to docs/resources/getting-started alone. A later same-family continuation then completed the shared-section path on the feature atlas itself by routing its higher-dimensional guide, input/device posture, control-profile roster, and runtime-control guide through the same shared helper ownership as the surrounding public routes, and widened responsive public-route proof across `/changelog`, `/open-source-notices`, `/privacy`, `/terms`, and `/shipping-payment`, while a later adjacent auth-entry parity follow-up also brought `/login` into that same owned mobile/tablet matrix, bringing the explicit public-route browser proof to `34` passed Playwright checks while the broader `13`-file/`79`-test website route/auth/release suite and `10`-file/`36`-test auth-server suite stayed green. The latest same-family high-traffic FAQ promotion follow-up then surfaced the shared browser-versus-desktop boundary Q&A directly on homepage and About as well, so operators no longer have to drill into deeper support/manual surfaces just to learn that the current browser lane is intentionally narrower than the desktop simulator and why that narrower posture is still the stronger shipping product shape. The latest same-family legal/distribution continuity follow-up then widened `/open-source-notices` and `/shipping-payment` again so those deeper public routes now also carry the live surface-authority map plus offline operator-manual continuity, while the shipping/payment lane also keeps the shared auth-method lineup visible, preventing the purchase, payment, source, and notices surfaces from degrading back into a materially thinner shell than the main brochure/manual routes. | | Browser-based operator/account dashboard | Implemented now | first-party `website/` app + shared auth/dashboard packet | A protected browser dashboard is now live for operator access, account state, download posture, browser-access boundary explanation, notices review, and bounded billing/entitlement status. It reuses the shared SuperTokens auth posture proven in FamiliarOS and ScriptoriumAI while remaining HyperTwist-specific in product content and boundary claims, the current auth-health surface now truthfully distinguishes configured versus reachable or ready shared-core posture while exposing fallback-active reason instead of hardcoding readiness, and the same dashboard now also surfaces launch-readiness truth for download URLs, checkout links, source/notices URLs, billing-secret/map configuration, and local-versus-public runtime deployment posture. The same shared-auth lane now also carries end-to-end optional provider parity for GitHub, Google, and bounded ORCID sign-in: the auth server owns the ORCID custom-provider path, the same-origin bundle emits matching ORCID frontend/server env truth, the browser client now registers ORCID through the supported third-party recipe provider contract instead of leaving that final step implicit, auth-health reports ORCID readiness alongside the other providers, and the public docs route now exposes the current browser-account method lineup instead of leaving provider truth visible only on the form pages. The surrounding public decision surfaces now also mirror that same provider-lineup truth on homepage, pricing, download, and support, so shared-auth reality is not hidden until operators reach either the auth forms or the deeper manual. The same protected overview now also consumes the server-backed Windows packaged-validation summary that the release-manifest authority exposes, so operators can see current higher-dimensional desktop proof without drilling into the dedicated downloads screen. The adjacent protected `/app/browser-access` and `/app/notices` routes now also reuse the bounded escalation-map and release-follow-through guidance from the public support/manual lane, so sign-in does not degrade operator troubleshooting quality into a thinner shell than the public surface. The protected overview and protected download center now also mirror the same launch-readiness, operator-access, and studio-rollout help lanes as the public support/manual surface, but translate those actions into direct signed-in dashboard, downloads, browser-access, account, notices, pricing, and release-notes targets instead of sending the operator back through anonymous auth detours. A later same-family parity follow-up then added the same blunt browser-versus-desktop reality explanation to the protected dashboard, protected downloads, and browser-access routes as well, so sign-in no longer makes the product boundary or unfinished XR/controller truth less explicit than the public manual. The next adjacent parity follow-up then extended that same compact reality panel into the protected account and notices routes too, so entitlement review and distribution/compliance work no longer soften the browser-support versus native-simulator split after sign-in. The auth entry pages now also render the same browser-versus-desktop truth before sign-in is even complete, so login and register no longer rely only on next-step routing plus later dashboard surfaces to explain what stays native and where the current XR/controller boundary still begins. A later same-family protected-shell continuation then widened signed-in auth truth itself, so the dashboard and account routes now surface the auth method, current provider lineup, shared-auth readiness, runtime origins, cookie/public-origin posture, fallback state, and deployment diagnostics directly instead of collapsing that truth down to a thin stack label. The protected fallback path now also preserves signed-in viewer plan/access posture from local session truth when live manifest authority is temporarily unavailable, while still keeping download authority conservative until the auth server returns. The shared browser auth layer now also has a bounded release-authority reconciliation path that refreshes and persists the local browser session snapshot from fresher manifest viewer truth, while the dashboard and account surfaces expose a compact live-authority sync notice summarizing what changed so operators can see the session catch-up clearly. That same auth layer now also clears stale stored sessions when shared auth is configured and `/api/auth/me` returns `401`, while still preserving the intended offline/local-fallback posture when the account API is merely unreachable or shared auth is not configured. The next adjacent protected-shell launch-authority continuation then added a dedicated signed-in `/app/launch-status` route, so launch posture, rollout blockers, release references, packaged proof, and signed-in next actions no longer collapse back to a single overview panel once account context exists. The follow-through continuation after that then threaded the same signed-in launch-status route back through protected release-action cards, release-reference panels, and the browser-access, download, account, and notices surfaces, so rollout authority remains attached to the surrounding signed-in operator shell instead of becoming an isolated page. Focused frontend coverage now also protects deep-link login redirect preservation, safe `next`-path normalization across auth entry points, fallback/email auth-bootstrap normalization, login/register continuation behavior, public download-gating behavior, protected-route/shell behavior, real lazy-route tree behavior for key public and protected paths, top-level app-bootstrap and SuperTokens-wrapper posture, login/register unhappy-path and OAuth-button behavior, support-topic fallback routing when live checkout is not configured, desktop-link verify-url/dashboard readiness behavior, and explicit `noindex,nofollow` posture on protected/auth browser surfaces. The validation lane now also has a bounded signed test-session harness under `TEST_MODE=testing` that proves `/api/auth/me`, stale-session invalidation, release-authority reconciliation, and `/api/auth/desktop-link` behavior through the live spawned auth-server process without widening production auth posture. A later same-family responsive-browser hardening follow-up then added Playwright viewport proof for `/app`, `/app/launch-status`, `/app/downloads`, `/app/browser-access`, `/app/account`, and `/app/notices`, so the shipped signed-in operator shell now has the same bounded mobile/tablet no-horizontal-overflow proof posture as the current public-route family. | +| Windows desktop installer, readiness, upgrade, and uninstall ownership | Implemented now | first-party Release 19 installer lane | `packaging/windows/HyperTwistInstaller.nsi`, `scripts/Build-HyperTwistWindowsInstaller.ps1`, `scripts/Test-HyperTwistWindowsReadiness.ps1`, and `scripts/Test-HyperTwistWindowsInstaller.ps1` now own the per-machine x64 Windows install lane above the Release 19 Shipping payload. The installer bundles a valid Microsoft-signed Visual C++ runtime, excludes debug/PDB payloads, carries a complete `143`-file SHA-256 manifest, refuses unsafe leaves and unowned non-empty roots, defers owned upgrade cleanup until prerequisites succeed, resolves app/readiness/uninstall/desktop shortcuts, and preserves current-user settings, saves, replays, and diagnostics on uninstall. The final `851049086`-byte artifact passed first install, full payload parity, installed readiness, stock-Shipping launch diagnostics, in-place upgrade, second parity, uninstall, user-data preservation, and cleanup from a path containing spaces. Its SHA-256 is `c31ed6b0b2ae1feb449c27e484d72dc2e07ba220e3cdafa4c3605429d638e0ea`; trusted Authenticode signing and a genuinely fresh Windows VM remain external release gates. | | Desktop download posture and browser-to-desktop pairing | Implemented now | first-party `website/` app + `website/server` desktop-link endpoints | Public download targets, dashboard-side release posture, and short-lived desktop-link token generation/verification are now first-party owned. The current server posture now enforces exact website-origin matching, bounded per-user issuance, one-time token consumption, and billing-backed plan/download entitlement resolution with focused `website/server` tests green on `2026-06-22`, and the verify handshake now returns the same resolved download-entitlement posture the dashboard sees instead of only identity plus plan/role. The same lane now also owns a shared `GET /api/releases/manifest` runtime authority for release version/channel/build/published/file-size/checksum/docs/source metadata, with anonymous callers intentionally denied raw download URLs while entitled session-backed callers receive the configured direct platform URL. That manifest now also carries first-party packaged-validation summary truth for the Windows higher-dimensional desktop lane, so the public `/download` page, the public `/resources` reference page, and the protected `/app/downloads` surface can project real package evidence for the dedicated-family `Magic120Cell` / `MagicCube5D` maps even while launch-tier release URLs remain unconfigured. That website-facing packaged proof is now sourced through a sanitized generated summary rendered from the checked-in authoritative `phase6c_dedicated_family_package_validation_report.json`, and the owned web-surface validation gate now checks that generated summary for freshness before approving the current public/auth/download/browser lane. The public `/download` page now keeps raw download URLs behind the protected dashboard instead of exposing them directly, preserves requested platform continuity through `/app/downloads?platform=...`, and surfaces that requested target again after auth handoff inside the protected release lane. The public download fallbacks now also open exact protected download and dashboard follow-through instead of stopping at a generic anonymous login hop. When live manifest authority is unavailable, the protected fallback path now still reflects the signed-in viewer posture from local session truth while continuing to withhold raw delivery authority. When live manifest authority resolves fresher entitled viewer truth than the local browser auth snapshot, the protected download-center lane now also follows that server-backed viewer posture for the actual download action instead of leaving the operator blocked behind stale local session state. Both the public and protected download surfaces now also carry first-party rollout steps, first-launch desktop setup guidance, browser-to-desktop pairing follow-through, and release/notices/source references so the desktop setup lane is more than a generic link bucket, while the support surface now carries an explicit escalation map separating account, package, runtime, and rollout/compliance problems. The protected dashboard overview and protected account surface now also mirror that same first-launch follow-through, and the protected browser-access/notices routes now mirror the same escalation separation, so post-sign-in operator guidance does not collapse back into a thinner release-only shell. The protected download center now also mirrors the same three help-topic lanes used by the public support/manual surface, but with direct signed-in actions for downloads, dashboard, account, browser access, notices, and release notes so entitled operators can stay inside the protected rollout lane once auth has already succeeded. The dashboard plus public launch-status callouts now consume the same manifest-backed Windows download truth instead of only static frontend config. Actual release URLs remain deployment configuration rather than hardcoded product truth. | | Paddle-ready pricing and billing webhook seam | Implemented now | first-party `website/` app + `website/server` billing endpoint | The public pricing surface now exists with plan structure, checkout-link configuration seams, and the same `/api/billing/paddle/webhook` endpoint family used by the broader product website lane. The current server now verifies `Paddle-Signature` against `PADDLE_WEBHOOK_SECRET` using the documented raw-body HMAC flow, persists a bounded first-party billing state file, and applies verified Paddle events into account/download entitlement state that the browser dashboard consumes, with focused `website/server` tests green on `2026-06-22`. The shared `GET /api/releases/manifest` authority now also carries public runtime commerce config for operator/studio checkout URLs and live plan-price strings, allowing the pricing page to switch from frontend build-time checkout assumptions to auth-server runtime truth when those values are configured. The fallback plan CTAs now also use exact protected release/browser-access targets when live checkout URLs are absent, instead of routing operators back into a vague public support detour. A spawned live-process proof now also verifies that a real signed webhook updates processed-event health and persisted billing state through the actual auth-server runtime, not only helper-level store tests, and transaction events no longer leak their id into stored `subscriptionId` state. Production checkout URLs, secret management, and broader operator/admin billing workflows remain deployment/application tasks, not shipped-code omissions. | | Public open-source notices and corresponding-source surface | Implemented now | first-party `website/` app + `HYPERTWIST_MPL_DISTRIBUTION_PLACEMENT_CHECKLIST_2026-05-25.md` | HyperTwist now has a stable public `Open Source Notices` route linked from pricing, download, and footer surfaces, satisfying the requirement that public distribution surfaces expose notice and corresponding-source guidance when shipped builds contain `MPL`-covered material. The exact public corresponding-source URL still must be configured before external launch. | diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/PRD.md b/docs/v6_5_deep_manual_pack/HyperTwist/PRD.md index a9520ad..0e402f6 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/PRD.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/PRD.md @@ -224,6 +224,14 @@ public pages, manuals, and operator closeouts: the `167`-entry ZIP reopened cleanly, and both the validation-archive and exact Downloads executables produced fresh semantic `launch-menu-renderable` frames +- the `2026-07-26` installer continuation then closed per-machine Windows + installation, readiness, in-place upgrade, and uninstall ownership above + that exact Release 19 payload: the final installer carries a complete + `143`-file SHA-256 manifest, bundles a valid Microsoft-signed Visual C++ + runtime, refuses unsafe or unowned non-empty targets, preserves user data, + and passed install-to-uninstall lifecycle validation from a path containing + spaces; this remains isolated-current-host evidence rather than a fabricated + fresh-machine or trusted-code-signing claim - Paddle checkout URLs, public Windows release URLs, production auth/runtime configuration, and corresponding-source/notices URLs remain deployment configuration gates, not hardcoded product facts diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md index 7ddfda9..0ca5b80 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md @@ -579,14 +579,24 @@ Current consolidated milestone snapshot: `-DisablePlugins=MovieRenderPipeline` plus `-SkipCookingEditorContent`, aggregate validation JSON `result: passed`, both packaged smoke maps green, and `BuildCookRun time: 1381.37 s` +- Release 19 Windows installer/uninstaller and isolated-host prerequisite + validation are now closed through first-party NSIS, PowerShell build, + readiness, and lifecycle ownership. The final `851049086`-byte installer + carries `143` manifest-owned payload files, bundles a valid Microsoft-signed + Visual C++ runtime, refuses unsafe or unowned non-empty install roots, + preserves user data on uninstall, and passed install, full hash parity, + stock-Shipping launch, in-place upgrade, second parity, uninstall, and + cleanup from a path containing spaces. Durable evidence lives under + `docs/generated/alpha_test_20260726_installer` - `Phase 10A` structural-only validation deprecation is now closed through `221` retained `Phase J` files carrying `443` deprecated validation identifiers and `443` disabled flags with `0` remaining live `HyperTwist.Validation.*` or `HyperTwist.FirstParty.Validation.*` names - no implementation roadmap gate remains inside the current `Phase 1` through `Phase 10` packet set; the remaining Alpha/release-engineering checks are the - operator-connected dashboard visual plus installer/uninstaller, code-signing, - clean-machine prerequisites, entitlement, and update-channel rollout proof, + operator-connected dashboard visual plus trusted code signing, genuinely + fresh-machine proof, entitlement-backed public release URLs, and + update-channel/rollback rollout proof, while the remaining optional product-widening branches are the alternative full-browser client path and any later dedicated native `MagicTile` interaction/runtime replacement work rather than new diff --git a/packaging/windows/HyperTwistInstaller.nsi b/packaging/windows/HyperTwistInstaller.nsi new file mode 100644 index 0000000..4d39d75 --- /dev/null +++ b/packaging/windows/HyperTwistInstaller.nsi @@ -0,0 +1,331 @@ +Unicode true + +!include "MUI2.nsh" +!include "LogicLib.nsh" +!include "WinVer.nsh" +!include "x64.nsh" +!include "FileFunc.nsh" + +!ifndef PRODUCT_VERSION + !error "PRODUCT_VERSION is required." +!endif +!ifndef DISPLAY_VERSION + !error "DISPLAY_VERSION is required." +!endif +!ifndef RELEASE_ID + !error "RELEASE_ID is required." +!endif +!ifndef OUTPUT_FILE + !error "OUTPUT_FILE is required." +!endif +!ifndef PAYLOAD_ROOT + !error "PAYLOAD_ROOT is required." +!endif +!ifndef PAYLOAD_MANIFEST + !error "PAYLOAD_MANIFEST is required." +!endif +!ifndef READINESS_SCRIPT + !error "READINESS_SCRIPT is required." +!endif +!ifndef VC_REDIST_PATH + !error "VC_REDIST_PATH is required." +!endif +!ifndef INSTALLER_ICON + !error "INSTALLER_ICON is required." +!endif +!ifndef PAYLOAD_SIZE_KB + !error "PAYLOAD_SIZE_KB is required." +!endif + +!define PRODUCT_NAME "HyperTwist" +!define PRODUCT_CHANNEL "Alpha" +!define PRODUCT_PUBLISHER "HyperTwist" +!define PRODUCT_WEBSITE "https://hypertwist.app" +!define PRODUCT_SUPPORT "https://hypertwist.app/support" +!define PRODUCT_REG_KEY "Software\HyperTwist\HyperTwist" +!define PRODUCT_UNINSTALL_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\HyperTwist" +!define INSTALL_MARKER "HyperTwist.install-marker" + +Name "${PRODUCT_NAME} ${PRODUCT_CHANNEL}" +Caption "${PRODUCT_NAME} ${DISPLAY_VERSION} Setup" +BrandingText "${PRODUCT_NAME} | Explore dimensions beyond the ordinary" +OutFile "${OUTPUT_FILE}" +InstallDir "$PROGRAMFILES64\HyperTwist" +RequestExecutionLevel admin + +CRCCheck force +SetDatablockOptimize on +SetCompressor /SOLID lzma +SetCompressorDictSize 64 +ShowInstDetails show +ShowUninstDetails show +ManifestDPIAware true +ManifestSupportedOS all + +Icon "${INSTALLER_ICON}" +UninstallIcon "${INSTALLER_ICON}" + +VIProductVersion "${PRODUCT_VERSION}" +VIAddVersionKey /LANG=1033 "ProductName" "${PRODUCT_NAME}" +VIAddVersionKey /LANG=1033 "ProductVersion" "${DISPLAY_VERSION}" +VIAddVersionKey /LANG=1033 "CompanyName" "${PRODUCT_PUBLISHER}" +VIAddVersionKey /LANG=1033 "FileDescription" "${PRODUCT_NAME} ${PRODUCT_CHANNEL} Installer" +VIAddVersionKey /LANG=1033 "FileVersion" "${PRODUCT_VERSION}" +VIAddVersionKey /LANG=1033 "LegalCopyright" "Copyright (c) HyperTwist" +VIAddVersionKey /LANG=1033 "OriginalFilename" "HyperTwist-Setup.exe" + +!define MUI_ABORTWARNING +!define MUI_ICON "${INSTALLER_ICON}" +!define MUI_UNICON "${INSTALLER_ICON}" +!define MUI_FINISHPAGE_LINK "Visit hypertwist.app" +!define MUI_FINISHPAGE_LINK_LOCATION "${PRODUCT_WEBSITE}" + +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_COMPONENTS +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH + +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES +!insertmacro MUI_UNPAGE_FINISH + +!insertmacro MUI_LANGUAGE "English" + +Function .onInit + SetRegView 64 + + ${IfNot} ${RunningX64} + MessageBox MB_OK|MB_ICONSTOP \ + "HyperTwist requires 64-bit Windows." \ + /SD IDOK + SetErrorLevel 10 + Quit + ${EndIf} + + ${IfNot} ${AtLeastWin10} + MessageBox MB_OK|MB_ICONSTOP \ + "HyperTwist requires Windows 10 version 22H2 or Windows 11." \ + /SD IDOK + SetErrorLevel 11 + Quit + ${EndIf} + + ReadRegStr $1 HKLM "${PRODUCT_REG_KEY}" "InstallLocation" + StrCmp $1 "" install_location_ready + StrCpy $INSTDIR $1 + +install_location_ready: + ReadRegStr $0 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" "CurrentBuildNumber" + IntCmp $0 19045 windows_build_ok windows_build_too_old windows_build_ok + +windows_build_too_old: + MessageBox MB_OK|MB_ICONSTOP \ + "This Windows build is too old for the supported HyperTwist Alpha baseline. Update to Windows 10 22H2 or Windows 11 and try again." \ + /SD IDOK + SetErrorLevel 12 + Quit + +windows_build_ok: +FunctionEnd + +Function CleanOwnedPayloadForUpgrade + ReadRegStr $0 HKLM "${PRODUCT_REG_KEY}" "InstallLocation" + StrCmp $0 $INSTDIR registry_owner_confirmed cleanup_done + +registry_owner_confirmed: + IfFileExists "$INSTDIR\Installer\${INSTALL_MARKER}" 0 cleanup_done + + DetailPrint "Removing the previous HyperTwist payload before upgrade." + RMDir /r "$INSTDIR\Content" + RMDir /r "$INSTDIR\Engine" + RMDir /r "$INSTDIR\UnrealHyperTwist" + RMDir /r "$INSTDIR\Installer" + Delete "$INSTDIR\Manifest_DebugFiles_Win64.txt" + Delete "$INSTDIR\Manifest_NonUFSFiles_Win64.txt" + Delete "$INSTDIR\Manifest_UFSFiles_Win64.txt" + Delete "$INSTDIR\UnrealHyperTwist.exe" + Delete "$INSTDIR\Uninstall HyperTwist.exe" + +cleanup_done: +FunctionEnd + +Function AssertSafeInstallLocation + ${GetFileName} "$INSTDIR" $0 + StrCmp $0 "HyperTwist" install_leaf_safe install_location_refused + +install_leaf_safe: + ReadRegStr $0 HKLM "${PRODUCT_REG_KEY}" "InstallLocation" + StrCmp $0 $INSTDIR install_registry_owner_candidate install_unowned_scan + +install_registry_owner_candidate: + IfFileExists "$INSTDIR\Installer\${INSTALL_MARKER}" install_location_safe install_unowned_scan + +install_unowned_scan: + FindFirst $0 $1 "$INSTDIR\*" + +install_unowned_scan_next: + StrCmp $1 "" install_unowned_empty + StrCmp $1 "." install_unowned_continue + StrCmp $1 ".." install_unowned_continue + FindClose $0 + MessageBox MB_OK|MB_ICONSTOP \ + "The selected HyperTwist folder is not empty and is not owned by an existing registered HyperTwist installation. Choose an empty folder named HyperTwist." \ + /SD IDOK + SetErrorLevel 14 + Abort + +install_unowned_continue: + FindNext $0 $1 + Goto install_unowned_scan_next + +install_unowned_empty: + FindClose $0 + Goto install_location_safe + +install_location_refused: + MessageBox MB_OK|MB_ICONSTOP \ + "The installation folder itself must be named HyperTwist, for example C:\Program Files\HyperTwist or D:\Games\HyperTwist." \ + /SD IDOK + SetErrorLevel 13 + Abort + +install_location_safe: +FunctionEnd + +Section -ValidateInstallLocation + Call AssertSafeInstallLocation +SectionEnd + +Section -MicrosoftRuntime + SetOutPath "$PLUGINSDIR" + File /oname=vc_redist.x64.exe "${VC_REDIST_PATH}" + DetailPrint "Installing or repairing the Microsoft Visual C++ 2015-2022 x64 runtime." + ExecWait '"$PLUGINSDIR\vc_redist.x64.exe" /install /quiet /norestart' $0 + + ${If} $0 == 3010 + DetailPrint "The Microsoft runtime requested a restart." + SetRebootFlag true + ${ElseIf} $0 == 1638 + DetailPrint "A compatible or newer Microsoft runtime is already installed." + ${ElseIf} $0 != 0 + MessageBox MB_OK|MB_ICONSTOP \ + "Microsoft Visual C++ runtime setup failed with exit code $0. HyperTwist was not installed." \ + /SD IDOK + SetErrorLevel 20 + Abort + ${EndIf} +SectionEnd + +Section "!HyperTwist application" SEC_CORE + SectionIn RO + AddSize ${PAYLOAD_SIZE_KB} + + ; Do not disturb a working prior install until every bundled prerequisite succeeds. + Call CleanOwnedPayloadForUpgrade + + SetOutPath "$INSTDIR" + File /r \ + /x "*.pdb" \ + /x "Manifest_DebugFiles_Win64.txt" \ + "${PAYLOAD_ROOT}\*" + + SetOutPath "$INSTDIR\Installer" + File /oname=payload-manifest.json "${PAYLOAD_MANIFEST}" + File /oname=Test-HyperTwistWindowsReadiness.ps1 "${READINESS_SCRIPT}" + + FileOpen $0 "$INSTDIR\Installer\${INSTALL_MARKER}" w + FileWrite $0 "product=HyperTwist$\r$\n" + FileWrite $0 "release=${RELEASE_ID}$\r$\n" + FileWrite $0 "version=${DISPLAY_VERSION}$\r$\n" + FileClose $0 + + WriteUninstaller "$INSTDIR\Uninstall HyperTwist.exe" + + WriteRegStr HKLM "${PRODUCT_REG_KEY}" "InstallLocation" "$INSTDIR" + WriteRegStr HKLM "${PRODUCT_REG_KEY}" "ReleaseId" "${RELEASE_ID}" + WriteRegStr HKLM "${PRODUCT_REG_KEY}" "DisplayVersion" "${DISPLAY_VERSION}" + + WriteRegStr HKLM "${PRODUCT_UNINSTALL_KEY}" "DisplayName" "${PRODUCT_NAME} ${PRODUCT_CHANNEL}" + WriteRegStr HKLM "${PRODUCT_UNINSTALL_KEY}" "DisplayVersion" "${DISPLAY_VERSION}" + WriteRegStr HKLM "${PRODUCT_UNINSTALL_KEY}" "Publisher" "${PRODUCT_PUBLISHER}" + WriteRegStr HKLM "${PRODUCT_UNINSTALL_KEY}" "InstallLocation" "$INSTDIR" + WriteRegStr HKLM "${PRODUCT_UNINSTALL_KEY}" "DisplayIcon" "$INSTDIR\UnrealHyperTwist.exe,0" + WriteRegStr HKLM "${PRODUCT_UNINSTALL_KEY}" "URLInfoAbout" "${PRODUCT_WEBSITE}" + WriteRegStr HKLM "${PRODUCT_UNINSTALL_KEY}" "HelpLink" "${PRODUCT_SUPPORT}" + WriteRegStr HKLM "${PRODUCT_UNINSTALL_KEY}" "UninstallString" "$\"$INSTDIR\Uninstall HyperTwist.exe$\"" + WriteRegStr HKLM "${PRODUCT_UNINSTALL_KEY}" "QuietUninstallString" "$\"$INSTDIR\Uninstall HyperTwist.exe$\" /S" + WriteRegDWORD HKLM "${PRODUCT_UNINSTALL_KEY}" "EstimatedSize" ${PAYLOAD_SIZE_KB} + WriteRegDWORD HKLM "${PRODUCT_UNINSTALL_KEY}" "NoModify" 1 + WriteRegDWORD HKLM "${PRODUCT_UNINSTALL_KEY}" "NoRepair" 1 + + SetShellVarContext all + CreateDirectory "$SMPROGRAMS\HyperTwist" + CreateShortCut "$SMPROGRAMS\HyperTwist\HyperTwist.lnk" \ + "$INSTDIR\UnrealHyperTwist.exe" "" "$INSTDIR\UnrealHyperTwist.exe" 0 + CreateShortCut "$SMPROGRAMS\HyperTwist\System Readiness Report.lnk" \ + "$SYSDIR\WindowsPowerShell\v1.0\powershell.exe" \ + "-NoProfile -ExecutionPolicy Bypass -File $\"$INSTDIR\Installer\Test-HyperTwistWindowsReadiness.ps1$\" -PayloadRoot $\"$INSTDIR$\" -TargetInstallRoot $\"$INSTDIR$\" -ExpectedPayloadManifestPath $\"$INSTDIR\Installer\payload-manifest.json$\" -WaitForUser" \ + "$INSTDIR\UnrealHyperTwist.exe" 0 + CreateShortCut "$SMPROGRAMS\HyperTwist\Uninstall HyperTwist.lnk" \ + "$INSTDIR\Uninstall HyperTwist.exe" +SectionEnd + +Section "Desktop shortcut" SEC_DESKTOP + SetShellVarContext all + CreateShortCut "$DESKTOP\HyperTwist.lnk" \ + "$INSTDIR\UnrealHyperTwist.exe" "" "$INSTDIR\UnrealHyperTwist.exe" 0 +SectionEnd + +LangString DESC_SEC_CORE ${LANG_ENGLISH} \ + "The complete HyperTwist desktop Alpha, puzzle runtimes, learning studio, browser shell, and local solver data." +LangString DESC_SEC_DESKTOP ${LANG_ENGLISH} \ + "Create an all-users HyperTwist shortcut on the desktop." + +!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${SEC_CORE} $(DESC_SEC_CORE) + !insertmacro MUI_DESCRIPTION_TEXT ${SEC_DESKTOP} $(DESC_SEC_DESKTOP) +!insertmacro MUI_FUNCTION_DESCRIPTION_END + +Section "Uninstall" + SetRegView 64 + ${GetFileName} "$INSTDIR" $1 + StrCmp $1 "HyperTwist" uninstall_leaf_safe uninstall_refused + +uninstall_leaf_safe: + ReadRegStr $0 HKLM "${PRODUCT_REG_KEY}" "InstallLocation" + StrCmp $0 $INSTDIR uninstall_registry_owned uninstall_refused + +uninstall_registry_owned: + IfFileExists "$INSTDIR\Installer\${INSTALL_MARKER}" uninstall_owned uninstall_refused + +uninstall_refused: + MessageBox MB_OK|MB_ICONSTOP \ + "HyperTwist registry ownership or the install marker is missing. The uninstaller will not delete an unverified directory." \ + /SD IDOK + SetErrorLevel 30 + Abort + +uninstall_owned: + SetShellVarContext all + Delete "$DESKTOP\HyperTwist.lnk" + RMDir /r "$SMPROGRAMS\HyperTwist" + + DeleteRegKey HKLM "${PRODUCT_UNINSTALL_KEY}" + DeleteRegKey HKLM "${PRODUCT_REG_KEY}" + DeleteRegKey /ifempty HKLM "Software\HyperTwist" + + RMDir /r "$INSTDIR\Content" + RMDir /r "$INSTDIR\Engine" + RMDir /r "$INSTDIR\UnrealHyperTwist" + Delete "$INSTDIR\Manifest_DebugFiles_Win64.txt" + Delete "$INSTDIR\Manifest_NonUFSFiles_Win64.txt" + Delete "$INSTDIR\Manifest_UFSFiles_Win64.txt" + Delete "$INSTDIR\UnrealHyperTwist.exe" + Delete "$INSTDIR\Installer\payload-manifest.json" + Delete "$INSTDIR\Installer\Test-HyperTwistWindowsReadiness.ps1" + Delete "$INSTDIR\Installer\${INSTALL_MARKER}" + RMDir "$INSTDIR\Installer" + Delete "$INSTDIR\Uninstall HyperTwist.exe" + RMDir "$INSTDIR" +SectionEnd diff --git a/scripts/Build-HyperTwistWindowsInstaller.ps1 b/scripts/Build-HyperTwistWindowsInstaller.ps1 new file mode 100644 index 0000000..fb558a4 --- /dev/null +++ b/scripts/Build-HyperTwistWindowsInstaller.ps1 @@ -0,0 +1,464 @@ +param( + [string]$PackageRoot = 'C:\HyperTwist_worktrees\phase10validate_packaged_alpha_release19_shipping_20260723', + [string]$OutputDirectory = 'C:\HyperTwist_worktrees\installer-release19-20260726', + [string]$InstallerFileName = 'HyperTwist-Alpha-Test-20260723-R19-Setup.exe', + [string]$ProductVersion = '0.19.0.0', + [string]$DisplayVersion = 'Alpha R19', + [string]$ReleaseId = '20260723-R19', + [string]$MakensisPath = 'C:\Program Files (x86)\NSIS\makensis.exe', + [string]$VcRedistPath = '', + [string]$ReportPath = '', + [switch]$KeepBuildContext +) + +$ErrorActionPreference = 'Stop' + +$ExpectedSolverTableSizeBytes = 676207080 +$ExpectedSolverTableSha256 = 'dc6de3d909a37afe2ba7f1f978543a13eae215bd20d954d66813524a8ca20034' + +function Write-Utf8JsonFile { + param( + [Parameter(Mandatory = $true)] + [string]$Path, + [Parameter(Mandatory = $true)] + [object]$Value + ) + + $ParentPath = Split-Path -Parent $Path + if (-not [string]::IsNullOrWhiteSpace($ParentPath)) + { + New-Item -ItemType Directory -Force -Path $ParentPath | Out-Null + } + + $Json = $Value | ConvertTo-Json -Depth 12 + $Utf8NoBom = New-Object System.Text.UTF8Encoding($false) + [System.IO.File]::WriteAllText($Path, $Json, $Utf8NoBom) +} + +function Resolve-WindowsPayloadRoot { + param( + [Parameter(Mandatory = $true)] + [string]$RootPath + ) + + $ResolvedRootPath = (Resolve-Path -LiteralPath $RootPath).Path + $WindowsCandidate = Join-Path $ResolvedRootPath 'Windows' + if (Test-Path -LiteralPath (Join-Path $WindowsCandidate 'UnrealHyperTwist.exe') -PathType Leaf) + { + return $WindowsCandidate + } + + if (Test-Path -LiteralPath (Join-Path $ResolvedRootPath 'UnrealHyperTwist.exe') -PathType Leaf) + { + return $ResolvedRootPath + } + + throw "No packaged Windows payload was found beneath '$RootPath'." +} + +function Get-NormalizedRelativePath { + param( + [Parameter(Mandatory = $true)] + [string]$RootPath, + [Parameter(Mandatory = $true)] + [string]$FilePath + ) + + $RootPrefix = $RootPath.TrimEnd('\', '/') + [System.IO.Path]::DirectorySeparatorChar + $ResolvedFilePath = [System.IO.Path]::GetFullPath($FilePath) + if (-not $ResolvedFilePath.StartsWith( + $RootPrefix, + [System.StringComparison]::OrdinalIgnoreCase)) + { + throw "File '$ResolvedFilePath' is outside payload root '$RootPath'." + } + + return $ResolvedFilePath.Substring($RootPrefix.Length).Replace('\', '/') +} + +function Assert-RequiredPayloadFile { + param( + [Parameter(Mandatory = $true)] + [string]$PayloadRoot, + [Parameter(Mandatory = $true)] + [string]$RelativePath, + [Int64]$ExpectedSizeBytes = -1, + [string]$ExpectedSha256 = '' + ) + + $RequiredPath = Join-Path $PayloadRoot $RelativePath + if (-not (Test-Path -LiteralPath $RequiredPath -PathType Leaf)) + { + throw "Required payload file '$RelativePath' was not found." + } + + $RequiredItem = Get-Item -LiteralPath $RequiredPath + if ($RequiredItem.Length -le 0) + { + throw "Required payload file '$RelativePath' is empty." + } + if ($ExpectedSizeBytes -ge 0 -and $RequiredItem.Length -ne $ExpectedSizeBytes) + { + throw "Required payload file '$RelativePath' has an unexpected size." + } + + $ActualSha256 = (Get-FileHash -LiteralPath $RequiredPath -Algorithm SHA256).Hash.ToLowerInvariant() + if (-not [string]::IsNullOrWhiteSpace($ExpectedSha256) ` + -and $ActualSha256 -ne $ExpectedSha256.ToLowerInvariant()) + { + throw "Required payload file '$RelativePath' has an unexpected SHA-256." + } +} + +function Resolve-VcRedistPath { + param( + [string]$RequestedPath + ) + + if (-not [string]::IsNullOrWhiteSpace($RequestedPath)) + { + if (-not (Test-Path -LiteralPath $RequestedPath -PathType Leaf)) + { + throw "Requested VC++ redistributable '$RequestedPath' was not found." + } + + return (Resolve-Path -LiteralPath $RequestedPath).Path + } + + $CandidateRoots = @( + 'C:\Program Files\Epic Games\UE_5.7\Engine\Extras\Redist', + 'C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Redist', + 'C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Redist' + ) + $Candidates = @( + foreach ($CandidateRoot in $CandidateRoots) + { + if (Test-Path -LiteralPath $CandidateRoot) + { + Get-ChildItem -LiteralPath $CandidateRoot ` + -Filter 'vc_redist.x64.exe' ` + -File ` + -Recurse ` + -ErrorAction SilentlyContinue + } + } + ) + $Candidate = $Candidates | + Sort-Object { [version]$_.VersionInfo.FileVersion } -Descending | + Select-Object -First 1 + if ($null -eq $Candidate) + { + throw 'No Microsoft Visual C++ 2015-2022 x64 redistributable was found.' + } + + return $Candidate.FullName +} + +if (-not (Test-Path -LiteralPath $PackageRoot -PathType Container)) +{ + throw "Package root '$PackageRoot' was not found." +} +if (-not (Test-Path -LiteralPath $MakensisPath -PathType Leaf)) +{ + throw "NSIS compiler '$MakensisPath' was not found." +} +try +{ + $ParsedProductVersion = [version]$ProductVersion +} +catch +{ + throw "ProductVersion '$ProductVersion' is not a valid numeric Windows file version." +} +if ($ParsedProductVersion.Build -lt 0 -or $ParsedProductVersion.Revision -lt 0) +{ + throw 'ProductVersion must contain exactly four numeric components.' +} +if ($InstallerFileName -ne [System.IO.Path]::GetFileName($InstallerFileName) ` + -or [System.IO.Path]::GetExtension($InstallerFileName) -ne '.exe') +{ + throw "InstallerFileName '$InstallerFileName' must be a leaf .exe filename." +} +foreach ($MetadataValue in @($DisplayVersion, $ReleaseId)) +{ + if ([string]::IsNullOrWhiteSpace($MetadataValue) ` + -or $MetadataValue -notmatch '^[A-Za-z0-9][A-Za-z0-9 ._-]{0,63}$') + { + throw "Installer metadata value '$MetadataValue' contains unsupported characters or length." + } +} + +$ResolvedPackageRoot = (Resolve-Path -LiteralPath $PackageRoot).Path +$ResolvedPayloadRoot = Resolve-WindowsPayloadRoot -RootPath $ResolvedPackageRoot +$ResolvedOutputDirectory = [System.IO.Path]::GetFullPath($OutputDirectory) +$ResolvedInstallerPath = Join-Path $ResolvedOutputDirectory $InstallerFileName +$PayloadRootPrefix = $ResolvedPayloadRoot.TrimEnd('\') + '\' +if ($ResolvedInstallerPath.StartsWith( + $PayloadRootPrefix, + [System.StringComparison]::OrdinalIgnoreCase)) +{ + throw 'Installer output must not be written inside its source payload.' +} +$RepositoryRoot = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot '..')).Path +$NsiScriptPath = Join-Path $RepositoryRoot 'packaging\windows\HyperTwistInstaller.nsi' +$ReadinessScriptPath = Join-Path $RepositoryRoot 'scripts\Test-HyperTwistWindowsReadiness.ps1' +if (-not (Test-Path -LiteralPath $NsiScriptPath -PathType Leaf)) +{ + throw "Installer definition '$NsiScriptPath' was not found." +} +if (-not (Test-Path -LiteralPath $ReadinessScriptPath -PathType Leaf)) +{ + throw "Readiness script '$ReadinessScriptPath' was not found." +} + +$ResolvedVcRedistPath = Resolve-VcRedistPath -RequestedPath $VcRedistPath +$VcRedistSignature = Get-AuthenticodeSignature -LiteralPath $ResolvedVcRedistPath +if ($VcRedistSignature.Status -ne [System.Management.Automation.SignatureStatus]::Valid) +{ + throw "VC++ redistributable '$ResolvedVcRedistPath' does not have a valid Authenticode signature." +} + +$RequiredRelativePaths = @( + 'UnrealHyperTwist.exe', + 'UnrealHyperTwist\Binaries\Win64\UnrealHyperTwist-Win64-Shipping.exe', + 'UnrealHyperTwist\Binaries\Win64\tbb12.dll', + 'UnrealHyperTwist\Binaries\Win64\tbbmalloc.dll', + 'UnrealHyperTwist\Content\Paks\UnrealHyperTwist-Windows.pak', + 'UnrealHyperTwist\Content\Paks\UnrealHyperTwist-Windows.utoc', + 'UnrealHyperTwist\Content\Paks\UnrealHyperTwist-Windows.ucas', + 'Content\Browser\index.html', + 'Content\Browser\src\browser-runtime-bootstrap.js', + 'Content\Browser\src\browser-spatial-runtime-fallback.js', + 'Content\Browser\dist\browser-spatial-runtime.js' +) +foreach ($RequiredRelativePath in $RequiredRelativePaths) +{ + Assert-RequiredPayloadFile ` + -PayloadRoot $ResolvedPayloadRoot ` + -RelativePath $RequiredRelativePath +} +Assert-RequiredPayloadFile ` + -PayloadRoot $ResolvedPayloadRoot ` + -RelativePath 'UnrealHyperTwist\Saved\twophase-ht.tbl' ` + -ExpectedSizeBytes $ExpectedSolverTableSizeBytes ` + -ExpectedSha256 $ExpectedSolverTableSha256 + +$ReparsePoints = @( + Get-ChildItem -LiteralPath $ResolvedPayloadRoot -Force -Recurse | + Where-Object { + $_.Attributes -band [System.IO.FileAttributes]::ReparsePoint + } +) +if ($ReparsePoints.Count -gt 0) +{ + throw ( + "The payload contains reparse points, which are not permitted in an installer source: " + + (($ReparsePoints | Select-Object -ExpandProperty FullName) -join ', ') + ) +} + +New-Item -ItemType Directory -Force -Path $ResolvedOutputDirectory | Out-Null +$ValidationDirectory = Join-Path $ResolvedOutputDirectory 'validation' +$BuildContextDirectory = Join-Path $ResolvedOutputDirectory 'build-context' +New-Item -ItemType Directory -Force -Path $ValidationDirectory | Out-Null +if (Test-Path -LiteralPath $BuildContextDirectory) +{ + Remove-Item -LiteralPath $BuildContextDirectory -Recurse -Force +} +New-Item -ItemType Directory -Force -Path $BuildContextDirectory | Out-Null + +if ([string]::IsNullOrWhiteSpace($ReportPath)) +{ + $ReportPath = Join-Path $ValidationDirectory 'windows-installer-build-report.json' +} +$PayloadManifestPath = Join-Path $ValidationDirectory 'windows-installer-payload-manifest.json' +$SourceReadinessReportPath = Join-Path $ValidationDirectory 'windows-installer-source-readiness-report.json' +$MakensisLogPath = Join-Path $ValidationDirectory 'makensis.log' +$InstallerIconPath = Join-Path $BuildContextDirectory 'HyperTwist.ico' + +$BuildReport = [ordered]@{ + reportVersion = 'ht-windows-installer-build/v1' + generatedAtUtc = [DateTime]::UtcNow.ToString('o') + result = 'failed' + releaseId = $ReleaseId + displayVersion = $DisplayVersion + productVersion = $ProductVersion + packageRoot = $ResolvedPackageRoot + payloadRoot = $ResolvedPayloadRoot + installerPath = $ResolvedInstallerPath + payloadManifestPath = $PayloadManifestPath + sourceReadinessReportPath = $SourceReadinessReportPath + makensisPath = (Resolve-Path -LiteralPath $MakensisPath).Path + makensisLogPath = $MakensisLogPath + vcRedist = $null + excludedPatterns = @('*.pdb', 'Manifest_DebugFiles_Win64.txt') + payloadFileCount = 0 + payloadSizeBytes = 0 + installerSizeBytes = 0 + installerSha256 = $null + installerSignatureStatus = $null + incompleteInstallerRemoved = $false + error = $null +} + +try +{ + $VcRedistItem = Get-Item -LiteralPath $ResolvedVcRedistPath + $BuildReport.vcRedist = [pscustomobject][ordered]@{ + path = $ResolvedVcRedistPath + version = [string]$VcRedistItem.VersionInfo.FileVersion + sizeBytes = [Int64]$VcRedistItem.Length + sha256 = (Get-FileHash -LiteralPath $ResolvedVcRedistPath -Algorithm SHA256).Hash.ToLowerInvariant() + signatureStatus = $VcRedistSignature.Status.ToString() + signer = [string]$VcRedistSignature.SignerCertificate.Subject + } + + $PayloadFiles = @( + Get-ChildItem -LiteralPath $ResolvedPayloadRoot -Force -File -Recurse | + Where-Object { + $_.Extension -ne '.pdb' ` + -and $_.Name -ne 'Manifest_DebugFiles_Win64.txt' + } | + Sort-Object FullName + ) + $PayloadManifestEntries = @( + foreach ($PayloadFile in $PayloadFiles) + { + [pscustomobject][ordered]@{ + relativePath = Get-NormalizedRelativePath ` + -RootPath $ResolvedPayloadRoot ` + -FilePath $PayloadFile.FullName + sizeBytes = [Int64]$PayloadFile.Length + sha256 = (Get-FileHash -LiteralPath $PayloadFile.FullName -Algorithm SHA256).Hash.ToLowerInvariant() + } + } + ) + $PayloadSizeBytes = [Int64](($PayloadFiles | Measure-Object Length -Sum).Sum) + $PayloadManifest = [pscustomobject][ordered]@{ + manifestVersion = 'ht-windows-installer-payload/v1' + generatedAtUtc = [DateTime]::UtcNow.ToString('o') + releaseId = $ReleaseId + sourcePayloadRoot = $ResolvedPayloadRoot + excludedPatterns = @('*.pdb', 'Manifest_DebugFiles_Win64.txt') + fileCount = $PayloadManifestEntries.Count + totalSizeBytes = $PayloadSizeBytes + files = $PayloadManifestEntries + } + Write-Utf8JsonFile -Path $PayloadManifestPath -Value $PayloadManifest + $BuildReport.payloadFileCount = $PayloadManifestEntries.Count + $BuildReport.payloadSizeBytes = $PayloadSizeBytes + + $ReadinessReport = & $ReadinessScriptPath ` + -PayloadRoot $ResolvedPayloadRoot ` + -TargetInstallRoot (Join-Path ([Environment]::GetFolderPath('ProgramFiles')) 'HyperTwist') ` + -ExpectedPayloadManifestPath $PayloadManifestPath ` + -ReportPath $SourceReadinessReportPath ` + -VerifyPayloadHashes ` + -PassThru + if ($ReadinessReport.result -eq 'blocked') + { + throw 'Source payload or Windows host readiness is blocked.' + } + + Add-Type -AssemblyName System.Drawing + $PayloadExecutablePath = Join-Path $ResolvedPayloadRoot 'UnrealHyperTwist.exe' + $InstallerIcon = [System.Drawing.Icon]::ExtractAssociatedIcon($PayloadExecutablePath) + if ($null -eq $InstallerIcon) + { + throw "No application icon could be extracted from '$PayloadExecutablePath'." + } + $IconStream = [System.IO.File]::Open( + $InstallerIconPath, + [System.IO.FileMode]::Create, + [System.IO.FileAccess]::Write, + [System.IO.FileShare]::None + ) + try + { + $InstallerIcon.Save($IconStream) + } + finally + { + $IconStream.Dispose() + $InstallerIcon.Dispose() + } + + if (Test-Path -LiteralPath $ResolvedInstallerPath) + { + Remove-Item -LiteralPath $ResolvedInstallerPath -Force + } + + $PayloadSizeKilobytes = [Math]::Ceiling($PayloadSizeBytes / 1KB) + $MakensisArguments = @( + '/V4', + "/DPRODUCT_VERSION=$ProductVersion", + "/DDISPLAY_VERSION=$DisplayVersion", + "/DRELEASE_ID=$ReleaseId", + "/DOUTPUT_FILE=$ResolvedInstallerPath", + "/DPAYLOAD_ROOT=$ResolvedPayloadRoot", + "/DPAYLOAD_MANIFEST=$PayloadManifestPath", + "/DREADINESS_SCRIPT=$ReadinessScriptPath", + "/DVC_REDIST_PATH=$ResolvedVcRedistPath", + "/DINSTALLER_ICON=$InstallerIconPath", + "/DPAYLOAD_SIZE_KB=$PayloadSizeKilobytes", + $NsiScriptPath + ) + + $MakensisOutput = @( + & $MakensisPath @MakensisArguments 2>&1 | + ForEach-Object { + $Line = $_.ToString() + [Console]::Out.WriteLine($Line) + $Line + } + ) + $MakensisExitCode = $LASTEXITCODE + [System.IO.File]::WriteAllLines( + $MakensisLogPath, + $MakensisOutput, + (New-Object System.Text.UTF8Encoding($false)) + ) + if ($MakensisExitCode -ne 0) + { + throw "NSIS compilation failed with exit code $MakensisExitCode." + } + if (-not (Test-Path -LiteralPath $ResolvedInstallerPath -PathType Leaf)) + { + throw "NSIS did not create '$ResolvedInstallerPath'." + } + + $InstallerItem = Get-Item -LiteralPath $ResolvedInstallerPath + if ($InstallerItem.Length -le 0) + { + throw 'The generated installer is empty.' + } + $InstallerSignature = Get-AuthenticodeSignature -LiteralPath $ResolvedInstallerPath + $BuildReport.installerSizeBytes = [Int64]$InstallerItem.Length + $BuildReport.installerSha256 = (Get-FileHash -LiteralPath $ResolvedInstallerPath -Algorithm SHA256).Hash.ToLowerInvariant() + $BuildReport.installerSignatureStatus = $InstallerSignature.Status.ToString() + $BuildReport.result = 'passed' +} +catch +{ + $BuildReport.error = $_.Exception.Message + throw +} +finally +{ + if ($BuildReport.result -ne 'passed' ` + -and (Test-Path -LiteralPath $ResolvedInstallerPath -PathType Leaf)) + { + Remove-Item -LiteralPath $ResolvedInstallerPath -Force + $BuildReport.incompleteInstallerRemoved = $true + } + Write-Utf8JsonFile -Path $ReportPath -Value $BuildReport + if (-not $KeepBuildContext -and (Test-Path -LiteralPath $BuildContextDirectory)) + { + Remove-Item -LiteralPath $BuildContextDirectory -Recurse -Force + } +} + +[Console]::Out.WriteLine('HyperTwist Windows installer built successfully.') +[Console]::Out.WriteLine("Installer: $ResolvedInstallerPath") +[Console]::Out.WriteLine("SHA-256: $($BuildReport.installerSha256)") +[Console]::Out.WriteLine("Report: $([System.IO.Path]::GetFullPath($ReportPath))") diff --git a/scripts/Test-HyperTwistWindowsInstaller.ps1 b/scripts/Test-HyperTwistWindowsInstaller.ps1 new file mode 100644 index 0000000..7420b63 --- /dev/null +++ b/scripts/Test-HyperTwistWindowsInstaller.ps1 @@ -0,0 +1,707 @@ +param( + [Parameter(Mandatory = $true)] + [string]$InstallerPath, + [string]$ValidationRoot = 'C:\HyperTwistInstallerValidation', + [string]$ReportPath = '', + [int]$LaunchSmokeSeconds = 20, + [switch]$KeepInstalled +) + +$ErrorActionPreference = 'Stop' + +function Write-Utf8JsonFile { + param( + [Parameter(Mandatory = $true)] + [string]$Path, + [Parameter(Mandatory = $true)] + [object]$Value + ) + + $ParentPath = Split-Path -Parent $Path + if (-not [string]::IsNullOrWhiteSpace($ParentPath)) + { + New-Item -ItemType Directory -Force -Path $ParentPath | Out-Null + } + + $Json = $Value | ConvertTo-Json -Depth 12 + $Utf8NoBom = New-Object System.Text.UTF8Encoding($false) + [System.IO.File]::WriteAllText($Path, $Json, $Utf8NoBom) +} + +function Invoke-InstallerProcess { + param( + [Parameter(Mandatory = $true)] + [string]$ExecutablePath, + [Parameter(Mandatory = $true)] + [string[]]$Arguments, + [Parameter(Mandatory = $true)] + [string]$Label + ) + + $Process = Start-Process ` + -FilePath $ExecutablePath ` + -ArgumentList $Arguments ` + -PassThru ` + -Wait + if ($Process.ExitCode -notin @(0, 3010)) + { + throw "$Label failed with exit code $($Process.ExitCode)." + } + + return [int]$Process.ExitCode +} + +function Invoke-InstallerProcessForExitCode { + param( + [Parameter(Mandatory = $true)] + [string]$ExecutablePath, + [Parameter(Mandatory = $true)] + [string[]]$Arguments + ) + + $Process = Start-Process ` + -FilePath $ExecutablePath ` + -ArgumentList $Arguments ` + -PassThru ` + -Wait + return [int]$Process.ExitCode +} + +function Resolve-PayloadRelativeFilePath { + param( + [Parameter(Mandatory = $true)] + [string]$RootPath, + [Parameter(Mandatory = $true)] + [string]$RelativePath + ) + + if ([string]::IsNullOrWhiteSpace($RelativePath)) + { + throw 'A payload-relative path cannot be empty.' + } + + $WindowsRelativePath = $RelativePath.Replace('/', '\') + if ([System.IO.Path]::IsPathRooted($WindowsRelativePath)) + { + throw "Payload-relative path '$RelativePath' must not be rooted." + } + + $ResolvedRootPath = [System.IO.Path]::GetFullPath($RootPath) + $RootPrefix = $ResolvedRootPath.TrimEnd('\', '/') + [System.IO.Path]::DirectorySeparatorChar + $ResolvedFilePath = [System.IO.Path]::GetFullPath( + (Join-Path $ResolvedRootPath $WindowsRelativePath) + ) + if (-not $ResolvedFilePath.StartsWith( + $RootPrefix, + [System.StringComparison]::OrdinalIgnoreCase)) + { + throw "Payload-relative path '$RelativePath' escapes root '$RootPath'." + } + + return $ResolvedFilePath +} + +function Test-PayloadManifest { + param( + [Parameter(Mandatory = $true)] + [string]$InstallRoot, + [Parameter(Mandatory = $true)] + [string]$ManifestPath + ) + + $Manifest = Get-Content -LiteralPath $ManifestPath -Raw | ConvertFrom-Json + $Failures = New-Object System.Collections.ArrayList + $ExpectedPaths = @{} + foreach ($Entry in @($Manifest.files)) + { + $EntryRelativePath = [string]$Entry.relativePath + $EntryPathKey = $EntryRelativePath.Replace('\', '/').ToLowerInvariant() + if ($ExpectedPaths.ContainsKey($EntryPathKey)) + { + [void]$Failures.Add("duplicate:$EntryRelativePath") + continue + } + $ExpectedPaths[$EntryPathKey] = $true + + try + { + $InstalledPath = Resolve-PayloadRelativeFilePath ` + -RootPath $InstallRoot ` + -RelativePath $EntryRelativePath + } + catch + { + [void]$Failures.Add("invalid-path:$EntryRelativePath") + continue + } + + if (-not (Test-Path -LiteralPath $InstalledPath -PathType Leaf)) + { + [void]$Failures.Add("missing:$EntryRelativePath") + continue + } + + $InstalledItem = Get-Item -LiteralPath $InstalledPath + if ($InstalledItem.Length -ne [Int64]$Entry.sizeBytes) + { + [void]$Failures.Add("size:$EntryRelativePath") + continue + } + + $InstalledHash = (Get-FileHash -LiteralPath $InstalledPath -Algorithm SHA256).Hash.ToLowerInvariant() + if ($InstalledHash -ne ([string]$Entry.sha256).ToLowerInvariant()) + { + [void]$Failures.Add("sha256:$EntryRelativePath") + } + } + + $AllowedInstallerPaths = @{} + @( + 'Installer/payload-manifest.json', + 'Installer/Test-HyperTwistWindowsReadiness.ps1', + 'Installer/HyperTwist.install-marker', + 'Uninstall HyperTwist.exe' + ) | ForEach-Object { $AllowedInstallerPaths[$_.ToLowerInvariant()] = $true } + $InstallRootPrefix = [System.IO.Path]::GetFullPath($InstallRoot).TrimEnd('\') + '\' + $UnexpectedFiles = @( + Get-ChildItem -LiteralPath $InstallRoot -File -Force -Recurse | + ForEach-Object { + $RelativePath = $_.FullName.Substring($InstallRootPrefix.Length).Replace('\', '/') + $RelativePathKey = $RelativePath.ToLowerInvariant() + if (-not $ExpectedPaths.ContainsKey($RelativePathKey) ` + -and -not $AllowedInstallerPaths.ContainsKey($RelativePathKey)) + { + $RelativePath + } + } + ) + $UnexpectedPdbFiles = @( + Get-ChildItem -LiteralPath $InstallRoot -Filter '*.pdb' -File -Recurse -ErrorAction SilentlyContinue + ) + return [pscustomobject][ordered]@{ + passed = $Failures.Count -eq 0 ` + -and $UnexpectedPdbFiles.Count -eq 0 ` + -and $UnexpectedFiles.Count -eq 0 + manifestEntryCount = @($Manifest.files).Count + failureCount = $Failures.Count + failures = @($Failures) + unexpectedFiles = $UnexpectedFiles + unexpectedPdbFiles = @($UnexpectedPdbFiles | Select-Object -ExpandProperty FullName) + } +} + +function Get-OwnedProcessRecords { + param( + [Parameter(Mandatory = $true)] + [string]$InstallRoot + ) + + $InstallPrefix = $InstallRoot.TrimEnd('\') + '\' + return @( + Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | + Where-Object { + -not [string]::IsNullOrWhiteSpace([string]$_.ExecutablePath) ` + -and ([string]$_.ExecutablePath).StartsWith( + $InstallPrefix, + [System.StringComparison]::OrdinalIgnoreCase) + } + ) +} + +function Stop-OwnedProcesses { + param( + [Parameter(Mandatory = $true)] + [string]$InstallRoot + ) + + $StoppedProcessIds = New-Object 'System.Collections.Generic.HashSet[int]' + for ($Attempt = 0; $Attempt -lt 8; $Attempt += 1) + { + $OwnedProcesses = @(Get-OwnedProcessRecords -InstallRoot $InstallRoot) + if ($OwnedProcesses.Count -eq 0) + { + break + } + + foreach ($OwnedProcess in $OwnedProcesses) + { + Stop-Process -Id ([int]$OwnedProcess.ProcessId) -Force -ErrorAction SilentlyContinue + [void]$StoppedProcessIds.Add([int]$OwnedProcess.ProcessId) + } + Start-Sleep -Milliseconds 250 + } + + return @($StoppedProcessIds) +} + +function Invoke-InstalledLaunchSmoke { + param( + [Parameter(Mandatory = $true)] + [string]$InstallRoot, + [Parameter(Mandatory = $true)] + [string]$EvidenceDirectory, + [int]$SmokeSeconds + ) + + $ExecutablePath = Join-Path $InstallRoot 'UnrealHyperTwist.exe' + $RuntimeLogPath = Join-Path $EvidenceDirectory 'installed-runtime.log' + $DiagnosticsPath = Join-Path $EvidenceDirectory 'installed-runtime.hyperdiagnostics.log' + $UserDirectory = Join-Path $EvidenceDirectory 'isolated-user' + New-Item -ItemType Directory -Force -Path $UserDirectory | Out-Null + + $Arguments = @( + '-NullRHI', + '-nosound', + '-windowed', + '-ResX=1280', + '-ResY=720', + '-log', + '-FORCELOGFLUSH', + "-abslog=$RuntimeLogPath", + "-HyperTwistDiagnosticsLog=`"$DiagnosticsPath`"", + "-UserDir=`"$UserDirectory`"" + ) + $StartedAtUtc = [DateTime]::UtcNow + $LaunchProcess = Start-Process ` + -FilePath $ExecutablePath ` + -ArgumentList $Arguments ` + -PassThru + Start-Sleep -Seconds $SmokeSeconds + + $OwnedProcesses = @(Get-OwnedProcessRecords -InstallRoot $InstallRoot) + $OwnedProcessIds = @($OwnedProcesses | Select-Object -ExpandProperty ProcessId) + $ListeningEndpoints = @( + if ($OwnedProcessIds.Count -gt 0) + { + Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue | + Where-Object { $OwnedProcessIds -contains [int]$_.OwningProcess } | + ForEach-Object { + [pscustomobject][ordered]@{ + processId = [int]$_.OwningProcess + localAddress = [string]$_.LocalAddress + localPort = [int]$_.LocalPort + } + } + } + ) + $FatalPatterns = @( + 'Fatal error:', + 'LowLevelFatalError', + 'Assertion failed:', + 'Critical error:', + 'Unhandled Exception:', + 'StaticShutdownAfterError' + ) + $RuntimeLines = @( + if (Test-Path -LiteralPath $RuntimeLogPath) + { + Get-Content -LiteralPath $RuntimeLogPath + } + ) + $DiagnosticsLines = @( + if (Test-Path -LiteralPath $DiagnosticsPath) + { + Get-Content -LiteralPath $DiagnosticsPath + } + ) + $FatalLines = @( + @($RuntimeLines + $DiagnosticsLines) | + Where-Object { + $Line = $_.ToString() + @($FatalPatterns | Where-Object { $Line -like "*$_*" }).Count -gt 0 + } + ) + $Initialized = @( + $DiagnosticsLines | + Where-Object { + $_.ToString().IndexOf( + '[Process] HyperTwist runtime module initialized.', + [System.StringComparison]::OrdinalIgnoreCase + ) -ge 0 + } + ).Count -gt 0 + + $StoppedProcessIds = @(Stop-OwnedProcesses -InstallRoot $InstallRoot) + $Passed = $OwnedProcessIds.Count -gt 0 ` + -and (Test-Path -LiteralPath $DiagnosticsPath -PathType Leaf) ` + -and $Initialized ` + -and $FatalLines.Count -eq 0 ` + -and @($ListeningEndpoints | Where-Object { $_.localPort -eq 1985 }).Count -eq 0 + return [pscustomobject][ordered]@{ + passed = $Passed + startedAtUtc = $StartedAtUtc.ToString('o') + launcherProcessId = [int]$LaunchProcess.Id + ownedProcessIds = $OwnedProcessIds + stoppedProcessIds = $StoppedProcessIds + runtimeLogPath = $RuntimeLogPath + diagnosticsPath = $DiagnosticsPath + diagnosticsInitialized = $Initialized + fatalLines = $FatalLines + listeningEndpoints = $ListeningEndpoints + } +} + +function Get-ShortcutEvidence { + param( + [Parameter(Mandatory = $true)] + [string]$Path + ) + + if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) + { + return [pscustomobject][ordered]@{ + path = $Path + exists = $false + targetPath = $null + arguments = $null + } + } + + $Shell = New-Object -ComObject WScript.Shell + $Shortcut = $null + try + { + $Shortcut = $Shell.CreateShortcut($Path) + return [pscustomobject][ordered]@{ + path = $Path + exists = $true + targetPath = [string]$Shortcut.TargetPath + arguments = [string]$Shortcut.Arguments + } + } + finally + { + if ($null -ne $Shortcut) + { + [void][System.Runtime.InteropServices.Marshal]::FinalReleaseComObject($Shortcut) + } + [void][System.Runtime.InteropServices.Marshal]::FinalReleaseComObject($Shell) + } +} + +$CurrentIdentity = [Security.Principal.WindowsIdentity]::GetCurrent() +$CurrentPrincipal = New-Object Security.Principal.WindowsPrincipal($CurrentIdentity) +if (-not $CurrentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) +{ + throw 'Installer lifecycle validation requires an elevated Windows session.' +} +if (-not (Test-Path -LiteralPath $InstallerPath -PathType Leaf)) +{ + throw "Installer '$InstallerPath' was not found." +} + +$ResolvedInstallerPath = (Resolve-Path -LiteralPath $InstallerPath).Path +$ResolvedValidationRoot = [System.IO.Path]::GetFullPath($ValidationRoot) +$RunId = [DateTime]::UtcNow.ToString('yyyyMMddTHHmmssZ') +$RunDirectory = Join-Path $ResolvedValidationRoot $RunId +$InstallRoot = Join-Path $RunDirectory 'HyperTwist' +$EvidenceDirectory = Join-Path $RunDirectory 'evidence' +New-Item -ItemType Directory -Force -Path $EvidenceDirectory | Out-Null +if ([string]::IsNullOrWhiteSpace($ReportPath)) +{ + $ReportPath = Join-Path $EvidenceDirectory 'windows-installer-lifecycle-report.json' +} + +$RepositoryRoot = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot '..')).Path +$ReadinessScriptPath = Join-Path $RepositoryRoot 'scripts\Test-HyperTwistWindowsReadiness.ps1' +$UninstallRegistryPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\HyperTwist' +$ProductRegistryPath = 'HKLM:\SOFTWARE\HyperTwist\HyperTwist' +$CommonProgramsPath = [Environment]::GetFolderPath('CommonPrograms') +$CommonDesktopPath = [Environment]::GetFolderPath('CommonDesktopDirectory') +$StartMenuDirectory = Join-Path $CommonProgramsPath 'HyperTwist' +$DesktopShortcutPath = Join-Path $CommonDesktopPath 'HyperTwist.lnk' + +if (Test-Path -LiteralPath $UninstallRegistryPath) +{ + throw 'A registered HyperTwist installation already exists; validation will not replace it.' +} + +$InstallerItem = Get-Item -LiteralPath $ResolvedInstallerPath +$Report = [ordered]@{ + reportVersion = 'ht-windows-installer-lifecycle/v1' + generatedAtUtc = [DateTime]::UtcNow.ToString('o') + result = 'failed' + validationClass = 'isolated-current-host' + cleanMachineAvailable = $false + cleanMachineBoundary = 'No Windows Sandbox, clean Hyper-V VM, or base VHD was available on the validation host.' + installerPath = $ResolvedInstallerPath + installerSizeBytes = [Int64]$InstallerItem.Length + installerSha256 = (Get-FileHash -LiteralPath $ResolvedInstallerPath -Algorithm SHA256).Hash.ToLowerInvariant() + installerSignatureStatus = (Get-AuthenticodeSignature -LiteralPath $ResolvedInstallerPath).Status.ToString() + installRoot = $InstallRoot + unownedNonEmptyRefusal = $null + firstInstallExitCode = $null + upgradeInstallExitCode = $null + payloadParityAfterFirstInstall = $null + payloadParityAfterUpgrade = $null + readiness = $null + launchSmoke = $null + uninstallExitCode = $null + registrationVerified = $false + shortcutsVerified = $false + shortcutDetails = $null + userDataPreserved = $false + installRootRemoved = $false + registrationRemoved = $false + shortcutsRemoved = $false + cleanupCompleted = $false + error = $null +} + +$UninstallerPath = Join-Path $InstallRoot 'Uninstall HyperTwist.exe' +$SentinelDirectory = Join-Path $env:LOCALAPPDATA 'UnrealHyperTwist\Saved\InstallerValidation' +$SentinelPath = Join-Path $SentinelDirectory "$RunId.txt" +$Installed = $false + +try +{ + New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null + $UnownedSentinelPath = Join-Path $InstallRoot 'unrelated-owner-sentinel.txt' + [System.IO.File]::WriteAllText( + $UnownedSentinelPath, + "Unowned install-root refusal sentinel $RunId", + (New-Object System.Text.UTF8Encoding($false)) + ) + $UnownedRefusalExitCode = Invoke-InstallerProcessForExitCode ` + -ExecutablePath $ResolvedInstallerPath ` + -Arguments @('/S', "/D=$InstallRoot") + if ($UnownedRefusalExitCode -eq 0) + { + $Installed = Test-Path -LiteralPath $UninstallerPath -PathType Leaf + } + $Report.unownedNonEmptyRefusal = [pscustomobject][ordered]@{ + passed = $UnownedRefusalExitCode -eq 14 ` + -and (Test-Path -LiteralPath $UnownedSentinelPath -PathType Leaf) ` + -and -not (Test-Path -LiteralPath $UninstallRegistryPath) ` + -and -not (Test-Path -LiteralPath $ProductRegistryPath) + exitCode = $UnownedRefusalExitCode + expectedExitCode = 14 + sentinelPreserved = Test-Path -LiteralPath $UnownedSentinelPath -PathType Leaf + registrationAbsent = ` + -not (Test-Path -LiteralPath $UninstallRegistryPath) ` + -and -not (Test-Path -LiteralPath $ProductRegistryPath) + } + if (-not $Report.unownedNonEmptyRefusal.passed) + { + throw 'The installer did not safely refuse an unowned non-empty installation root.' + } + Remove-Item -LiteralPath $InstallRoot -Recurse -Force + + $Report.firstInstallExitCode = Invoke-InstallerProcess ` + -ExecutablePath $ResolvedInstallerPath ` + -Arguments @('/S', "/D=$InstallRoot") ` + -Label 'Silent installer' + $Installed = $true + + $ManifestPath = Join-Path $InstallRoot 'Installer\payload-manifest.json' + if (-not (Test-Path -LiteralPath $ManifestPath -PathType Leaf)) + { + throw 'The installed payload manifest is missing.' + } + $Report.payloadParityAfterFirstInstall = Test-PayloadManifest ` + -InstallRoot $InstallRoot ` + -ManifestPath $ManifestPath + if (-not $Report.payloadParityAfterFirstInstall.passed) + { + throw 'Installed payload parity failed after first install.' + } + + $RegisteredProduct = Get-ItemProperty -LiteralPath $UninstallRegistryPath + $RegisteredOwner = Get-ItemProperty -LiteralPath $ProductRegistryPath + $Report.registrationVerified = ` + [System.IO.Path]::GetFullPath([string]$RegisteredProduct.InstallLocation) -eq $InstallRoot ` + -and [System.IO.Path]::GetFullPath([string]$RegisteredOwner.InstallLocation) -eq $InstallRoot + if (-not $Report.registrationVerified) + { + throw 'Add/Remove Programs or product registration does not match the isolated install root.' + } + + $ExpectedApplicationPath = Join-Path $InstallRoot 'UnrealHyperTwist.exe' + $ExpectedReadinessScriptPath = Join-Path $InstallRoot 'Installer\Test-HyperTwistWindowsReadiness.ps1' + $ExpectedManifestPath = Join-Path $InstallRoot 'Installer\payload-manifest.json' + $ExpectedUninstallerPath = Join-Path $InstallRoot 'Uninstall HyperTwist.exe' + $ApplicationShortcut = Get-ShortcutEvidence ` + -Path (Join-Path $StartMenuDirectory 'HyperTwist.lnk') + $ReadinessShortcut = Get-ShortcutEvidence ` + -Path (Join-Path $StartMenuDirectory 'System Readiness Report.lnk') + $UninstallShortcut = Get-ShortcutEvidence ` + -Path (Join-Path $StartMenuDirectory 'Uninstall HyperTwist.lnk') + $DesktopShortcut = Get-ShortcutEvidence -Path $DesktopShortcutPath + $Report.shortcutDetails = [pscustomobject][ordered]@{ + application = $ApplicationShortcut + readiness = $ReadinessShortcut + uninstall = $UninstallShortcut + desktop = $DesktopShortcut + } + $Report.shortcutsVerified = ` + $ApplicationShortcut.exists ` + -and $ApplicationShortcut.targetPath -eq $ExpectedApplicationPath ` + -and $ReadinessShortcut.exists ` + -and $ReadinessShortcut.targetPath.EndsWith( + '\WindowsPowerShell\v1.0\powershell.exe', + [System.StringComparison]::OrdinalIgnoreCase + ) ` + -and $ReadinessShortcut.arguments.Contains($ExpectedReadinessScriptPath) ` + -and $ReadinessShortcut.arguments.Contains($ExpectedManifestPath) ` + -and $ReadinessShortcut.arguments.Contains('-WaitForUser') ` + -and $UninstallShortcut.exists ` + -and $UninstallShortcut.targetPath -eq $ExpectedUninstallerPath ` + -and $DesktopShortcut.exists ` + -and $DesktopShortcut.targetPath -eq $ExpectedApplicationPath + if (-not $Report.shortcutsVerified) + { + throw 'One or more installer-managed shortcuts are missing or have incorrect targets or arguments.' + } + + $ReadinessReportPath = Join-Path $EvidenceDirectory 'installed-readiness-report.json' + $Report.readiness = & $ReadinessScriptPath ` + -PayloadRoot $InstallRoot ` + -TargetInstallRoot $InstallRoot ` + -ExpectedPayloadManifestPath $ManifestPath ` + -ReportPath $ReadinessReportPath ` + -VerifyPayloadHashes ` + -PassThru + if ($Report.readiness.result -eq 'blocked') + { + throw 'Installed-host readiness validation is blocked.' + } + + New-Item -ItemType Directory -Force -Path $SentinelDirectory | Out-Null + [System.IO.File]::WriteAllText( + $SentinelPath, + "HyperTwist installer validation sentinel $RunId", + (New-Object System.Text.UTF8Encoding($false)) + ) + + $Report.launchSmoke = Invoke-InstalledLaunchSmoke ` + -InstallRoot $InstallRoot ` + -EvidenceDirectory $EvidenceDirectory ` + -SmokeSeconds $LaunchSmokeSeconds + if (-not $Report.launchSmoke.passed) + { + throw 'Installed executable launch smoke failed.' + } + + $Report.upgradeInstallExitCode = Invoke-InstallerProcess ` + -ExecutablePath $ResolvedInstallerPath ` + -Arguments @('/S', "/D=$InstallRoot") ` + -Label 'Silent in-place upgrade' + $ManifestPath = Join-Path $InstallRoot 'Installer\payload-manifest.json' + $Report.payloadParityAfterUpgrade = Test-PayloadManifest ` + -InstallRoot $InstallRoot ` + -ManifestPath $ManifestPath + if (-not $Report.payloadParityAfterUpgrade.passed) + { + throw 'Installed payload parity failed after in-place upgrade.' + } + + if (-not $KeepInstalled) + { + $Report.uninstallExitCode = Invoke-InstallerProcess ` + -ExecutablePath $UninstallerPath ` + -Arguments @('/S') ` + -Label 'Silent uninstaller' + $Installed = $false + Start-Sleep -Seconds 3 + + $Report.userDataPreserved = Test-Path -LiteralPath $SentinelPath -PathType Leaf + $Report.installRootRemoved = -not (Test-Path -LiteralPath $InstallRoot) + $Report.registrationRemoved = ` + -not (Test-Path -LiteralPath $UninstallRegistryPath) ` + -and -not (Test-Path -LiteralPath $ProductRegistryPath) + $Report.shortcutsRemoved = ` + -not (Test-Path -LiteralPath $StartMenuDirectory) ` + -and -not (Test-Path -LiteralPath $DesktopShortcutPath) + + if (-not $Report.userDataPreserved) + { + throw 'Default uninstall removed current-user data unexpectedly.' + } + if (-not $Report.installRootRemoved) + { + throw 'The isolated install root remains after uninstall.' + } + if (-not $Report.registrationRemoved) + { + throw 'Installer registry state remains after uninstall.' + } + if (-not $Report.shortcutsRemoved) + { + throw 'Installer-managed shortcuts remain after uninstall.' + } + } + + $Report.cleanupCompleted = -not $KeepInstalled + $Report.result = 'passed' +} +catch +{ + $Report.error = $_.Exception.Message + throw +} +finally +{ + [void](Stop-OwnedProcesses -InstallRoot $InstallRoot) + if ($Installed -and -not $KeepInstalled -and (Test-Path -LiteralPath $UninstallerPath -PathType Leaf)) + { + try + { + [void](Invoke-InstallerProcess ` + -ExecutablePath $UninstallerPath ` + -Arguments @('/S') ` + -Label 'Failure-cleanup uninstaller') + Start-Sleep -Seconds 3 + $Installed = $false + } + catch + { + if ([string]::IsNullOrWhiteSpace([string]$Report.error)) + { + $Report.error = $_.Exception.Message + } + else + { + $Report.error = "$($Report.error) | Cleanup: $($_.Exception.Message)" + } + } + } + + if (-not $KeepInstalled ` + -and (Test-Path -LiteralPath $InstallRoot) ` + -and -not (Test-Path -LiteralPath $UninstallRegistryPath) ` + -and -not (Test-Path -LiteralPath $ProductRegistryPath)) + { + Remove-Item -LiteralPath $InstallRoot -Recurse -Force + } + + if (Test-Path -LiteralPath $SentinelPath) + { + Remove-Item -LiteralPath $SentinelPath -Force + } + if ((Test-Path -LiteralPath $SentinelDirectory) ` + -and @(Get-ChildItem -LiteralPath $SentinelDirectory -Force).Count -eq 0) + { + Remove-Item -LiteralPath $SentinelDirectory -Force + } + + if (-not $KeepInstalled) + { + $Report.installRootRemoved = -not (Test-Path -LiteralPath $InstallRoot) + $Report.registrationRemoved = ` + -not (Test-Path -LiteralPath $UninstallRegistryPath) ` + -and -not (Test-Path -LiteralPath $ProductRegistryPath) + $Report.shortcutsRemoved = ` + -not (Test-Path -LiteralPath $StartMenuDirectory) ` + -and -not (Test-Path -LiteralPath $DesktopShortcutPath) + $Report.cleanupCompleted = ` + -not $Installed ` + -and $Report.installRootRemoved ` + -and $Report.registrationRemoved ` + -and $Report.shortcutsRemoved + } + + Write-Utf8JsonFile -Path $ReportPath -Value $Report +} + +[Console]::Out.WriteLine('HyperTwist Windows installer lifecycle validation passed.') +[Console]::Out.WriteLine("Report: $([System.IO.Path]::GetFullPath($ReportPath))") diff --git a/scripts/Test-HyperTwistWindowsReadiness.ps1 b/scripts/Test-HyperTwistWindowsReadiness.ps1 new file mode 100644 index 0000000..4bdcdbf --- /dev/null +++ b/scripts/Test-HyperTwistWindowsReadiness.ps1 @@ -0,0 +1,617 @@ +param( + [string]$PayloadRoot = '', + [string]$TargetInstallRoot = '', + [string]$ExpectedPayloadManifestPath = '', + [string]$ReportPath = '', + [Int64]$MinimumMemoryBytes = 8589934592, + [Int64]$MinimumFreeSpaceBytes = 4294967296, + [int]$MinimumWindowsBuild = 19045, + [switch]$VerifyPayloadHashes, + [switch]$WaitForUser, + [switch]$PassThru +) + +$ErrorActionPreference = 'Stop' + +$ExpectedSolverTableSizeBytes = 676207080 +$ExpectedSolverTableSha256 = 'dc6de3d909a37afe2ba7f1f978543a13eae215bd20d954d66813524a8ca20034' +$RequiredVcRuntimeVersion = [version]'14.44.35211.0' + +function Write-Utf8JsonFile { + param( + [Parameter(Mandatory = $true)] + [string]$Path, + [Parameter(Mandatory = $true)] + [object]$Value + ) + + $ParentPath = Split-Path -Parent $Path + if (-not [string]::IsNullOrWhiteSpace($ParentPath)) + { + New-Item -ItemType Directory -Force -Path $ParentPath | Out-Null + } + + $Json = $Value | ConvertTo-Json -Depth 12 + $Utf8NoBom = New-Object System.Text.UTF8Encoding($false) + [System.IO.File]::WriteAllText($Path, $Json, $Utf8NoBom) +} + +function Get-NormalizedRelativePath { + param( + [Parameter(Mandatory = $true)] + [string]$RootPath, + [Parameter(Mandatory = $true)] + [string]$FilePath + ) + + $RootPrefix = $RootPath.TrimEnd('\', '/') + [System.IO.Path]::DirectorySeparatorChar + $ResolvedFilePath = [System.IO.Path]::GetFullPath($FilePath) + if (-not $ResolvedFilePath.StartsWith( + $RootPrefix, + [System.StringComparison]::OrdinalIgnoreCase)) + { + throw "File '$ResolvedFilePath' is outside payload root '$RootPath'." + } + + return $ResolvedFilePath.Substring($RootPrefix.Length).Replace('\', '/') +} + +function Resolve-PayloadRelativeFilePath { + param( + [Parameter(Mandatory = $true)] + [string]$RootPath, + [Parameter(Mandatory = $true)] + [string]$RelativePath + ) + + if ([string]::IsNullOrWhiteSpace($RelativePath)) + { + throw 'A payload-relative path cannot be empty.' + } + + $WindowsRelativePath = $RelativePath.Replace('/', '\') + if ([System.IO.Path]::IsPathRooted($WindowsRelativePath)) + { + throw "Payload-relative path '$RelativePath' must not be rooted." + } + + $ResolvedRootPath = [System.IO.Path]::GetFullPath($RootPath) + $RootPrefix = $ResolvedRootPath.TrimEnd('\', '/') + [System.IO.Path]::DirectorySeparatorChar + $ResolvedFilePath = [System.IO.Path]::GetFullPath( + (Join-Path $ResolvedRootPath $WindowsRelativePath) + ) + if (-not $ResolvedFilePath.StartsWith( + $RootPrefix, + [System.StringComparison]::OrdinalIgnoreCase)) + { + throw "Payload-relative path '$RelativePath' escapes root '$RootPath'." + } + + return $ResolvedFilePath +} + +function Get-ExistingPathRoot { + param( + [Parameter(Mandatory = $true)] + [string]$Path + ) + + $CandidatePath = [System.IO.Path]::GetFullPath($Path) + while (-not (Test-Path -LiteralPath $CandidatePath)) + { + $ParentPath = Split-Path -Parent $CandidatePath + if ([string]::IsNullOrWhiteSpace($ParentPath) -or $ParentPath -eq $CandidatePath) + { + throw "No existing parent could be resolved for '$Path'." + } + + $CandidatePath = $ParentPath + } + + return $CandidatePath +} + +function Get-VcRuntimeEvidence { + $RegistryPaths = @( + 'HKLM:\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64', + 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\VisualStudio\14.0\VC\Runtimes\x64' + ) + + foreach ($RegistryPath in $RegistryPaths) + { + if (-not (Test-Path -LiteralPath $RegistryPath)) + { + continue + } + + $Runtime = Get-ItemProperty -LiteralPath $RegistryPath + $VersionText = ([string]$Runtime.Version).TrimStart('v') + try + { + $ParsedVersion = [version]$VersionText + } + catch + { + continue + } + + return [pscustomobject][ordered]@{ + registryPath = $RegistryPath + installed = [int]$Runtime.Installed -eq 1 + version = $ParsedVersion.ToString() + } + } + + return $null +} + +if ([string]::IsNullOrWhiteSpace($TargetInstallRoot)) +{ + $TargetInstallRoot = Join-Path ([Environment]::GetFolderPath('ProgramFiles')) 'HyperTwist' +} + +if ([string]::IsNullOrWhiteSpace($ReportPath)) +{ + $ReportPath = Join-Path $env:TEMP 'HyperTwist-readiness.json' +} + +$Checks = New-Object System.Collections.ArrayList +function Add-ReadinessCheck { + param( + [Parameter(Mandatory = $true)] + [string]$Id, + [Parameter(Mandatory = $true)] + [ValidateSet('required', 'optional', 'informational')] + [string]$Class, + [Parameter(Mandatory = $true)] + [bool]$Passed, + [Parameter(Mandatory = $true)] + [string]$Summary, + [object]$Evidence = $null + ) + + [void]$Checks.Add([pscustomobject][ordered]@{ + id = $Id + class = $Class + passed = $Passed + summary = $Summary + evidence = $Evidence + }) +} + +$OperatingSystem = Get-CimInstance Win32_OperatingSystem +$ComputerSystem = Get-CimInstance Win32_ComputerSystem +$WindowsBuild = [int]$OperatingSystem.BuildNumber +$Is64BitOperatingSystem = [Environment]::Is64BitOperatingSystem + +Add-ReadinessCheck ` + -Id 'windows-x64' ` + -Class 'required' ` + -Passed $Is64BitOperatingSystem ` + -Summary 'HyperTwist requires a 64-bit Windows operating system.' ` + -Evidence ([pscustomobject][ordered]@{ + architecture = [string]$OperatingSystem.OSArchitecture + processArchitecture = [string]$env:PROCESSOR_ARCHITECTURE + }) + +Add-ReadinessCheck ` + -Id 'windows-supported-build' ` + -Class 'required' ` + -Passed ($WindowsBuild -ge $MinimumWindowsBuild) ` + -Summary "HyperTwist requires Windows build $MinimumWindowsBuild or newer." ` + -Evidence ([pscustomobject][ordered]@{ + caption = [string]$OperatingSystem.Caption + version = [string]$OperatingSystem.Version + build = $WindowsBuild + minimumBuild = $MinimumWindowsBuild + }) + +$TotalMemoryBytes = [Int64]$ComputerSystem.TotalPhysicalMemory +Add-ReadinessCheck ` + -Id 'system-memory' ` + -Class 'required' ` + -Passed ($TotalMemoryBytes -ge $MinimumMemoryBytes) ` + -Summary 'At least 8 GiB of physical memory is required for the desktop Alpha.' ` + -Evidence ([pscustomobject][ordered]@{ + totalBytes = $TotalMemoryBytes + minimumBytes = $MinimumMemoryBytes + }) + +$ExistingInstallPathRoot = Get-ExistingPathRoot -Path $TargetInstallRoot +$TargetDriveName = [System.IO.Path]::GetPathRoot($ExistingInstallPathRoot).TrimEnd('\').TrimEnd(':') +$TargetDrive = Get-PSDrive -Name $TargetDriveName +$AvailableFreeSpaceBytes = [Int64]$TargetDrive.Free +Add-ReadinessCheck ` + -Id 'install-volume-free-space' ` + -Class 'required' ` + -Passed ($AvailableFreeSpaceBytes -ge $MinimumFreeSpaceBytes) ` + -Summary 'The target volume must have enough free space for installation and first-run caches.' ` + -Evidence ([pscustomobject][ordered]@{ + targetInstallRoot = [System.IO.Path]::GetFullPath($TargetInstallRoot) + availableBytes = $AvailableFreeSpaceBytes + minimumBytes = $MinimumFreeSpaceBytes + }) + +$VcRuntime = Get-VcRuntimeEvidence +$VcRuntimeReady = $null -ne $VcRuntime ` + -and $VcRuntime.installed ` + -and [version]$VcRuntime.version -ge $RequiredVcRuntimeVersion +Add-ReadinessCheck ` + -Id 'vc-runtime-x64' ` + -Class 'required' ` + -Passed $VcRuntimeReady ` + -Summary "Microsoft Visual C++ 2015-2022 x64 runtime $RequiredVcRuntimeVersion or newer is required and is bundled by the installer." ` + -Evidence $VcRuntime + +$SystemDirectory = [Environment]::SystemDirectory +$RequiredSystemLibraries = @( + 'd3d11.dll', + 'd3d12.dll', + 'dxgi.dll', + 'mf.dll', + 'mfplat.dll', + 'mfreadwrite.dll', + 'xinput1_4.dll' +) +$MissingSystemLibraries = @( + $RequiredSystemLibraries | + Where-Object { -not (Test-Path -LiteralPath (Join-Path $SystemDirectory $_)) } +) +Add-ReadinessCheck ` + -Id 'windows-media-and-graphics-libraries' ` + -Class 'required' ` + -Passed ($MissingSystemLibraries.Count -eq 0) ` + -Summary 'Windows DirectX, XInput, and Media Foundation runtime libraries must be available.' ` + -Evidence ([pscustomobject][ordered]@{ + systemDirectory = $SystemDirectory + requiredLibraries = $RequiredSystemLibraries + missingLibraries = $MissingSystemLibraries + }) + +$VideoControllers = @( + Get-CimInstance Win32_VideoController -ErrorAction SilentlyContinue | + ForEach-Object { + [pscustomobject][ordered]@{ + name = [string]$_.Name + driverVersion = [string]$_.DriverVersion + driverDate = if ($null -eq $_.DriverDate) { $null } else { $_.DriverDate.ToString('o') } + adapterRamBytes = [Int64]$_.AdapterRAM + status = [string]$_.Status + } + } +) +Add-ReadinessCheck ` + -Id 'graphics-adapter' ` + -Class 'required' ` + -Passed ($VideoControllers.Count -gt 0) ` + -Summary 'At least one Windows graphics adapter must be visible.' ` + -Evidence $VideoControllers + +$SoundDevices = @( + Get-CimInstance Win32_SoundDevice -ErrorAction SilentlyContinue | + ForEach-Object { + [pscustomobject][ordered]@{ + name = [string]$_.Name + status = [string]$_.Status + } + } +) +Add-ReadinessCheck ` + -Id 'audio-device' ` + -Class 'optional' ` + -Passed ($SoundDevices.Count -gt 0) ` + -Summary 'An audio device is optional for puzzle play and recommended for narration and speech features.' ` + -Evidence $SoundDevices + +$OpenXrRegistryPaths = @( + 'HKLM:\SOFTWARE\Khronos\OpenXR\1', + 'HKLM:\SOFTWARE\WOW6432Node\Khronos\OpenXR\1' +) +$OpenXrRuntime = $null +foreach ($OpenXrRegistryPath in $OpenXrRegistryPaths) +{ + if (Test-Path -LiteralPath $OpenXrRegistryPath) + { + $OpenXrRuntime = Get-ItemProperty -LiteralPath $OpenXrRegistryPath + break + } +} +Add-ReadinessCheck ` + -Id 'openxr-runtime' ` + -Class 'optional' ` + -Passed ($null -ne $OpenXrRuntime -and -not [string]::IsNullOrWhiteSpace([string]$OpenXrRuntime.ActiveRuntime)) ` + -Summary 'An active OpenXR runtime is optional for keyboard/mouse play and required only for live headset sessions.' ` + -Evidence $(if ($null -eq $OpenXrRuntime) { + $null + } else { + [pscustomobject][ordered]@{ activeRuntime = [string]$OpenXrRuntime.ActiveRuntime } + }) + +$ResolvedPayloadRoot = $null +if (-not [string]::IsNullOrWhiteSpace($PayloadRoot)) +{ + if (-not (Test-Path -LiteralPath $PayloadRoot -PathType Container)) + { + throw "Payload root '$PayloadRoot' was not found." + } + + $ResolvedPayloadRoot = (Resolve-Path -LiteralPath $PayloadRoot).Path + $RequiredPayloadFiles = @( + [pscustomobject]@{ path = 'UnrealHyperTwist.exe'; size = -1; sha256 = '' }, + [pscustomobject]@{ path = 'UnrealHyperTwist\Binaries\Win64\UnrealHyperTwist-Win64-Shipping.exe'; size = -1; sha256 = '' }, + [pscustomobject]@{ path = 'UnrealHyperTwist\Binaries\Win64\tbb12.dll'; size = -1; sha256 = '' }, + [pscustomobject]@{ path = 'UnrealHyperTwist\Binaries\Win64\tbbmalloc.dll'; size = -1; sha256 = '' }, + [pscustomobject]@{ path = 'UnrealHyperTwist\Content\Paks\UnrealHyperTwist-Windows.pak'; size = -1; sha256 = '' }, + [pscustomobject]@{ path = 'UnrealHyperTwist\Content\Paks\UnrealHyperTwist-Windows.utoc'; size = -1; sha256 = '' }, + [pscustomobject]@{ path = 'UnrealHyperTwist\Content\Paks\UnrealHyperTwist-Windows.ucas'; size = -1; sha256 = '' }, + [pscustomobject]@{ path = 'UnrealHyperTwist\Saved\twophase-ht.tbl'; size = $ExpectedSolverTableSizeBytes; sha256 = $ExpectedSolverTableSha256 }, + [pscustomobject]@{ path = 'Content\Browser\index.html'; size = -1; sha256 = '' }, + [pscustomobject]@{ path = 'Content\Browser\src\browser-runtime-bootstrap.js'; size = -1; sha256 = '' }, + [pscustomobject]@{ path = 'Content\Browser\src\browser-spatial-runtime-fallback.js'; size = -1; sha256 = '' }, + [pscustomobject]@{ path = 'Content\Browser\dist\browser-spatial-runtime.js'; size = -1; sha256 = '' }, + [pscustomobject]@{ path = 'Engine\Binaries\ThirdParty\Windows\XAudio2_9\x64\XAudio2_9Redist.dll'; size = -1; sha256 = '' }, + [pscustomobject]@{ path = 'Engine\Binaries\ThirdParty\OpenXR\win64\openxr_loader.dll'; size = -1; sha256 = '' } + ) + + $PayloadFailures = New-Object System.Collections.ArrayList + foreach ($RequiredPayloadFile in $RequiredPayloadFiles) + { + $RequiredPath = Join-Path $ResolvedPayloadRoot $RequiredPayloadFile.path + if (-not (Test-Path -LiteralPath $RequiredPath -PathType Leaf)) + { + [void]$PayloadFailures.Add("missing:$($RequiredPayloadFile.path)") + continue + } + + $RequiredItem = Get-Item -LiteralPath $RequiredPath + if ($RequiredItem.Length -le 0) + { + [void]$PayloadFailures.Add("empty:$($RequiredPayloadFile.path)") + continue + } + + if ([Int64]$RequiredPayloadFile.size -ge 0 ` + -and $RequiredItem.Length -ne [Int64]$RequiredPayloadFile.size) + { + [void]$PayloadFailures.Add("size:$($RequiredPayloadFile.path)") + continue + } + + if (-not [string]::IsNullOrWhiteSpace([string]$RequiredPayloadFile.sha256)) + { + $ActualHash = (Get-FileHash -LiteralPath $RequiredPath -Algorithm SHA256).Hash.ToLowerInvariant() + if ($ActualHash -ne ([string]$RequiredPayloadFile.sha256).ToLowerInvariant()) + { + [void]$PayloadFailures.Add("sha256:$($RequiredPayloadFile.path)") + } + } + } + + $CefRuntimeFiles = @( + Get-ChildItem ` + -LiteralPath (Join-Path $ResolvedPayloadRoot 'Engine\Binaries\ThirdParty\CEF3\Win64') ` + -Filter 'libcef.dll' ` + -File ` + -Recurse ` + -ErrorAction SilentlyContinue + ) + if ($CefRuntimeFiles.Count -eq 0) + { + [void]$PayloadFailures.Add('missing:Engine/Binaries/ThirdParty/CEF3/Win64/*/libcef.dll') + } + + Add-ReadinessCheck ` + -Id 'required-payload-files' ` + -Class 'required' ` + -Passed ($PayloadFailures.Count -eq 0) ` + -Summary 'The installed payload must contain every required executable, runtime, cooked-data, browser, solver, and OpenXR file.' ` + -Evidence ([pscustomobject][ordered]@{ + payloadRoot = $ResolvedPayloadRoot + requiredFileCount = $RequiredPayloadFiles.Count + 1 + cefRuntimeFiles = @( + $CefRuntimeFiles | + ForEach-Object { + Get-NormalizedRelativePath ` + -RootPath $ResolvedPayloadRoot ` + -FilePath $_.FullName + } + ) + failures = @($PayloadFailures) + }) + + $OuterExecutablePath = Join-Path $ResolvedPayloadRoot 'UnrealHyperTwist.exe' + if (Test-Path -LiteralPath $OuterExecutablePath -PathType Leaf) + { + $Signature = Get-AuthenticodeSignature -LiteralPath $OuterExecutablePath + Add-ReadinessCheck ` + -Id 'desktop-code-signature' ` + -Class 'optional' ` + -Passed ($Signature.Status -eq [System.Management.Automation.SignatureStatus]::Valid) ` + -Summary 'The Alpha payload is currently unsigned; production distribution requires Authenticode signing.' ` + -Evidence ([pscustomobject][ordered]@{ + status = $Signature.Status.ToString() + signer = if ($null -eq $Signature.SignerCertificate) { + $null + } else { + [string]$Signature.SignerCertificate.Subject + } + }) + } +} + +if (-not [string]::IsNullOrWhiteSpace($ExpectedPayloadManifestPath)) +{ + if ($null -eq $ResolvedPayloadRoot) + { + throw 'ExpectedPayloadManifestPath requires PayloadRoot.' + } + if (-not (Test-Path -LiteralPath $ExpectedPayloadManifestPath -PathType Leaf)) + { + throw "Payload manifest '$ExpectedPayloadManifestPath' was not found." + } + + $Manifest = Get-Content -LiteralPath $ExpectedPayloadManifestPath -Raw | ConvertFrom-Json + $ManifestFailures = New-Object System.Collections.ArrayList + $SeenManifestPaths = @{} + $ManifestEntries = @($Manifest.files) + if ([string]$Manifest.manifestVersion -ne 'ht-windows-installer-payload/v1') + { + [void]$ManifestFailures.Add("manifest-version:$($Manifest.manifestVersion)") + } + + $DeclaredFileCount = 0 + if (-not [int]::TryParse([string]$Manifest.fileCount, [ref]$DeclaredFileCount) ` + -or $DeclaredFileCount -ne $ManifestEntries.Count) + { + [void]$ManifestFailures.Add("manifest-file-count:$($Manifest.fileCount)") + } + + $DeclaredTotalSizeBytes = 0L + if (-not [Int64]::TryParse( + [string]$Manifest.totalSizeBytes, + [ref]$DeclaredTotalSizeBytes + ) -or $DeclaredTotalSizeBytes -lt 0) + { + [void]$ManifestFailures.Add("manifest-total-size:$($Manifest.totalSizeBytes)") + } + + $ComputedTotalSizeBytes = 0L + foreach ($ManifestEntry in $ManifestEntries) + { + $ManifestRelativePath = [string]$ManifestEntry.relativePath + $ManifestPathKey = $ManifestRelativePath.Replace('\', '/').ToLowerInvariant() + if ($SeenManifestPaths.ContainsKey($ManifestPathKey)) + { + [void]$ManifestFailures.Add("duplicate:$ManifestRelativePath") + continue + } + $SeenManifestPaths[$ManifestPathKey] = $true + + $ManifestEntrySizeBytes = 0L + if (-not [Int64]::TryParse( + [string]$ManifestEntry.sizeBytes, + [ref]$ManifestEntrySizeBytes + ) -or $ManifestEntrySizeBytes -lt 0) + { + [void]$ManifestFailures.Add("invalid-size:$ManifestRelativePath") + continue + } + $ComputedTotalSizeBytes += $ManifestEntrySizeBytes + + $ExpectedManifestHash = ([string]$ManifestEntry.sha256).ToLowerInvariant() + if ($ExpectedManifestHash -notmatch '^[0-9a-f]{64}$') + { + [void]$ManifestFailures.Add("invalid-sha256:$ManifestRelativePath") + continue + } + + try + { + $ManifestFilePath = Resolve-PayloadRelativeFilePath ` + -RootPath $ResolvedPayloadRoot ` + -RelativePath $ManifestRelativePath + } + catch + { + [void]$ManifestFailures.Add("invalid-path:$ManifestRelativePath") + continue + } + + if (-not (Test-Path -LiteralPath $ManifestFilePath -PathType Leaf)) + { + [void]$ManifestFailures.Add("missing:$ManifestRelativePath") + continue + } + + $ManifestItem = Get-Item -LiteralPath $ManifestFilePath + if ($ManifestItem.Length -ne $ManifestEntrySizeBytes) + { + [void]$ManifestFailures.Add("size:$ManifestRelativePath") + continue + } + + if ($VerifyPayloadHashes) + { + $ManifestHash = (Get-FileHash -LiteralPath $ManifestFilePath -Algorithm SHA256).Hash.ToLowerInvariant() + if ($ManifestHash -ne $ExpectedManifestHash) + { + [void]$ManifestFailures.Add("sha256:$ManifestRelativePath") + } + } + } + if ($DeclaredTotalSizeBytes -ne $ComputedTotalSizeBytes) + { + [void]$ManifestFailures.Add( + "manifest-total-size-parity:$DeclaredTotalSizeBytes/$ComputedTotalSizeBytes" + ) + } + + Add-ReadinessCheck ` + -Id 'payload-manifest-parity' ` + -Class 'required' ` + -Passed ($ManifestFailures.Count -eq 0) ` + -Summary 'The installed payload must match the release manifest without missing, truncated, or altered files.' ` + -Evidence ([pscustomobject][ordered]@{ + manifestPath = (Resolve-Path -LiteralPath $ExpectedPayloadManifestPath).Path + manifestVersion = [string]$Manifest.manifestVersion + manifestEntryCount = $ManifestEntries.Count + declaredFileCount = $DeclaredFileCount + declaredTotalSizeBytes = $DeclaredTotalSizeBytes + computedTotalSizeBytes = $ComputedTotalSizeBytes + hashesVerified = [bool]$VerifyPayloadHashes + failures = @($ManifestFailures) + }) +} + +$RequiredFailures = @( + $Checks | + Where-Object { $_.class -eq 'required' -and -not $_.passed } +) +$OptionalWarnings = @( + $Checks | + Where-Object { $_.class -eq 'optional' -and -not $_.passed } +) +$Result = if ($RequiredFailures.Count -gt 0) { + 'blocked' +} elseif ($OptionalWarnings.Count -gt 0) { + 'ready-with-warnings' +} else { + 'ready' +} + +$Report = [pscustomobject][ordered]@{ + reportVersion = 'ht-windows-readiness/v1' + generatedAtUtc = [DateTime]::UtcNow.ToString('o') + result = $Result + payloadRoot = $ResolvedPayloadRoot + targetInstallRoot = [System.IO.Path]::GetFullPath($TargetInstallRoot) + requiredFailureCount = $RequiredFailures.Count + optionalWarningCount = $OptionalWarnings.Count + checks = @($Checks) +} + +Write-Utf8JsonFile -Path $ReportPath -Value $Report +[Console]::Out.WriteLine(( + "HyperTwist Windows readiness: {0} ({1} required failure(s), {2} optional warning(s))." -f + $Result, + $RequiredFailures.Count, + $OptionalWarnings.Count +)) +[Console]::Out.WriteLine("Report: $([System.IO.Path]::GetFullPath($ReportPath))") + +if ($PassThru) +{ + return $Report +} + +if ($WaitForUser) +{ + [void](Read-Host 'Press Enter to close the HyperTwist readiness report') +} + +if ($Result -eq 'blocked') +{ + exit 2 +} + +exit 0