From a960c0c1b08aa8f65927cdbb22cef64ab0e815a3 Mon Sep 17 00:00:00 2001 From: Mat4m0 Date: Mon, 31 Aug 2026 01:38:34 +0200 Subject: [PATCH] feat(windows): add signed Squirrel distribution foundation --- .github/workflows/portable-native-build.yml | 21 +++++ apps/launcher/src/App.vue | 13 +-- .../src/components/KnownIssuesView.vue | 4 +- .../src/components/MapsSettings.test.ts | 4 +- apps/launcher/src/components/MapsSettings.vue | 5 +- .../UpdateGameFilesSettings.test.ts | 2 +- apps/launcher/src/update-game-files-copy.ts | 4 +- assets/AppIcon.ico | Bin 0 -> 84359 bytes forge.config.ts | 44 +++++++++- package.json | 4 +- pnpm-lock.yaml | 76 +++++++++++++++++ pnpm-workspace.yaml | 3 + scripts/build.mjs | 1 + scripts/release-manifest.ts | 2 +- scripts/update-feeds.ts | 12 +-- scripts/windows-icon.ts | 74 +++++++++++++++++ scripts/windows-package-probe.ts | 72 ++++++++++++++++ src/main/app-updater.ts | 25 ++++-- src/main/main.ts | 50 ++++++++++-- src/main/windows-native-host.ts | 6 ++ src/main/windows-shell.ts | 4 +- src/main/windows-squirrel-startup.ts | 77 ++++++++++++++++++ src/native/windows-host/host.cpp | 42 ++++++++++ src/shared/distribution-channel.ts | 4 + src/shared/project-identity.ts | 48 +++++++++-- src/shared/release-manifest.ts | 19 +++-- tests/policy/source-release-pipeline.test.ts | 2 +- .../policy/source-windows-credentials.test.ts | 12 +++ .../source-windows-distribution.test.ts | 28 +++++++ tests/unit/app-updater.test.ts | 50 ++++++++++-- tests/unit/release-manifest.test.ts | 37 ++++++++- tests/unit/windows-icon.test.ts | 29 +++++++ tests/unit/windows-native-host.test.ts | 3 + tests/unit/windows-package-probe.test.ts | 37 +++++++++ tests/unit/windows-squirrel-startup.test.ts | 66 +++++++++++++++ 35 files changed, 818 insertions(+), 62 deletions(-) create mode 100644 assets/AppIcon.ico create mode 100644 scripts/windows-icon.ts create mode 100644 scripts/windows-package-probe.ts create mode 100644 src/main/windows-squirrel-startup.ts create mode 100644 tests/policy/source-windows-distribution.test.ts create mode 100644 tests/unit/windows-icon.test.ts create mode 100644 tests/unit/windows-package-probe.test.ts create mode 100644 tests/unit/windows-squirrel-startup.test.ts diff --git a/.github/workflows/portable-native-build.yml b/.github/workflows/portable-native-build.yml index d4e0a34a..97271e46 100644 --- a/.github/workflows/portable-native-build.yml +++ b/.github/workflows/portable-native-build.yml @@ -11,6 +11,9 @@ on: - "scripts/build.mjs" - "scripts/platform-storage-probe.ts" - "scripts/windows-credential-probe.ts" + - "scripts/windows-icon.ts" + - "scripts/windows-package-probe.ts" + - "forge.config.ts" - "src/**" - "tests/integration/gw-dat-dimensions.test.ts" - "package.json" @@ -63,3 +66,21 @@ jobs: run: >- node --import ./scripts/ts-hook.mjs --test tests/integration/gw-dat-dimensions.test.ts + - name: Build the unsigned Windows Squirrel package + if: runner.os == 'Windows' + env: + GW_PACKAGE_INTENT: local + run: pnpm make -- --platform=win32 --arch=x64 + - name: Inspect the exact Windows package boundary + if: runner.os == 'Windows' + run: pnpm test:windows-package + - name: Prove the local package has no trusted distribution signature + if: runner.os == 'Windows' + shell: pwsh + run: | + $setup = Get-ChildItem "out/make/squirrel.windows/x64/*Setup.exe" + if ($setup.Count -ne 1) { throw "Expected one Setup executable" } + $signature = Get-AuthenticodeSignature $setup.FullName + if ($signature.Status -ne "NotSigned") { + throw "Local package unexpectedly has status $($signature.Status)" + } diff --git a/apps/launcher/src/App.vue b/apps/launcher/src/App.vue index feb1b10f..d522ee66 100644 --- a/apps/launcher/src/App.vue +++ b/apps/launcher/src/App.vue @@ -219,6 +219,9 @@ const toolLabels: Readonly> = { "xunlai-storage": "Xunlai Storage", }; const profileIcons = { swords: Swords, archive: Archive, map: MapIcon, scroll: ScrollText, shield: Shield, star: Star, crown: Crown, flame: Flame } as const; +const primaryModifierName = computed(() => + snapshot.value.platform === "macos" ? "Command" : "Ctrl" +); function checked(event: Event): boolean { return (event.currentTarget as HTMLInputElement).checked; @@ -301,12 +304,12 @@ async function captureToolShortcut(tool: GlobalTool) { if (!result) return; if (result.status === "captured") { if (await runAction("The shortcut could not be saved.", () => native?.tools.replaceShortcut({ tool, binding: result.binding }))) shortcutMessage.value = "Shortcut saved."; - } else if (result.status === "reserved") shortcutMessage.value = "That shortcut is used by macOS or this application."; + } else if (result.status === "reserved") shortcutMessage.value = "That shortcut is used by your system or this application."; else if (result.status === "conflict") { pendingShortcutReplacement.value = { tool, binding: result.binding }; shortcutMessage.value = `That shortcut is already used by ${toolLabels[result.tool]}.`; } - else if (result.status === "invalid") shortcutMessage.value = "Use Command with a letter or number."; + else if (result.status === "invalid") shortcutMessage.value = `Use ${primaryModifierName.value} with a letter or number.`; else shortcutMessage.value = "Shortcut change cancelled."; } @@ -342,7 +345,7 @@ async function replaceToolShortcut() {
{{ playableNotice.title }}{{ playableNotice.detail }}
-
You are offlineYou can play with the game files already on this Mac.
+
You are offlineYou can play with the game files already on this computer.
{{ backgroundDownloadBanner.title }}{{ backgroundDownloadBanner.detail }}
Help cover the yearly costsApple Developer Program, domain, and hosting
@@ -395,7 +398,7 @@ async function replaceToolShortcut() {
Restart neededYour change is saved.Applies after your next normal restart.
- +
- +
diff --git a/apps/launcher/src/components/KnownIssuesView.vue b/apps/launcher/src/components/KnownIssuesView.vue index f3221684..225f06ae 100644 --- a/apps/launcher/src/components/KnownIssuesView.vue +++ b/apps/launcher/src/components/KnownIssuesView.vue @@ -8,11 +8,11 @@ const emit = defineEmits<{ external: [kind: LauncherExternalLink] }>();