windows-development-sandbox 1.27.0 → 1.27.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 (40) hide show
  1. package/dist/app/features/unreal/unreal.run.js +13 -3
  2. package/dist/bin/agent-sandbox.js +2 -2
  3. package/dist/bin/{chunk-ZXCAVVS4.js → chunk-4TLSWPG7.js} +1 -1
  4. package/dist/bin/{chunk-JXL6ZOO3.js → chunk-QUHNXHVG.js} +1 -1
  5. package/dist/bin/{chunk-6WWMBCT2.js → chunk-RSIX7C6D.js} +60 -17
  6. package/dist/bin/claude-host.js +2 -2
  7. package/dist/bin/claude-sbx.js +2 -2
  8. package/dist/bin/codex-sbx.js +2 -2
  9. package/dist/bin/gemini-sbx.js +2 -2
  10. package/dist/bin/sandbox.js +2 -2
  11. package/dist/bin/sbx-hide.js +2 -2
  12. package/dist/bin/sbx-show.js +2 -2
  13. package/dist/cli/commands/launch/launch.js +2 -1
  14. package/dist/guest/setup/unity-agent.js +4 -1
  15. package/dist/guest/toolchains/engines/epic-launcher.d.ts +1 -1
  16. package/dist/guest/toolchains/engines/epic-launcher.js +11 -7
  17. package/dist/guest/toolchains/engines/unreal-status.js +1 -1
  18. package/dist/guest/toolchains/native/mesa.js +35 -11
  19. package/dist/guest/toolchains/native/unreal.js +1 -0
  20. package/dist/host/accounts/deliver.d.ts +3 -0
  21. package/dist/host/accounts/deliver.js +19 -0
  22. package/dist/host/accounts/source.d.ts +2 -0
  23. package/dist/host/accounts/source.js +2 -0
  24. package/dist/host/pc-installs/discover.js +3 -2
  25. package/dist/host/pc-installs/epic.js +3 -0
  26. package/dist/host/pc-installs/unity.d.ts +2 -0
  27. package/dist/host/pc-installs/unity.js +12 -3
  28. package/dist/host/pc-installs/unreal.d.ts +2 -0
  29. package/dist/host/pc-installs/unreal.js +24 -8
  30. package/dist/runtime/daemon.js +38 -14
  31. package/dist/runtime/main.js +38 -14
  32. package/dist/selftest/engines.test.js +30 -1
  33. package/dist/selftest/mixamo.test.js +1 -1
  34. package/dist/selftest/service-accounts.test.js +12 -1
  35. package/dist/shared/unreal/status.d.ts +1 -0
  36. package/dist/shared/unreal/status.js +2 -0
  37. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  38. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  39. package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
  40. package/package.json +1 -1
@@ -19,7 +19,11 @@ import { ConfigFile } from '../settings/index.js';
19
19
  import { forgetEngineFolder, openEpicLauncher, registerEngineFolder, unrealOverview, } from './unreal.def.js';
20
20
  const win = path.win32;
21
21
  /** The launcher the sandbox installs (the 64-bit one of launcher 17 and later). */
22
- const GUEST_LAUNCHER = 'C:\\Program Files (x86)\\Epic Games\\Launcher\\Portal\\Binaries\\Win64\\EpicGamesLauncher.exe';
22
+ /** Where the launcher installs in the sandbox: Program Files for newer launchers, (x86) for older ones. */
23
+ const GUEST_LAUNCHERS = [
24
+ 'C:\\Program Files\\Epic Games\\Launcher\\Portal\\Binaries\\Win64\\EpicGamesLauncher.exe',
25
+ 'C:\\Program Files (x86)\\Epic Games\\Launcher\\Portal\\Binaries\\Win64\\EpicGamesLauncher.exe',
26
+ ];
23
27
  const MAX_PACKS_SHOWN = 40;
24
28
  const s = (text) => safeText(text);
25
29
  const sn = (text) => (text === null ? null : safeText(text));
@@ -204,10 +208,16 @@ export const openEpicLauncherRun = implement(openEpicLauncher, async (ctx, _inpu
204
208
  const status = new RunDir(ctx.dataPath).readSandboxStatus();
205
209
  if (status?.phase !== 'ready')
206
210
  fail('The sandbox is not ready yet.', 'Try again in a moment.');
207
- if (!readStatus(ctx.dataPath)?.launcher.installed)
211
+ const launcher = readStatus(ctx.dataPath)?.launcher;
212
+ if (!launcher?.installed)
208
213
  fail('The Epic Games Launcher is not installed in this sandbox.', 'It is installed when the sandbox is set up for Unreal with the licence accepted (Unreal set-up settings).');
209
214
  io.progress('Opening the Epic Games Launcher in the sandbox…');
210
- const result = await new SandboxChannel(ctx.dataPath).exec(['C:\\Windows\\explorer.exe', GUEST_LAUNCHER], status.guestWorkspace ?? 'C:\\', 30);
215
+ const result = await new SandboxChannel(ctx.dataPath).exec(
216
+ // The guest's status only picks one of the known paths.
217
+ [
218
+ 'C:\\Windows\\explorer.exe',
219
+ GUEST_LAUNCHERS.find((p) => p.toLowerCase() === launcher.exe?.toLowerCase()) ?? GUEST_LAUNCHERS[0],
220
+ ], status.guestWorkspace ?? 'C:\\', 30);
211
221
  // explorer.exe returns 1 even when it started the program.
212
222
  if (!result.ok && result.exitCode !== 1)
213
223
  fail('The sandbox could not open it: ' + safeText(result.message));
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-JXL6ZOO3.js";
6
- import "./chunk-6WWMBCT2.js";
5
+ } from "./chunk-QUHNXHVG.js";
6
+ import "./chunk-RSIX7C6D.js";
7
7
 
8
8
  // src/bin/agent-sandbox.ts
9
9
  run(process.argv.slice(2));
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
2
2
  import {
3
3
  attach
4
- } from "./chunk-6WWMBCT2.js";
4
+ } from "./chunk-RSIX7C6D.js";
5
5
  export {
6
6
  attach
7
7
  };
@@ -257,7 +257,7 @@ import {
257
257
  withOperationCommands,
258
258
  working,
259
259
  writeJson
260
- } from "./chunk-6WWMBCT2.js";
260
+ } from "./chunk-RSIX7C6D.js";
261
261
 
262
262
  // node_modules/commander/lib/error.js
