windows-development-sandbox 1.23.2 → 1.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/config.example.json +5 -3
  2. package/dist/app/features/data/data.def.d.ts +4 -4
  3. package/dist/app/features/data/data.run.d.ts +1 -1
  4. package/dist/app/features/defs.d.ts +161 -6
  5. package/dist/app/features/defs.js +7 -0
  6. package/dist/app/features/projects/projects.def.d.ts +1 -1
  7. package/dist/app/features/projects/projects.run.d.ts +1 -1
  8. package/dist/app/features/unreal/index.d.ts +2 -0
  9. package/dist/app/features/unreal/index.js +3 -0
  10. package/dist/app/features/unreal/unreal.def.d.ts +236 -0
  11. package/dist/app/features/unreal/unreal.def.js +379 -0
  12. package/dist/app/features/unreal/unreal.run.d.ts +96 -0
  13. package/dist/app/features/unreal/unreal.run.js +217 -0
  14. package/dist/app/registry.js +5 -0
  15. package/dist/bin/agent-sandbox.js +2 -2
  16. package/dist/bin/{chunk-7YPEDAJD.js → chunk-3GAXLATK.js} +58 -5
  17. package/dist/bin/{chunk-USTRSHIE.js → chunk-F4PVBN23.js} +2615 -1515
  18. package/dist/bin/{chunk-QFSJX2KW.js → chunk-G3TG4ZT7.js} +1 -1
  19. package/dist/bin/claude-host.js +2 -2
  20. package/dist/bin/claude-sbx.js +2 -2
  21. package/dist/bin/codex-sbx.js +2 -2
  22. package/dist/bin/gemini-sbx.js +2 -2
  23. package/dist/bin/sandbox.js +2 -2
  24. package/dist/bin/sbx-hide.js +2 -2
  25. package/dist/bin/sbx-show.js +2 -2
  26. package/dist/cli/commands/launch/launch.js +5 -2
  27. package/dist/cli/commands/registry.js +3 -0
  28. package/dist/cli/manifest.d.ts +18 -0
  29. package/dist/cli/manifest.js +26 -0
  30. package/dist/cli/menu/coverage-exempt.js +1 -0
  31. package/dist/cli/menu/pages/sandbox-page.d.ts +3 -0
  32. package/dist/cli/menu/pages/sandbox-page.js +34 -4
  33. package/dist/cli/menu/views.js +2 -0
  34. package/dist/core/config/defaults.js +18 -5
  35. package/dist/core/detect/toolchains.js +0 -6
  36. package/dist/core/schema/config.d.ts +10 -9
  37. package/dist/core/schema/pc-installs.d.ts +3 -1
  38. package/dist/core/schema/pc-installs.js +10 -1
  39. package/dist/core/schema/toolchains.d.ts +1 -2
  40. package/dist/core/schema/toolchains.js +4 -7
  41. package/dist/guest/accounts/kinds/epic.js +18 -13
  42. package/dist/guest/agents/agent-files.d.ts +11 -7
  43. package/dist/guest/agents/agent-files.js +37 -11
  44. package/dist/guest/agents/local-state.d.ts +2 -1
  45. package/dist/guest/agents/local-state.js +2 -1
  46. package/dist/guest/apps/after-install.d.ts +1 -1
  47. package/dist/guest/apps/after-install.js +6 -1
  48. package/dist/guest/apps/app-installer.d.ts +1 -0
  49. package/dist/guest/apps/app-installer.js +13 -3
  50. package/dist/guest/apps/comet-unpack.js +3 -2
  51. package/dist/guest/apps/resolvers-gamedev.js +55 -1
  52. package/dist/guest/boot/boot-console.d.ts +28 -0
  53. package/dist/guest/boot/boot-console.js +98 -0
  54. package/dist/guest/boot/boot.d.ts +8 -1
  55. package/dist/guest/boot/boot.js +80 -43
  56. package/dist/guest/boot/graph.d.ts +9 -5
  57. package/dist/guest/boot/graph.js +8 -2
  58. package/dist/guest/boot/guide.js +24 -0
  59. package/dist/guest/boot/progress-view.d.ts +116 -0
  60. package/dist/guest/boot/progress-view.js +456 -0
  61. package/dist/guest/boot/runner.d.ts +20 -10
  62. package/dist/guest/boot/runner.js +39 -47
  63. package/dist/guest/daemon/daemon.js +3 -0
  64. package/dist/guest/runtime/log-line.d.ts +7 -0
  65. package/dist/guest/runtime/log-line.js +18 -0
  66. package/dist/guest/runtime/log.d.ts +17 -4
  67. package/dist/guest/runtime/log.js +40 -17
  68. package/dist/guest/runtime/process.d.ts +9 -0
  69. package/dist/guest/runtime/process.js +69 -8
  70. package/dist/guest/setup/addon-mcp.d.ts +7 -0
  71. package/dist/guest/setup/addon-mcp.js +19 -4
  72. package/dist/guest/setup/browser-gpu.js +14 -11
  73. package/dist/guest/setup/environment.js +9 -4
  74. package/dist/guest/setup/headroom.js +4 -0
  75. package/dist/guest/setup/hooks.d.ts +2 -0
  76. package/dist/guest/setup/hooks.js +4 -0
  77. package/dist/guest/setup/path-plan.d.ts +12 -0
  78. package/dist/guest/setup/path-plan.js +18 -0
  79. package/dist/guest/toolchains/engines/epic-launcher.d.ts +26 -0
  80. package/dist/guest/toolchains/engines/epic-launcher.js +127 -0
  81. package/dist/guest/toolchains/engines/unreal-engines.d.ts +21 -1
  82. package/dist/guest/toolchains/engines/unreal-engines.js +163 -3
  83. package/dist/guest/toolchains/engines/unreal-status.d.ts +13 -0
  84. package/dist/guest/toolchains/engines/unreal-status.js +41 -0
  85. package/dist/guest/toolchains/native/debuggers.d.ts +6 -0
  86. package/dist/guest/toolchains/native/debuggers.js +57 -0
  87. package/dist/guest/toolchains/native/mesa.js +15 -8
  88. package/dist/guest/toolchains/native/rust-extras.d.ts +7 -0
  89. package/dist/guest/toolchains/native/rust-extras.js +23 -1
  90. package/dist/guest/toolchains/native/unreal.d.ts +2 -2
  91. package/dist/guest/toolchains/native/unreal.js +96 -20
  92. package/dist/guest/toolchains/native/visual-studio.d.ts +14 -14
  93. package/dist/guest/toolchains/native/visual-studio.js +145 -174
  94. package/dist/guest/toolchains/native/vs-persist.d.ts +189 -0
  95. package/dist/guest/toolchains/native/vs-persist.js +256 -0
  96. package/dist/guest/toolchains/native/vs-request.d.ts +6 -26
  97. package/dist/guest/toolchains/native/vs-request.js +16 -50
  98. package/dist/guest/toolchains/plan.js +23 -15
  99. package/dist/guest/toolchains/tools/vscode-profile.js +2 -0
  100. package/dist/host/accounts/kinds/epic.js +11 -5
  101. package/dist/host/accounts/source.d.ts +2 -0
  102. package/dist/host/accounts/source.js +1 -0
  103. package/dist/host/migrate/migrations/drop-vs-install-files.d.ts +5 -0
  104. package/dist/host/migrate/migrations/drop-vs-install-files.js +34 -0
  105. package/dist/host/migrate/registry.js +7 -1
  106. package/dist/host/pc-installs/discover.d.ts +2 -0
  107. package/dist/host/pc-installs/discover.js +25 -5
  108. package/dist/host/pc-installs/epic.d.ts +3 -0
  109. package/dist/host/pc-installs/epic.js +33 -2
  110. package/dist/host/pc-installs/source.d.ts +1 -1
  111. package/dist/host/pc-installs/unreal-vault.d.ts +11 -0
  112. package/dist/host/pc-installs/unreal-vault.js +78 -0
  113. package/dist/host/pc-installs/unreal.d.ts +36 -0
  114. package/dist/host/pc-installs/unreal.js +145 -2
  115. package/dist/host/profile/claude-profile.d.ts +8 -0
  116. package/dist/host/profile/claude-profile.js +43 -4
  117. package/dist/host/profile/index.d.ts +1 -1
  118. package/dist/host/profile/index.js +1 -1
  119. package/dist/runtime/agent.js +148 -33
  120. package/dist/runtime/daemon.js +3146 -1546
  121. package/dist/runtime/db.js +148 -33
  122. package/dist/runtime/main.js +3982 -2049
  123. package/dist/runtime/netproxy.js +84 -13
  124. package/dist/runtime/tool.js +125 -31
  125. package/dist/runtime/view.js +85 -14
  126. package/dist/selftest/agent-paths.test.js +18 -1
  127. package/dist/selftest/agent-settings.test.js +36 -5
  128. package/dist/selftest/agents-web.test.js +12 -0
  129. package/dist/selftest/boot-console.test.d.ts +2 -0
  130. package/dist/selftest/boot-console.test.js +291 -0
  131. package/dist/selftest/boot-runner.test.js +10 -12
  132. package/dist/selftest/engines.test.js +602 -3
  133. package/dist/selftest/guest-boot.test.js +7 -1
  134. package/dist/selftest/heavy-load.test.js +3 -1
  135. package/dist/selftest/hostile-input.test.js +11 -1
  136. package/dist/selftest/index.js +4 -0
  137. package/dist/selftest/layout.test.js +4 -0
  138. package/dist/selftest/menu-screens.js +3 -1
  139. package/dist/selftest/migrate.test.js +21 -0
  140. package/dist/selftest/mixamo.test.d.ts +2 -0
  141. package/dist/selftest/mixamo.test.js +60 -0
  142. package/dist/selftest/persisted-installs.test.js +169 -33
  143. package/dist/selftest/secrets.test.js +36 -1
  144. package/dist/selftest/support/engine-golden.js +5 -0
  145. package/dist/selftest/support/fixtures.d.ts +4 -3
  146. package/dist/selftest/support/unreal-sample.d.ts +2 -0
  147. package/dist/selftest/support/unreal-sample.js +83 -0
  148. package/dist/selftest/toolchains.test.js +32 -2
  149. package/dist/shared/accounts/index.d.ts +1 -1
  150. package/dist/shared/accounts/index.js +1 -1
  151. package/dist/shared/accounts/kinds/epic.d.ts +6 -1
  152. package/dist/shared/accounts/kinds/epic.js +11 -1
  153. package/dist/shared/apps/associations.d.ts +7 -0
  154. package/dist/shared/apps/associations.js +26 -0
  155. package/dist/shared/apps/catalogue-agents-web.d.ts +4 -1
  156. package/dist/shared/apps/catalogue-agents-web.js +5 -2
  157. package/dist/shared/apps/catalogue-gamedev.js +71 -1
  158. package/dist/shared/apps/index.d.ts +2 -0
  159. package/dist/shared/apps/index.js +2 -0
  160. package/dist/shared/apps/preview.d.ts +2 -0
  161. package/dist/shared/apps/preview.js +10 -0
  162. package/dist/shared/browser-policy.d.ts +14 -0
  163. package/dist/shared/browser-policy.js +22 -0
  164. package/dist/shared/channel/schema.d.ts +3 -6
  165. package/dist/shared/io/index.d.ts +1 -0
  166. package/dist/shared/io/index.js +1 -0
  167. package/dist/shared/io/rotate-log.d.ts +2 -0
  168. package/dist/shared/io/rotate-log.js +16 -0
  169. package/dist/shared/memory/policy.d.ts +5 -0
  170. package/dist/shared/memory/policy.js +7 -0
  171. package/dist/shared/paths/pc-installs.d.ts +13 -2
  172. package/dist/shared/paths/pc-installs.js +1 -0
  173. package/dist/shared/pty/terminal.js +2 -1
  174. package/dist/shared/readiness.d.ts +0 -1
  175. package/dist/shared/readiness.js +0 -1
  176. package/dist/shared/signer.d.ts +4 -0
  177. package/dist/shared/signer.js +2 -0
  178. package/dist/shared/unreal/engines.d.ts +161 -0
  179. package/dist/shared/unreal/engines.js +308 -0
  180. package/dist/shared/unreal/guide.d.ts +19 -0
  181. package/dist/shared/unreal/guide.js +105 -0
  182. package/dist/shared/unreal/index.d.ts +7 -0
  183. package/dist/shared/unreal/index.js +8 -0
  184. package/dist/shared/unreal/launcher-files.d.ts +88 -0
  185. package/dist/shared/unreal/launcher-files.js +292 -0
  186. package/dist/shared/unreal/mixamo.d.ts +5 -0
  187. package/dist/shared/unreal/mixamo.js +16 -0
  188. package/dist/shared/unreal/room.d.ts +17 -0
  189. package/dist/shared/unreal/room.js +34 -0
  190. package/dist/shared/unreal/status.d.ts +54 -0
  191. package/dist/shared/unreal/status.js +57 -0
  192. package/dist/shared/unreal/vault.d.ts +21 -0
  193. package/dist/shared/unreal/vault.js +67 -0
  194. package/dist/shared/unreal/vs-check.d.ts +39 -0
  195. package/dist/shared/unreal/vs-check.js +79 -0
  196. package/dist/sync/watcher.js +5 -1
  197. package/dist/web/index.html +1 -1
  198. package/dist/web/{main-CCILLX2M.js → main-FNE6XEHA.js} +84 -84
  199. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  200. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  201. package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
  202. package/package.json +1 -1
  203. package/sandbox/csharp/VsSetup.cs +210 -0
  204. package/sandbox/scripts/get-vs-setup.ps1 +93 -0
  205. package/sandbox/scripts/restore-visual-studio.ps1 +78 -0
  206. package/sandbox/scripts/save-visual-studio.ps1 +106 -0
  207. package/sandbox/scripts/set-registry-values.ps1 +35 -0
  208. package/schemas/config.schema.json +38 -9
  209. package/schemas/sandbox-config.schema.json +11 -8
  210. package/dist/guest/toolchains/native/autosdk.d.ts +0 -3
  211. package/dist/guest/toolchains/native/autosdk.js +0 -50
