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
@@ -12,6 +12,7 @@ import { setupDotnet } from './native/dotnet.js';
12
12
  import { setupMesa } from './native/mesa.js';
13
13
  import { setupMsvc } from './native/msvc/index.js';
14
14
  import { fetchDirectX, setupUnreal } from './native/unreal.js';
15
+ import { setupUnrealEngines } from './engines/unreal-engines.js';
15
16
  import { fetchRustupInit, setupRust } from './native/rust.js';
16
17
  import { setupNode } from './languages/node.js';
17
18
  import { setupPwsh } from './tools/pwsh.js';
@@ -21,7 +22,7 @@ import { fetchPhp, setupPhp } from './languages/php.js';
21
22
  import { setupRuby } from './languages/ruby.js';
22
23
  import { fetchVcRuntime, setupVcRuntime } from './native/vcruntime.js';
23
24
  import { setupVscode } from './tools/vscode.js';
24
- import { ensureVisualStudio, prefetchVisualStudio, unrealUsesAutosdk, visualStudioWanted, } from './native/visual-studio.js';
25
+ import { ensureVisualStudio, prefetchVisualStudio, visualStudioSaved, visualStudioWanted, } from './native/visual-studio.js';
25
26
  import { duringSession } from '../runtime/project-context.js';
26
27
  import { setupSteam } from './steam/steam.js';
27
28
  import { setupGodot } from './engines/godot.js';
@@ -32,7 +33,7 @@ import { setupFlutter } from './mobile/flutter.js';
32
33
  import { setupDart } from './mobile/dart.js';
33
34
  import { setupReactNative } from './mobile/react-native.js';
34
35
  import { setupKotlin } from './languages/kotlin.js';
35
- import { rustHost, rustCaches, cargoDefaults, cargoShim, linkGate, resolveTargetDir, targetSummary, } from './native/rust-extras.js';
36
+ import { rustHost, rustCaches, cargoDefaults, cargoShim, linkGate, lldLink, resolveTargetDir, targetSummary, } from './native/rust-extras.js';
36
37
  import { setupPythonTools } from './languages/python-tools.js';
