windows-development-sandbox 1.25.0 → 1.27.0
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.
- package/config.example.json +17 -5
- package/dist/app/features/accounts/services.def.d.ts +1 -1
- package/dist/app/features/accounts/services.def.js +1 -1
- package/dist/app/features/data/data.def.d.ts +4 -4
- package/dist/app/features/data/data.run.d.ts +1 -1
- package/dist/app/features/defs.d.ts +356 -6
- package/dist/app/features/defs.js +15 -0
- package/dist/app/features/projects/projects.def.d.ts +1 -1
- package/dist/app/features/projects/projects.run.d.ts +1 -1
- package/dist/app/features/settings/categories.def.js +1 -0
- package/dist/app/features/unity/index.d.ts +2 -0
- package/dist/app/features/unity/index.js +3 -0
- package/dist/app/features/unity/unity.def.d.ts +294 -0
- package/dist/app/features/unity/unity.def.js +471 -0
- package/dist/app/features/unity/unity.run.d.ts +142 -0
- package/dist/app/features/unity/unity.run.js +363 -0
- package/dist/app/features/unreal/index.d.ts +2 -0
- package/dist/app/features/unreal/index.js +3 -0
- package/dist/app/features/unreal/unreal.def.d.ts +236 -0
- package/dist/app/features/unreal/unreal.def.js +379 -0
- package/dist/app/features/unreal/unreal.run.d.ts +96 -0
- package/dist/app/features/unreal/unreal.run.js +217 -0
- package/dist/app/registry.js +11 -0
- package/dist/bin/agent-sandbox.js +2 -2
- package/dist/bin/{chunk-IZRGTSQ5.js → chunk-6WWMBCT2.js} +4632 -1941
- package/dist/bin/{chunk-SUYYVIDW.js → chunk-JXL6ZOO3.js} +126 -6
- package/dist/bin/{chunk-KH4MI2U5.js → chunk-ZXCAVVS4.js} +1 -1
- package/dist/bin/claude-host.js +2 -2
- package/dist/bin/claude-sbx.js +2 -2
- package/dist/bin/codex-sbx.js +2 -2
- package/dist/bin/gemini-sbx.js +2 -2
- package/dist/bin/sandbox.js +2 -2
- package/dist/bin/sbx-hide.js +2 -2
- package/dist/bin/sbx-show.js +2 -2
- package/dist/cli/commands/launch/launch.js +17 -2
- package/dist/cli/commands/registry.js +6 -0
- package/dist/cli/manifest.d.ts +51 -1
- package/dist/cli/manifest.js +58 -1
- package/dist/cli/menu/coverage-exempt.js +2 -0
- package/dist/cli/menu/pages/sandbox-page.d.ts +6 -0
- package/dist/cli/menu/pages/sandbox-page.js +71 -4
- package/dist/cli/menu/views.js +4 -0
- package/dist/core/config/defaults.js +33 -6
- package/dist/core/schema/accounts.d.ts +3 -0
- package/dist/core/schema/accounts.js +1 -0
- package/dist/core/schema/config.d.ts +32 -14
- package/dist/core/schema/github.d.ts +1 -0
- package/dist/core/schema/github.js +3 -0
- package/dist/core/schema/pc-installs.d.ts +5 -2
- package/dist/core/schema/pc-installs.js +16 -2
- package/dist/core/schema/toolchains.d.ts +8 -0
- package/dist/core/schema/toolchains.js +20 -5
- package/dist/guest/accounts/adapters.js +2 -0
- package/dist/guest/accounts/apply.d.ts +4 -2
- package/dist/guest/accounts/kinds/epic.js +18 -13
- package/dist/guest/accounts/kinds/perplexityapp.d.ts +7 -0
- package/dist/guest/accounts/kinds/perplexityapp.js +53 -0
- package/dist/guest/accounts/kinds/unity.d.ts +27 -0
- package/dist/guest/accounts/kinds/unity.js +164 -7
- package/dist/guest/apps/after-install.d.ts +1 -1
- package/dist/guest/apps/after-install.js +6 -1
- package/dist/guest/apps/app-installer.d.ts +1 -0
- package/dist/guest/apps/app-installer.js +31 -3
- package/dist/guest/apps/comet-unpack.js +3 -2
- package/dist/guest/apps/resolvers-gamedev.js +55 -1
- package/dist/guest/apps/store-app.d.ts +4 -0
- package/dist/guest/apps/store-app.js +19 -0
- package/dist/guest/apps/store-check.d.ts +8 -0
- package/dist/guest/apps/store-check.js +33 -0
- package/dist/guest/boot/boot.js +15 -2
- package/dist/guest/boot/guide.js +42 -0
- package/dist/guest/browser/comet.d.ts +3 -1
- package/dist/guest/browser/comet.js +1 -1
- package/dist/guest/browser/perplexity-app.d.ts +12 -0
- package/dist/guest/browser/perplexity-app.js +60 -0
- package/dist/guest/daemon/daemon.js +5 -0
- package/dist/guest/devtools/devtool.d.ts +1 -1
- package/dist/guest/devtools/registry.js +1 -2
- package/dist/guest/entries/tool.js +1 -1
- package/dist/guest/setup/addon-mcp.d.ts +12 -0
- package/dist/guest/setup/addon-mcp.js +29 -4
- package/dist/guest/setup/browser-gpu.js +14 -11
- package/dist/guest/setup/github-token.js +4 -4
- package/dist/guest/setup/headroom.js +4 -0
- package/dist/guest/setup/unity-agent.d.ts +11 -0
- package/dist/guest/setup/unity-agent.js +36 -0
- package/dist/guest/toolchains/engines/epic-launcher.d.ts +26 -0
- package/dist/guest/toolchains/engines/epic-launcher.js +127 -0
- package/dist/guest/toolchains/engines/unity-cli.d.ts +24 -0
- package/dist/guest/toolchains/engines/unity-cli.js +74 -0
- package/dist/guest/toolchains/engines/unity-hub.d.ts +2 -0
- package/dist/guest/toolchains/engines/unity-hub.js +27 -0
- package/dist/guest/toolchains/engines/unity-release.d.ts +4 -15
- package/dist/guest/toolchains/engines/unity-release.js +17 -5
- package/dist/guest/toolchains/engines/unity-status.d.ts +11 -0
- package/dist/guest/toolchains/engines/unity-status.js +39 -0
- package/dist/guest/toolchains/engines/unity.d.ts +20 -1
- package/dist/guest/toolchains/engines/unity.js +250 -3
- package/dist/guest/toolchains/engines/unreal-engines.d.ts +21 -1
- package/dist/guest/toolchains/engines/unreal-engines.js +163 -3
- package/dist/guest/toolchains/engines/unreal-status.d.ts +13 -0
- package/dist/guest/toolchains/engines/unreal-status.js +41 -0
- package/dist/guest/toolchains/native/debuggers.d.ts +6 -0
- package/dist/guest/toolchains/native/debuggers.js +57 -0
- package/dist/guest/toolchains/native/mesa.js +15 -8
- package/dist/guest/toolchains/native/rust-extras.d.ts +7 -0
- package/dist/guest/toolchains/native/rust-extras.js +23 -1
- package/dist/guest/toolchains/native/unreal.d.ts +2 -2
- package/dist/guest/toolchains/native/unreal.js +96 -18
- package/dist/guest/toolchains/plan.js +17 -2
- package/dist/guest/toolchains/tools/cli.js +2 -2
- package/dist/guest/toolchains/tools/vscode-profile.js +2 -0
- package/dist/host/accounts/deliver.js +4 -0
- package/dist/host/accounts/kinds/epic.js +11 -5
- package/dist/host/accounts/kinds/perplexityapp.d.ts +2 -0
- package/dist/host/accounts/kinds/perplexityapp.js +8 -0
- package/dist/host/accounts/kinds/unity.d.ts +1 -1
- package/dist/host/accounts/kinds/unity.js +60 -8
- package/dist/host/accounts/leases.d.ts +2 -1
- package/dist/host/accounts/source.d.ts +2 -0
- package/dist/host/accounts/source.js +1 -0
- package/dist/host/accounts/sources.js +2 -0
- package/dist/host/apps/index.d.ts +1 -0
- package/dist/host/apps/index.js +2 -0
- package/dist/host/apps/store-package.d.ts +51 -0
- package/dist/host/apps/store-package.js +209 -0
- package/dist/host/github/deliver.d.ts +8 -3
- package/dist/host/github/deliver.js +42 -29
- package/dist/host/github/index.d.ts +1 -0
- package/dist/host/github/index.js +1 -0
- package/dist/host/github/pc-login.d.ts +27 -0
- package/dist/host/github/pc-login.js +129 -0
- package/dist/host/pc-installs/discover.d.ts +6 -0
- package/dist/host/pc-installs/discover.js +37 -5
- package/dist/host/pc-installs/epic.d.ts +3 -0
- package/dist/host/pc-installs/epic.js +33 -2
- package/dist/host/pc-installs/source.d.ts +1 -1
- package/dist/host/pc-installs/unity-assets.d.ts +5 -0
- package/dist/host/pc-installs/unity-assets.js +18 -0
- package/dist/host/pc-installs/unity.d.ts +20 -0
- package/dist/host/pc-installs/unity.js +135 -2
- package/dist/host/pc-installs/unreal-vault.d.ts +11 -0
- package/dist/host/pc-installs/unreal-vault.js +78 -0
- package/dist/host/pc-installs/unreal.d.ts +36 -0
- package/dist/host/pc-installs/unreal.js +145 -2
- package/dist/host/profile/claude-profile.d.ts +8 -0
- package/dist/host/profile/claude-profile.js +43 -4
- package/dist/host/profile/index.d.ts +1 -1
- package/dist/host/profile/index.js +1 -1
- package/dist/runtime/agent.js +197 -18
- package/dist/runtime/daemon.js +3938 -1386
- package/dist/runtime/db.js +197 -18
- package/dist/runtime/main.js +4336 -1978
- package/dist/runtime/netproxy.js +197 -18
- package/dist/runtime/tool.js +214 -38
- package/dist/runtime/view.js +197 -18
- package/dist/selftest/agent-paths.test.js +1 -0
- package/dist/selftest/agents-web.test.js +14 -1
- package/dist/selftest/engines.test.js +602 -3
- package/dist/selftest/github.test.js +64 -1
- package/dist/selftest/heavy-load.test.js +3 -1
- package/dist/selftest/hostile-input.test.js +11 -1
- package/dist/selftest/index.js +12 -0
- package/dist/selftest/menu-screens.js +5 -1
- package/dist/selftest/mixamo.test.d.ts +2 -0
- package/dist/selftest/mixamo.test.js +60 -0
- package/dist/selftest/persisted-installs.test.js +1 -1
- package/dist/selftest/secrets.test.js +36 -1
- package/dist/selftest/service-accounts.test.js +1 -1
- package/dist/selftest/store-app.test.d.ts +2 -0
- package/dist/selftest/store-app.test.js +162 -0
- package/dist/selftest/support/engine-golden.js +10 -0
- package/dist/selftest/support/fixtures.d.ts +13 -2
- package/dist/selftest/support/unity-sample.d.ts +2 -0
- package/dist/selftest/support/unity-sample.js +80 -0
- package/dist/selftest/support/unreal-sample.d.ts +2 -0
- package/dist/selftest/support/unreal-sample.js +83 -0
- package/dist/selftest/toolchains.test.js +32 -2
- package/dist/selftest/unity-accounts.test.d.ts +2 -0
- package/dist/selftest/unity-accounts.test.js +106 -0
- package/dist/selftest/unity-agent.test.d.ts +2 -0
- package/dist/selftest/unity-agent.test.js +47 -0
- package/dist/selftest/unity-page.test.d.ts +2 -0
- package/dist/selftest/unity-page.test.js +177 -0
- package/dist/selftest/unity-setup.test.d.ts +2 -0
- package/dist/selftest/unity-setup.test.js +49 -0
- package/dist/selftest/unity.test.js +238 -3
- package/dist/shared/accounts/index.d.ts +2 -1
- package/dist/shared/accounts/index.js +2 -1
- package/dist/shared/accounts/kinds/epic.d.ts +6 -1
- package/dist/shared/accounts/kinds/epic.js +11 -1
- package/dist/shared/accounts/kinds/unity.d.ts +28 -0
- package/dist/shared/accounts/kinds/unity.js +88 -0
- package/dist/shared/accounts/kinds.d.ts +2 -2
- package/dist/shared/accounts/kinds.js +23 -1
- package/dist/shared/accounts/payloads.d.ts +52 -0
- package/dist/shared/accounts/payloads.js +14 -2
- package/dist/shared/apps/associations.d.ts +7 -0
- package/dist/shared/apps/associations.js +26 -0
- package/dist/shared/apps/catalogue-agents-web.d.ts +11 -1
- package/dist/shared/apps/catalogue-agents-web.js +28 -2
- package/dist/shared/apps/catalogue-gamedev.d.ts +3 -0
- package/dist/shared/apps/catalogue-gamedev.js +102 -1
- package/dist/shared/apps/catalogue.d.ts +8 -0
- package/dist/shared/apps/index.d.ts +5 -2
- package/dist/shared/apps/index.js +5 -2
- package/dist/shared/apps/preview.d.ts +2 -0
- package/dist/shared/apps/preview.js +10 -0
- package/dist/shared/apps/selection.d.ts +6 -0
- package/dist/shared/apps/selection.js +9 -0
- package/dist/shared/apps/store-package.d.ts +31 -0
- package/dist/shared/apps/store-package.js +34 -0
- package/dist/shared/browser-policy.d.ts +14 -0
- package/dist/shared/browser-policy.js +22 -0
- package/dist/shared/channel/schema.d.ts +24 -0
- package/dist/shared/channel/secrets.d.ts +17 -0
- package/dist/shared/io/index.d.ts +1 -0
- package/dist/shared/io/index.js +1 -0
- package/dist/shared/io/rotate-log.d.ts +2 -0
- package/dist/shared/io/rotate-log.js +16 -0
- package/dist/shared/memory/policy.d.ts +5 -0
- package/dist/shared/memory/policy.js +7 -0
- package/dist/shared/paths/pc-installs.d.ts +13 -2
- package/dist/shared/paths/pc-installs.js +2 -0
- package/dist/shared/readiness.d.ts +2 -3
- package/dist/shared/readiness.js +2 -3
- package/dist/shared/signer.d.ts +4 -0
- package/dist/shared/signer.js +2 -0
- package/dist/shared/unity/asset-cache.d.ts +20 -0
- package/dist/shared/unity/asset-cache.js +44 -0
- package/dist/shared/unity/cli.d.ts +40 -0
- package/dist/shared/unity/cli.js +77 -0
- package/dist/shared/unity/editors.d.ts +46 -0
- package/dist/shared/unity/editors.js +89 -0
- package/dist/shared/unity/guide.d.ts +9 -0
- package/dist/shared/unity/guide.js +84 -0
- package/dist/shared/unity/index.d.ts +9 -0
- package/dist/shared/unity/index.js +11 -0
- package/dist/shared/unity/manifest.d.ts +10 -0
- package/dist/shared/unity/manifest.js +53 -0
- package/dist/shared/unity/mcp.d.ts +8 -0
- package/dist/shared/unity/mcp.js +12 -0
- package/dist/shared/unity/release.d.ts +28 -0
- package/dist/shared/unity/release.js +83 -0
- package/dist/shared/unity/status.d.ts +59 -0
- package/dist/shared/unity/status.js +45 -0
- package/dist/shared/unity/version.d.ts +27 -0
- package/dist/shared/unity/version.js +75 -0
- package/dist/shared/unreal/engines.d.ts +161 -0
- package/dist/shared/unreal/engines.js +308 -0
- package/dist/shared/unreal/guide.d.ts +19 -0
- package/dist/shared/unreal/guide.js +105 -0
- package/dist/shared/unreal/index.d.ts +7 -0
- package/dist/shared/unreal/index.js +8 -0
- package/dist/shared/unreal/launcher-files.d.ts +88 -0
- package/dist/shared/unreal/launcher-files.js +292 -0
- package/dist/shared/unreal/mixamo.d.ts +5 -0
- package/dist/shared/unreal/mixamo.js +16 -0
- package/dist/shared/unreal/room.d.ts +17 -0
- package/dist/shared/unreal/room.js +34 -0
- package/dist/shared/unreal/status.d.ts +54 -0
- package/dist/shared/unreal/status.js +57 -0
- package/dist/shared/unreal/vault.d.ts +21 -0
- package/dist/shared/unreal/vault.js +67 -0
- package/dist/shared/unreal/vs-check.d.ts +39 -0
- package/dist/shared/unreal/vs-check.js +79 -0
- package/dist/web/index.html +1 -1
- package/dist/web/{main-CCILLX2M.js → main-VPMYTSUL.js} +87 -87
- package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
- package/native/scripts/get-store-package.ps1 +58 -0
- package/package.json +1 -1
- package/sandbox/addons/openupm-cli/package-lock.json +2921 -0
- package/sandbox/addons/openupm-cli/package.json +7 -0
- package/sandbox/addons/unity-mcp/requirements.in +1 -0
- package/sandbox/addons/unity-mcp/requirements.txt +1165 -0
- package/sandbox/csharp/AppActivation.cs +38 -0
- package/sandbox/csharp/VsSetup.cs +210 -0
- package/sandbox/scripts/get-vs-setup.ps1 +93 -0
- package/sandbox/scripts/install-store-package.ps1 +28 -0
- package/sandbox/scripts/set-registry-values.ps1 +35 -0
- package/sandbox/scripts/start-store-app.ps1 +29 -0
- package/schemas/config.schema.json +96 -8
- package/schemas/sandbox-config.schema.json +46 -6
- package/dist/guest/devtools/unity.d.ts +0 -2
- package/dist/guest/devtools/unity.js +0 -7
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// tools\unity\status.json: what the sandbox found for Unity, for the guide and the Unity page on the PC. The boot's
|
|
2
|
+
// project step writes its editor choice, the licence step the licence, the daemon the editors; every write keeps what
|
|
3
|
+
// the other parts wrote.
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { readValid, writeJsonAtomic } from '../../../shared/io/json.js';
|
|
6
|
+
import { UNITY_STATUS_FILE, UnityStatusSchema } from '../../../shared/unity/index.js';
|
|
7
|
+
import { mkdirp } from '../../runtime/fs.js';
|
|
8
|
+
import { tool } from '../tool-env.js';
|
|
9
|
+
export const UNITY_DIR = tool('unity');
|
|
10
|
+
/** The Unity CLI, one folder per version (unity.exe). */
|
|
11
|
+
export const CLI_DIR = path.join(UNITY_DIR, 'cli');
|
|
12
|
+
/** The editors on the data disk: one folder per version (Editor\Unity.exe). */
|
|
13
|
+
export const EDITORS_DIR = path.join(UNITY_DIR, 'editors');
|
|
14
|
+
/** Asset Store downloads made in the sandbox (assetCache "persisted"). */
|
|
15
|
+
export const ASSET_DIR = path.join(UNITY_DIR, 'asset-store');
|
|
16
|
+
const FILE = path.join(UNITY_DIR, UNITY_STATUS_FILE);
|
|
17
|
+
const EMPTY = {
|
|
18
|
+
updatedAt: '',
|
|
19
|
+
cli: null,
|
|
20
|
+
editors: [],
|
|
21
|
+
projects: [],
|
|
22
|
+
licence: { state: 'unknown', kind: '', detail: 'not checked', user: null },
|
|
23
|
+
assetCache: { mode: 'local', dir: null },
|
|
24
|
+
pcAssets: [],
|
|
25
|
+
hub: { installed: false, path: null },
|
|
26
|
+
mcp: null,
|
|
27
|
+
skills: false,
|
|
28
|
+
problem: null,
|
|
29
|
+
};
|
|
30
|
+
export function readUnityStatus() {
|
|
31
|
+
return readValid(UnityStatusSchema, FILE) ?? EMPTY;
|
|
32
|
+
}
|
|
33
|
+
/** Changes the status with `change` and writes it (whole, atomically). */
|
|
34
|
+
export function updateUnityStatus(change) {
|
|
35
|
+
const next = { ...change(readUnityStatus()), updatedAt: new Date().toISOString() };
|
|
36
|
+
mkdirp(UNITY_DIR);
|
|
37
|
+
writeJsonAtomic(FILE, next);
|
|
38
|
+
return next;
|
|
39
|
+
}
|
|
@@ -1,4 +1,23 @@
|
|
|
1
|
+
import { type UnityEditor, type WantedEditor } from '../../../shared/unity/index.js';
|
|
1
2
|
import type { Toolchains } from '../../../core/types.js';
|
|
2
3
|
import type { ToolCtx } from '../step.js';
|
|
3
4
|
import type { ToolEnv } from '../tool-env.js';
|
|
4
|
-
|
|
5
|
+
import { type UnityCli } from './unity-cli.js';
|
|
6
|
+
/** Every editor this sandbox has. */
|
|
7
|
+
export declare function sandboxEditors(ctx: Pick<ToolCtx, 'pcInstalls'>): UnityEditor[];
|
|
8
|
+
/**
|
|
9
|
+
* The editor a project asks for: its ProjectVersion.txt (read bounded) always wins, since opening a project in another
|
|
10
|
+
* version upgrades its files; the version the setting names is only for folders without one.
|
|
11
|
+
*/
|
|
12
|
+
export declare function wantedEditor(config: Pick<Toolchains['unity'], 'version'>, workspace: string): WantedEditor | null;
|
|
13
|
+
/** Whether the project's editor is already here (a first install goes to the background). */
|
|
14
|
+
export declare function unityEditorSaved(config: Toolchains['unity'], ctx: Pick<ToolCtx, 'pcInstalls'>): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Installs one editor version on the data disk with the CLI (into a staging folder with TEMP inside it) and returns its
|
|
17
|
+
* folder. One heavy slot while the CLI runs.
|
|
18
|
+
*/
|
|
19
|
+
export declare function ensureUnityEditor(cli: UnityCli, version: string, changeset: string | null, modules: readonly string[], keepVersions: number): Promise<string>;
|
|
20
|
+
/** While the sandbox runs: lists the editors again (the user may have installed one with Hub) for the status. */
|
|
21
|
+
export declare function captureUnityEditors(): Promise<void>;
|
|
22
|
+
/** The editors, the project's choice, the CLI, the licence, the asset cache: set up and written to the status. */
|
|
23
|
+
export declare function setupUnity(env: ToolEnv, config: Toolchains['unity'], ctx: ToolCtx): Promise<void>;
|
|
@@ -1,4 +1,251 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// Unity (toolchains.unity), for each project: the Unity CLI on PATH (pinned, checked), every editor the sandbox has
|
|
2
|
+
// (this PC's, read-only, and the data disk's) registered with it, the editor the project's ProjectVersion.txt names
|
|
3
|
+
// (installed on the data disk when nothing has that exact version), UNITY_EDITOR, the Asset Store cache, the licence,
|
|
4
|
+
// Unity Hub when asked and the agent's MCP server and skills. Machine-wide writes only add; a project that joins
|
|
5
|
+
// later only adds its own editor choice. A project is never opened or changed here.
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { readBoundedFile } from '../../../shared/io/bounded.js';
|
|
9
|
+
import { chooseEditor, editorsAddArgs, findEditors, installArgs, installModulesArgs, installNeedBytes, installPathArgs, unitySummaryLine, wantedVersion, } from '../../../shared/unity/index.js';
|
|
10
|
+
import { errMsg } from '../../../core/util/errors.js';
|
|
11
|
+
import { activateUnityLicence } from '../../accounts/kinds/unity.js';
|
|
12
|
+
import { exists, mkdirp } from '../../runtime/fs.js';
|
|
13
|
+
import { log, warn } from '../../runtime/log.js';
|
|
14
|
+
import { dryRun } from '../../runtime/paths.js';
|
|
15
|
+
import { heavy } from '../../runtime/pools.js';
|
|
16
|
+
import { runScriptWithJsonAsync } from '../../runtime/process.js';
|
|
17
|
+
import { projectWorkspace } from '../../runtime/project-context.js';
|
|
18
|
+
import { session } from '../../runtime/session.js';
|
|
19
|
+
import { setupUnityAgentTools } from '../../setup/unity-agent.js';
|
|
20
|
+
import { ensurePersisted, installRecords } from '../persisted.js';
|
|
21
|
+
import { ensureUnityCli, runUnityCli, runUnityCliChecked } from './unity-cli.js';
|
|
22
|
+
import { ensureUnityHub } from './unity-hub.js';
|
|
23
|
+
import { findUnityRelease } from './unity-release.js';
|
|
24
|
+
import { ASSET_DIR, EDITORS_DIR, readUnityStatus, updateUnityStatus } from './unity-status.js';
|
|
25
|
+
const MAX_PROJECT_VERSION_BYTES = 64 * 1024;
|
|
26
|
+
const MARKER = 'Editor\\Unity.exe';
|
|
27
|
+
const TERMS = 'https://unity.com/legal/terms-of-service';
|
|
28
|
+
const FALLBACK_NEED_BYTES = 15 * 1024 ** 3;
|
|
29
|
+
/** The real disk, for the editor search. */
|
|
30
|
+
const DISK = {
|
|
31
|
+
exists: (file) => exists(file),
|
|
32
|
+
list: (dir) => {
|
|
33
|
+
try {
|
|
34
|
+
return fs.readdirSync(dir).slice(0, 500);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return []; // Not a folder: nothing in it.
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
/** This PC's editors the host mapped (session.json's pcInstalls; the id is the version). */
|
|
42
|
+
function mappedEditors(ctx) {
|
|
43
|
+
return ctx.pcInstalls.items.filter((i) => i.kind === 'unity').map((i) => ({ guest: i.guest, id: i.id }));
|
|
44
|
+
}
|
|
45
|
+
/** Every editor this sandbox has. */
|
|
46
|
+
export function sandboxEditors(ctx) {
|
|
47
|
+
return findEditors({ mapped: mappedEditors(ctx), dataRoot: EDITORS_DIR }, DISK);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The editor a project asks for: its ProjectVersion.txt (read bounded) always wins, since opening a project in another
|
|
51
|
+
* version upgrades its files; the version the setting names is only for folders without one.
|
|
52
|
+
*/
|
|
53
|
+
export function wantedEditor(config, workspace) {
|
|
54
|
+
const file = path.join(workspace, 'ProjectSettings', 'ProjectVersion.txt');
|
|
55
|
+
const bytes = readBoundedFile(file, MAX_PROJECT_VERSION_BYTES);
|
|
56
|
+
return wantedVersion(bytes ? bytes.toString('utf8') : null, config.version);
|
|
57
|
+
}
|
|
58
|
+
/** Whether the project's editor is already here (a first install goes to the background). */
|
|
59
|
+
export function unityEditorSaved(config, ctx) {
|
|
60
|
+
const wanted = wantedEditor(config, projectWorkspace());
|
|
61
|
+
if (!wanted)
|
|
62
|
+
return true;
|
|
63
|
+
return sandboxEditors(ctx).some((e) => e.version === wanted.version);
|
|
64
|
+
}
|
|
65
|
+
function sizeOf(e) {
|
|
66
|
+
if (e.origin !== 'data')
|
|
67
|
+
return null;
|
|
68
|
+
return installRecords().find((r) => r.name === 'unity/editors' && r.version === e.version)?.sizeBytes ?? null;
|
|
69
|
+
}
|
|
70
|
+
const statusEditor = (e) => ({
|
|
71
|
+
version: e.version,
|
|
72
|
+
root: e.root,
|
|
73
|
+
origin: e.origin,
|
|
74
|
+
writable: e.writable,
|
|
75
|
+
modules: [...e.modules],
|
|
76
|
+
sizeBytes: sizeOf(e),
|
|
77
|
+
});
|
|
78
|
+
/**
|
|
79
|
+
* Installs one editor version on the data disk with the CLI (into a staging folder with TEMP inside it) and returns its
|
|
80
|
+
* folder. One heavy slot while the CLI runs.
|
|
81
|
+
*/
|
|
82
|
+
export async function ensureUnityEditor(cli, version, changeset, modules, keepVersions) {
|
|
83
|
+
const release = await findUnityRelease(version);
|
|
84
|
+
const cs = changeset ?? release?.changeset ?? null;
|
|
85
|
+
const need = release ? installNeedBytes(release, modules) : FALLBACK_NEED_BYTES;
|
|
86
|
+
const installed = await ensurePersisted({ name: 'unity/editors', version, needBytes: need, marker: MARKER, what: 'Unity ' + version, keepVersions }, async (staging, temp) => {
|
|
87
|
+
log('Installing Unity ' + version + ' on the data disk (several GB; this takes a while) ...');
|
|
88
|
+
await heavy.run(() => runUnityCliChecked(cli.exe, installArgs(version, cs, modules, staging), { temp }));
|
|
89
|
+
});
|
|
90
|
+
return installed.dir;
|
|
91
|
+
}
|
|
92
|
+
/** Adds modules to an editor on the data disk. */
|
|
93
|
+
async function addModules(cli, editor, modules) {
|
|
94
|
+
const temp = path.join(editor.root, '.temp');
|
|
95
|
+
mkdirp(temp);
|
|
96
|
+
try {
|
|
97
|
+
log('Adding to Unity ' + editor.version + ': ' + modules.join(', ') + ' ...');
|
|
98
|
+
const args = installModulesArgs(editor.version, modules, editor.root);
|
|
99
|
+
await heavy.run(() => runUnityCliChecked(cli.exe, args, { temp }));
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
fs.rmSync(temp, { recursive: true, force: true });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** While the sandbox runs: lists the editors again (the user may have installed one with Hub) for the status. */
|
|
106
|
+
export function captureUnityEditors() {
|
|
107
|
+
const unity = session.toolchains.unity;
|
|
108
|
+
if (dryRun || unity.enabled !== true || !exists(EDITORS_DIR))
|
|
109
|
+
return Promise.resolve();
|
|
110
|
+
const editors = sandboxEditors({ pcInstalls: session.pcInstalls ?? { items: [] } });
|
|
111
|
+
const describe = (list) => list.map((e) => e.root + '|' + e.modules.join(',')).join(';');
|
|
112
|
+
if (describe(editors) !== describe(readUnityStatus().editors))
|
|
113
|
+
updateUnityStatus((s) => ({ ...s, editors: editors.slice(0, 32).map(statusEditor) }));
|
|
114
|
+
return Promise.resolve();
|
|
115
|
+
}
|
|
116
|
+
/** The editors, the project's choice, the CLI, the licence, the asset cache: set up and written to the status. */
|
|
117
|
+
export async function setupUnity(env, config, ctx) {
|
|
118
|
+
if (!config.acceptLicense)
|
|
119
|
+
throw new Error('Unity is set up only with toolchains.unity.acceptLicense = true: you accept the Unity terms of service (' +
|
|
120
|
+
TERMS +
|
|
121
|
+
') and the editor needs a Unity licence (Personal is free).');
|
|
122
|
+
const workspace = projectWorkspace();
|
|
123
|
+
if (dryRun) {
|
|
124
|
+
log('(dry run) would set up Unity ' + config.version + ' for ' + workspace);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const cli = await ensureUnityCli(config.cliVersion);
|
|
128
|
+
env.prepend.push(path.dirname(cli.exe));
|
|
129
|
+
let problem = null;
|
|
130
|
+
const quiet = (what) => (e) => warn('Unity CLI: ' + what + ' (' + errMsg(e) + ')');
|
|
131
|
+
// Settings that only keep the CLI quiet: a failure is not worth stopping for.
|
|
132
|
+
await runUnityCli(cli.exe, ['config', 'update-check', 'off'], { timeoutMs: 60_000 }).catch(quiet('update-check'));
|
|
133
|
+
await runUnityCli(cli.exe, ['analytics', 'opt-out'], { timeoutMs: 60_000 }).catch(quiet('analytics'));
|
|
134
|
+
mkdirp(EDITORS_DIR);
|
|
135
|
+
await runUnityCli(cli.exe, installPathArgs(EDITORS_DIR), { timeoutMs: 60_000 }).catch(quiet('install-path'));
|
|
136
|
+
let editors = sandboxEditors(ctx);
|
|
137
|
+
if (editors.length)
|
|
138
|
+
await runUnityCli(cli.exe, editorsAddArgs(editors.map((e) => e.root)), { timeoutMs: 120_000 }).catch(quiet('editors add'));
|
|
139
|
+
const wanted = wantedEditor(config, workspace);
|
|
140
|
+
let choice = chooseEditor(editors, wanted, config.modules, true);
|
|
141
|
+
const install = choice.install;
|
|
142
|
+
if (install) {
|
|
143
|
+
try {
|
|
144
|
+
const { version, changeset } = install;
|
|
145
|
+
await ensureUnityEditor(cli, version, changeset, config.modules, config.keepVersions);
|
|
146
|
+
editors = sandboxEditors(ctx);
|
|
147
|
+
choice = chooseEditor(editors, wanted, config.modules, true);
|
|
148
|
+
}
|
|
149
|
+
catch (e) {
|
|
150
|
+
problem = 'Unity ' + install.version + ' was not installed: ' + errMsg(e);
|
|
151
|
+
warn(problem);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
let editor = choice.editor;
|
|
155
|
+
if (editor) {
|
|
156
|
+
const have = editor.modules;
|
|
157
|
+
const lack = config.modules.filter((m) => !have.includes(m));
|
|
158
|
+
if (lack.length && editor.writable) {
|
|
159
|
+
try {
|
|
160
|
+
await addModules(cli, editor, lack);
|
|
161
|
+
editors = sandboxEditors(ctx);
|
|
162
|
+
const root = editor.root;
|
|
163
|
+
editor = editors.find((e) => e.root === root) ?? editor;
|
|
164
|
+
}
|
|
165
|
+
catch (e) {
|
|
166
|
+
problem = 'Unity modules were not added: ' + errMsg(e);
|
|
167
|
+
warn(problem);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else if (lack.length) {
|
|
171
|
+
problem =
|
|
172
|
+
'Unity ' +
|
|
173
|
+
editor.version +
|
|
174
|
+
' from this PC has no ' +
|
|
175
|
+
lack[0] +
|
|
176
|
+
" module: add it with Unity Hub on your PC, or remove that editor from This PC's editors to install one here.";
|
|
177
|
+
warn(problem);
|
|
178
|
+
}
|
|
179
|
+
env.vars.UNITY_EDITOR = editor.exe;
|
|
180
|
+
}
|
|
181
|
+
// The Asset Store cache: where downloads made here go (HKCU too, so an editor started from Explorer agrees).
|
|
182
|
+
// For "pc" the mapped Asset Store folder (guest of the unity-assets item) sits inside the cache root.
|
|
183
|
+
const pcAssets = ctx.pcInstalls.items.filter((i) => i.kind === 'unity-assets').map((i) => i.guest);
|
|
184
|
+
let cacheDir = null;
|
|
185
|
+
if (config.assetCache === 'persisted')
|
|
186
|
+
cacheDir = mkdirp(ASSET_DIR);
|
|
187
|
+
else if (config.assetCache === 'pc' && pcAssets[0])
|
|
188
|
+
cacheDir = path.win32.dirname(pcAssets[0]);
|
|
189
|
+
if (cacheDir) {
|
|
190
|
+
env.vars.ASSETSTORE_CACHE_PATH = cacheDir;
|
|
191
|
+
if (!ctx.duringSession)
|
|
192
|
+
await runScriptWithJsonAsync('set-registry-values.ps1', 'PlanFile', {
|
|
193
|
+
values: [{ hive: 'HKCU', key: 'Environment', name: 'ASSETSTORE_CACHE_PATH', value: cacheDir }],
|
|
194
|
+
}).catch((e) => warn('Unity: the Asset Store cache was not set for Explorer (' + errMsg(e) + ')'));
|
|
195
|
+
}
|
|
196
|
+
const licence = await activateUnityLicence({ editorExe: editor?.exe ?? null, unityExe: cli.exe }).catch((e) => {
|
|
197
|
+
warn('Unity licence: ' + errMsg(e));
|
|
198
|
+
return readUnityStatus().licence;
|
|
199
|
+
});
|
|
200
|
+
let hub = readUnityStatus().hub;
|
|
201
|
+
if (config.hub)
|
|
202
|
+
try {
|
|
203
|
+
hub = { installed: true, path: await ensureUnityHub() };
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
warn('Unity Hub was not installed: ' + errMsg(e));
|
|
207
|
+
}
|
|
208
|
+
const agent = await setupUnityAgentTools({
|
|
209
|
+
unityExe: cli.exe,
|
|
210
|
+
projectPath: workspace,
|
|
211
|
+
mcp: config.mcp,
|
|
212
|
+
skills: config.agentSkills,
|
|
213
|
+
}).catch((e) => {
|
|
214
|
+
warn('Unity agent tools: ' + errMsg(e));
|
|
215
|
+
return { mcp: null, skills: false };
|
|
216
|
+
});
|
|
217
|
+
const chosen = editor;
|
|
218
|
+
const status = updateUnityStatus((s) => {
|
|
219
|
+
const mine = s.projects.find((p) => p.workspace.toLowerCase() === workspace.toLowerCase());
|
|
220
|
+
return {
|
|
221
|
+
...s,
|
|
222
|
+
cli: { version: cli.version, path: cli.exe },
|
|
223
|
+
editors: editors.slice(0, 32).map(statusEditor),
|
|
224
|
+
projects: [
|
|
225
|
+
...s.projects.filter((p) => p !== mine),
|
|
226
|
+
{
|
|
227
|
+
workspace,
|
|
228
|
+
version: wanted?.version ?? null,
|
|
229
|
+
editor: chosen?.root ?? null,
|
|
230
|
+
reason: choice.reason,
|
|
231
|
+
bridge: mine?.bridge ?? null,
|
|
232
|
+
},
|
|
233
|
+
].slice(-64),
|
|
234
|
+
licence,
|
|
235
|
+
assetCache: { mode: config.assetCache, dir: cacheDir },
|
|
236
|
+
pcAssets: pcAssets.slice(0, 4),
|
|
237
|
+
hub,
|
|
238
|
+
mcp: agent.mcp ?? (ctx.duringSession ? s.mcp : null),
|
|
239
|
+
skills: agent.skills || (ctx.duringSession && s.skills),
|
|
240
|
+
problem,
|
|
241
|
+
};
|
|
242
|
+
});
|
|
243
|
+
env.summary.push(unitySummaryLine(status, workspace));
|
|
244
|
+
log('Unity: ' +
|
|
245
|
+
editors.length +
|
|
246
|
+
' editor(s); ' +
|
|
247
|
+
(editor ? editor.version + ' at ' + editor.root : 'none for this project') +
|
|
248
|
+
' (' +
|
|
249
|
+
choice.reason +
|
|
250
|
+
')');
|
|
4
251
|
}
|
|
@@ -1,4 +1,24 @@
|
|
|
1
|
+
import { type EngineFs, type LauncherEntry, type MappedEngine, type UnrealEngine } from '../../../shared/unreal/index.js';
|
|
1
2
|
import type { Toolchains } from '../../../core/types.js';
|
|
2
3
|
import type { ToolCtx } from '../step.js';
|
|
3
4
|
import type { ToolEnv } from '../tool-env.js';
|
|
4
|
-
|
|
5
|
+
/** The real disk, read bounded. */
|
|
6
|
+
export declare const DISK: EngineFs;
|
|
7
|
+
/** This PC's engines the host mapped (session.json's pcInstalls). */
|
|
8
|
+
export declare function mappedEngines(ctx: Pick<ToolCtx, 'pcInstalls'>): MappedEngine[];
|
|
9
|
+
/** Every engine this sandbox has (an install the launcher has not finished is not one yet). */
|
|
10
|
+
export declare function sandboxEngines(ctx: Pick<ToolCtx, 'pcInstalls'>, disk?: EngineFs): UnrealEngine[];
|
|
11
|
+
/** The LauncherInstalled.dat entries for the installed builds: the launcher's own saved ones, else made from them. */
|
|
12
|
+
export declare function launcherEntries(engines: readonly UnrealEngine[], ctx: Pick<ToolCtx, 'pcInstalls'>): LauncherEntry[];
|
|
13
|
+
/** Registers the engines machine-wide: the registry, LauncherInstalled.dat, the .uproject file association. */
|
|
14
|
+
export declare function registerEngines(engines: readonly UnrealEngine[], selectorFrom: UnrealEngine | null, ctx: Pick<ToolCtx, 'pcInstalls'>, aliases?: readonly {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
readonly dir: string;
|
|
17
|
+
}[]): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* While the sandbox runs (the daemon, every few minutes): saves what the launcher learned about engines it installed
|
|
20
|
+
* on the data disk and, when something is new, registers them and lists them for the Unreal page.
|
|
21
|
+
*/
|
|
22
|
+
export declare function captureLauncherEngines(): Promise<void>;
|
|
23
|
+
/** The project's engine on PATH, its variables, the guide's facts; the engines registered. */
|
|
24
|
+
export declare function setupUnrealEngines(env: ToolEnv, config: Toolchains['unreal'], ctx: ToolCtx): Promise<void>;
|
|
@@ -1,4 +1,164 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// Unreal engines, for each project: every engine the sandbox has (this PC's, mapped read-only or as folders you
|
|
2
|
+
// registered; the launcher's and other folders in tools\unreal\engines; source builds in project roots) is registered
|
|
3
|
+
// (the registry, the launcher's LauncherInstalled.dat, the .uproject file association), and the engine the project's
|
|
4
|
+
// .uproject names (else the newest) goes on its PATH with UE_ROOT, the derived data cache on the data disk and, with
|
|
5
|
+
// Unreal 5.8's MCP plugin, the editor's MCP server in Claude Code. Machine-wide writes only add; a project that joins
|
|
6
|
+
// later only adds its own engine choice.
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { readBoundedFile } from '../../../shared/io/bounded.js';
|
|
10
|
+
import { chooseEngine, ddcVars, engineExtraDirs, engineToolDirs, engineVars, fileAssociationPlan, findEngines, findUproject, hasMcpPlugin, incompleteDirs, itemSizes, launcherManifestsDir, launcherEntriesFor, registryPlan, UNREAL_MCP_URL, unrealSummaryLine, uprojectAssociation, versionSelectorIn, versionText, } from '../../../shared/unreal/index.js';
|
|
11
|
+
import { errMsg } from '../../../core/util/errors.js';
|
|
12
|
+
import { exists, mkdirp } from '../../runtime/fs.js';
|
|
13
|
+
import { log, warn } from '../../runtime/log.js';
|
|
14
|
+
import { dryRun } from '../../runtime/paths.js';
|
|
15
|
+
import { runScriptWithJsonAsync } from '../../runtime/process.js';
|
|
16
|
+
import { projectWorkspace } from '../../runtime/project-context.js';
|
|
17
|
+
import { session } from '../../runtime/session.js';
|
|
18
|
+
import { setUnrealMcp, writeAddonMcpServers } from '../../setup/addon-mcp.js';
|
|
19
|
+
import { saveLauncherState, savedEntries, writeLauncherEntries } from './epic-launcher.js';
|
|
20
|
+
import { DDC_DIR, ENGINES_DIR, SAVED_DIR, updateUnrealStatus } from './unreal-status.js';
|
|
21
|
+
const SAVED_MANIFESTS = path.join(SAVED_DIR, 'Manifests');
|
|
22
|
+
/** The real disk, read bounded. */
|
|
23
|
+
export const DISK = {
|
|
24
|
+
exists: (file) => exists(file),
|
|
25
|
+
read: (file, max) => readBoundedFile(file, max),
|
|
26
|
+
list: (dir) => {
|
|
27
|
+
try {
|
|
28
|
+
return fs.readdirSync(dir).slice(0, 500);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return []; // Not a folder: nothing in it.
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
/** This PC's engines the host mapped (session.json's pcInstalls). */
|
|
36
|
+
export function mappedEngines(ctx) {
|
|
37
|
+
return ctx.pcInstalls.items
|
|
38
|
+
.filter((i) => i.kind === 'unreal')
|
|
39
|
+
.map((i) => ({
|
|
40
|
+
guest: i.guest,
|
|
41
|
+
id: i.id,
|
|
42
|
+
...(i.writable ? { writable: true } : {}),
|
|
43
|
+
...(i.buildId ? { buildId: i.buildId } : {}),
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
/** The project roots of this sandbox (their folders may hold source builds). */
|
|
47
|
+
function projectRoots() {
|
|
48
|
+
const roots = (session.mappedProjects ?? []).filter((m) => m.root).map((m) => m.guest);
|
|
49
|
+
return [...new Set([...roots, ...(session.roots ?? [])])].filter((r) => exists(r));
|
|
50
|
+
}
|
|
51
|
+
/** The engines the launcher installed in tools\unreal\engines (its saved manifests and entries name them). */
|
|
52
|
+
function launcherDirs() {
|
|
53
|
+
return [...savedEntries().map((e) => e.InstallLocation), ...itemSizes(SAVED_MANIFESTS).keys()];
|
|
54
|
+
}
|
|
55
|
+
/** Every engine this sandbox has (an install the launcher has not finished is not one yet). */
|
|
56
|
+
export function sandboxEngines(ctx, disk = DISK) {
|
|
57
|
+
const incomplete = incompleteDirs(launcherManifestsDir(process.env.ProgramData ?? 'C:\\ProgramData'), SAVED_MANIFESTS);
|
|
58
|
+
return findEngines({
|
|
59
|
+
mapped: mappedEngines(ctx),
|
|
60
|
+
enginesRoot: ENGINES_DIR,
|
|
61
|
+
launcherDirs: launcherDirs(),
|
|
62
|
+
projectRoots: projectRoots(),
|
|
63
|
+
}, disk).filter((e) => !incomplete.has(path.win32.normalize(e.dir).toLowerCase()));
|
|
64
|
+
}
|
|
65
|
+
/** The LauncherInstalled.dat entries for the installed builds: the launcher's own saved ones, else made from them. */
|
|
66
|
+
export function launcherEntries(engines, ctx) {
|
|
67
|
+
return launcherEntriesFor(engines, savedEntries(), ctx.pcInstalls.items);
|
|
68
|
+
}
|
|
69
|
+
/** Registers the engines machine-wide: the registry, LauncherInstalled.dat, the .uproject file association. */
|
|
70
|
+
export async function registerEngines(engines, selectorFrom, ctx, aliases = []) {
|
|
71
|
+
const selector = selectorFrom ? versionSelectorIn(selectorFrom.dir) : null;
|
|
72
|
+
const values = [
|
|
73
|
+
...registryPlan(engines, aliases),
|
|
74
|
+
...(selector && exists(selector) ? fileAssociationPlan(selector) : []),
|
|
75
|
+
];
|
|
76
|
+
if (values.length)
|
|
77
|
+
await runScriptWithJsonAsync('set-registry-values.ps1', 'PlanFile', { values });
|
|
78
|
+
try {
|
|
79
|
+
writeLauncherEntries(launcherEntries(engines, ctx));
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
warn("Unreal: the Epic Games Launcher's LauncherInstalled.dat was left as it is (" + errMsg(e) + ')');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* While the sandbox runs (the daemon, every few minutes): saves what the launcher learned about engines it installed
|
|
87
|
+
* on the data disk and, when something is new, registers them and lists them for the Unreal page.
|
|
88
|
+
*/
|
|
89
|
+
export async function captureLauncherEngines() {
|
|
90
|
+
const unreal = session.toolchains.unreal;
|
|
91
|
+
if (dryRun || unreal.enabled !== true || !unreal.persistEngines || !saveLauncherState())
|
|
92
|
+
return;
|
|
93
|
+
const ctx = { pcInstalls: session.pcInstalls ?? { items: [] } };
|
|
94
|
+
const engines = sandboxEngines(ctx);
|
|
95
|
+
await registerEngines(engines, engines[0] ?? null, ctx);
|
|
96
|
+
const sizes = itemSizes(SAVED_MANIFESTS);
|
|
97
|
+
updateUnrealStatus((s) => ({ ...s, engines: engines.map((e) => statusEngine(e, sizes)) }));
|
|
98
|
+
}
|
|
99
|
+
const statusEngine = (e, sizes) => ({
|
|
100
|
+
id: e.id,
|
|
101
|
+
dir: e.dir,
|
|
102
|
+
version: versionText(e.version),
|
|
103
|
+
build: e.build,
|
|
104
|
+
origin: e.origin,
|
|
105
|
+
writable: e.writable,
|
|
106
|
+
sizeBytes: sizes.get(e.dir.toLowerCase()) ?? null,
|
|
107
|
+
});
|
|
108
|
+
/** The project's engine on PATH, its variables, the guide's facts; the engines registered. */
|
|
109
|
+
export async function setupUnrealEngines(env, config, ctx) {
|
|
110
|
+
const workspace = projectWorkspace();
|
|
111
|
+
const engines = sandboxEngines(ctx);
|
|
112
|
+
const uproject = findUproject(workspace, DISK);
|
|
113
|
+
const text = uproject ? readBoundedFile(uproject, 1024 * 1024) : null;
|
|
114
|
+
const association = (text ? uprojectAssociation(text.toString('utf8')) : null) ?? { kind: 'none' };
|
|
115
|
+
const choice = chooseEngine(engines, association);
|
|
116
|
+
const engine = choice.engine;
|
|
117
|
+
if (dryRun) {
|
|
118
|
+
log('(dry run) would register ' + engines.length + ' Unreal engines and use ' + (engine?.dir ?? 'none'));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
await registerEngines(engines, engine ?? engines[0] ?? null, ctx, engine && choice.alias ? [{ id: choice.alias, dir: engine.dir }] : []);
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
warn('Unreal: registering the engines failed (' + errMsg(e) + '); Build.bat and RunUAT.bat still work from PATH');
|
|
126
|
+
}
|
|
127
|
+
const ddc = config.derivedDataCache === 'persisted' ? mkdirp(DDC_DIR) : null;
|
|
128
|
+
Object.assign(env.vars, ddcVars(config.derivedDataCache, DDC_DIR));
|
|
129
|
+
if (engine) {
|
|
130
|
+
env.prepend.push(...engineToolDirs(engine.dir, DISK));
|
|
131
|
+
env.append.push(...engineExtraDirs(engine.dir, DISK));
|
|
132
|
+
Object.assign(env.vars, engineVars(engine));
|
|
133
|
+
}
|
|
134
|
+
const mcp = engine && hasMcpPlugin(engine, DISK) ? UNREAL_MCP_URL : null;
|
|
135
|
+
if (mcp || !ctx.duringSession) {
|
|
136
|
+
setUnrealMcp(mcp);
|
|
137
|
+
try {
|
|
138
|
+
writeAddonMcpServers();
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
warn("Unreal: the editor's MCP server was not added to Claude Code (" + errMsg(e) + ')');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const sizes = itemSizes(SAVED_MANIFESTS);
|
|
145
|
+
const status = updateUnrealStatus((s) => ({
|
|
146
|
+
...s,
|
|
147
|
+
engines: engines.map((e) => statusEngine(e, sizes)),
|
|
148
|
+
projects: [
|
|
149
|
+
...s.projects.filter((p) => p.workspace.toLowerCase() !== workspace.toLowerCase()),
|
|
150
|
+
{ workspace, engine: engine?.dir ?? null, reason: choice.reason },
|
|
151
|
+
].slice(-64),
|
|
152
|
+
ddc: { mode: config.derivedDataCache, dir: ddc },
|
|
153
|
+
vaults: ctx.pcInstalls.items.filter((i) => i.kind === 'unreal-vault').map((i) => i.guest),
|
|
154
|
+
mcpUrl: mcp ?? (ctx.duringSession ? s.mcpUrl : null),
|
|
155
|
+
}));
|
|
156
|
+
env.summary.push(unrealSummaryLine(status, workspace));
|
|
157
|
+
log('Unreal: ' +
|
|
158
|
+
engines.length +
|
|
159
|
+
' engine(s); ' +
|
|
160
|
+
(engine ? 'this project uses ' + engine.id + ' at ' + engine.dir : 'none for this project') +
|
|
161
|
+
' (' +
|
|
162
|
+
choice.reason +
|
|
163
|
+
')');
|
|
4
164
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type UnrealStatus } from '../../../shared/unreal/index.js';
|
|
2
|
+
export declare const UNREAL_DIR: string;
|
|
3
|
+
/** Where the Epic Games Launcher installs engines (persistEngines). */
|
|
4
|
+
export declare const ENGINES_DIR: string;
|
|
5
|
+
/** The engines' local derived data cache (derivedDataCache "persisted"). */
|
|
6
|
+
export declare const DDC_DIR: string;
|
|
7
|
+
/** The launcher's manifests and entries of the engines in ENGINES_DIR, kept for later boots. */
|
|
8
|
+
export declare const SAVED_DIR: string;
|
|
9
|
+
export declare function readUnrealStatus(): UnrealStatus;
|
|
10
|
+
/** Changes the status with `change` and writes it (whole, atomically). */
|
|
11
|
+
export declare function updateUnrealStatus(change: (s: UnrealStatus) => UnrealStatus): UnrealStatus;
|
|
12
|
+
/** A fresh status at boot: the machine parts start over, the engines and projects are found again. */
|
|
13
|
+
export declare function resetUnrealStatus(): void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// tools\unreal\status.json: what the sandbox found for Unreal, for the guide and the Unreal page on the PC. The boot's
|
|
2
|
+
// machine-wide step writes the launcher, Visual Studio and debugger parts, each project's step its engine; every write
|
|
3
|
+
// keeps what the other parts wrote.
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { readValid, writeJsonAtomic } from '../../../shared/io/json.js';
|
|
6
|
+
import { UNREAL_STATUS_FILE, UnrealStatusSchema } from '../../../shared/unreal/index.js';
|
|
7
|
+
import { mkdirp } from '../../runtime/fs.js';
|
|
8
|
+
import { tool } from '../tool-env.js';
|
|
9
|
+
export const UNREAL_DIR = tool('unreal');
|
|
10
|
+
/** Where the Epic Games Launcher installs engines (persistEngines). */
|
|
11
|
+
export const ENGINES_DIR = path.join(UNREAL_DIR, 'engines');
|
|
12
|
+
/** The engines' local derived data cache (derivedDataCache "persisted"). */
|
|
13
|
+
export const DDC_DIR = path.join(UNREAL_DIR, 'ddc');
|
|
14
|
+
/** The launcher's manifests and entries of the engines in ENGINES_DIR, kept for later boots. */
|
|
15
|
+
export const SAVED_DIR = path.join(UNREAL_DIR, 'launcher');
|
|
16
|
+
const FILE = path.join(UNREAL_DIR, UNREAL_STATUS_FILE);
|
|
17
|
+
const EMPTY = {
|
|
18
|
+
updatedAt: '',
|
|
19
|
+
engines: [],
|
|
20
|
+
projects: [],
|
|
21
|
+
launcher: { installed: false, installLocation: null, problem: null, freeBytes: null, needBytes: 0 },
|
|
22
|
+
visualStudio: { version: '', path: '', how: '', check: '', ok: false },
|
|
23
|
+
debugger: null,
|
|
24
|
+
ddc: { mode: 'local', dir: null },
|
|
25
|
+
vaults: [],
|
|
26
|
+
mcpUrl: null,
|
|
27
|
+
};
|
|
28
|
+
export function readUnrealStatus() {
|
|
29
|
+
return readValid(UnrealStatusSchema, FILE) ?? EMPTY;
|
|
30
|
+
}
|
|
31
|
+
/** Changes the status with `change` and writes it (whole, atomically). */
|
|
32
|
+
export function updateUnrealStatus(change) {
|
|
33
|
+
const next = { ...change(readUnrealStatus()), updatedAt: new Date().toISOString() };
|
|
34
|
+
mkdirp(UNREAL_DIR);
|
|
35
|
+
writeJsonAtomic(FILE, next);
|
|
36
|
+
return next;
|
|
37
|
+
}
|
|
38
|
+
/** A fresh status at boot: the machine parts start over, the engines and projects are found again. */
|
|
39
|
+
export function resetUnrealStatus() {
|
|
40
|
+
updateUnrealStatus(() => EMPTY);
|
|
41
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** KitsRoot10 from the registry (64-bit view), or null. */
|
|
2
|
+
export declare function kitsRoot(): string | null;
|
|
3
|
+
/** The debuggers' x64 folder when cdb.exe is in it, else null. */
|
|
4
|
+
export declare function debuggersDir(root?: string | null): string | null;
|
|
5
|
+
/** The debuggers' folder, installed with the SDK installer when missing; null when there is no SDK to add them to. */
|
|
6
|
+
export declare function ensureDebuggers(): Promise<string | null>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// The Windows SDK's Debugging Tools (cdb, windbg, kd) for reading Unreal crash dumps: not part of the SDK Visual Studio
|
|
2
|
+
// installs, so the Windows SDK installer (downloaded once into tools\visualstudio, Microsoft's signature checked) adds
|
|
3
|
+
// only that feature into the SDK the registry names (KitsRoot10). Visual Studio kept on the data disk has its SDK
|
|
4
|
+
// there, so the debuggers are installed once and come back with it (~45 s, 370 MB).
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { SYSTEM } from '../../../shared/system.js';
|
|
7
|
+
import { runInstaller } from '../../runtime/pools.js';
|
|
8
|
+
import { execOutput } from '../../runtime/process.js';
|
|
9
|
+
import { exists } from '../../runtime/fs.js';
|
|
10
|
+
import { log } from '../../runtime/log.js';
|
|
11
|
+
import { SIGNERS, signedInstaller } from '../installers.js';
|
|
12
|
+
import { tool } from '../tool-env.js';
|
|
13
|
+
/** The Windows SDK 10.0.22621 installer (the SDK version Visual Studio's Unreal components install). */
|
|
14
|
+
const SDK_SETUP_URL = 'https://go.microsoft.com/fwlink/?linkid=2311806';
|
|
15
|
+
const SDK_SETUP = tool('visualstudio', 'winsdksetup-22621.exe');
|
|
16
|
+
/** KitsRoot10 from the registry (64-bit view), or null. */
|
|
17
|
+
export function kitsRoot() {
|
|
18
|
+
const out = execOutput(SYSTEM.reg(), [
|
|
19
|
+
'query',
|
|
20
|
+
'HKLM\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots',
|
|
21
|
+
'/v',
|
|
22
|
+
'KitsRoot10',
|
|
23
|
+
'/reg:64',
|
|
24
|
+
]);
|
|
25
|
+
const m = /KitsRoot10\s+REG_SZ\s+(.+)$/im.exec(out);
|
|
26
|
+
return m ? m[1].trim() : null;
|
|
27
|
+
}
|
|
28
|
+
/** The debuggers' x64 folder when cdb.exe is in it, else null. */
|
|
29
|
+
export function debuggersDir(root = kitsRoot()) {
|
|
30
|
+
if (!root)
|
|
31
|
+
return null;
|
|
32
|
+
const dir = path.join(root, 'Debuggers', 'x64');
|
|
33
|
+
return exists(path.join(dir, 'cdb.exe')) ? dir : null;
|
|
34
|
+
}
|
|
35
|
+
/** The debuggers' folder, installed with the SDK installer when missing; null when there is no SDK to add them to. */
|
|
36
|
+
export async function ensureDebuggers() {
|
|
37
|
+
const root = kitsRoot();
|
|
38
|
+
if (!root)
|
|
39
|
+
return null;
|
|
40
|
+
const have = debuggersDir(root);
|
|
41
|
+
if (have)
|
|
42
|
+
return have;
|
|
43
|
+
const setup = await signedInstaller(SDK_SETUP_URL, SDK_SETUP, 'the Windows SDK installer, for its debuggers (~1.5 MB, then ~100 MB)', SIGNERS.microsoft);
|
|
44
|
+
log('Installing the Windows SDK debuggers (cdb, windbg) into ' + root + ' ...');
|
|
45
|
+
const code = await runInstaller(setup, [
|
|
46
|
+
'/features',
|
|
47
|
+
'OptionId.WindowsDesktopDebuggers',
|
|
48
|
+
'/quiet',
|
|
49
|
+
'/norestart',
|
|
50
|
+
'/ceip',
|
|
51
|
+
'off',
|
|
52
|
+
]);
|
|
53
|
+
const dir = debuggersDir(root);
|
|
54
|
+
if (!dir)
|
|
55
|
+
throw new Error('the Windows SDK installer did not add the debuggers (exit ' + code + ')');
|
|
56
|
+
return dir;
|
|
57
|
+
}
|