@@ -5,6 +5,7 @@ import fs from 'node:fs';
5
5
  import http from 'node:http';
6
6
  import path from 'node:path';
7
7
  import { spawn } from 'node:child_process';
8
+ import { rotateLog } from '../../shared/io/index.js';
8
9
  import { HEADROOM_PORT, headroomArgs, headroomEnv, headroomUrl } from '../../shared/apps/index.js';
9
10
  import { logsDir } from '../runtime/paths.js';
10
11
  import { portOpen, waitForPort } from '../runtime/ports.js';
@@ -12,6 +13,8 @@ import { log } from '../runtime/log.js';
12
13
  import { installedAddon } from './agent-addons.js';
13
14
  /** The proxy's log (it redacts keys and tokens in what it logs). */
14
15
  export const HEADROOM_LOG = path.join(logsDir, 'headroom.log');
16
+ /** The proxy logs every request and the file lives on the data disk: past this it is kept as headroom.log.1. */
17
+ const HEADROOM_LOG_LIMIT = 20 * 1024 * 1024;
15
18
  /** Whether Headroom's own health check answers on the port (not some other program that took it). */
16
19
  function answersAsHeadroom(port) {
17
20
  return new Promise((resolve) => {
@@ -53,6 +56,7 @@ async function startProxy(env) {
53
56
  return false;
54
57
  if (await portOpen(HEADROOM_PORT))
55
58
  return true;
59
+ rotateLog(HEADROOM_LOG, HEADROOM_LOG_LIMIT);
56
60
  const out = fs.openSync(HEADROOM_LOG, 'a');
57
61
  try {
58
62
  spawn(exe, headroomArgs(HEADROOM_PORT), {
@@ -4,6 +4,8 @@ export interface HookTarget {
4
4
  cwd: string;
5
5
  }
6
6
  export declare function projectHooks(name: string, cwd: string): HookTarget;
7
+ /** Whether `stage` has hooks to run (they write to the terminal themselves). */
8
+ export declare function hasHooks(stage: string, target?: HookTarget): boolean;
7
9
  export declare function runHooks(stage: string, env: NodeJS.ProcessEnv, target?: HookTarget): void;
8
10
  /** The same without blocking: the session daemon keeps answering other requests while a project's hooks run. */
9
11
  export declare function runHooksAsync(stage: string, env: NodeJS.ProcessEnv, target: HookTarget): Promise<void>;
@@ -45,6 +45,10 @@ const failed = (stage, name, status) => {
45
45
  throw new Error(message);
46
46
  warn(message);
47
47
  };
48
+ /** Whether `stage` has hooks to run (they write to the terminal themselves). */
49
+ export function hasHooks(stage, target = BOOT) {
50
+ return hooksOf(stage, target).length > 0;
51
+ }
48
52
  export function runHooks(stage, env, target = BOOT) {
49
53
  for (const { name, command } of hooksOf(stage, target)) {
50
54
  log('Hook [' + stage + ']: ' + name);
@@ -13,6 +13,18 @@ export interface PathPlanInput {
13
13
  }
14
14
  /** Ordered, de-duplicated (case, trailing slash) PATH folders; missing and dropped folders are left out. */
15
15
  export declare function planPath(input: PathPlanInput): string[];
16
+ /**
17
+ * The PATH folders of the agent environment (sessions, the machine PATH, terminal tabs): the sandbox's own commands
18
+ * folder first, because its `claude` / `codex` shims carry the sandbox's flags and must beat a copy installed with
19
+ * `npm i -g` in nvm's node folder; then the toolchains' folders, the process's own and the appended ones. No
20
+ * duplicates (matched like pathKey); MSVC's tools before Git's usr\bin.
21
+ */
22
+ export declare function sessionPath(o: {
23
+ readonly commands: string;
24
+ readonly prepend: readonly string[];
25
+ readonly base: readonly string[];
26
+ readonly append: readonly string[];
27
+ }): string[];
16
28
  /** `base` with the folders of `extra` it lacks added at the end (a process keeps its own order and entries). */
17
29
  export declare function appendMissing(base: string, extra: string, skip?: (folder: string) => boolean): string;
18
30
  /**
@@ -32,6 +32,24 @@ export function planPath(input) {
32
32
  }
33
33
  return msvcBeforeGitUsr(result);
34
34
  }
35
+ /**
36
+ * The PATH folders of the agent environment (sessions, the machine PATH, terminal tabs): the sandbox's own commands
37
+ * folder first, because its `claude` / `codex` shims carry the sandbox's flags and must beat a copy installed with
38
+ * `npm i -g` in nvm's node folder; then the toolchains' folders, the process's own and the appended ones. No
39
+ * duplicates (matched like pathKey); MSVC's tools before Git's usr\bin.
40
+ */
41
+ export function sessionPath(o) {
42
+ const seen = new Set();
43
+ const result = [];
44
+ for (const folder of [o.commands, ...o.prepend, ...o.base, ...o.append]) {
45
+ const key = pathKey(folder);
46
+ if (!folder.trim() || seen.has(key))
47
+ continue;
48
+ seen.add(key);
49
+ result.push(folder);
50
+ }
51
+ return msvcBeforeGitUsr(result);
52
+ }
35
53
  /** `base` with the folders of `extra` it lacks added at the end (a process keeps its own order and entries). */
36
54
  export function appendMissing(base, extra, skip = () => false) {
37
55
  const seen = new Set(splitPath(base).map(pathKey));
@@ -0,0 +1,26 @@
1
+ import { type LauncherEntry } from '../../../shared/unreal/index.js';
2
+ /** The launcher's program (launcher 17+ is 64-bit; older ones were in Win32). */
3
+ export declare function launcherExe(): string | null;
4
+ /** Downloads the launcher's MSI ahead of the install (once). */
5
+ export declare function fetchLauncher(): Promise<void>;
6
+ /** Installs the launcher when this sandbox does not have it yet (the MSI, quietly). Returns its program. */
7
+ export declare function ensureLauncher(): Promise<string>;
8
+ export interface LauncherPlaceResult {
9
+ readonly installLocation: string | null;
10
+ readonly problem: string | null;
11
+ readonly freeBytes: number | null;
12
+ readonly needBytes: number;
13
+ }
14
+ /** Points the launcher's default install folder at tools\unreal\engines (every settings file it may read). */
15
+ export declare function placeEngines(persist: boolean): LauncherPlaceResult;
16
+ /** The saved LauncherInstalled.dat entries of engines in tools\unreal\engines whose folder is still there. */
17
+ export declare function savedEntries(): LauncherEntry[];
18
+ /** Puts the saved manifests back where the launcher reads them; the engine folders they name. */
19
+ export declare function restoreLauncherItems(): string[];
20
+ /** Merges these entries into the launcher's LauncherInstalled.dat (foreign entries kept). */
21
+ export declare function writeLauncherEntries(entries: readonly LauncherEntry[]): void;
22
+ /**
23
+ * Saves what the launcher knows about engines it installed into tools\unreal\engines (new or changed manifests, its
24
+ * LauncherInstalled.dat entries). Cheap: a few small files. Returns whether something new was saved.
25
+ */
26
+ export declare function saveLauncherState(): boolean;
@@ -0,0 +1,127 @@
1
+ // The Epic Games Launcher in the sandbox: installed every boot from its MSI (downloaded once into tools\unreal, run
2
+ // only when Epic's signature checks out; a sandbox loses Program Files), pointed at the data disk for engines
3
+ // (GameUserSettings.ini [Launcher] DefaultAppInstallLocation, in the encoding the file has), and given back what it
4
+ // knew about the engines it installed there: their .item manifests and LauncherInstalled.dat entries are saved on the
5
+ // data disk while the sandbox runs (the daemon calls saveLauncherState) and put back at the next boot.
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { z } from 'zod';
9
+ import { readBoundedFile } from '../../../shared/io/bounded.js';
10
+ import { readValid, writeJsonAtomic } from '../../../shared/io/json.js';
11
+ import { SYSTEM } from '../../../shared/system.js';
12
+ import { enginePlaceProblem, ENGINE_NEED_BYTES, entriesUnder, launcherInstalledFile, launcherManifestsDir, launcherSettingsFiles, MAX_LAUNCHER_FILE_BYTES, mergeLauncherInstalled, restoreItems, saveItems, setLauncherInstallLocation, } from '../../../shared/unreal/index.js';
13
+ import { errMsg } from '../../../core/util/errors.js';
14
+ import { runInstaller } from '../../runtime/pools.js';
15
+ import { exists, mkdirp } from '../../runtime/fs.js';
16
+ import { log } from '../../runtime/log.js';
17
+ import { SIGNERS, signedInstaller } from '../installers.js';
18
+ import { ENGINES_DIR, SAVED_DIR, UNREAL_DIR } from './unreal-status.js';
19
+ const MSI_URL = 'https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/installer/download/EpicGamesLauncherInstaller.msi';
20
+ const MSI_FILE = path.join(UNREAL_DIR, 'EpicGamesLauncherInstaller.msi');
21
+ const SAVED_MANIFESTS = path.join(SAVED_DIR, 'Manifests');
22
+ const SAVED_ENTRIES = path.join(SAVED_DIR, 'LauncherInstalled.json');
23
+ const programData = () => process.env.ProgramData ?? 'C:\\ProgramData';
24
+ const localAppData = () => process.env.LOCALAPPDATA ?? '';
25
+ /** The launcher's program (launcher 17+ is 64-bit; older ones were in Win32). */
26
+ export function launcherExe() {
27
+ const root = path.join(process.env['ProgramFiles(x86)'] ?? 'C:\\Program Files (x86)', 'Epic Games', 'Launcher');
28
+ for (const arch of ['Win64', 'Win32']) {
29
+ const exe = path.join(root, 'Portal', 'Binaries', arch, 'EpicGamesLauncher.exe');
30
+ if (exists(exe))
31
+ return exe;
32
+ }
33
+ return null;
34
+ }
35
+ /** Downloads the launcher's MSI ahead of the install (once). */
36
+ export async function fetchLauncher() {
37
+ if (!launcherExe())
38
+ await launcherMsi();
39
+ }
40
+ const launcherMsi = () => signedInstaller(MSI_URL, MSI_FILE, 'the Epic Games Launcher (~250 MB once)', SIGNERS.epic);
41
+ /** Installs the launcher when this sandbox does not have it yet (the MSI, quietly). Returns its program. */
42
+ export async function ensureLauncher() {
43
+ const have = launcherExe();
44
+ if (have)
45
+ return have;
46
+ const msi = await launcherMsi();
47
+ log('Installing the Epic Games Launcher ...');
48
+ const code = await runInstaller(SYSTEM.msiexec(), ['/i', msi, '/qn', '/norestart']);
49
+ const exe = launcherExe();
50
+ if ((code !== 0 && code !== 3010) || !exe)
51
+ throw new Error('the Epic Games Launcher installer failed (msiexec exit ' + code + ')');
52
+ return exe;
53
+ }
54
+ /** Points the launcher's default install folder at tools\unreal\engines (every settings file it may read). */
55
+ export function placeEngines(persist) {
56
+ let error;
57
+ if (persist)
58
+ try {
59
+ mkdirp(ENGINES_DIR);
60
+ for (const file of launcherSettingsFiles(localAppData()))
61
+ setLauncherInstallLocation(file, ENGINES_DIR);
62
+ }
63
+ catch (e) {
64
+ error = "could not write the launcher's settings (" + errMsg(e) + ')';
65
+ }
66
+ else
67
+ error = 'toolchains.unreal.persistEngines is off';
68
+ const set = persist && !error;
69
+ const { problem, freeBytes } = enginePlaceProblem(set ? { set } : { set, error: error }, ENGINES_DIR);
70
+ return { installLocation: set ? ENGINES_DIR : null, problem, freeBytes, needBytes: ENGINE_NEED_BYTES };
71
+ }
72
+ const SavedEntriesSchema = z.object({
73
+ entries: z
74
+ .array(z.object({
75
+ InstallLocation: z.string().max(1024),
76
+ NamespaceId: z.string().max(200),
77
+ ItemId: z.string().max(200),
78
+ ArtifactId: z.string().max(200),
79
+ AppVersion: z.string().max(200),
80
+ AppName: z.string().max(200),
81
+ }))
82
+ .max(64),
83
+ });
84
+ /** The saved LauncherInstalled.dat entries of engines in tools\unreal\engines whose folder is still there. */
85
+ export function savedEntries() {
86
+ return (readValid(SavedEntriesSchema, SAVED_ENTRIES)?.entries ?? []).filter((e) => e.InstallLocation.toLowerCase().startsWith(ENGINES_DIR.toLowerCase() + '\\') && exists(e.InstallLocation));
87
+ }
88
+ /** Puts the saved manifests back where the launcher reads them; the engine folders they name. */
89
+ export function restoreLauncherItems() {
90
+ return restoreItems(SAVED_MANIFESTS, launcherManifestsDir(programData()), ENGINES_DIR, exists);
91
+ }
92
+ /** Merges these entries into the launcher's LauncherInstalled.dat (foreign entries kept). */
93
+ export function writeLauncherEntries(entries) {
94
+ if (!entries.length)
95
+ return;
96
+ const file = launcherInstalledFile(programData());
97
+ const bytes = readBoundedFile(file, MAX_LAUNCHER_FILE_BYTES);
98
+ if (!bytes && fs.existsSync(file))
99
+ throw new Error(file + ' is too big to read');
100
+ const next = mergeLauncherInstalled(bytes ? bytes.toString('utf8') : null, entries);
101
+ mkdirp(path.dirname(file));
102
+ fs.writeFileSync(file, next, 'utf8');
103
+ }
104
+ /**
105
+ * Saves what the launcher knows about engines it installed into tools\unreal\engines (new or changed manifests, its
106
+ * LauncherInstalled.dat entries). Cheap: a few small files. Returns whether something new was saved.
107
+ */
108
+ export function saveLauncherState() {
109
+ if (!exists(ENGINES_DIR))
110
+ return false;
111
+ const items = saveItems(launcherManifestsDir(programData()), SAVED_MANIFESTS, ENGINES_DIR);
112
+ const bytes = readBoundedFile(launcherInstalledFile(programData()), MAX_LAUNCHER_FILE_BYTES);
113
+ const live = bytes ? entriesUnder(bytes.toString('utf8'), ENGINES_DIR) : [];
114
+ const old = readValid(SavedEntriesSchema, SAVED_ENTRIES)?.entries ?? [];
115
+ const merged = [
116
+ ...old.filter((o) => !live.some((l) => l.InstallLocation.toLowerCase() === o.InstallLocation.toLowerCase())),
117
+ ...live,
118
+ ].filter((e) => exists(e.InstallLocation));
119
+ const changed = JSON.stringify(merged) !== JSON.stringify(old);
120
+ if (changed) {
121
+ mkdirp(SAVED_DIR);
122
+ writeJsonAtomic(SAVED_ENTRIES, { entries: merged.slice(0, 64) });
123
+ }
124
+ if (items.length || changed)
125
+ log('Unreal: saved what the Epic Games Launcher knows about its engines on the data disk');
126
+ return items.length > 0 || changed;
127
+ }
@@ -1,4 +1,24 @@
1
+ import { type EngineFs, type LauncherEntry, type MappedEngine, type UnrealEngine } from '../../../shared/unreal/index.js';
1
2
  import type { Toolchains } from '../../../core/types.js';
2
3
  import type { ToolCtx } from '../step.js';
3
4
  import type { ToolEnv } from '../tool-env.js';
4
- export declare function setupUnrealEngines(env: ToolEnv, _config: Toolchains['unreal'], _ctx: ToolCtx): Promise<void>;
5
+ /** The real disk, read bounded. */
6
+ export declare const DISK: EngineFs;
7
+ /** This PC's engines the host mapped (session.json's pcInstalls). */
8
+ export declare function mappedEngines(ctx: Pick<ToolCtx, 'pcInstalls'>): MappedEngine[];
9
+ /** Every engine this sandbox has (an install the launcher has not finished is not one yet). */
10
+ export declare function sandboxEngines(ctx: Pick<ToolCtx, 'pcInstalls'>, disk?: EngineFs): UnrealEngine[];
11
+ /** The LauncherInstalled.dat entries for the installed builds: the launcher's own saved ones, else made from them. */
12
+ export declare function launcherEntries(engines: readonly UnrealEngine[], ctx: Pick<ToolCtx, 'pcInstalls'>): LauncherEntry[];
13
+ /** Registers the engines machine-wide: the registry, LauncherInstalled.dat, the .uproject file association. */
14
+ export declare function registerEngines(engines: readonly UnrealEngine[], selectorFrom: UnrealEngine | null, ctx: Pick<ToolCtx, 'pcInstalls'>, aliases?: readonly {
15
+ readonly id: string;
16
+ readonly dir: string;
17
+ }[]): Promise<void>;
18
+ /**
19
+ * While the sandbox runs (the daemon, every few minutes): saves what the launcher learned about engines it installed
20
+ * on the data disk and, when something is new, registers them and lists them for the Unreal page.
21
+ */
22
+ export declare function captureLauncherEngines(): Promise<void>;
23
+ /** The project's engine on PATH, its variables, the guide's facts; the engines registered. */
24
+ export declare function setupUnrealEngines(env: ToolEnv, config: Toolchains['unreal'], ctx: ToolCtx): Promise<void>;
@@ -1,4 +1,164 @@
1
- import { notAvailableYet } from '../not-yet.js';
2
- export async function setupUnrealEngines(env, _config, _ctx) {
3
- notAvailableYet(env, 'Unreal engines');
1
+ // Unreal engines, for each project: every engine the sandbox has (this PC's, mapped read-only or as folders you
2
+ // registered; the launcher's and other folders in tools\unreal\engines; source builds in project roots) is registered
3
+ // (the registry, the launcher's LauncherInstalled.dat, the .uproject file association), and the engine the project's
4
+ // .uproject names (else the newest) goes on its PATH with UE_ROOT, the derived data cache on the data disk and, with
5
+ // Unreal 5.8's MCP plugin, the editor's MCP server in Claude Code. Machine-wide writes only add; a project that joins
6
+ // later only adds its own engine choice.
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { readBoundedFile } from '../../../shared/io/bounded.js';
10
+ import { chooseEngine, ddcVars, engineExtraDirs, engineToolDirs, engineVars, fileAssociationPlan, findEngines, findUproject, hasMcpPlugin, incompleteDirs, itemSizes, launcherManifestsDir, launcherEntriesFor, registryPlan, UNREAL_MCP_URL, unrealSummaryLine, uprojectAssociation, versionSelectorIn, versionText, } from '../../../shared/unreal/index.js';
11
+ import { errMsg } from '../../../core/util/errors.js';
12
+ import { exists, mkdirp } from '../../runtime/fs.js';
13
+ import { log, warn } from '../../runtime/log.js';
14
+ import { dryRun } from '../../runtime/paths.js';
15
+ import { runScriptWithJsonAsync } from '../../runtime/process.js';
16
+ import { projectWorkspace } from '../../runtime/project-context.js';
17
+ import { session } from '../../runtime/session.js';
18
+ import { setUnrealMcp, writeAddonMcpServers } from '../../setup/addon-mcp.js';
19
+ import { saveLauncherState, savedEntries, writeLauncherEntries } from './epic-launcher.js';
20
+ import { DDC_DIR, ENGINES_DIR, SAVED_DIR, updateUnrealStatus } from './unreal-status.js';
21
+ const SAVED_MANIFESTS = path.join(SAVED_DIR, 'Manifests');
22
+ /** The real disk, read bounded. */
23
+ export const DISK = {
24
+ exists: (file) => exists(file),
25
+ read: (file, max) => readBoundedFile(file, max),
26
+ list: (dir) => {
27
+ try {
28
+ return fs.readdirSync(dir).slice(0, 500);
29
+ }
30
+ catch {
31
+ return []; // Not a folder: nothing in it.
32
+ }
33
+ },
34
+ };
35
+ /** This PC's engines the host mapped (session.json's pcInstalls). */
36
+ export function mappedEngines(ctx) {
37
+ return ctx.pcInstalls.items
38
+ .filter((i) => i.kind === 'unreal')
39
+ .map((i) => ({
40
+ guest: i.guest,
41
+ id: i.id,
42
+ ...(i.writable ? { writable: true } : {}),
43
+ ...(i.buildId ? { buildId: i.buildId } : {}),
44
+ }));
45
+ }
46
+ /** The project roots of this sandbox (their folders may hold source builds). */
47
+ function projectRoots() {
48
+ const roots = (session.mappedProjects ?? []).filter((m) => m.root).map((m) => m.guest);
49
+ return [...new Set([...roots, ...(session.roots ?? [])])].filter((r) => exists(r));
50
+ }
51
+ /** The engines the launcher installed in tools\unreal\engines (its saved manifests and entries name them). */
52
+ function launcherDirs() {
53
+ return [...savedEntries().map((e) => e.InstallLocation), ...itemSizes(SAVED_MANIFESTS).keys()];
54
+ }
55
+ /** Every engine this sandbox has (an install the launcher has not finished is not one yet). */
56
+ export function sandboxEngines(ctx, disk = DISK) {
57
+ const incomplete = incompleteDirs(launcherManifestsDir(process.env.ProgramData ?? 'C:\\ProgramData'), SAVED_MANIFESTS);
58
+ return findEngines({
59
+ mapped: mappedEngines(ctx),
60
+ enginesRoot: ENGINES_DIR,
61
+ launcherDirs: launcherDirs(),
62
+ projectRoots: projectRoots(),
63
+ }, disk).filter((e) => !incomplete.has(path.win32.normalize(e.dir).toLowerCase()));
64
+ }
65
+ /** The LauncherInstalled.dat entries for the installed builds: the launcher's own saved ones, else made from them. */
66
+ export function launcherEntries(engines, ctx) {
67
+ return launcherEntriesFor(engines, savedEntries(), ctx.pcInstalls.items);
68
+ }
69
+ /** Registers the engines machine-wide: the registry, LauncherInstalled.dat, the .uproject file association. */
70
+ export async function registerEngines(engines, selectorFrom, ctx, aliases = []) {
71
+ const selector = selectorFrom ? versionSelectorIn(selectorFrom.dir) : null;
72
+ const values = [
73
+ ...registryPlan(engines, aliases),
74
+ ...(selector && exists(selector) ? fileAssociationPlan(selector) : []),
75
+ ];
76
+ if (values.length)
77
+ await runScriptWithJsonAsync('set-registry-values.ps1', 'PlanFile', { values });
78
+ try {
79
+ writeLauncherEntries(launcherEntries(engines, ctx));
80
+ }
81
+ catch (e) {
82
+ warn("Unreal: the Epic Games Launcher's LauncherInstalled.dat was left as it is (" + errMsg(e) + ')');
83
+ }
84
+ }
85
+ /**
86
+ * While the sandbox runs (the daemon, every few minutes): saves what the launcher learned about engines it installed
87
+ * on the data disk and, when something is new, registers them and lists them for the Unreal page.
88
+ */
89
+ export async function captureLauncherEngines() {
90
+ const unreal = session.toolchains.unreal;
91
+ if (dryRun || unreal.enabled !== true || !unreal.persistEngines || !saveLauncherState())
92
+ return;
93
+ const ctx = { pcInstalls: session.pcInstalls ?? { items: [] } };
94
+ const engines = sandboxEngines(ctx);
95
+ await registerEngines(engines, engines[0] ?? null, ctx);
96
+ const sizes = itemSizes(SAVED_MANIFESTS);
97
+ updateUnrealStatus((s) => ({ ...s, engines: engines.map((e) => statusEngine(e, sizes)) }));
98
+ }
99
+ const statusEngine = (e, sizes) => ({
100
+ id: e.id,
101
+ dir: e.dir,
102
+ version: versionText(e.version),
103
+ build: e.build,
104
+ origin: e.origin,
105
+ writable: e.writable,
106
+ sizeBytes: sizes.get(e.dir.toLowerCase()) ?? null,
107
+ });
108
+ /** The project's engine on PATH, its variables, the guide's facts; the engines registered. */
109
+ export async function setupUnrealEngines(env, config, ctx) {
110
+ const workspace = projectWorkspace();
111
+ const engines = sandboxEngines(ctx);
112
+ const uproject = findUproject(workspace, DISK);
113
+ const text = uproject ? readBoundedFile(uproject, 1024 * 1024) : null;
114
+ const association = (text ? uprojectAssociation(text.toString('utf8')) : null) ?? { kind: 'none' };
115
+ const choice = chooseEngine(engines, association);
116
+ const engine = choice.engine;
117
+ if (dryRun) {
118
+ log('(dry run) would register ' + engines.length + ' Unreal engines and use ' + (engine?.dir ?? 'none'));
119
+ return;
120
+ }
121
+ try {
122
+ await registerEngines(engines, engine ?? engines[0] ?? null, ctx, engine && choice.alias ? [{ id: choice.alias, dir: engine.dir }] : []);
123
+ }
124
+ catch (e) {
125
+ warn('Unreal: registering the engines failed (' + errMsg(e) + '); Build.bat and RunUAT.bat still work from PATH');
126
+ }
127
+ const ddc = config.derivedDataCache === 'persisted' ? mkdirp(DDC_DIR) : null;
128
+ Object.assign(env.vars, ddcVars(config.derivedDataCache, DDC_DIR));
129
+ if (engine) {
130
+ env.prepend.push(...engineToolDirs(engine.dir, DISK));
131
+ env.append.push(...engineExtraDirs(engine.dir, DISK));
132
+ Object.assign(env.vars, engineVars(engine));
133
+ }
134
+ const mcp = engine && hasMcpPlugin(engine, DISK) ? UNREAL_MCP_URL : null;
135
+ if (mcp || !ctx.duringSession) {
136
+ setUnrealMcp(mcp);
137
+ try {
138
+ writeAddonMcpServers();
139
+ }
140
+ catch (e) {
141
+ warn("Unreal: the editor's MCP server was not added to Claude Code (" + errMsg(e) + ')');
142
+ }
143
+ }
144
+ const sizes = itemSizes(SAVED_MANIFESTS);
145
+ const status = updateUnrealStatus((s) => ({
146
+ ...s,
147
+ engines: engines.map((e) => statusEngine(e, sizes)),
148
+ projects: [
149
+ ...s.projects.filter((p) => p.workspace.toLowerCase() !== workspace.toLowerCase()),
150
+ { workspace, engine: engine?.dir ?? null, reason: choice.reason },
151
+ ].slice(-64),
152
+ ddc: { mode: config.derivedDataCache, dir: ddc },
153
+ vaults: ctx.pcInstalls.items.filter((i) => i.kind === 'unreal-vault').map((i) => i.guest),
154
+ mcpUrl: mcp ?? (ctx.duringSession ? s.mcpUrl : null),
155
+ }));
156
+ env.summary.push(unrealSummaryLine(status, workspace));
157
+ log('Unreal: ' +
158
+ engines.length +
159
+ ' engine(s); ' +
160
+ (engine ? 'this project uses ' + engine.id + ' at ' + engine.dir : 'none for this project') +
161
+ ' (' +
162
+ choice.reason +
163
+ ')');
4
164
  }
@@ -0,0 +1,13 @@
1
+ import { type UnrealStatus } from '../../../shared/unreal/index.js';
2
+ export declare const UNREAL_DIR: string;
3
+ /** Where the Epic Games Launcher installs engines (persistEngines). */
4
+ export declare const ENGINES_DIR: string;
5
+ /** The engines' local derived data cache (derivedDataCache "persisted"). */
6
+ export declare const DDC_DIR: string;
7
+ /** The launcher's manifests and entries of the engines in ENGINES_DIR, kept for later boots. */
8
+ export declare const SAVED_DIR: string;
9
+ export declare function readUnrealStatus(): UnrealStatus;
10
+ /** Changes the status with `change` and writes it (whole, atomically). */
11
+ export declare function updateUnrealStatus(change: (s: UnrealStatus) => UnrealStatus): UnrealStatus;
12
+ /** A fresh status at boot: the machine parts start over, the engines and projects are found again. */
13
+ export declare function resetUnrealStatus(): void;
@@ -0,0 +1,41 @@
1
+ // tools\unreal\status.json: what the sandbox found for Unreal, for the guide and the Unreal page on the PC. The boot's
2
+ // machine-wide step writes the launcher, Visual Studio and debugger parts, each project's step its engine; every write
3
+ // keeps what the other parts wrote.
4
+ import path from 'node:path';
5
+ import { readValid, writeJsonAtomic } from '../../../shared/io/json.js';
6
+ import { UNREAL_STATUS_FILE, UnrealStatusSchema } from '../../../shared/unreal/index.js';
7
+ import { mkdirp } from '../../runtime/fs.js';
8
+ import { tool } from '../tool-env.js';
9
+ export const UNREAL_DIR = tool('unreal');
10
+ /** Where the Epic Games Launcher installs engines (persistEngines). */
11
+ export const ENGINES_DIR = path.join(UNREAL_DIR, 'engines');
12
+ /** The engines' local derived data cache (derivedDataCache "persisted"). */
13
+ export const DDC_DIR = path.join(UNREAL_DIR, 'ddc');
14
+ /** The launcher's manifests and entries of the engines in ENGINES_DIR, kept for later boots. */
15
+ export const SAVED_DIR = path.join(UNREAL_DIR, 'launcher');
16
+ const FILE = path.join(UNREAL_DIR, UNREAL_STATUS_FILE);
17
+ const EMPTY = {
18
+ updatedAt: '',
19
+ engines: [],
20
+ projects: [],
21
+ launcher: { installed: false, installLocation: null, problem: null, freeBytes: null, needBytes: 0 },
22
+ visualStudio: { version: '', path: '', how: '', check: '', ok: false },
23
+ debugger: null,
24
+ ddc: { mode: 'local', dir: null },
25
+ vaults: [],
26
+ mcpUrl: null,
27
+ };
28
+ export function readUnrealStatus() {
29
+ return readValid(UnrealStatusSchema, FILE) ?? EMPTY;
30
+ }
31
+ /** Changes the status with `change` and writes it (whole, atomically). */
32
+ export function updateUnrealStatus(change) {
33
+ const next = { ...change(readUnrealStatus()), updatedAt: new Date().toISOString() };
34
+ mkdirp(UNREAL_DIR);
35
+ writeJsonAtomic(FILE, next);
36
+ return next;
37
+ }
38
+ /** A fresh status at boot: the machine parts start over, the engines and projects are found again. */
39
+ export function resetUnrealStatus() {
40
+ updateUnrealStatus(() => EMPTY);
41
+ }
@@ -0,0 +1,6 @@
1
+ /** KitsRoot10 from the registry (64-bit view), or null. */
2
+ export declare function kitsRoot(): string | null;
3
+ /** The debuggers' x64 folder when cdb.exe is in it, else null. */
4
+ export declare function debuggersDir(root?: string | null): string | null;
5
+ /** The debuggers' folder, installed with the SDK installer when missing; null when there is no SDK to add them to. */
6
+ export declare function ensureDebuggers(): Promise<string | null>;
@@ -0,0 +1,57 @@
1
+ // The Windows SDK's Debugging Tools (cdb, windbg, kd) for reading Unreal crash dumps: not part of the SDK Visual Studio
2
+ // installs, so the Windows SDK installer (downloaded once into tools\visualstudio, Microsoft's signature checked) adds
3
+ // only that feature into the SDK the registry names (KitsRoot10). Visual Studio kept on the data disk has its SDK
4
+ // there, so the debuggers are installed once and come back with it (~45 s, 370 MB).
5
+ import path from 'node:path';
6
+ import { SYSTEM } from '../../../shared/system.js';
7
+ import { runInstaller } from '../../runtime/pools.js';
8
+ import { execOutput } from '../../runtime/process.js';
9
+ import { exists } from '../../runtime/fs.js';
10
+ import { log } from '../../runtime/log.js';
11
+ import { SIGNERS, signedInstaller } from '../installers.js';
12
+ import { tool } from '../tool-env.js';
13
+ /** The Windows SDK 10.0.22621 installer (the SDK version Visual Studio's Unreal components install). */
14
+ const SDK_SETUP_URL = 'https://go.microsoft.com/fwlink/?linkid=2311806';
15
+ const SDK_SETUP = tool('visualstudio', 'winsdksetup-22621.exe');
16
+ /** KitsRoot10 from the registry (64-bit view), or null. */
17
+ export function kitsRoot() {
18
+ const out = execOutput(SYSTEM.reg(), [
19
+ 'query',
20
+ 'HKLM\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots',
21
+ '/v',
22
+ 'KitsRoot10',
23
+ '/reg:64',
24
+ ]);
25
+ const m = /KitsRoot10\s+REG_SZ\s+(.+)$/im.exec(out);
26
+ return m ? m[1].trim() : null;
27
+ }
28
+ /** The debuggers' x64 folder when cdb.exe is in it, else null. */
29
+ export function debuggersDir(root = kitsRoot()) {
30
+ if (!root)
31
+ return null;
32
+ const dir = path.join(root, 'Debuggers', 'x64');
33
+ return exists(path.join(dir, 'cdb.exe')) ? dir : null;
34
+ }
35
+ /** The debuggers' folder, installed with the SDK installer when missing; null when there is no SDK to add them to. */
36
+ export async function ensureDebuggers() {
37
+ const root = kitsRoot();
38
+ if (!root)
39
+ return null;
40
+ const have = debuggersDir(root);
41
+ if (have)
42
+ return have;
43
+ const setup = await signedInstaller(SDK_SETUP_URL, SDK_SETUP, 'the Windows SDK installer, for its debuggers (~1.5 MB, then ~100 MB)', SIGNERS.microsoft);
44
+ log('Installing the Windows SDK debuggers (cdb, windbg) into ' + root + ' ...');
45
+ const code = await runInstaller(setup, [
46
+ '/features',
47
+ 'OptionId.WindowsDesktopDebuggers',
48
+ '/quiet',
49
+ '/norestart',
50
+ '/ceip',
51
+ 'off',
52
+ ]);
53
+ const dir = debuggersDir(root);
54
+ if (!dir)
55
+ throw new Error('the Windows SDK installer did not add the debuggers (exit ' + code + ')');
56
+ return dir;
57
+ }
@@ -5,7 +5,8 @@ import { SYSTEM } from '../../../shared/system.js';
5
5
  import { dryRun } from '../../runtime/paths.js';
6
6
  import { execAsync, scriptOutput } from '../../runtime/process.js';
7
7
  import { exists } from '../../runtime/fs.js';
8
- import { log } from '../../runtime/log.js';
8
+ import { log, warn } from '../../runtime/log.js';
9
+ import { errMsg } from '../../../core/util/errors.js';
9
10
  import { arch, tool } from '../tool-env.js';
10
11
  import { installFromGithub } from '../install.js';
11
12
  /**
@@ -33,6 +34,8 @@ export async function setupMesa(env, config) {
33
34
  await setupLavapipe(env, dir);
34
35
  }
35
36
  const VULKAN_RUNTIME = 'https://sdk.lunarg.com/sdk/download/latest/windows/vulkan-runtime-components.zip';
37
+ /** Used when "latest" is not published yet (LunarG announces a version before its runtime zip is up: a 404). */
38
+ const VULKAN_RUNTIME_PINNED = 'https://sdk.lunarg.com/sdk/download/1.4.350.0/windows/vulkan-runtime-components.zip';
36
39
  /**
37
40
  * Software Vulkan: Mesa's lavapipe plus the Khronos loader (vulkan-1.dll, from LunarG's runtime components; Windows
38
41
  * Sandbox has no Vulkan loader). The loader ignores VK_DRIVER_FILES in elevated processes, and everything in the
@@ -43,15 +46,19 @@ const setupLavapipe = async (env, mesaDir) => {
43
46
  if (!exists(icd))
44
47
  throw new Error('this Mesa build has no lavapipe (' + path.basename(icd) + ')');
45
48
  const dir = tool('vulkan');
46
- if (!exists(path.join(dir, 'vulkan-1.dll'))) {
47
- log('Downloading the Vulkan loader (~10 MB once) ...');
49
+ // vulkaninfo.exe too: a copy kept from an older runtime zip may lack it.
50
+ if (!exists(path.join(dir, 'vulkan-1.dll')) || !exists(path.join(dir, 'vulkaninfo.exe'))) {
51
+ log('Downloading the Vulkan loader (~20 MB once) ...');
48
52
  // LunarG publishes no checksum for this zip; it comes over HTTPS from LunarG, the Khronos SDK's maintainer.
49
53
  // The zip has x86 and x64 folders: only the 64-bit loader (and vulkaninfo) is extracted.
50
- await downloader.archive(VULKAN_RUNTIME, dir, {
51
- locate: 'vulkan-1.dll',
52
- fileName: 'vulkan-runtime.zip',
53
- members: ['*/x64/*'],
54
- });
54
+ const fetchRuntime = (url) => downloader.archive(url, dir, { locate: 'vulkan-1.dll', fileName: 'vulkan-runtime.zip', members: ['*/x64/*'] });
55
+ try {
56
+ await fetchRuntime(VULKAN_RUNTIME);
57
+ }
58
+ catch (e) {
59
+ warn('The latest Vulkan runtime could not be downloaded (' + errMsg(e) + '); using version 1.4.350.0.');
60
+ await fetchRuntime(VULKAN_RUNTIME_PINNED);
61
+ }
55
62
  }
56
63
  const register = (file) => execAsync(SYSTEM.reg(), ['add', 'HKLM\\SOFTWARE\\Khronos\\Vulkan\\Drivers', '/v', file, '/t', 'REG_DWORD', '/d', '0', '/f'], { stdio: 'ignore' });
57
64
  await register(icd);
@@ -14,6 +14,13 @@ export declare function cargoShim(env: ToolEnv, rust: Toolchains['rust']): void;
14
14
  * run at once in the whole sandbox and, with linker "lld", has MinGW's gcc use Rust's own lld instead of GNU ld.
15
15
  */
16
16
  export declare function linkGate(env: ToolEnv, rust: Toolchains['rust'], host: string): void;
17
+ /**
18
+ * MSVC host: link.exe writes an executable into a mapped folder (the workspace, C:\AgentData) as all zeros, so a
19
+ * fresh build script or test program fails with "The file or directory is corrupted and unreadable" (os error 1392).
20
+ * Rust's own lld (a copy of rust-lld.exe named lld-link.exe, which is how lld picks its mode) writes them whole.
21
+ * `rust.linker` "gnu" keeps link.exe; a linker the user set for the target wins.
22
+ */
23
+ export declare function lldLink(env: ToolEnv, rust: Toolchains['rust'], host: string): void;
17
24
  /**
18
25
  * Cargo defaults that fit Windows, written only when there is no $CARGO_HOME\config.toml yet: fewer parallel jobs
19
26
  * (parallel rustc ran out of paging file) and, for the GNU host, only line tables as debug info (full debug info made