37
38
  export function plan(config, versions, o) {
38
39
  const apps = Boolean(o.apps && (o.apps.install.length || Object.keys(o.apps.custom).length));
@@ -42,8 +43,6 @@ export function plan(config, versions, o) {
42
43
  const android = wanted(config.android.enabled, 'android');
43
44
  // One Visual Studio for every toolchain that needs the real one (Unreal, Flutter desktop, Unity IL2CPP).
44
45
  const vs = visualStudioWanted(config, (setting, name) => wanted(setting, name));
45
- // Unreal on the portable MSVC (AutoSDK) needs that MSVC even when nothing else asked for it.
46
- const autosdk = unrealUsesAutosdk(config, (setting, name) => wanted(setting, name));
47
46
  const ctx = {
48
47
  refresh: o.refresh,
49
48
  pcInstalls: o.pcInstalls ?? { items: [] },
@@ -69,32 +68,38 @@ export function plan(config, versions, o) {
69
68
  enabled: wanted(config.cpp.enabled, 'cpp') || wanted(config.rust.enabled, 'rust'),
70
69
  install: (env) => setupCpp(env, config.cpp),
71
70
  },
72
- // Visual Studio brings MSVC and the real vswhere: the portable MSVC (and its stand-in vswhere) stays out.
71
+ // Visual Studio brings MSVC and the real vswhere: the portable MSVC (and its stand-in vswhere) stays out. Only the
72
+ // first install goes to the background: a saved copy is back in seconds, before the agent starts.
73
73
  {
74
74
  name: 'Visual Studio',
75
75
  enabled: vs !== null,
76
76
  shared: true,
77
- background: deferred,
77
+ background: deferred && !visualStudioSaved(vs),
78
78
  ...ahead(() => prefetchVisualStudio(vs)),
79
79
  install: async (env) => {
80
- await ensureVisualStudio(env, vs, { keepInstallFiles: config.unreal.keepInstallFiles, lowPriority: deferred });
80
+ await ensureVisualStudio(env, vs, { lowPriority: deferred });
81
81
  },
82
82
  },
83
83
  {
84
84
  name: 'Unreal',
85
85
  enabled: unreal,
86
86
  shared: true,
87
- after: ['Visual Studio', 'MSVC'],
87
+ after: ['Visual Studio'],
88
88
  ...ahead(() => fetchDirectX(config.unreal)),
89
89
  install: (env) => setupUnreal(env, config.unreal, ctx),
90
90
  },
91
+ // Each project's engine (its .uproject's EngineAssociation) on its PATH: not shared, a project that joins later
92
+ // gets its own. It registers every engine too, so this PC's engines work without the licence step above.
93
+ {
94
+ name: 'Unreal engines',
95
+ enabled: unreal,
96
+ after: ['Unreal'],
97
+ install: (env) => setupUnrealEngines(env, config.unreal, ctx),
98
+ },
91
99
  {
92
100
  name: 'MSVC',
93
- enabled: (wanted(config.msvc.enabled, 'msvc') || (autosdk && config.msvc.enabled !== false)) && vs === null,
94
- // The same Visual Studio licence terms, which Unreal's acceptLicense already names.
95
- install: (env) => setupMsvc(env, autosdk
96
- ? { ...config.msvc, acceptLicense: config.msvc.acceptLicense || config.unreal.acceptLicense }
97
- : config.msvc),
101
+ enabled: wanted(config.msvc.enabled, 'msvc') && vs === null,
102
+ install: (env) => setupMsvc(env, config.msvc),
98
103
  },
99
104
  // Rust links with MSVC (msvc host) or MinGW's gcc (GNU host): whichever it uses goes first.
100
105
  {
@@ -112,6 +117,8 @@ export function plan(config, versions, o) {
112
117
  cargoDefaults(rustHost(config));
113
118
  cargoShim(env, rust);
114
119
  linkGate(env, rust, rustHost(config));
120
+ lldLink(env, rust, rustHost(config));
121
+ lldLink(env, rust, rustHost(config));
115
122
  }
116
123
  },
117
124
  },
@@ -167,10 +174,11 @@ export function plan(config, versions, o) {
167
174
  },
168
175
  { name: 'Ruby', enabled: wanted(config.ruby.enabled, 'ruby'), install: (env) => setupRuby(env, config.ruby) },
169
176
  // PHP installs the Visual C++ runtime itself: one installer at a time.
170
- // Many apps (bat, delta, Neovim, most GUIs, your own programs) are built against it too.
177
+ // Many apps (bat, delta, Neovim, most GUIs, your own programs) are built against it too, and so is what Visual
178
+ // Studio builds: a Visual Studio put back from the data disk does not bring it, as its installer did.
171
179
  {
172
180
  name: 'Visual C++ runtime',
173
- enabled: config.vcRuntime.enabled || apps,
181
+ enabled: config.vcRuntime.enabled || apps || vs !== null,
174
182
  shared: true,
175
183
  ...ahead(fetchVcRuntime),
176
184
  install: async (env) => {
@@ -54,6 +54,8 @@ const BY_TOOLCHAIN = {
54
54
  rust: ['rust-lang.rust-analyzer', 'vadimcn.vscode-lldb'],
55
55
  cpp: ['ms-vscode.cpptools', 'ms-vscode.cmake-tools'],
56
56
  msvc: ['ms-vscode.cpptools', 'ms-vscode.cmake-tools'],
57
+ // Unreal C++ (IntelliSense from the generated .code-workspace) and the C# of the *.Build.cs / *.Target.cs rules.
58
+ unreal: ['ms-vscode.cpptools', 'ms-dotnettools.csharp'],
57
59
  dotnet: ['ms-dotnettools.csharp'],
58
60
  ruby: ['shopify.ruby-lsp'],
59
61
  php: ['bmewburn.vscode-intelephense-client', 'xdebug.php-debug'],
@@ -1,15 +1,21 @@
1
- // Epic Games: this PC's launcher sign-in ("Remember me" in its GameUserSettings.ini, often UTF-16). Taking it signs
2
- // the launcher on this PC out the first time a sandbox uses it (a sign-in works in one place at a time).
1
+ // Epic Games: this PC's launcher sign-in ("Remember me" in its GameUserSettings.ini, often UTF-16; launcher 17+ keeps
2
+ // it in Config\WindowsEditor, older ones in Config\Windows). Taking it signs the launcher on this PC out the first time
3
+ // a sandbox uses it (a sign-in works in one place at a time).
3
4
  import path from 'node:path';
4
- import { epicSettingsFile, MAX_EPIC_INI_BYTES, readEpicLogin, } from '../../../shared/accounts/index.js';
5
+ import { epicSettingsFiles, MAX_EPIC_INI_BYTES, readEpicLogin, } from '../../../shared/accounts/index.js';
5
6
  import { decodeText, readBoundedFile } from '../../../shared/io/index.js';
6
7
  export const epicSource = {
7
8
  fromPc(pc, from) {
8
9
  const localAppData = from ? path.resolve(from) : pc.localAppData;
9
10
  if (!localAppData)
10
11
  return null;
11
- const bytes = readBoundedFile(epicSettingsFile(localAppData), MAX_EPIC_INI_BYTES);
12
- return bytes ? readEpicLogin(decodeText(bytes).text) : null;
12
+ for (const file of epicSettingsFiles(localAppData)) {
13
+ const bytes = readBoundedFile(file, MAX_EPIC_INI_BYTES);
14
+ const login = bytes ? readEpicLogin(decodeText(bytes).text) : null;
15
+ if (login)
16
+ return login;
17
+ }
18
+ return null;
13
19
  },
14
20
  summary: () => null,
15
21
  };
@@ -4,6 +4,8 @@ export interface PcEnv {
4
4
  readonly localAppData: string;
5
5
  readonly appData: string;
6
6
  readonly programData: string;
7
+ /** C:\Program Files (where the Epic Games Launcher installs engines by default). */
8
+ readonly programFiles?: string;
7
9
  /** The environment the command runs with (a variable set in this terminal). */
8
10
  readonly env?: Readonly<Record<string, string | undefined>>;
9
11
  /** A variable of this Windows user's saved environment (HKCU\Environment), or null when it is not set. */
@@ -32,6 +32,7 @@ export function pcEnv() {
32
32
  localAppData: env.LOCALAPPDATA ?? '',
33
33
  appData: env.APPDATA ?? '',
34
34
  programData: env.ProgramData ?? env.PROGRAMDATA ?? 'C:\\ProgramData',
35
+ programFiles: env.ProgramFiles ?? 'C:\\Program Files',
35
36
  env,
36
37
  userVariable: savedUserVariable,
37
38
  };
@@ -0,0 +1,5 @@
1
+ import type { Migration } from '../migration.js';
2
+ import { type Json } from './config-file.js';
3
+ /** The retired values in a config file. */
4
+ export declare function retiredVsSettings(file: Json): string[][];
5
+ export declare const dropVsInstallFiles: Migration;
@@ -0,0 +1,34 @@
1
+ // 1.24 keeps Visual Studio itself on the data disk, so two Unreal settings are gone: toolchains.unreal.keepInstallFiles
2
+ // (the offline install files, which the installer never managed to make in a sandbox) and visualStudio "autosdk" (the
3
+ // portable MSVC instead of Visual Studio, which nothing needs now that Visual Studio is put back in seconds). A config
4
+ // file that still has them would no longer load: they are removed (the rest and the comments stay).
5
+ import _ from 'lodash';
6
+ import { readFileObject, removeValues } from './config-file.js';
7
+ const KEEP_INSTALL_FILES = ['toolchains', 'unreal', 'keepInstallFiles'];
8
+ const VISUAL_STUDIO = ['toolchains', 'unreal', 'visualStudio'];
9
+ /** The retired values in a config file. */
10
+ export function retiredVsSettings(file) {
11
+ return [
12
+ ...(_.has(file, KEEP_INSTALL_FILES) ? [KEEP_INSTALL_FILES] : []),
13
+ ...(_.get(file, VISUAL_STUDIO) === 'autosdk' ? [VISUAL_STUDIO] : []),
14
+ ];
15
+ }
16
+ export const dropVsInstallFiles = {
17
+ id: '2026-11-drop-vs-install-files',
18
+ introducedIn: '1.24.0',
19
+ scope: 'user',
20
+ needsIdleSandbox: false,
21
+ summary: 'Removes toolchains.unreal.keepInstallFiles and visualStudio "autosdk": Visual Studio is now kept on the data disk.',
22
+ applies(ctx) {
23
+ const file = readFileObject(ctx.configPath);
24
+ return file !== null && retiredVsSettings(file).length > 0;
25
+ },
26
+ touches(ctx) {
27
+ return ctx.configPath ? [ctx.configPath] : [];
28
+ },
29
+ async run(ctx, note) {
30
+ const parsed = readFileObject(ctx.configPath);
31
+ if (ctx.configPath && parsed)
32
+ removeValues(ctx.configPath, retiredVsSettings(parsed), 'No longer needed (Visual Studio is kept)', note);
33
+ },
34
+ };
@@ -1,4 +1,10 @@
1
+ import { dropVsInstallFiles } from './migrations/drop-vs-install-files.js';
1
2
  import { followMachineStorage } from './migrations/follow-machine-storage.js';
2
3
  import { pinStorageRoot } from './migrations/pin-storage-root.js';
3
4
  import { unpinConfigDefaults } from './migrations/unpin-config-defaults.js';
4
- export const MIGRATIONS = [unpinConfigDefaults, followMachineStorage, pinStorageRoot];
5
+ export const MIGRATIONS = [
6
+ unpinConfigDefaults,
7
+ followMachineStorage,
8
+ pinStorageRoot,
9
+ dropVsInstallFiles,
10
+ ];
@@ -10,6 +10,8 @@ export interface DiscoverOptions {
10
10
  readonly packageRoot: string;
11
11
  readonly storageRoot: string;
12
12
  readonly sources?: readonly PcInstallSource[];
13
+ /** This launch sets the sandbox up for Unreal ("auto" maps this PC's engines and vault cache then). */
14
+ readonly unrealOn?: boolean;
13
15
  }
14
16
  export interface DiscoveredInstalls {
15
17
  readonly mappings: ResolvedMapping[];
@@ -5,21 +5,29 @@ import { pcEnv } from '../accounts/index.js';
5
5
  import { epicInstalls } from './epic.js';
6
6
  import { steamInstalls } from './steam.js';
7
7
  import { unityInstalls } from './unity.js';
8
- import { unrealInstalls } from './unreal.js';
8
+ import { unrealFolders, unrealInstalls } from './unreal.js';
9
+ import { unrealVault } from './unreal-vault.js';
9
10
  /** At most this many installs are mapped (each one is a mapping in the .wsb). */
10
11
  export const MAX_PC_INSTALLS = 40;
11
12
  export const PC_INSTALL_SOURCES = [
12
13
  steamInstalls,
13
14
  epicInstalls,
15
+ unrealFolders,
14
16
  unrealInstalls,
17
+ unrealVault,
15
18
  unityInstalls,
16
19
  ];
20
+ /** What a setting asks for: false (nothing), true (everything) or a list; "auto" follows the Unreal toolchain. */
21
+ function wantedOf(s, setting, unrealOn) {
22
+ const value = s[setting];
23
+ return value === 'auto' ? unrealOn : value;
24
+ }
17
25
  export function discoverPcInstalls(s, o, pc = pcEnv()) {
18
26
  const mappings = [];
19
27
  const items = [];
20
28
  const notes = [];
21
29
  for (const source of o.sources ?? PC_INSTALL_SOURCES) {
22
- const wanted = s[source.setting];
30
+ const wanted = wantedOf(s, source.setting, o.unrealOn === true);
23
31
  if (wanted === false || (Array.isArray(wanted) && !wanted.length))
24
32
  continue;
25
33
  let found = [];
@@ -37,14 +45,22 @@ export function discoverPcInstalls(s, o, pc = pcEnv()) {
37
45
  }
38
46
  try {
39
47
  const guest = pcInstallGuest(install.kind, install.id);
40
- if (mappings.some((m) => m.guest.toLowerCase() === guest.toLowerCase()))
48
+ // Once each: a folder you registered (writable, listed first) is not mapped again read-only.
49
+ const same = (m) => m.guest.toLowerCase() === guest.toLowerCase() || m.host.toLowerCase() === install.host.toLowerCase();
50
+ if (mappings.some(same))
41
51
  continue;
42
52
  if (!dirExists(install.host)) {
43
53
  notes.push('pcInstalls: ' + install.name + ' is not there any more (' + install.host + ').');
44
54
  continue;
45
55
  }
46
56
  assertSafeMapping(install.host, o.packageRoot, o.storageRoot);
47
- mappings.push({ host: install.host, guest, readOnly: true, label: 'this PC, read-only' });
57
+ const writable = install.writable === true;
58
+ mappings.push({
59
+ host: install.host,
60
+ guest,
61
+ readOnly: !writable,
62
+ label: writable ? 'this PC, writable' : 'this PC, read-only',
63
+ });
48
64
  const { host: _host, ...shown } = install;
49
65
  items.push({ ...shown, guest });
50
66
  }
@@ -53,7 +69,11 @@ export function discoverPcInstalls(s, o, pc = pcEnv()) {
53
69
  }
54
70
  }
55
71
  if (Array.isArray(wanted) && found.length < wanted.length)
56
- notes.push('pcInstalls.' + source.setting + ': some of the listed ones are not installed on this PC.');
72
+ notes.push('pcInstalls.' +
73
+ source.setting +
74
+ (source.setting === 'unrealEngineFolders'
75
+ ? ': some of the listed folders are missing or hold no engine (Engine\\Build\\Build.version).'
76
+ : ': some of the listed ones are not installed on this PC.'));
57
77
  }
58
78
  return { mappings, manifest: { items }, notes };
59
79
  }
@@ -1,2 +1,5 @@
1
+ import type { PcInstall } from '../../shared/paths/index.js';
1
2
  import type { PcInstallSource } from './source.js';
3
+ /** The games a LauncherInstalled.dat lists that `wanted` maps (by app name), each once. */
4
+ export declare function findEpicGames(text: string | null, wanted: true | readonly (string | number)[]): PcInstall[];
2
5
  export declare const epicInstalls: PcInstallSource;
@@ -1,7 +1,38 @@
1
+ // Epic Games Launcher games of this PC (pcInstalls.epicGames): the InstallationList of its LauncherInstalled.dat
2
+ // (%ProgramData%\Epic\UnrealEngineLauncher), engines (UE_x.y) left to unreal.ts. C:\PcInstalls\epic\<AppName>.
3
+ import path from 'node:path';
4
+ import { readBoundedFile } from '../../shared/io/index.js';
5
+ import { launcherInstallations, launcherInstalledFile, MAX_LAUNCHER_FILE_BYTES } from '../../shared/unreal/index.js';
6
+ const APP = /^[A-Za-z0-9._-]{1,100}$/;
7
+ /** The games a LauncherInstalled.dat lists that `wanted` maps (by app name), each once. */
8
+ export function findEpicGames(text, wanted) {
9
+ const names = wanted === true ? null : new Set(wanted.map((w) => String(w).toLowerCase()));
10
+ const found = [];
11
+ for (const i of text ? launcherInstallations(text) : []) {
12
+ if (!APP.test(i.appName) || /^\.+$/.test(i.appName) || /^UE_\d/.test(i.appName))
13
+ continue;
14
+ if (names && !names.has(i.appName.toLowerCase()))
15
+ continue;
16
+ if (found.some((f) => f.id.toLowerCase() === i.appName.toLowerCase()))
17
+ continue;
18
+ found.push({
19
+ kind: 'epic-game',
20
+ id: i.appName,
21
+ name: path.win32.basename(i.installLocation).slice(0, 100) || i.appName,
22
+ ...(i.ids ? { version: i.ids.appVersion.slice(0, 60), launcher: i.ids } : {}),
23
+ host: path.win32.normalize(i.installLocation),
24
+ guest: '',
25
+ });
26
+ }
27
+ return found;
28
+ }
1
29
  export const epicInstalls = {
2
30
  kind: 'epic-game',
3
31
  setting: 'epicGames',
4
- find(_wanted, _pc) {
5
- return [];
32
+ find(wanted, pc) {
33
+ const bytes = readBoundedFile(launcherInstalledFile(pc.programData), MAX_LAUNCHER_FILE_BYTES);
34
+ if (!bytes)
35
+ throw new Error('the Epic Games Launcher has no LauncherInstalled.dat here');
36
+ return findEpicGames(bytes.toString('utf8'), wanted);
6
37
  },
7
38
  };
@@ -3,7 +3,7 @@ import type { PcEnv } from '../accounts/index.js';
3
3
  export interface PcInstallSource {
4
4
  readonly kind: PcInstall['kind'];
5
5
  /** The settings key that turns it on (pcInstalls.<setting>). */
6
- readonly setting: 'steamGames' | 'epicGames' | 'unrealEngines' | 'unityEditors';
6
+ readonly setting: 'steamGames' | 'epicGames' | 'unrealEngines' | 'unrealEngineFolders' | 'unrealAssets' | 'unityEditors';
7
7
  /** What is installed: everything (true) or the ones listed. Bounded reads only (registry, manifests). */
8
8
  find(wanted: true | readonly (string | number)[], pc: PcEnv): PcInstall[];
9
9
  }
@@ -0,0 +1,11 @@
1
+ import type { PcInstall } from '../../shared/paths/index.js';
2
+ import { type VaultFs } from '../../shared/unreal/index.js';
3
+ import type { PcEnv } from '../accounts/index.js';
4
+ import type { PcInstallSource } from './source.js';
5
+ /** The vault cache folders of this PC: the ones its launcher settings name, else the default. */
6
+ export declare function vaultDirs(pc: Pick<PcEnv, 'localAppData' | 'programData'>, isDir: (dir: string) => boolean): string[];
7
+ /** The vault folders as installs to map (read-only). */
8
+ export declare function vaultInstalls(dirs: readonly string[]): PcInstall[];
9
+ /** This PC's disk for vaultPacks (bounded lists). */
10
+ export declare const PC_VAULT_FS: VaultFs;
11
+ export declare const unrealVault: PcInstallSource;
@@ -0,0 +1,78 @@
1
+ // The Epic Games Launcher's vault cache of this PC (pcInstalls.unrealAssets): the Fab and Marketplace packs you
2
+ // downloaded, mapped read-only at C:\PcInstalls\unreal\Vault (Vault2 ... for more folders), so what you download on the
3
+ // PC is in the sandbox at once. Its folders come from the launcher's settings (shared/unreal/vault.ts), else the
4
+ // default one; only folders that exist are mapped.
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { epicSettingsFiles, MAX_EPIC_INI_BYTES } from '../../shared/accounts/index.js';
8
+ import { decodeText, readBoundedFile } from '../../shared/io/index.js';
9
+ import { defaultVaultDir, vaultDirsFromIni } from '../../shared/unreal/index.js';
10
+ const MAX_VAULTS = 4;
11
+ /** The vault cache folders of this PC: the ones its launcher settings name, else the default. */
12
+ export function vaultDirs(pc, isDir) {
13
+ const named = pc.localAppData
14
+ ? epicSettingsFiles(pc.localAppData).flatMap((file) => {
15
+ const bytes = readBoundedFile(file, MAX_EPIC_INI_BYTES);
16
+ return bytes ? vaultDirsFromIni(decodeText(bytes).text) : [];
17
+ })
18
+ : [];
19
+ const all = [...named, defaultVaultDir(pc.programData)];
20
+ return all
21
+ .filter((d, i) => all.findIndex((o) => o.toLowerCase() === d.toLowerCase()) === i)
22
+ .filter(isDir)
23
+ .slice(0, MAX_VAULTS);
24
+ }
25
+ /** The vault folders as installs to map (read-only). */
26
+ export function vaultInstalls(dirs) {
27
+ return dirs.map((dir, i) => ({
28
+ kind: 'unreal-vault',
29
+ id: i ? 'Vault' + (i + 1) : 'Vault',
30
+ name: 'Epic Games Launcher vault cache' + (i ? ' ' + (i + 1) : ''),
31
+ host: dir,
32
+ guest: '',
33
+ }));
34
+ }
35
+ const isDir = (dir) => {
36
+ try {
37
+ return fs.statSync(dir).isDirectory();
38
+ }
39
+ catch {
40
+ return false;
41
+ }
42
+ };
43
+ /** This PC's disk for vaultPacks (bounded lists). */
44
+ export const PC_VAULT_FS = {
45
+ exists: (file) => fs.existsSync(file),
46
+ list: (dir) => {
47
+ try {
48
+ return fs.readdirSync(dir).slice(0, 5000);
49
+ }
50
+ catch {
51
+ return []; // Not a folder: nothing in it.
52
+ }
53
+ },
54
+ size: (file) => {
55
+ try {
56
+ const s = fs.lstatSync(file);
57
+ return s.isFile() ? s.size : null;
58
+ }
59
+ catch {
60
+ return null;
61
+ }
62
+ },
63
+ isDir: (dir) => {
64
+ try {
65
+ return fs.lstatSync(dir).isDirectory();
66
+ }
67
+ catch {
68
+ return false;
69
+ }
70
+ },
71
+ };
72
+ export const unrealVault = {
73
+ kind: 'unreal-vault',
74
+ setting: 'unrealAssets',
75
+ find(_wanted, pc) {
76
+ return vaultInstalls(vaultDirs(pc, isDir)).map((v) => ({ ...v, host: path.win32.normalize(v.host) }));
77
+ },
78
+ };
@@ -1,2 +1,38 @@
1
+ import type { PcInstall } from '../../shared/paths/index.js';
2
+ import type { PcEnv } from '../accounts/index.js';
1
3
  import type { PcInstallSource } from './source.js';
4
+ /** At most this many engines of this PC are mapped. */
5
+ export declare const MAX_PC_ENGINES = 8;
6
+ /** What discovery reads on this PC (the self-test passes fixtures). */
7
+ export interface UnrealPcFiles {
8
+ /** LauncherInstalled.dat's text, or null. */
9
+ readonly launcherInstalled: string | null;
10
+ /** `reg query HKCU\Software\Epic Games\Unreal Engine\Builds` output ('' when the key is missing). */
11
+ readonly builds: string;
12
+ /** The launcher's default engines folder (<Program Files>\Epic Games). */
13
+ readonly defaultRoot: string;
14
+ readonly list: (dir: string) => string[];
15
+ readonly read: (file: string, max: number) => Buffer | null;
16
+ }
17
+ /** The values of `reg query <key>` output: the name and string of each REG_SZ / REG_EXPAND_SZ line. */
18
+ export declare function regValues(text: string): {
19
+ name: string;
20
+ value: string;
21
+ }[];
22
+ /** The engines of this PC that `wanted` maps, each folder once, at most MAX_PC_ENGINES. */
23
+ export declare function findUnrealInstalls(files: UnrealPcFiles, wanted: true | readonly (string | number)[]): PcInstall[];
24
+ /** A source build of this PC's Builds key. */
25
+ export interface PcSourceBuild {
26
+ readonly buildId: string;
27
+ readonly version: string;
28
+ readonly host: string;
29
+ }
30
+ /** Source builds this PC registered (the Builds key). They are not mapped by themselves: UBT writes inside them. */
31
+ export declare function pcSourceBuilds(files: Pick<UnrealPcFiles, 'read' | 'builds'>): PcSourceBuild[];
32
+ /** The engine folders you registered (pcInstalls.unrealEngineFolders), mapped writable. A folder that is a source
33
+ * build of this PC keeps the PC's build id. */
34
+ export declare function findEngineFolders(files: Pick<UnrealPcFiles, 'read' | 'builds'>, folders: readonly (string | number)[]): PcInstall[];
35
+ /** This PC's files (reg.exe through system32, bounded reads). */
36
+ export declare function unrealPcFiles(pc: PcEnv): UnrealPcFiles;
2
37
  export declare const unrealInstalls: PcInstallSource;
38
+ export declare const unrealFolders: PcInstallSource;
@@ -1,7 +1,150 @@
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
4
+ // C:\PcInstalls\unreal\UE_5.4. Source builds (HKCU\Software\Epic Games\Unreal Engine\Builds) are NOT mapped by
5
+ // themselves, as UBT writes inside an engine. pcInstalls.unrealEngineFolders: folders you registered, mapped writable
6
+ // (dir-<name>; a source build of this PC keeps its build id). Every file is read bounded.
7
+ import { spawnSync } from 'node:child_process';
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { readBoundedFile } from '../../shared/io/index.js';
11
+ import { SYSTEM } from '../../shared/system.js';
12
+ import { BUILDS_KEY, launcherInstallations, launcherInstalledFile, MAX_LAUNCHER_FILE_BYTES, parseBuildVersion, versionText, } from '../../shared/unreal/index.js';
13
+ const win = path.win32;
14
+ /** At most this many engines of this PC are mapped. */
15
+ export const MAX_PC_ENGINES = 8;
16
+ const APP_NAME = /^UE_(\d{1,2}\.\d{1,2})$/;
17
+ /** The values of `reg query <key>` output: the name and string of each REG_SZ / REG_EXPAND_SZ line. */
18
+ export function regValues(text) {
19
+ return text
20
+ .split(/\r?\n/)
21
+ .slice(0, 500)
22
+ .flatMap((line) => {
23
+ const m = /^\s{2,}(\S(?:.{0,200}?\S)?)\s{2,}REG_(?:EXPAND_)?SZ\s{2,}(.{1,1000})$/.exec(line);
24
+ return m ? [{ name: m[1], value: m[2].trim() }] : [];
25
+ });
26
+ }
27
+ const versionOf = (dir, files) => {
28
+ const bytes = files.read(win.join(dir, 'Engine', 'Build', 'Build.version'), 64 * 1024);
29
+ return bytes ? parseBuildVersion(bytes.toString('utf8')) : null;
30
+ };
31
+ const isWanted = (wanted, v) => wanted === true || wanted.map((w) => String(w).replace(/^UE_/i, '')).includes(v.major + '.' + v.minor);
32
+ /** The engines of this PC that `wanted` maps, each folder once, at most MAX_PC_ENGINES. */
33
+ export function findUnrealInstalls(files, wanted) {
34
+ const found = [];
35
+ const add = (install) => {
36
+ const key = win.resolve(install.host).toLowerCase();
37
+ if (found.length >= MAX_PC_ENGINES)
38
+ return;
39
+ if (found.some((f) => f.id === install.id || win.resolve(f.host).toLowerCase() === key))
40
+ return;
41
+ found.push(install);
42
+ };
43
+ const installed = (dir, ids) => {
44
+ const v = versionOf(dir, files);
45
+ if (!v || !isWanted(wanted, v))
46
+ return;
47
+ add({
48
+ kind: 'unreal',
49
+ id: 'UE_' + v.major + '.' + v.minor,
50
+ name: 'Unreal Engine ' + versionText(v),
51
+ version: versionText(v),
52
+ host: win.normalize(dir),
53
+ guest: '',
54
+ ...(ids ? { launcher: ids } : {}),
55
+ });
56
+ };
57
+ for (const i of files.launcherInstalled ? launcherInstallations(files.launcherInstalled) : [])
58
+ if (APP_NAME.test(i.appName))
59
+ 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));
65
+ return found;
66
+ }
67
+ /** Source builds this PC registered (the Builds key). They are not mapped by themselves: UBT writes inside them. */
68
+ export function pcSourceBuilds(files) {
69
+ const found = [];
70
+ for (const { name, value } of regValues(files.builds)) {
71
+ const dir = value.replace(/\//g, '\\');
72
+ if (!win.isAbsolute(dir) || found.length >= MAX_PC_ENGINES)
73
+ continue;
74
+ const v = versionOf(dir, files);
75
+ if (v)
76
+ found.push({ buildId: name.slice(0, 64), version: versionText(v), host: win.normalize(dir) });
77
+ }
78
+ return found;
79
+ }
80
+ /** The engine folders you registered (pcInstalls.unrealEngineFolders), mapped writable. A folder that is a source
81
+ * build of this PC keeps the PC's build id. */
82
+ export function findEngineFolders(files, folders) {
83
+ const found = [];
84
+ const sources = pcSourceBuilds(files);
85
+ for (const raw of folders.map(String)) {
86
+ const dir = win.normalize(raw);
87
+ if (!win.isAbsolute(dir) || found.some((f) => f.host.toLowerCase() === dir.toLowerCase()))
88
+ continue;
89
+ const v = versionOf(dir, files);
90
+ if (!v)
91
+ continue;
92
+ const base = win
93
+ .basename(dir)
94
+ .replace(/[^A-Za-z0-9._-]/g, '')
95
+ .replace(/^\.+/, '')
96
+ .slice(0, 40) || 'engine';
97
+ const id = 'dir-' + base;
98
+ const source = sources.find((b) => b.host.toLowerCase() === dir.toLowerCase());
99
+ found.push({
100
+ kind: 'unreal',
101
+ id: found.some((f) => f.id === id) ? id + '-' + (found.length + 1) : id,
102
+ name: 'Unreal Engine ' + versionText(v) + ' (' + win.basename(dir).slice(0, 60) + ')',
103
+ version: versionText(v),
104
+ host: dir,
105
+ guest: '',
106
+ writable: true,
107
+ ...(source ? { buildId: source.buildId } : {}),
108
+ });
109
+ }
110
+ return found;
111
+ }
112
+ function listDir(dir) {
113
+ try {
114
+ return fs.readdirSync(dir).slice(0, 500);
115
+ }
116
+ catch {
117
+ return []; // Not there: nothing in it.
118
+ }
119
+ }
120
+ /** This PC's files (reg.exe through system32, bounded reads). */
121
+ export function unrealPcFiles(pc) {
122
+ const query = spawnSync(SYSTEM.reg(), ['query', 'HKCU\\' + BUILDS_KEY], {
123
+ encoding: 'utf8',
124
+ windowsHide: true,
125
+ timeout: 15_000,
126
+ maxBuffer: 256 * 1024,
127
+ });
128
+ const dat = readBoundedFile(launcherInstalledFile(pc.programData), MAX_LAUNCHER_FILE_BYTES);
129
+ return {
130
+ launcherInstalled: dat ? dat.toString('utf8') : null,
131
+ builds: query.status === 0 ? String(query.stdout ?? '') : '',
132
+ defaultRoot: win.join(pc.programFiles ?? 'C:\\Program Files', 'Epic Games'),
133
+ list: listDir,
134
+ read: readBoundedFile,
135
+ };
136
+ }
1
137
  export const unrealInstalls = {
2
138
  kind: 'unreal',
3
139
  setting: 'unrealEngines',
4
- find(_wanted, _pc) {
5
- return [];
140
+ find(wanted, pc) {
141
+ return findUnrealInstalls(unrealPcFiles(pc), wanted);
142
+ },
143
+ };
144
+ export const unrealFolders = {
145
+ kind: 'unreal',
146
+ setting: 'unrealEngineFolders',
147
+ find(wanted, pc) {
148
+ return wanted === true ? [] : findEngineFolders(unrealPcFiles(pc), wanted);
6
149
  },
7
150
  };