263
263
  var require_error = __commonJS({
@@ -62605,7 +62605,8 @@ function pcEnv() {
62605
62605
  programData: env.ProgramData ?? env.PROGRAMDATA ?? "C:\\ProgramData",
62606
62606
  programFiles: env.ProgramFiles ?? "C:\\Program Files",
62607
62607
  env,
62608
- userVariable: savedUserVariable
62608
+ userVariable: savedUserVariable,
62609
+ drives: () => diskUsage(env.ProgramData ?? "C:\\").map((d) => d.drive)
62609
62610
  };
62610
62611
  }
62611
62612
  function notAvailableYet(kind) {
@@ -62926,6 +62927,18 @@ function howToSave(spec) {
62926
62927
  if (spec.returns) return "sign in once in this sandbox, or: sandbox account import " + spec.kind;
62927
62928
  return spec.sources.includes("typed") ? "save one: sandbox account login " + spec.kind : "save one: sandbox account import " + spec.kind + " --from <file>";
62928
62929
  }
62930
+ function takeFromPc(kind, pc, store) {
62931
+ const source = HOST_SOURCES[kind];
62932
+ if (!pc || !source.fromPc) return null;
62933
+ let payload = null;
62934
+ try {
62935
+ payload = source.fromPc(pc);
62936
+ } catch {
62937
+ return null;
62938
+ }
62939
+ if (payload) store.save(kind, payload, "pc", source.summary(payload));
62940
+ return payload;
62941
+ }
62929
62942
  function prepareServiceAccounts(i) {
62930
62943
  const store = i.store ?? new ServiceAccountStore(i.storageRoot);
62931
62944
  const leases = i.leases ?? new AccountLeases(i.storageRoot);
@@ -62955,6 +62968,7 @@ function prepareServiceAccounts(i) {
62955
62968
  } catch {
62956
62969
  unreadable = true;
62957
62970
  }
62971
+ if (!payload && !unreadable) payload = takeFromPc(kind, i.pc, store);
62958
62972
  if (spec.singleHolder) {
62959
62973
  const claim2 = leases.claim(kind, dataSet, i.launchId, payload ? fingerprint(payload) : null, i.isRunning);
62960
62974
  if (!claim2.ok) {
@@ -63275,6 +63289,8 @@ var UnrealStatusSchema = external_exports.object({
63275
63289
  ).max(64),
63276
63290
  launcher: external_exports.object({
63277
63291
  installed: external_exports.boolean(),
63292
+ /** Its program (Program Files or Program Files (x86), by launcher version); null when there is none. */
63293
+ exe: Text3().nullable().default(null),
63278
63294
  /** Where the launcher installs engines; null when it could not be set. */
63279
63295
  installLocation: Text3().nullable(),
63280
63296
  /** Why engines would not go to the data disk (shown loudly), null when they do. */
@@ -63394,6 +63410,7 @@ function findEpicGames(text, wanted) {
63394
63410
  const found = [];
63395
63411
  for (const i of text ? launcherInstallations(text) : []) {
63396
63412
  if (!APP.test(i.appName) || /^\.+$/.test(i.appName) || /^UE_\d/.test(i.appName)) continue;
63413
+ if (i.ids?.namespaceId.toLowerCase() === "ue" || /^UE_\d/i.test(path117.win32.basename(i.installLocation))) continue;
63397
63414
  if (names && !names.has(i.appName.toLowerCase())) continue;
63398
63415
  if (found.some((f) => f.id.toLowerCase() === i.appName.toLowerCase())) continue;
63399
63416
  found.push({
@@ -63804,10 +63821,12 @@ function listedEditors(files) {
63804
63821
  }
63805
63822
  function findUnityEditorsOnPc(files, wanted) {
63806
63823
  const candidates = [];
63807
- const hubFolders = [win8.join(files.programFiles, "Unity", "Hub", "Editor"), secondaryRoot(files)].filter(
63808
- (d) => d !== null
63809
- );
63810
- for (const folder of hubFolders)
63824
+ const hubFolders = [
63825
+ win8.join(files.programFiles, "Unity", "Hub", "Editor"),
63826
+ secondaryRoot(files),
63827
+ ...files.otherHubFolders ?? []
63828
+ ].filter((d) => d !== null);
63829
+ for (const folder of new Set(hubFolders.map((d) => win8.resolve(d))))
63811
63830
  for (const name2 of files.list(folder).slice(0, MAX_LISTED).sort())
63812
63831
  if (UNITY_VERSION.test(name2)) candidates.push({ version: name2, root: win8.join(folder, name2) });
63813
63832
  candidates.push(...listedEditors(files));
@@ -63853,7 +63872,11 @@ function unityPcFiles(pc) {
63853
63872
  isDir,
63854
63873
  list: listDir,
63855
63874
  appData: pc.appData,
63856
- programFiles: pc.programFiles ?? "C:\\Program Files"
63875
+ programFiles: pc.programFiles ?? "C:\\Program Files",
63876
+ otherHubFolders: (pc.drives?.() ?? []).flatMap((d) => [
63877
+ win8.join(d, "Program Files", "Unity", "Hub", "Editor"),
63878
+ win8.join(d, "Unity", "Hub", "Editor")
63879
+ ])
63857
63880
  };
63858
63881
  }
63859
63882
  function assetCacheEnv(pc) {
@@ -63924,10 +63947,17 @@ function findUnrealInstalls(files, wanted) {
63924
63947
  ...ids ? { launcher: ids } : {}
63925
63948
  });
63926
63949
  };
63927
- for (const i of files.launcherInstalled ? launcherInstallations(files.launcherInstalled) : [])
63928
- if (APP_NAME.test(i.appName)) installed3(i.installLocation, i.ids ?? void 0);
63929
- for (const name2 of files.list(files.defaultRoot).filter((n) => APP_NAME.test(n)).sort())
63930
- installed3(win9.join(files.defaultRoot, name2));
63950
+ const listed = files.launcherInstalled ? launcherInstallations(files.launcherInstalled) : [];
63951
+ for (const i of listed) if (APP_NAME.test(i.appName)) installed3(i.installLocation, i.ids ?? void 0);
63952
+ const roots = [files.defaultRoot, ...files.otherRoots ?? [], ...listed.map((i) => win9.dirname(i.installLocation))];
63953
+ const seen = /* @__PURE__ */ new Set();
63954
+ for (const root of roots) {
63955
+ const key3 = win9.resolve(root).toLowerCase();
63956
+ if (seen.has(key3) || seen.size >= 32) continue;
63957
+ seen.add(key3);
63958
+ for (const name2 of files.list(root).filter((n) => APP_NAME.test(n)).sort())
63959
+ installed3(win9.join(root, name2));
63960
+ }
63931
63961
  return found;
63932
63962
  }
63933
63963
  function pcSourceBuilds(files) {
@@ -63983,6 +64013,10 @@ function unrealPcFiles(pc) {
63983
64013
  launcherInstalled: dat ? dat.toString("utf8") : null,
63984
64014
  builds: query.status === 0 ? String(query.stdout ?? "") : "",
63985
64015
  defaultRoot: win9.join(pc.programFiles ?? "C:\\Program Files", "Epic Games"),
64016
+ otherRoots: (pc.drives?.() ?? []).flatMap((d) => [
64017
+ win9.join(d, "Program Files", "Epic Games"),
64018
+ win9.join(d, "Epic Games")
64019
+ ]),
63986
64020
  list: listDir2,
63987
64021
  read: readBoundedFile
63988
64022
  };
@@ -64066,11 +64100,11 @@ var unrealVault = {
64066
64100
  // src/host/pc-installs/discover.ts
64067
64101
  var MAX_PC_INSTALLS = 40;
64068
64102
  var PC_INSTALL_SOURCES = [
64069
- steamInstalls,
64070
- epicInstalls,
64071
64103
  unrealFolders,
64072
64104
  unrealInstalls,
64073
64105
  unrealVault,
64106
+ steamInstalls,
64107
+ epicInstalls,
64074
64108
  unityInstalls,
64075
64109
  unityAssets
64076
64110
  ];
@@ -83686,7 +83720,10 @@ var unrealView = defineView({
83686
83720
  // src/app/features/unreal/unreal.run.ts
83687
83721
  import path192 from "node:path";
83688
83722
  var win10 = path192.win32;
83689
- var GUEST_LAUNCHER = "C:\\Program Files (x86)\\Epic Games\\Launcher\\Portal\\Binaries\\Win64\\EpicGamesLauncher.exe";
83723
+ var GUEST_LAUNCHERS = [
83724
+ "C:\\Program Files\\Epic Games\\Launcher\\Portal\\Binaries\\Win64\\EpicGamesLauncher.exe",
83725
+ "C:\\Program Files (x86)\\Epic Games\\Launcher\\Portal\\Binaries\\Win64\\EpicGamesLauncher.exe"
83726
+ ];
83690
83727
  var MAX_PACKS_SHOWN = 40;
83691
83728
  var s = (text) => safeText(text);
83692
83729
  var sn = (text) => text === null ? null : safeText(text);
@@ -83848,14 +83885,19 @@ var openEpicLauncherRun = implement(openEpicLauncher, async (ctx, _input, io) =>
83848
83885
  fail("No sandbox is running.", "Start one for a project with a .uproject; the launcher opens in its window.");
83849
83886
  const status = new RunDir(ctx.dataPath).readSandboxStatus();
83850
83887
  if (status?.phase !== "ready") fail("The sandbox is not ready yet.", "Try again in a moment.");
83851
- if (!readStatus(ctx.dataPath)?.launcher.installed)
83888
+ const launcher = readStatus(ctx.dataPath)?.launcher;
83889
+ if (!launcher?.installed)
83852
83890
  fail(
83853
83891
  "The Epic Games Launcher is not installed in this sandbox.",
83854
83892
  "It is installed when the sandbox is set up for Unreal with the licence accepted (Unreal set-up settings)."
83855
83893
  );
83856
83894
  io.progress("Opening the Epic Games Launcher in the sandbox\u2026");
83857
83895
  const result = await new SandboxChannel(ctx.dataPath).exec(
83858
- ["C:\\Windows\\explorer.exe", GUEST_LAUNCHER],
83896
+ // The guest's status only picks one of the known paths.
83897
+ [
83898
+ "C:\\Windows\\explorer.exe",
83899
+ GUEST_LAUNCHERS.find((p) => p.toLowerCase() === launcher.exe?.toLowerCase()) ?? GUEST_LAUNCHERS[0]
83900
+ ],
83859
83901
  status.guestWorkspace ?? "C:\\",
83860
83902
  30
83861
83903
  );
@@ -85146,7 +85188,7 @@ async function attachOpened(ctx, session) {
85146
85188
  nextStep("sandbox attach " + session);
85147
85189
  return 0;
85148
85190
  }
85149
- const { attach: attach2 } = await import("./chunk-ZXCAVVS4.js");
85191
+ const { attach: attach2 } = await import("./chunk-4TLSWPG7.js");
85150
85192
  return attach2({ ...ctx, options: { ...ctx.options, positionals: [session] } });
85151
85193
  }
85152
85194
  function definitionHandler(def) {
@@ -85694,7 +85736,8 @@ var Launcher = class {
85694
85736
  dataPath: run3.dataPath,
85695
85737
  launchId,
85696
85738
  // A new launch needs no sandbox running, so a lease held elsewhere is from one that stopped.
85697
- isRunning: () => isSandboxRunning()
85739
+ isRunning: () => isSandboxRunning(),
85740
+ pc: pcEnv()
85698
85741
  });
85699
85742
  const store = generateOnly ? null : await prepareStorePackages(storeApps(config3.apps.install), {
85700
85743
  storageRoot: ctx.storageRoot,
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-JXL6ZOO3.js";
6
- import "./chunk-6WWMBCT2.js";
5
+ } from "./chunk-QUHNXHVG.js";
6
+ import "./chunk-RSIX7C6D.js";
7
7
 
8
8
  // src/bin/claude-host.ts
9
9
  var args = process.argv.slice(2);
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-JXL6ZOO3.js";
6
- import "./chunk-6WWMBCT2.js";
5
+ } from "./chunk-QUHNXHVG.js";
6
+ import "./chunk-RSIX7C6D.js";
7
7
 
8
8
  // src/bin/claude-sbx.ts
9
9
  run(process.argv.slice(2), { harness: "claude" });
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-JXL6ZOO3.js";
6
- import "./chunk-6WWMBCT2.js";
5
+ } from "./chunk-QUHNXHVG.js";
6
+ import "./chunk-RSIX7C6D.js";
7
7
 
8
8
  // src/bin/codex-sbx.ts
9
9
  run(process.argv.slice(2), { harness: "codex" });
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-JXL6ZOO3.js";
6
- import "./chunk-6WWMBCT2.js";
5
+ } from "./chunk-QUHNXHVG.js";
6
+ import "./chunk-RSIX7C6D.js";
7
7
 
8
8
  // src/bin/gemini-sbx.ts
9
9
  run(process.argv.slice(2), { harness: "gemini" });
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-JXL6ZOO3.js";
6
- import "./chunk-6WWMBCT2.js";
5
+ } from "./chunk-QUHNXHVG.js";
6
+ import "./chunk-RSIX7C6D.js";
7
7
 
8
8
  // src/bin/sandbox.ts
9
9
  run(process.argv.slice(2));
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-JXL6ZOO3.js";
6
- import "./chunk-6WWMBCT2.js";
5
+ } from "./chunk-QUHNXHVG.js";
6
+ import "./chunk-RSIX7C6D.js";
7
7
 
8
8
  // src/bin/sbx-hide.ts
9
9
  run(process.argv.slice(2), { command: "hide" });
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-JXL6ZOO3.js";
6
- import "./chunk-6WWMBCT2.js";
5
+ } from "./chunk-QUHNXHVG.js";
6
+ import "./chunk-RSIX7C6D.js";
7
7
 
8
8
  // src/bin/sbx-show.ts
9
9
  run(process.argv.slice(2), { command: "show" });
@@ -10,7 +10,7 @@ import { findSecretFiles } from '../../../host/profile/index.js';
10
10
  import { RepoGuardStore } from '../../../host/guard/index.js';
11
11
  import { hostPublicIp, prepareVpn, readVpnLine, vpnNotes } from '../../../host/vpn/index.js';
12
12
  import { prepareGithubToken } from '../../../host/github/index.js';
13
- import { prepareServiceAccounts } from '../../../host/accounts/index.js';
13
+ import { pcEnv, prepareServiceAccounts } from '../../../host/accounts/index.js';
14
14
  import { prepareStorePackages } from '../../../host/apps/index.js';
15
15
  import { storeApps } from '../../../shared/apps/index.js';
16
16
  import { discoverPcInstalls } from '../../../host/pc-installs/index.js';
@@ -194,6 +194,7 @@ class Launcher {
194
194
  launchId,
195
195
  // A new launch needs no sandbox running, so a lease held elsewhere is from one that stopped.
196
196
  isRunning: () => isSandboxRunning(),
197
+ pc: pcEnv(),
197
198
  });
198
199
  // Store apps (apps.install): their packages are fetched with this PC's own Store tooling.
199
200
  const store = generateOnly
@@ -10,7 +10,10 @@ import { setEngineMcp, writeAddonMcpServers } from './addon-mcp.js';
10
10
  async function syncSkills(unityExe, agent) {
11
11
  const home = agent === 'claude' ? { CLAUDE_CONFIG_DIR: claudeDir } : { CODEX_HOME: path.join(stateDir, 'codex') };
12
12
  try {
13
- await execAsync(unityExe, ['skill', 'sync', agent], { env: { ...process.env, ...UNITY_CLI_ENV, ...home } });
13
+ // The Unity CLI's client names: claude-code, codex.
14
+ await execAsync(unityExe, ['skill', 'sync', agent === 'claude' ? 'claude-code' : 'codex'], {
15
+ env: { ...process.env, ...UNITY_CLI_ENV, ...home },
16
+ });
14
17
  return true;
15
18
  }
16
19
  catch (e) {
@@ -1,5 +1,5 @@
1
1
  import { type LauncherEntry } from '../../../shared/unreal/index.js';
2
- /** The launcher's program (launcher 17+ is 64-bit; older ones were in Win32). */
2
+ /** The launcher's program: newer MSIs install into Program Files, older ones into Program Files (x86). */
3
3
  export declare function launcherExe(): string | null;
4
4
  /** Downloads the launcher's MSI ahead of the install (once). */
5
5
  export declare function fetchLauncher(): Promise<void>;
@@ -22,14 +22,18 @@ const SAVED_MANIFESTS = path.join(SAVED_DIR, 'Manifests');
22
22
  const SAVED_ENTRIES = path.join(SAVED_DIR, 'LauncherInstalled.json');
23
23
  const programData = () => process.env.ProgramData ?? 'C:\\ProgramData';
24
24
  const localAppData = () => process.env.LOCALAPPDATA ?? '';
25
- /** The launcher's program (launcher 17+ is 64-bit; older ones were in Win32). */
25
+ /** The launcher's program: newer MSIs install into Program Files, older ones into Program Files (x86). */
26
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
- }
27
+ const roots = [
28
+ process.env.ProgramFiles ?? 'C:\\Program Files',
29
+ process.env['ProgramFiles(x86)'] ?? 'C:\\Program Files (x86)',
30
+ ];
31
+ for (const root of roots)
32
+ for (const arch of ['Win64', 'Win32']) {
33
+ const exe = path.join(root, 'Epic Games', 'Launcher', 'Portal', 'Binaries', arch, 'EpicGamesLauncher.exe');
34
+ if (exists(exe))
35
+ return exe;
36
+ }
33
37
  return null;
34
38
  }
35
39
  /** Downloads the launcher's MSI ahead of the install (once). */
@@ -18,7 +18,7 @@ const EMPTY = {
18
18
  updatedAt: '',
19
19
  engines: [],
20
20
  projects: [],
21
- launcher: { installed: false, installLocation: null, problem: null, freeBytes: null, needBytes: 0 },
21
+ launcher: { installed: false, exe: null, installLocation: null, problem: null, freeBytes: null, needBytes: 0 },
22
22
  visualStudio: { version: '', path: '', how: '', check: '', ok: false },
23
23
  debugger: null,
24
24
  ddc: { mode: 'local', dir: null },
@@ -1,11 +1,11 @@
1
1
  // Mesa for running OpenGL and Vulkan programs without a GPU (llvmpipe, lavapipe).
2
2
  import path from 'node:path';
3
- import { downloader } from '../../../shared/download/index.js';
3
+ import { downloader, fetchJson } from '../../../shared/download/index.js';
4
4
  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, warn } from '../../runtime/log.js';
8
+ import { log } from '../../runtime/log.js';
9
9
  import { errMsg } from '../../../core/util/errors.js';
10
10
  import { arch, tool } from '../tool-env.js';
11
11
  import { installFromGithub } from '../install.js';
@@ -33,9 +33,28 @@ export async function setupMesa(env, config) {
33
33
  if (config.vulkan)
34
34
  await setupLavapipe(env, dir);
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
+ /** LunarG's newest SDK version per platform ({"windows": "1.4.363.1", ...}). */
37
+ const VULKAN_LATEST = 'https://vulkan.lunarg.com/sdk/latest.json';
38
+ const runtimeUrl = (version) => 'https://sdk.lunarg.com/sdk/download/' + version + '/windows/vulkan-runtime-components.zip';
39
+ /** Known to have the runtime zip: used when LunarG cannot be asked, or none of its versions has one. */
40
+ const VULKAN_PINNED = '1.4.363.0';
41
+ /**
42
+ * The versions to try for the runtime zip, newest first: LunarG's latest, its x.y.z.0 (a point release such as
43
+ * 1.4.363.1 reissues the SDK without the runtime zip), then the pinned one.
44
+ */
45
+ async function runtimeVersions() {
46
+ let latest = null;
47
+ try {
48
+ const value = (await fetchJson(VULKAN_LATEST)).windows;
49
+ if (typeof value === 'string' && /^\d{1,2}\.\d{1,2}\.\d{1,4}\.\d{1,3}$/.test(value))
50
+ latest = value;
51
+ }
52
+ catch {
53
+ // LunarG not reachable: the pinned version.
54
+ }
55
+ const found = latest ? [latest, latest.replace(/\.\d+$/, '.0')] : [];
56
+ return [...new Set([...found, VULKAN_PINNED])];
57
+ }
39
58
  /**
40
59
  * Software Vulkan: Mesa's lavapipe plus the Khronos loader (vulkan-1.dll, from LunarG's runtime components; Windows
41
60
  * Sandbox has no Vulkan loader). The loader ignores VK_DRIVER_FILES in elevated processes, and everything in the
@@ -52,12 +71,17 @@ const setupLavapipe = async (env, mesaDir) => {
52
71
  // LunarG publishes no checksum for this zip; it comes over HTTPS from LunarG, the Khronos SDK's maintainer.
53
72
  // The zip has x86 and x64 folders: only the 64-bit loader (and vulkaninfo) is extracted.
54
73
  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);
74
+ const versions = await runtimeVersions();
75
+ for (const [n, version] of versions.entries()) {
76
+ try {
77
+ await fetchRuntime(runtimeUrl(version));
78
+ log('Vulkan loader ' + version + ' installed');
79
+ break;
80
+ }
81
+ catch (e) {
82
+ if (n === versions.length - 1)
83
+ throw new Error('the Vulkan loader could not be downloaded (' + errMsg(e) + ')');
84
+ }
61
85
  }
62
86
  }
63
87
  const register = (file) => execAsync(SYSTEM.reg(), ['add', 'HKLM\\SOFTWARE\\Khronos\\Vulkan\\Drivers', '/v', file, '/t', 'REG_DWORD', '/d', '0', '/f'], { stdio: 'ignore' });
@@ -110,6 +110,7 @@ export async function setupUnreal(env, config, _ctx) {
110
110
  ...s,
111
111
  launcher: {
112
112
  installed: launcher !== null,
113
+ exe: launcher,
113
114
  installLocation: place.installLocation,
114
115
  problem: place.problem,
115
116
  freeBytes: place.freeBytes,
@@ -2,6 +2,7 @@ import type { BootWant } from '../../core/local-config/index.js';
2
2
  import type { Config } from '../../core/types.js';
3
3
  import { type Payloads, type ServiceKind, type UsedBy } from '../../shared/accounts/index.js';
4
4
  import { AccountLeases } from './leases.js';
5
+ import type { PcEnv } from './source.js';
5
6
  import { ServiceAccountStore } from './store.js';
6
7
  export interface AccountDelivery {
7
8
  readonly payloads: Payloads;
@@ -22,5 +23,7 @@ export interface DeliveryInput {
22
23
  readonly isRunning: (dataSet: string) => boolean;
23
24
  readonly store?: ServiceAccountStore;
24
25
  readonly leases?: AccountLeases;
26
+ /** This PC's folders: a shared kind with nothing saved takes this PC's own sign-in (left out: never). */
27
+ readonly pc?: PcEnv;
25
28
  }
26
29
  export declare function prepareServiceAccounts(i: DeliveryInput): AccountDelivery;
@@ -2,6 +2,7 @@ import { fingerprint, SERVICE_KINDS, SERVICE_SPECS, serviceWanted, } from '../..
2
2
  import { setNameOf } from '../storage/index.js';
3
3
  import { takeHandoffs } from './handoff.js';
4
4
  import { AccountLeases } from './leases.js';
5
+ import { HOST_SOURCES } from './sources.js';
5
6
  import { ServiceAccountStore } from './store.js';
6
7
  /** How to get a first sign-in of a kind, for the note when none is saved. */
7
8
  function howToSave(spec) {
@@ -21,6 +22,22 @@ function howToSave(spec) {
21
22
  ? 'save one: sandbox account login ' + spec.kind
22
23
  : 'save one: sandbox account import ' + spec.kind + ' --from <file>';
23
24
  }
25
+ /** The first time a shared kind has nothing saved: this PC's own sign-in, saved like `sandbox account import`. */
26
+ function takeFromPc(kind, pc, store) {
27
+ const source = HOST_SOURCES[kind];
28
+ if (!pc || !source.fromPc)
29
+ return null;
30
+ let payload = null;
31
+ try {
32
+ payload = source.fromPc(pc);
33
+ }
34
+ catch {
35
+ return null; // This PC's program has no usable sign-in: the note says how to save one.
36
+ }
37
+ if (payload)
38
+ store.save(kind, payload, 'pc', source.summary(payload));
39
+ return payload;
40
+ }
24
41
  export function prepareServiceAccounts(i) {
25
42
  const store = i.store ?? new ServiceAccountStore(i.storageRoot);
26
43
  const leases = i.leases ?? new AccountLeases(i.storageRoot);
@@ -53,6 +70,8 @@ export function prepareServiceAccounts(i) {
53
70
  catch {
54
71
  unreadable = true;
55
72
  }
73
+ if (!payload && !unreadable)
74
+ payload = takeFromPc(kind, i.pc, store);
56
75
  if (spec.singleHolder) {
57
76
  const claim = leases.claim(kind, dataSet, i.launchId, payload ? fingerprint(payload) : null, i.isRunning);
58
77
  if (!claim.ok) {
@@ -10,6 +10,8 @@ export interface PcEnv {
10
10
  readonly env?: Readonly<Record<string, string | undefined>>;
11
11
  /** A variable of this Windows user's saved environment (HKCU\Environment), or null when it is not set. */
12
12
  readonly userVariable?: (name: string) => string | null;
13
+ /** This PC's local drives ("D:\\"), asked only by the searches that look on every drive. */
14
+ readonly drives?: () => readonly string[];
13
15
  }
14
16
  /** The value of `name` in `reg query <key> /v <name>` output, or null. */
15
17
  export declare function parseRegValue(text: string, name: string): string | null;
@@ -2,6 +2,7 @@
2
2
  // (fromFile, `sandbox account import <kind> --from <file>`), or typed (fromAnswers, `sandbox account login <kind>`).
3
3
  // One module per kind under kinds/; sources.ts lists them.
4
4
  import { spawnSync } from 'node:child_process';
5
+ import { diskUsage } from '../../core/disk.js';
5
6
  import { fail } from '../../core/util/index.js';
6
7
  import { SYSTEM } from '../../shared/system.js';
7
8
  import { refuseInSelfTest } from '../test-guard.js';
@@ -35,6 +36,7 @@ export function pcEnv() {
35
36
  programFiles: env.ProgramFiles ?? 'C:\\Program Files',
36
37
  env,
37
38
  userVariable: savedUserVariable,
39
+ drives: () => diskUsage(env.ProgramData ?? 'C:\\').map((d) => d.drive),
38
40
  };
39
41
  }
40
42
  /** What a kind whose host side is not written yet says. */
@@ -10,12 +10,13 @@ import { unrealFolders, unrealInstalls } from './unreal.js';
10
10
  import { unrealVault } from './unreal-vault.js';
11
11
  /** At most this many installs are mapped (each one is a mapping in the .wsb). */
12
12
  export const MAX_PC_INSTALLS = 40;
13
+ // Engines first: a folder is mapped once, as the first source that finds it.
13
14
  export const PC_INSTALL_SOURCES = [
14
- steamInstalls,
15
- epicInstalls,
16
15
  unrealFolders,
17
16
  unrealInstalls,
18
17
  unrealVault,
18
+ steamInstalls,
19
+ epicInstalls,
19
20
  unityInstalls,
20
21
  unityAssets,
21
22
  ];
@@ -11,6 +11,9 @@ export function findEpicGames(text, wanted) {
11
11
  for (const i of text ? launcherInstallations(text) : []) {
12
12
  if (!APP.test(i.appName) || /^\.+$/.test(i.appName) || /^UE_\d/.test(i.appName))
13
13
  continue;
14
+ // Engines and what the launcher puts into one (the Fab plugin lists the engine's folder) are unreal.ts's.
15
+ if (i.ids?.namespaceId.toLowerCase() === 'ue' || /^UE_\d/i.test(path.win32.basename(i.installLocation)))
16
+ continue;
14
17
  if (names && !names.has(i.appName.toLowerCase()))
15
18
  continue;
16
19
  if (found.some((f) => f.id.toLowerCase() === i.appName.toLowerCase()))
@@ -10,6 +10,8 @@ export interface UnityPcFiles {
10
10
  list(dir: string): string[];
11
11
  readonly appData: string;
12
12
  readonly programFiles: string;
13
+ /** Hub's folder on every other drive (D:\Program Files\Unity\Hub\Editor, D:\Unity\Hub\Editor ...). */
14
+ readonly otherHubFolders?: readonly string[];
13
15
  }
14
16
  /** The editors of this PC: all of them (true) or the listed versions. Each version once, each folder once. */
15
17
  export declare function findUnityEditorsOnPc(files: UnityPcFiles, wanted: true | readonly string[]): PcInstall[];
@@ -1,4 +1,5 @@
1
- // Unity editors of this PC (pcInstalls.unityEditors): Unity Hub's default folder (<Program Files>\Unity\Hub\Editor),
1
+ // Unity editors of this PC (pcInstalls.unityEditors): Unity Hub's default folder on every drive
2
+ // (<drive>\Program Files\Unity\Hub\Editor, <drive>\Unity\Hub\Editor),
2
3
  // the folder named by %APPDATA%\UnityHub\secondaryInstallPath.json and the editors in editors-v2.json. An editor counts
3
4
  // only with Editor\Unity.exe; its folder is mapped read-only at C:\PcInstalls\unity\<version>. Every file is read
4
5
  // bounded and every name is checked: what Hub wrote is data, never trusted.
@@ -67,8 +68,12 @@ function listedEditors(files) {
67
68
  /** The editors of this PC: all of them (true) or the listed versions. Each version once, each folder once. */
68
69
  export function findUnityEditorsOnPc(files, wanted) {
69
70
  const candidates = [];
70
- const hubFolders = [win.join(files.programFiles, 'Unity', 'Hub', 'Editor'), secondaryRoot(files)].filter((d) => d !== null);
71
- for (const folder of hubFolders)
71
+ const hubFolders = [
72
+ win.join(files.programFiles, 'Unity', 'Hub', 'Editor'),
73
+ secondaryRoot(files),
74
+ ...(files.otherHubFolders ?? []),
75
+ ].filter((d) => d !== null);
76
+ for (const folder of new Set(hubFolders.map((d) => win.resolve(d))))
72
77
  for (const name of files.list(folder).slice(0, MAX_LISTED).sort())
73
78
  if (UNITY_VERSION.test(name))
74
79
  candidates.push({ version: name, root: win.join(folder, name) });
@@ -125,6 +130,10 @@ export function unityPcFiles(pc) {
125
130
  list: listDir,
126
131
  appData: pc.appData,
127
132
  programFiles: pc.programFiles ?? 'C:\\Program Files',
133
+ otherHubFolders: (pc.drives?.() ?? []).flatMap((d) => [
134
+ win.join(d, 'Program Files', 'Unity', 'Hub', 'Editor'),
135
+ win.join(d, 'Unity', 'Hub', 'Editor'),
136
+ ]),
128
137
  };
129
138
  }
130
139
  /** The cache root the environment names: this terminal's variable, else the saved user one. */
@@ -11,6 +11,8 @@ export interface UnrealPcFiles {
11
11
  readonly builds: string;
12
12
  /** The launcher's default engines folder (<Program Files>\Epic Games). */
13
13
  readonly defaultRoot: string;
14
+ /** More folders that may hold UE_x.y folders: the same places on every drive (D:\Program Files\Epic Games ...). */
15
+ readonly otherRoots?: readonly string[];
14
16
  readonly list: (dir: string) => string[];
15
17
  readonly read: (file: string, max: number) => Buffer | null;
16
18
  }
@@ -1,6 +1,7 @@
1
1
  // Unreal Engine versions of this PC (pcInstalls.unrealEngines): the launcher's LauncherInstalled.dat (engines it
2
- // installed, with its own ids) and the launcher's default folders (<Program Files>\Epic Games\UE_*, for an install
3
- // whose LauncherInstalled.dat is gone). Each must hold Engine\Build\Build.version; it is mapped read-only at
2
+ // installed, with its own ids) and folders named UE_x.y in the launcher's default places on every drive
3
+ // (<drive>\Program Files\Epic Games, <drive>\Epic Games) and next to what it lists (an install whose entry is gone).
4
+ // Each must hold Engine\Build\Build.version; it is mapped read-only at
4
5
  // C:\PcInstalls\unreal\UE_5.4. Source builds (HKCU\Software\Epic Games\Unreal Engine\Builds) are NOT mapped by
5
6
  // themselves, as UBT writes inside an engine. pcInstalls.unrealEngineFolders: folders you registered, mapped writable
6
7
  // (dir-<name>; a source build of this PC keeps its build id). Every file is read bounded.
@@ -54,14 +55,25 @@ export function findUnrealInstalls(files, wanted) {
54
55
  ...(ids ? { launcher: ids } : {}),
55
56
  });
56
57
  };
57
- for (const i of files.launcherInstalled ? launcherInstallations(files.launcherInstalled) : [])
58
+ const listed = files.launcherInstalled ? launcherInstallations(files.launcherInstalled) : [];
59
+ for (const i of listed)
58
60
  if (APP_NAME.test(i.appName))
59
61
  installed(i.installLocation, i.ids ?? undefined);
60
- for (const name of files
61
- .list(files.defaultRoot)
62
- .filter((n) => APP_NAME.test(n))
63
- .sort())
64
- installed(win.join(files.defaultRoot, name));
62
+ // Folders named like an engine: the default places, and the folders that hold what the launcher installed (an
63
+ // engine whose entry is gone often sits next to one that is listed, or next to a game).
64
+ const roots = [files.defaultRoot, ...(files.otherRoots ?? []), ...listed.map((i) => win.dirname(i.installLocation))];
65
+ const seen = new Set();
66
+ for (const root of roots) {
67
+ const key = win.resolve(root).toLowerCase();
68
+ if (seen.has(key) || seen.size >= 32)
69
+ continue;
70
+ seen.add(key);
71
+ for (const name of files
72
+ .list(root)
73
+ .filter((n) => APP_NAME.test(n))
74
+ .sort())
75
+ installed(win.join(root, name));
76
+ }
65
77
  return found;
66
78
  }
67
79
  /** Source builds this PC registered (the Builds key). They are not mapped by themselves: UBT writes inside them. */
@@ -130,6 +142,10 @@ export function unrealPcFiles(pc) {
130
142
  launcherInstalled: dat ? dat.toString('utf8') : null,
131
143
  builds: query.status === 0 ? String(query.stdout ?? '') : '',
132
144
  defaultRoot: win.join(pc.programFiles ?? 'C:\\Program Files', 'Epic Games'),
145
+ otherRoots: (pc.drives?.() ?? []).flatMap((d) => [
146
+ win.join(d, 'Program Files', 'Epic Games'),
147
+ win.join(d, 'Epic Games'),
148
+ ]),
133
149
  list: listDir,
134
150
  read: readBoundedFile,
135
151
  };
@@ -30451,6 +30451,8 @@ var UnrealStatusSchema = external_exports.object({
30451
30451
  ).max(64),
30452
30452
  launcher: external_exports.object({
30453
30453
  installed: external_exports.boolean(),
30454
+ /** Its program (Program Files or Program Files (x86), by launcher version); null when there is none. */
30455
+ exe: Text2().nullable().default(null),
30454
30456
  /** Where the launcher installs engines; null when it could not be set. */
30455
30457
  installLocation: Text2().nullable(),
30456
30458
  /** Why engines would not go to the data disk (shown loudly), null when they do. */
@@ -31568,7 +31570,7 @@ var EMPTY = {
31568
31570
  updatedAt: "",
31569
31571
  engines: [],
31570
31572
  projects: [],
31571
- launcher: { installed: false, installLocation: null, problem: null, freeBytes: null, needBytes: 0 },
31573
+ launcher: { installed: false, exe: null, installLocation: null, problem: null, freeBytes: null, needBytes: 0 },
31572
31574
  visualStudio: { version: "", path: "", how: "", check: "", ok: false },
31573
31575
  debugger: null,
31574
31576
  ddc: { mode: "local", dir: null },
@@ -31596,11 +31598,15 @@ var SAVED_ENTRIES = path44.join(SAVED_DIR, "LauncherInstalled.json");
31596
31598
  var programData = () => process.env.ProgramData ?? "C:\\ProgramData";
31597
31599
  var localAppData2 = () => process.env.LOCALAPPDATA ?? "";
31598
31600
  function launcherExe() {
31599
- const root = path44.join(process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)", "Epic Games", "Launcher");
31600
- for (const arch2 of ["Win64", "Win32"]) {
31601
- const exe = path44.join(root, "Portal", "Binaries", arch2, "EpicGamesLauncher.exe");
31602
- if (exists(exe)) return exe;
31603
- }
31601
+ const roots = [
31602
+ process.env.ProgramFiles ?? "C:\\Program Files",
31603
+ process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)"
31604
+ ];
31605
+ for (const root of roots)
31606
+ for (const arch2 of ["Win64", "Win32"]) {
31607
+ const exe = path44.join(root, "Epic Games", "Launcher", "Portal", "Binaries", arch2, "EpicGamesLauncher.exe");
31608
+ if (exists(exe)) return exe;
31609
+ }
31604
31610
  return null;
31605
31611
  }
31606
31612
  async function fetchLauncher() {
@@ -34573,8 +34579,19 @@ async function setupMesa(env, config2) {
34573
34579
  );
34574
34580
  if (config2.vulkan) await setupLavapipe(env, dir);
34575
34581
  }
34576
- var VULKAN_RUNTIME = "https://sdk.lunarg.com/sdk/download/latest/windows/vulkan-runtime-components.zip";
34577
- var VULKAN_RUNTIME_PINNED = "https://sdk.lunarg.com/sdk/download/1.4.350.0/windows/vulkan-runtime-components.zip";
34582
+ var VULKAN_LATEST = "https://vulkan.lunarg.com/sdk/latest.json";
34583
+ var runtimeUrl = (version2) => "https://sdk.lunarg.com/sdk/download/" + version2 + "/windows/vulkan-runtime-components.zip";
34584
+ var VULKAN_PINNED = "1.4.363.0";
34585
+ async function runtimeVersions() {
34586
+ let latest = null;
34587
+ try {
34588
+ const value = (await fetchJson(VULKAN_LATEST)).windows;
34589
+ if (typeof value === "string" && /^\d{1,2}\.\d{1,2}\.\d{1,4}\.\d{1,3}$/.test(value)) latest = value;
34590
+ } catch {
34591
+ }
34592
+ const found = latest ? [latest, latest.replace(/\.\d+$/, ".0")] : [];
34593
+ return [.../* @__PURE__ */ new Set([...found, VULKAN_PINNED])];
34594
+ }
34578
34595
  var setupLavapipe = async (env, mesaDir) => {
34579
34596
  const icd = path74.join(mesaDir, arch === "arm64" ? "lvp_icd.aarch64.json" : "lvp_icd.x86_64.json");
34580
34597
  if (!exists(icd)) throw new Error("this Mesa build has no lavapipe (" + path74.basename(icd) + ")");
@@ -34582,11 +34599,15 @@ var setupLavapipe = async (env, mesaDir) => {
34582
34599
  if (!exists(path74.join(dir, "vulkan-1.dll")) || !exists(path74.join(dir, "vulkaninfo.exe"))) {
34583
34600
  log("Downloading the Vulkan loader (~20 MB once) ...");
34584
34601
  const fetchRuntime = (url2) => downloader.archive(url2, dir, { locate: "vulkan-1.dll", fileName: "vulkan-runtime.zip", members: ["*/x64/*"] });
34585
- try {
34586
- await fetchRuntime(VULKAN_RUNTIME);
34587
- } catch (e) {
34588
- warn("The latest Vulkan runtime could not be downloaded (" + errMsg(e) + "); using version 1.4.350.0.");
34589
- await fetchRuntime(VULKAN_RUNTIME_PINNED);
34602
+ const versions = await runtimeVersions();
34603
+ for (const [n, version2] of versions.entries()) {
34604
+ try {
34605
+ await fetchRuntime(runtimeUrl(version2));
34606
+ log("Vulkan loader " + version2 + " installed");
34607
+ break;
34608
+ } catch (e) {
34609
+ if (n === versions.length - 1) throw new Error("the Vulkan loader could not be downloaded (" + errMsg(e) + ")");
34610
+ }
34590
34611
  }
34591
34612
  }
34592
34613
  const register = (file3) => execAsync(
@@ -35702,6 +35723,7 @@ async function setupUnreal(env, config2, _ctx) {
35702
35723
  ...s,
35703
35724
  launcher: {
35704
35725
  installed: launcher !== null,
35726
+ exe: launcher,
35705
35727
  installLocation: place.installLocation,
35706
35728
  problem: place.problem,
35707
35729
  freeBytes: place.freeBytes,
@@ -36960,7 +36982,9 @@ import path97 from "node:path";
36960
36982
  async function syncSkills(unityExe, agent) {
36961
36983
  const home = agent === "claude" ? { CLAUDE_CONFIG_DIR: claudeDir } : { CODEX_HOME: path97.join(stateDir, "codex") };
36962
36984
  try {
36963
- await execAsync(unityExe, ["skill", "sync", agent], { env: { ...process.env, ...UNITY_CLI_ENV, ...home } });
36985
+ await execAsync(unityExe, ["skill", "sync", agent === "claude" ? "claude-code" : "codex"], {
36986
+ env: { ...process.env, ...UNITY_CLI_ENV, ...home }
36987
+ });
36964
36988
  return true;
36965
36989
  } catch (e) {
36966
36990
  warn("Unity skills for " + agent + " were not synced: " + (e instanceof Error ? e.message : String(e)));
@@ -29528,6 +29528,8 @@ var UnrealStatusSchema = external_exports.object({
29528
29528
  ).max(64),
29529
29529
  launcher: external_exports.object({
29530
29530
  installed: external_exports.boolean(),
29531
+ /** Its program (Program Files or Program Files (x86), by launcher version); null when there is none. */
29532
+ exe: Text2().nullable().default(null),
29531
29533
  /** Where the launcher installs engines; null when it could not be set. */
29532
29534
  installLocation: Text2().nullable(),
29533
29535
  /** Why engines would not go to the data disk (shown loudly), null when they do. */
@@ -30875,7 +30877,7 @@ var EMPTY = {
30875
30877
  updatedAt: "",
30876
30878
  engines: [],
30877
30879
  projects: [],
30878
- launcher: { installed: false, installLocation: null, problem: null, freeBytes: null, needBytes: 0 },
30880
+ launcher: { installed: false, exe: null, installLocation: null, problem: null, freeBytes: null, needBytes: 0 },
30879
30881
  visualStudio: { version: "", path: "", how: "", check: "", ok: false },
30880
30882
  debugger: null,
30881
30883
  ddc: { mode: "local", dir: null },
@@ -30903,11 +30905,15 @@ var SAVED_ENTRIES = path34.join(SAVED_DIR, "LauncherInstalled.json");
30903
30905
  var programData = () => process.env.ProgramData ?? "C:\\ProgramData";
30904
30906
  var localAppData2 = () => process.env.LOCALAPPDATA ?? "";
30905
30907
  function launcherExe() {
30906
- const root = path34.join(process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)", "Epic Games", "Launcher");
30907
- for (const arch2 of ["Win64", "Win32"]) {
30908
- const exe = path34.join(root, "Portal", "Binaries", arch2, "EpicGamesLauncher.exe");
30909
- if (exists(exe)) return exe;
30910
- }
30908
+ const roots = [
30909
+ process.env.ProgramFiles ?? "C:\\Program Files",
30910
+ process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)"
30911
+ ];
30912
+ for (const root of roots)
30913
+ for (const arch2 of ["Win64", "Win32"]) {
30914
+ const exe = path34.join(root, "Epic Games", "Launcher", "Portal", "Binaries", arch2, "EpicGamesLauncher.exe");
30915
+ if (exists(exe)) return exe;
30916
+ }
30911
30917
  return null;
30912
30918
  }
30913
30919
  async function fetchLauncher() {
@@ -33859,8 +33865,19 @@ async function setupMesa(env, config3) {
33859
33865
  );
33860
33866
  if (config3.vulkan) await setupLavapipe(env, dir);
33861
33867
  }
33862
- var VULKAN_RUNTIME = "https://sdk.lunarg.com/sdk/download/latest/windows/vulkan-runtime-components.zip";
33863
- var VULKAN_RUNTIME_PINNED = "https://sdk.lunarg.com/sdk/download/1.4.350.0/windows/vulkan-runtime-components.zip";
33868
+ var VULKAN_LATEST = "https://vulkan.lunarg.com/sdk/latest.json";
33869
+ var runtimeUrl = (version2) => "https://sdk.lunarg.com/sdk/download/" + version2 + "/windows/vulkan-runtime-components.zip";
33870
+ var VULKAN_PINNED = "1.4.363.0";
33871
+ async function runtimeVersions() {
33872
+ let latest = null;
33873
+ try {
33874
+ const value = (await fetchJson(VULKAN_LATEST)).windows;
33875
+ if (typeof value === "string" && /^\d{1,2}\.\d{1,2}\.\d{1,4}\.\d{1,3}$/.test(value)) latest = value;
33876
+ } catch {
33877
+ }
33878
+ const found = latest ? [latest, latest.replace(/\.\d+$/, ".0")] : [];
33879
+ return [.../* @__PURE__ */ new Set([...found, VULKAN_PINNED])];
33880
+ }
33864
33881
  var setupLavapipe = async (env, mesaDir) => {
33865
33882
  const icd = path64.join(mesaDir, arch === "arm64" ? "lvp_icd.aarch64.json" : "lvp_icd.x86_64.json");
33866
33883
  if (!exists(icd)) throw new Error("this Mesa build has no lavapipe (" + path64.basename(icd) + ")");
@@ -33868,11 +33885,15 @@ var setupLavapipe = async (env, mesaDir) => {
33868
33885
  if (!exists(path64.join(dir, "vulkan-1.dll")) || !exists(path64.join(dir, "vulkaninfo.exe"))) {
33869
33886
  log("Downloading the Vulkan loader (~20 MB once) ...");
33870
33887
  const fetchRuntime = (url2) => downloader.archive(url2, dir, { locate: "vulkan-1.dll", fileName: "vulkan-runtime.zip", members: ["*/x64/*"] });
33871
- try {
33872
- await fetchRuntime(VULKAN_RUNTIME);
33873
- } catch (e) {
33874
- warn("The latest Vulkan runtime could not be downloaded (" + errMsg(e) + "); using version 1.4.350.0.");
33875
- await fetchRuntime(VULKAN_RUNTIME_PINNED);
33888
+ const versions = await runtimeVersions();
33889
+ for (const [n, version2] of versions.entries()) {
33890
+ try {
33891
+ await fetchRuntime(runtimeUrl(version2));
33892
+ log("Vulkan loader " + version2 + " installed");
33893
+ break;
33894
+ } catch (e) {
33895
+ if (n === versions.length - 1) throw new Error("the Vulkan loader could not be downloaded (" + errMsg(e) + ")");
33896
+ }
33876
33897
  }
33877
33898
  }
33878
33899
  const register = (file3) => execAsync(
@@ -34940,6 +34961,7 @@ async function setupUnreal(env, config3, _ctx) {
34940
34961
  ...s,
34941
34962
  launcher: {
34942
34963
  installed: launcher !== null,
34964
+ exe: launcher,
34943
34965
  installLocation: place.installLocation,
34944
34966
  problem: place.problem,
34945
34967
  freeBytes: place.freeBytes,
@@ -37670,7 +37692,9 @@ import path87 from "node:path";
37670
37692
  async function syncSkills(unityExe, agent) {
37671
37693
  const home = agent === "claude" ? { CLAUDE_CONFIG_DIR: claudeDir } : { CODEX_HOME: path87.join(stateDir, "codex") };
37672
37694
  try {
37673
- await execAsync(unityExe, ["skill", "sync", agent], { env: { ...process.env, ...UNITY_CLI_ENV, ...home } });
37695
+ await execAsync(unityExe, ["skill", "sync", agent === "claude" ? "claude-code" : "codex"], {
37696
+ env: { ...process.env, ...UNITY_CLI_ENV, ...home }
37697
+ });
37674
37698
  return true;
37675
37699
  } catch (e) {
37676
37700
  warn("Unity skills for " + agent + " were not synced: " + (e instanceof Error ? e.message : String(e)));
@@ -255,6 +255,7 @@ export const enginesSuite = {
255
255
  projects: [{ workspace: 'C:\\Projects\\Work\\MyGame', engine: pcEngine, reason: 'the project asks for 5.4' }],
256
256
  launcher: {
257
257
  installed: true,
258
+ exe: null,
258
259
  installLocation: enginesRoot,
259
260
  problem: null,
260
261
  freeBytes: null,
@@ -380,6 +381,14 @@ export const enginesSuite = {
380
381
  AppVersion: 'v',
381
382
  AppName: 'UE_5.3',
382
383
  },
384
+ {
385
+ InstallLocation: pcLauncher,
386
+ NamespaceId: 'ue',
387
+ ItemId: 'fab',
388
+ ArtifactId: 'FabPlugin_5.4',
389
+ AppVersion: '5.4.4-1+++UE5+Release-5.4-0.0.21',
390
+ AppName: 'FabPlugin_5.4',
391
+ },
383
392
  ],
384
393
  });
385
394
  const reg = '\r\nHKEY_CURRENT_USER\\Software\\Epic Games\\Unreal Engine\\Builds\r\n {2D9FCA5D-4E7B-4C12-8B6E-1A2B3C4D5E6F} REG_SZ ' +
@@ -401,13 +410,33 @@ export const enginesSuite = {
401
410
  t.check(pcFound.every((i) => i.host !== path.join(pcRoot, 'gone')), 'an entry whose folder is gone is left out');
402
411
  t.equal(findUnrealInstalls(files, ['5.6']).map((i) => i.host), [pcDefault], 'a list maps only those versions');
403
412
  t.equal(findUnrealInstalls({ ...files, launcherInstalled: null, builds: '' }, true).map((i) => i.id), ['UE_5.4', 'UE_5.6'], 'no LauncherInstalled.dat: the default folders');
413
+ const otherDrive = makeEngine(path.join(pcRoot, 'F', 'Program Files', 'Epic Games', 'UE_5.8'), [5, 8, 0], true);
414
+ t.equal(findUnrealInstalls({
415
+ ...files,
416
+ launcherInstalled: null,
417
+ defaultRoot: path.join(pcRoot, 'none'),
418
+ otherRoots: [path.join(pcRoot, 'F', 'Program Files', 'Epic Games')],
419
+ }, true).map((i) => i.host), [otherDrive], 'an engine in the default place on another drive is found');
420
+ const nextTo = JSON.stringify({
421
+ InstallationList: [
422
+ {
423
+ InstallLocation: path.join(pcRoot, 'F', 'Program Files', 'Epic Games', 'SomeGame'),
424
+ NamespaceId: 'fn',
425
+ ItemId: 'g',
426
+ ArtifactId: 'SomeGame',
427
+ AppVersion: '1',
428
+ AppName: 'SomeGame',
429
+ },
430
+ ],
431
+ });
432
+ t.equal(findUnrealInstalls({ ...files, launcherInstalled: nextTo, defaultRoot: path.join(pcRoot, 'none') }, true).map((i) => i.id), ['UE_5.8'], 'and one next to what the launcher lists');
404
433
  const folders = findEngineFolders({ read: DISK.read, builds: reg }, [
405
434
  pcSource,
406
435
  path.join(pcRoot, 'nothing'),
407
436
  'relative',
408
437
  ]);
409
438
  t.equal(folders.map((f) => [f.id, f.writable]), [['dir-UnrealEngine', true]], 'a registered folder is mapped writable; one without an engine is left out');
410
- t.equal(findEpicGames(dat, true).map((g) => g.id), ['Fortnite'], "the launcher's games are the epic-game installs (engines are not)");
439
+ t.equal(findEpicGames(dat, true).map((g) => g.id), ['Fortnite'], "the launcher's games are the epic-game installs (engines and the Fab plugin in one are not)");
411
440
  t.equal(findEpicGames(dat, ['nothing']), [], 'a list maps only those games');
412
441
  t.check(engineFolderProblem(pcSource, DISK.read) === null, 'Register an engine folder accepts an engine folder');
413
442
  t.includes(engineFolderProblem(pcRoot, DISK.read) ?? '', 'not an Unreal Engine folder', 'and refuses one without Build.version');
@@ -18,7 +18,7 @@ function statusWith(version) {
18
18
  },
19
19
  ],
20
20
  projects: [{ workspace: PROJECT.workspace, engine: 'C:\\E', reason: 'test' }],
21
- launcher: { installed: false, installLocation: null, problem: null, freeBytes: null, needBytes: 1 },
21
+ launcher: { installed: false, exe: null, installLocation: null, problem: null, freeBytes: null, needBytes: 1 },
22
22
  visualStudio: { version: '17.14', path: 'C:/VS', how: 'kept', check: 'ok', ok: true },
23
23
  debugger: null,
24
24
  ddc: { mode: 'persisted', dir: null },
@@ -18,7 +18,7 @@ import { clearSecrets, SecretStore } from '../host/secrets/index.js';
18
18
  import { applyServiceAccounts, readDelivered } from '../guest/accounts/apply.js';
19
19
  import { AccountWatch } from '../guest/accounts/watch.js';
20
20
  import { epicAdapter } from '../guest/accounts/kinds/epic.js';
21
- import { epicSettingsFile, fingerprint, readEpicLogin, SERVICE_KINDS, SERVICE_SPECS, serviceWanted, withEpicLogin, } from '../shared/accounts/index.js';
21
+ import { epicSettingsFile, epicSettingsFiles, fingerprint, readEpicLogin, SERVICE_KINDS, SERVICE_SPECS, serviceWanted, withEpicLogin, } from '../shared/accounts/index.js';
22
22
  import { RunDir } from '../shared/channel/index.js';
23
23
  import { decodeText, encodeText, readBoundedFile } from '../shared/io/index.js';
24
24
  const SIGN_IN = 'PlantedEpicSignIn' + 'AbCdEf0123456789+/'.repeat(6) + '==';
@@ -141,6 +141,17 @@ export const serviceAccountsSuite = {
141
141
  signIns.remove('epic');
142
142
  const first = deliver();
143
143
  t.check(!first.payloads.epic && first.leased.includes('epic') && launchLeases.holder('epic')?.launchId === 'L-boot', 'nothing saved yet: the lease is claimed anyway, so a first sign-in made there can come back');
144
+ const pcLocal = t.dir('svc-pc-local');
145
+ for (const file of epicSettingsFiles(pcLocal)) {
146
+ fs.mkdirSync(path.dirname(file), { recursive: true });
147
+ fs.writeFileSync(file, INI);
148
+ }
149
+ const thisPc = { localAppData: pcLocal, appData: t.dir('svc-pc-roaming'), programData: t.dir('svc-pc-data') };
150
+ signIns.remove('epic');
151
+ const fromPc = deliver({ pc: thisPc });
152
+ t.check(fromPc.payloads.epic?.data === SIGN_IN && signIns.info('epic')?.source === 'pc', "shared with nothing saved: this PC's own sign-in is taken and saved, as import would");
153
+ signIns.remove('epic');
154
+ t.check(!deliver().payloads.epic, 'without this PC (pc left out) nothing is taken');
144
155
  const sharedRun = await f.run({}, config);
145
156
  const launched = generateSandbox({
146
157
  packageRoot: t.packageRoot,
@@ -28,6 +28,7 @@ export declare const UnrealStatusSchema: z.ZodObject<{
28
28
  }, z.core.$strip>>;
29
29
  launcher: z.ZodObject<{
30
30
  installed: z.ZodBoolean;
31
+ exe: z.ZodDefault<z.ZodNullable<z.ZodString>>;
31
32
  installLocation: z.ZodNullable<z.ZodString>;
32
33
  problem: z.ZodNullable<z.ZodString>;
33
34
  freeBytes: z.ZodNullable<z.ZodNumber>;
@@ -30,6 +30,8 @@ export const UnrealStatusSchema = z.object({
30
30
  .max(64),
31
31
  launcher: z.object({
32
32
  installed: z.boolean(),
33
+ /** Its program (Program Files or Program Files (x86), by launcher version); null when there is none. */
34
+ exe: Text().nullable().default(null),
33
35
  /** Where the launcher installs engines; null when it could not be set. */
34
36
  installLocation: Text().nullable(),
35
37
  /** Why engines would not go to the data disk (shown loudly), null when they do. */
@@ -8,9 +8,9 @@ using System.Runtime.InteropServices;
8
8
  [assembly: AssemblyCompany("windows-development-sandbox (open source)")]
9
9
  [assembly: AssemblyProduct("Windows Development Sandbox")]
10
10
  [assembly: AssemblyCopyright("MIT License, windows-development-sandbox contributors")]
11
- [assembly: AssemblyVersion("1.27.0.0")]
12
- [assembly: AssemblyFileVersion("1.27.0.0")]
13
- [assembly: AssemblyInformationalVersion("1.27.0")]
11
+ [assembly: AssemblyVersion("1.27.1.0")]
12
+ [assembly: AssemblyFileVersion("1.27.1.0")]
13
+ [assembly: AssemblyInformationalVersion("1.27.1")]
14
14
  [assembly: ComVisible(false)]
15
15
  // computecore.dll and computestorage.dll are not KnownDLLs: load them from System32 only, never from the exe's folder.
16
16
  [assembly: DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
@@ -8,7 +8,7 @@ using System.Runtime.InteropServices;
8
8
  [assembly: AssemblyCompany("windows-development-sandbox (open source)")]
9
9
  [assembly: AssemblyProduct("Windows Development Sandbox")]
10
10
  [assembly: AssemblyCopyright("MIT License, windows-development-sandbox contributors")]
11
- [assembly: AssemblyVersion("1.27.0.0")]
12
- [assembly: AssemblyFileVersion("1.27.0.0")]
13
- [assembly: AssemblyInformationalVersion("1.27.0")]
11
+ [assembly: AssemblyVersion("1.27.1.0")]
12
+ [assembly: AssemblyFileVersion("1.27.1.0")]
13
+ [assembly: AssemblyInformationalVersion("1.27.1")]
14
14
  [assembly: ComVisible(false)]
@@ -9,7 +9,7 @@ using System.Runtime.InteropServices;
9
9
  [assembly: AssemblyCompany("windows-development-sandbox (open source)")]
10
10
  [assembly: AssemblyProduct("Windows Development Sandbox")]
11
11
  [assembly: AssemblyCopyright("MIT License, windows-development-sandbox contributors")]
12
- [assembly: AssemblyVersion("1.27.0.0")]
13
- [assembly: AssemblyFileVersion("1.27.0.0")]
14
- [assembly: AssemblyInformationalVersion("1.27.0")]
12
+ [assembly: AssemblyVersion("1.27.1.0")]
13
+ [assembly: AssemblyFileVersion("1.27.1.0")]
14
+ [assembly: AssemblyInformationalVersion("1.27.1")]
15
15
  [assembly: ComVisible(false)]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "windows-development-sandbox",
3
- "version": "1.27.0",
3
+ "version": "1.27.1",
4
4
  "description": "A disposable Windows Sandbox for development, with or without an AI agent (Claude Code, Codex, Gemini, opencode, ...): the folder you run it from is the workspace, toolchains and databases for your project install themselves, and nothing else on your PC is reachable.",
5
5
  "type": "module",
6
6
  "license": "MIT",