windows-development-sandbox 1.25.0 → 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.
- package/config.example.json +5 -2
- 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 +161 -6
- package/dist/app/features/defs.js +7 -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/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 +5 -0
- package/dist/bin/agent-sandbox.js +2 -2
- package/dist/bin/{chunk-SUYYVIDW.js → chunk-3GAXLATK.js} +58 -5
- package/dist/bin/{chunk-IZRGTSQ5.js → chunk-F4PVBN23.js} +2465 -1392
- package/dist/bin/{chunk-KH4MI2U5.js → chunk-G3TG4ZT7.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 +5 -2
- package/dist/cli/commands/registry.js +3 -0
- package/dist/cli/manifest.d.ts +18 -0
- package/dist/cli/manifest.js +26 -0
- package/dist/cli/menu/coverage-exempt.js +1 -0
- package/dist/cli/menu/pages/sandbox-page.d.ts +3 -0
- package/dist/cli/menu/pages/sandbox-page.js +34 -4
- package/dist/cli/menu/views.js +2 -0
- package/dist/core/config/defaults.js +18 -4
- package/dist/core/schema/config.d.ts +9 -7
- package/dist/core/schema/pc-installs.d.ts +3 -1
- package/dist/core/schema/pc-installs.js +10 -1
- package/dist/core/schema/toolchains.js +1 -1
- package/dist/guest/accounts/kinds/epic.js +18 -13
- 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 +13 -3
- package/dist/guest/apps/comet-unpack.js +3 -2
- package/dist/guest/apps/resolvers-gamedev.js +55 -1
- package/dist/guest/boot/boot.js +14 -1
- package/dist/guest/boot/guide.js +24 -0
- package/dist/guest/daemon/daemon.js +3 -0
- package/dist/guest/setup/addon-mcp.d.ts +7 -0
- package/dist/guest/setup/addon-mcp.js +19 -4
- package/dist/guest/setup/browser-gpu.js +14 -11
- package/dist/guest/setup/headroom.js +4 -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/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 +12 -1
- package/dist/guest/toolchains/tools/vscode-profile.js +2 -0
- package/dist/host/accounts/kinds/epic.js +11 -5
- package/dist/host/accounts/source.d.ts +2 -0
- package/dist/host/accounts/source.js +1 -0
- package/dist/host/pc-installs/discover.d.ts +2 -0
- package/dist/host/pc-installs/discover.js +25 -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/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 +81 -7
- package/dist/runtime/daemon.js +2645 -1287
- package/dist/runtime/db.js +81 -7
- package/dist/runtime/main.js +2901 -1722
- package/dist/runtime/netproxy.js +81 -7
- package/dist/runtime/tool.js +81 -7
- package/dist/runtime/view.js +81 -7
- package/dist/selftest/agent-paths.test.js +1 -0
- package/dist/selftest/agents-web.test.js +12 -0
- package/dist/selftest/engines.test.js +602 -3
- package/dist/selftest/heavy-load.test.js +3 -1
- package/dist/selftest/hostile-input.test.js +11 -1
- package/dist/selftest/index.js +2 -0
- package/dist/selftest/menu-screens.js +3 -1
- package/dist/selftest/mixamo.test.d.ts +2 -0
- package/dist/selftest/mixamo.test.js +60 -0
- package/dist/selftest/secrets.test.js +36 -1
- package/dist/selftest/support/engine-golden.js +3 -0
- package/dist/selftest/support/fixtures.d.ts +3 -1
- 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/shared/accounts/index.d.ts +1 -1
- package/dist/shared/accounts/index.js +1 -1
- package/dist/shared/accounts/kinds/epic.d.ts +6 -1
- package/dist/shared/accounts/kinds/epic.js +11 -1
- 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 +4 -1
- package/dist/shared/apps/catalogue-agents-web.js +5 -2
- package/dist/shared/apps/catalogue-gamedev.js +71 -1
- package/dist/shared/apps/index.d.ts +2 -0
- package/dist/shared/apps/index.js +2 -0
- package/dist/shared/apps/preview.d.ts +2 -0
- package/dist/shared/apps/preview.js +10 -0
- package/dist/shared/browser-policy.d.ts +14 -0
- package/dist/shared/browser-policy.js +22 -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 +1 -0
- package/dist/shared/readiness.d.ts +0 -1
- package/dist/shared/readiness.js +0 -1
- package/dist/shared/signer.d.ts +4 -0
- package/dist/shared/signer.js +2 -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-FNE6XEHA.js} +84 -84
- package/package.json +1 -1
- package/sandbox/csharp/VsSetup.cs +210 -0
- package/sandbox/scripts/get-vs-setup.ps1 +93 -0
- package/sandbox/scripts/set-registry-values.ps1 +35 -0
- package/schemas/config.schema.json +35 -2
- package/schemas/sandbox-config.schema.json +8 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// The machine policy of the sandbox's browsers (Edge and Chrome), written under HKLM at every boot by the guest: no GPU
|
|
2
|
+
// and the folder downloads go to. Pure (no reads or writes here): the guest runs reg.exe with these entries and the
|
|
3
|
+
// self-test checks them.
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
export const BROWSER_POLICY_KEYS = [
|
|
6
|
+
'HKLM\\SOFTWARE\\Policies\\Microsoft\\Edge',
|
|
7
|
+
'HKLM\\SOFTWARE\\Policies\\Google\\Chrome',
|
|
8
|
+
];
|
|
9
|
+
/**
|
|
10
|
+
* The policies for each browser: no hardware acceleration (today's behaviour) and, when a downloads folder is given,
|
|
11
|
+
* DefaultDownloadDirectory (the default only: Playwright's download path and the user's own choice still win).
|
|
12
|
+
*/
|
|
13
|
+
export function browserPolicies(o) {
|
|
14
|
+
if (o.downloads !== null && (!path.win32.isAbsolute(o.downloads) || /["\r\n%]/.test(o.downloads)))
|
|
15
|
+
throw new Error('not a folder');
|
|
16
|
+
return BROWSER_POLICY_KEYS.flatMap((key) => [
|
|
17
|
+
{ key, name: 'HardwareAccelerationModeEnabled', type: 'REG_DWORD', data: '0' },
|
|
18
|
+
...(o.downloads === null
|
|
19
|
+
? []
|
|
20
|
+
: [{ key, name: 'DefaultDownloadDirectory', type: 'REG_SZ', data: o.downloads }]),
|
|
21
|
+
]);
|
|
22
|
+
}
|
package/dist/shared/io/index.js
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Keeps a log that a detached program appends to from growing for ever: past the limit it becomes <log>.1.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
/** Moves `file` to `file + '.1'` (replacing the older copy) when it is over `maxBytes`; true when it moved. */
|
|
4
|
+
export function rotateLog(file, maxBytes) {
|
|
5
|
+
try {
|
|
6
|
+
if (fs.statSync(file).size <= maxBytes)
|
|
7
|
+
return false;
|
|
8
|
+
fs.rmSync(file + '.1', { force: true });
|
|
9
|
+
fs.renameSync(file, file + '.1');
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
// No log yet, or it is held open: the next start tries again.
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -67,3 +67,8 @@ export declare function chooseTargetDir(targetDir: string, freeBytes: number): {
|
|
|
67
67
|
};
|
|
68
68
|
/** The most heavy compilers (rustc, clippy-driver, link ...) that run at once in the agents' job: only with vGPU on. */
|
|
69
69
|
export declare function maxCompilers(cpus: number, profile: LoadProfile): number | null;
|
|
70
|
+
/**
|
|
71
|
+
* The cargo variable that names the linker of a Rust host (`x86_64-pc-windows-msvc` ->
|
|
72
|
+
* `CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER`).
|
|
73
|
+
*/
|
|
74
|
+
export declare function cargoLinkerVar(host: string): string;
|
|
@@ -108,3 +108,10 @@ export function chooseTargetDir(targetDir, freeBytes) {
|
|
|
108
108
|
export function maxCompilers(cpus, profile) {
|
|
109
109
|
return profile === 'gentle' ? Math.min(4, Math.max(2, Math.floor(cpus / 4))) : null;
|
|
110
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* The cargo variable that names the linker of a Rust host (`x86_64-pc-windows-msvc` ->
|
|
113
|
+
* `CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER`).
|
|
114
|
+
*/
|
|
115
|
+
export function cargoLinkerVar(host) {
|
|
116
|
+
return 'CARGO_TARGET_' + host.toUpperCase().replace(/[^A-Z0-9]/g, '_') + '_LINKER';
|
|
117
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Where this PC's installs appear in the sandbox (reserved: RESERVED_GUEST). */
|
|
2
2
|
export declare const PC_INSTALLS_GUEST = "C:\\PcInstalls";
|
|
3
|
-
export type PcInstallKind = 'steam-game' | 'steam-library' | 'epic-game' | 'unreal' | 'unity';
|
|
3
|
+
export type PcInstallKind = 'steam-game' | 'steam-library' | 'epic-game' | 'unreal' | 'unreal-vault' | 'unity';
|
|
4
4
|
export interface PcInstall {
|
|
5
5
|
readonly kind: PcInstallKind;
|
|
6
6
|
/** The Steam app ID or library (library1), the Epic app name, the engine (UE_5.4) or editor version. */
|
|
@@ -9,8 +9,19 @@ export interface PcInstall {
|
|
|
9
9
|
readonly version?: string;
|
|
10
10
|
/** The folder on this PC (never sent to the sandbox). */
|
|
11
11
|
readonly host: string;
|
|
12
|
-
/** Where it appears in the sandbox, read-only
|
|
12
|
+
/** Where it appears in the sandbox, read-only unless `writable`. */
|
|
13
13
|
readonly guest: string;
|
|
14
|
+
/** Mapped writable: an engine folder you registered (pcInstalls.unrealEngineFolders). */
|
|
15
|
+
readonly writable?: boolean;
|
|
16
|
+
/** An Unreal source build's registered name on this PC (HKCU\Software\Epic Games\Unreal Engine\Builds). */
|
|
17
|
+
readonly buildId?: string;
|
|
18
|
+
/** The Epic Games Launcher's own ids of the app (from this PC's LauncherInstalled.dat). */
|
|
19
|
+
readonly launcher?: {
|
|
20
|
+
readonly namespaceId: string;
|
|
21
|
+
readonly itemId: string;
|
|
22
|
+
readonly artifactId: string;
|
|
23
|
+
readonly appVersion: string;
|
|
24
|
+
};
|
|
14
25
|
}
|
|
15
26
|
/** What session.json says about them: no host paths. */
|
|
16
27
|
export interface PcInstallManifest {
|
|
@@ -3,7 +3,6 @@ export declare const NOT_READY: {
|
|
|
3
3
|
readonly toolchains: readonly ["unity", "godot", "android", "flutter", "dart", "reactNative"];
|
|
4
4
|
/** Service sign-ins whose host or guest side is a stub: the menu and the web do not list them. */
|
|
5
5
|
readonly kinds: readonly ["unity", "keystore", "play", "expo", "appstore"];
|
|
6
|
-
readonly unreal: readonly ["engines"];
|
|
7
6
|
};
|
|
8
7
|
export declare const isToolchainReady: (name: string) => boolean;
|
|
9
8
|
export declare const isKindReady: (kind: string) => boolean;
|
package/dist/shared/readiness.js
CHANGED
|
@@ -5,7 +5,6 @@ export const NOT_READY = {
|
|
|
5
5
|
toolchains: ['unity', 'godot', 'android', 'flutter', 'dart', 'reactNative'],
|
|
6
6
|
/** Service sign-ins whose host or guest side is a stub: the menu and the web do not list them. */
|
|
7
7
|
kinds: ['unity', 'keystore', 'play', 'expo', 'appstore'],
|
|
8
|
-
unreal: ['engines'],
|
|
9
8
|
};
|
|
10
9
|
export const isToolchainReady = (name) => !NOT_READY.toolchains.includes(name);
|
|
11
10
|
export const isKindReady = (kind) => !NOT_READY.kinds.includes(kind);
|
package/dist/shared/signer.d.ts
CHANGED
|
@@ -21,4 +21,8 @@ export declare const SIGNERS: {
|
|
|
21
21
|
readonly unity: {
|
|
22
22
|
readonly subject: readonly ["^CN=\"?Unity Technologies"];
|
|
23
23
|
};
|
|
24
|
+
/** The Epic Games Launcher's MSI (checked on EpicInstaller-20.3.3.msi). */
|
|
25
|
+
readonly epic: {
|
|
26
|
+
readonly subject: readonly ["^CN=Epic Games Inc\\., O=Epic Games Inc\\., L=Cary, S=North Carolina, C=US"];
|
|
27
|
+
};
|
|
24
28
|
};
|
package/dist/shared/signer.js
CHANGED
|
@@ -12,4 +12,6 @@ export const SIGNERS = {
|
|
|
12
12
|
microsoft: { subject: ['^CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US$'] },
|
|
13
13
|
valve: { subject: ['^CN="?Valve(,| Corp)'] },
|
|
14
14
|
unity: { subject: ['^CN="?Unity Technologies'] },
|
|
15
|
+
/** The Epic Games Launcher's MSI (checked on EpicInstaller-20.3.3.msi). */
|
|
16
|
+
epic: { subject: ['^CN=Epic Games Inc\\., O=Epic Games Inc\\., L=Cary, S=North Carolina, C=US'] },
|
|
15
17
|
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/** Where an engine came from, which decides whether it is writable and how it is listed. */
|
|
2
|
+
export type EngineOrigin =
|
|
3
|
+
/** Installed on this PC, mapped read-only under C:\PcInstalls\unreal (pcInstalls.unrealEngines). */
|
|
4
|
+
'pc'
|
|
5
|
+
/** A folder of this PC you registered (pcInstalls.unrealEngineFolders), mapped writable. */
|
|
6
|
+
| 'pc-folder'
|
|
7
|
+
/** Installed by the Epic Games Launcher in a sandbox, in tools\unreal\engines on the data disk. */
|
|
8
|
+
| 'launcher'
|
|
9
|
+
/** Another engine folder in tools\unreal\engines (a source build or a copy). */
|
|
10
|
+
| 'data'
|
|
11
|
+
/** A source build found in a project root (C:\Projects\<root>\<folder>). */
|
|
12
|
+
| 'project';
|
|
13
|
+
export declare const ORIGIN_LABELS: Readonly<Record<EngineOrigin, string>>;
|
|
14
|
+
export interface BuildVersion {
|
|
15
|
+
readonly major: number;
|
|
16
|
+
readonly minor: number;
|
|
17
|
+
readonly patch: number;
|
|
18
|
+
readonly changelist: number;
|
|
19
|
+
readonly branch: string;
|
|
20
|
+
}
|
|
21
|
+
/** One engine the sandbox knows. */
|
|
22
|
+
export interface UnrealEngine {
|
|
23
|
+
/** UE_5.4 for an installed build; the source build's registered name (a GUID or a plain name). */
|
|
24
|
+
readonly id: string;
|
|
25
|
+
/** The engine's folder (holds Engine\Build\Build.version). */
|
|
26
|
+
readonly dir: string;
|
|
27
|
+
readonly version: BuildVersion;
|
|
28
|
+
/** "installed": a launcher or installed build (InstalledBuild.txt); "source": built from the engine's source. */
|
|
29
|
+
readonly build: 'installed' | 'source';
|
|
30
|
+
readonly origin: EngineOrigin;
|
|
31
|
+
readonly writable: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** How engine files are read: bounded reads, folder lists, existence. */
|
|
34
|
+
export interface EngineFs {
|
|
35
|
+
exists(file: string): boolean;
|
|
36
|
+
/** At most `max` bytes, null when it is missing, too big or not a file. */
|
|
37
|
+
read(file: string, max: number): Buffer | null;
|
|
38
|
+
/** The names in a folder ([] when it is not one). */
|
|
39
|
+
list(dir: string): string[];
|
|
40
|
+
}
|
|
41
|
+
/** Engine\Build\Build.version: the version of an engine folder, or null when the text is not one. */
|
|
42
|
+
export declare function parseBuildVersion(text: string): BuildVersion | null;
|
|
43
|
+
/** "5.4": the name the launcher and the registry give an engine version. */
|
|
44
|
+
export declare const majorMinor: (v: BuildVersion) => string;
|
|
45
|
+
/** "5.4.4": the full version for people. */
|
|
46
|
+
export declare const versionText: (v: BuildVersion) => string;
|
|
47
|
+
/** "UE_5.4": the launcher's app name (and the folder it installs into). */
|
|
48
|
+
export declare const launcherAppName: (v: BuildVersion) => string;
|
|
49
|
+
export declare function compareVersions(a: BuildVersion, b: BuildVersion): number;
|
|
50
|
+
/** Engine\Build\Build.version of a folder, or null when it is not an engine. */
|
|
51
|
+
export declare function readEngineVersion(dir: string, fs: EngineFs): BuildVersion | null;
|
|
52
|
+
/** Whether an engine folder is an installed build (what the launcher installs): Engine\Build\InstalledBuild.txt. */
|
|
53
|
+
export declare function isInstalledBuild(dir: string, fs: EngineFs): boolean;
|
|
54
|
+
/** Whether a name can be a source build's registered name (UnrealVersionSelector writes a {GUID}). */
|
|
55
|
+
export declare const isBuildId: (id: string) => boolean;
|
|
56
|
+
/**
|
|
57
|
+
* A source build's registered name when it has none of its own: a GUID made from its folder, so it is the same every
|
|
58
|
+
* boot and a project that was switched to it keeps finding it.
|
|
59
|
+
*/
|
|
60
|
+
export declare function stableBuildId(dir: string): string;
|
|
61
|
+
/** The engine in a folder, or null when it is not one. `id`: a source build's registered name (else one is made). */
|
|
62
|
+
export declare function engineAt(dir: string, origin: EngineOrigin, writable: boolean, fs: EngineFs, id?: string): UnrealEngine | null;
|
|
63
|
+
/** What the guest knows of this PC's engines (session.json's pcInstalls items of kind "unreal"). */
|
|
64
|
+
export interface MappedEngine {
|
|
65
|
+
readonly guest: string;
|
|
66
|
+
readonly id: string;
|
|
67
|
+
/** A registered folder of this PC: mapped writable. */
|
|
68
|
+
readonly writable?: boolean;
|
|
69
|
+
/** A source build's registered name on this PC (HKCU ... Builds), kept so the project's association still matches. */
|
|
70
|
+
readonly buildId?: string;
|
|
71
|
+
}
|
|
72
|
+
export interface EngineSources {
|
|
73
|
+
/** Engines of this PC mapped into the sandbox. */
|
|
74
|
+
readonly mapped: readonly MappedEngine[];
|
|
75
|
+
/** tools\unreal\engines on the data disk. */
|
|
76
|
+
readonly enginesRoot: string;
|
|
77
|
+
/** The folders of the engines the launcher installed there (from its saved manifests). */
|
|
78
|
+
readonly launcherDirs: readonly string[];
|
|
79
|
+
/** Project roots in the sandbox (C:\Projects\<root>): their folders are looked at for source builds. */
|
|
80
|
+
readonly projectRoots: readonly string[];
|
|
81
|
+
}
|
|
82
|
+
/** At most this many engines are used. */
|
|
83
|
+
export declare const MAX_ENGINES = 16;
|
|
84
|
+
/** Every engine the sandbox can use, in a fixed order (this PC's, the data disk's, the project roots'), once each. */
|
|
85
|
+
export declare function findEngines(s: EngineSources, fs: EngineFs): UnrealEngine[];
|
|
86
|
+
export type EngineAssociation = {
|
|
87
|
+
readonly kind: 'none';
|
|
88
|
+
} | {
|
|
89
|
+
readonly kind: 'version';
|
|
90
|
+
readonly version: string;
|
|
91
|
+
} | {
|
|
92
|
+
readonly kind: 'build';
|
|
93
|
+
readonly id: string;
|
|
94
|
+
};
|
|
95
|
+
/** What a .uproject's EngineAssociation names: "5.4" (an installed build), "{GUID}" or a name (a source build). */
|
|
96
|
+
export declare function parseEngineAssociation(value: unknown): EngineAssociation;
|
|
97
|
+
/** The EngineAssociation of a .uproject's text (null when it is not JSON). */
|
|
98
|
+
export declare function uprojectAssociation(text: string): EngineAssociation | null;
|
|
99
|
+
/** A project's .uproject: in its folder, else one folder down (the first by name), or null. */
|
|
100
|
+
export declare function findUproject(workspace: string, fs: EngineFs): string | null;
|
|
101
|
+
export interface EngineChoice {
|
|
102
|
+
readonly engine: UnrealEngine | null;
|
|
103
|
+
/** Why this one, in plain words (or why none). */
|
|
104
|
+
readonly reason: string;
|
|
105
|
+
/** The name the project gave a source build that is not registered here by that name: register it for this engine. */
|
|
106
|
+
readonly alias?: string;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The engine a project builds with: the one its EngineAssociation names (a version: a writable one before this PC's
|
|
110
|
+
* read-only one; a build: that registered build), else the newest. Null when the project names one that is not here.
|
|
111
|
+
*/
|
|
112
|
+
export declare function chooseEngine(engines: readonly UnrealEngine[], association: EngineAssociation): EngineChoice;
|
|
113
|
+
/** One registry value the sandbox writes (string values only). */
|
|
114
|
+
export interface RegistryValue {
|
|
115
|
+
readonly hive: 'HKLM' | 'HKCU';
|
|
116
|
+
readonly key: string;
|
|
117
|
+
/** '' is the default value. */
|
|
118
|
+
readonly name: string;
|
|
119
|
+
readonly value: string;
|
|
120
|
+
}
|
|
121
|
+
/** Installed builds: where an installer records them (UnrealVersionSelector and the editor read it). */
|
|
122
|
+
export declare const INSTALLED_KEY = "SOFTWARE\\EpicGames\\Unreal Engine";
|
|
123
|
+
/** Source builds: UnrealVersionSelector's list (name = the build's id, value = its folder). */
|
|
124
|
+
export declare const BUILDS_KEY = "Software\\Epic Games\\Unreal Engine\\Builds";
|
|
125
|
+
/**
|
|
126
|
+
* The installed build of each version that the editor and UnrealVersionSelector use: one per major.minor, chosen the
|
|
127
|
+
* way chooseEngine chooses (writable before read-only, then the newest).
|
|
128
|
+
*/
|
|
129
|
+
export declare function preferredInstalled(engines: readonly UnrealEngine[]): UnrealEngine[];
|
|
130
|
+
/**
|
|
131
|
+
* The registry values that make Unreal find the engines: an installed build as HKLM\SOFTWARE\EpicGames\Unreal
|
|
132
|
+
* Engine\<5.4> InstalledDirectory (one per version: the one a project would pick), a source build as a value under
|
|
133
|
+
* HKCU\Software\Epic Games\Unreal Engine\Builds. `aliases`: names a project uses for a source build here (its GUID on
|
|
134
|
+
* this PC), registered for the folder they stand for, so the .uproject works unchanged.
|
|
135
|
+
*/
|
|
136
|
+
export declare function registryPlan(engines: readonly UnrealEngine[], aliases?: readonly {
|
|
137
|
+
readonly id: string;
|
|
138
|
+
readonly dir: string;
|
|
139
|
+
}[]): RegistryValue[];
|
|
140
|
+
/** UnrealVersionSelector inside an engine (it opens, switches and generates project files for .uproject files). */
|
|
141
|
+
export declare function versionSelectorIn(dir: string): string;
|
|
142
|
+
/**
|
|
143
|
+
* The .uproject file association UnrealVersionSelector /fileassociations writes (per user): open in the editor, launch
|
|
144
|
+
* the game, generate Visual Studio project files, switch the engine version.
|
|
145
|
+
*/
|
|
146
|
+
export declare function fileAssociationPlan(selector: string): RegistryValue[];
|
|
147
|
+
/** The folders of an engine an agent runs its tools from (those that exist), in PATH order. */
|
|
148
|
+
export declare function engineToolDirs(dir: string, fs: Pick<EngineFs, 'exists'>): string[];
|
|
149
|
+
/** The engine folders put at the END of PATH: they hold many DLLs, which every program started from PATH would load. */
|
|
150
|
+
export declare function engineExtraDirs(dir: string, fs: Pick<EngineFs, 'exists'>): string[];
|
|
151
|
+
/** Variables for the project's engine: UE_ROOT (the engine folder) and UE_ENGINE_DIR (its Engine folder). */
|
|
152
|
+
export declare function engineVars(engine: UnrealEngine): Record<string, string>;
|
|
153
|
+
/**
|
|
154
|
+
* The derived data cache: UE-LocalDataCachePath moves the local cache (and from 5.4 the Zen store, in its Zen
|
|
155
|
+
* subfolder) out of the engine and off the sandbox's disk. "local" leaves Unreal's own place (the sandbox's disk).
|
|
156
|
+
*/
|
|
157
|
+
export declare function ddcVars(mode: 'persisted' | 'local', ddcDir: string): Record<string, string>;
|
|
158
|
+
/** The editor's MCP server (Unreal 5.8's ModelContextProtocol plugin): its default address. */
|
|
159
|
+
export declare const UNREAL_MCP_URL = "http://127.0.0.1:8000/mcp";
|
|
160
|
+
/** Whether an engine ships the ModelContextProtocol plugin (5.8 and later). */
|
|
161
|
+
export declare function hasMcpPlugin(engine: UnrealEngine, fs: Pick<EngineFs, 'exists'>): boolean;
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
// Unreal engines, pure: what an engine folder is (Engine\Build\Build.version; Engine\Build\InstalledBuild.txt makes it
|
|
2
|
+
// an installed build), where the sandbox finds them (this PC read-only, the Epic Games Launcher's folder on the data
|
|
3
|
+
// disk, source builds in a project root or registered by you), which one a project builds with (its .uproject's
|
|
4
|
+
// EngineAssociation, then a writable engine over a read-only one, then the newest), and what the sandbox writes so
|
|
5
|
+
// UnrealBuildTool, UnrealVersionSelector and the editor find them (registry plan) and an agent can run them (PATH,
|
|
6
|
+
// variables, the derived data cache). Every file is read through the EngineFs it is given (bounded): the guest passes
|
|
7
|
+
// the real disk, the self-test its fixtures.
|
|
8
|
+
import crypto from 'node:crypto';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
const win = path.win32;
|
|
12
|
+
export const ORIGIN_LABELS = {
|
|
13
|
+
pc: 'this PC, read-only',
|
|
14
|
+
'pc-folder': 'your folder on this PC',
|
|
15
|
+
launcher: 'Epic Games Launcher, data disk',
|
|
16
|
+
data: 'data disk',
|
|
17
|
+
project: 'in a project folder',
|
|
18
|
+
};
|
|
19
|
+
const BuildVersionSchema = z.object({
|
|
20
|
+
MajorVersion: z.number().int().min(4).max(99),
|
|
21
|
+
MinorVersion: z.number().int().min(0).max(99),
|
|
22
|
+
PatchVersion: z.number().int().min(0).max(999),
|
|
23
|
+
Changelist: z.number().int().optional(),
|
|
24
|
+
BranchName: z.string().optional(),
|
|
25
|
+
});
|
|
26
|
+
const stripBom = (text) => text.replace(/^\uFEFF/, '');
|
|
27
|
+
/** Engine\Build\Build.version: the version of an engine folder, or null when the text is not one. */
|
|
28
|
+
export function parseBuildVersion(text) {
|
|
29
|
+
try {
|
|
30
|
+
const v = BuildVersionSchema.parse(JSON.parse(stripBom(text)));
|
|
31
|
+
return {
|
|
32
|
+
major: v.MajorVersion,
|
|
33
|
+
minor: v.MinorVersion,
|
|
34
|
+
patch: v.PatchVersion,
|
|
35
|
+
changelist: v.Changelist ?? 0,
|
|
36
|
+
branch: (v.BranchName ?? '').replace(/[^\x20-\x7e]/g, '').slice(0, 100),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null; // Not JSON, or not a Build.version.
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** "5.4": the name the launcher and the registry give an engine version. */
|
|
44
|
+
export const majorMinor = (v) => v.major + '.' + v.minor;
|
|
45
|
+
/** "5.4.4": the full version for people. */
|
|
46
|
+
export const versionText = (v) => v.major + '.' + v.minor + '.' + v.patch;
|
|
47
|
+
/** "UE_5.4": the launcher's app name (and the folder it installs into). */
|
|
48
|
+
export const launcherAppName = (v) => 'UE_' + majorMinor(v);
|
|
49
|
+
export function compareVersions(a, b) {
|
|
50
|
+
return a.major - b.major || a.minor - b.minor || a.patch - b.patch || a.changelist - b.changelist;
|
|
51
|
+
}
|
|
52
|
+
/** Engine\Build\Build.version of a folder, or null when it is not an engine. */
|
|
53
|
+
export function readEngineVersion(dir, fs) {
|
|
54
|
+
const bytes = fs.read(win.join(dir, 'Engine', 'Build', 'Build.version'), 64 * 1024);
|
|
55
|
+
return bytes ? parseBuildVersion(bytes.toString('utf8')) : null;
|
|
56
|
+
}
|
|
57
|
+
/** Whether an engine folder is an installed build (what the launcher installs): Engine\Build\InstalledBuild.txt. */
|
|
58
|
+
export function isInstalledBuild(dir, fs) {
|
|
59
|
+
return fs.exists(win.join(dir, 'Engine', 'Build', 'InstalledBuild.txt'));
|
|
60
|
+
}
|
|
61
|
+
const BUILD_ID = /^[^\x00-\x1f"\\/]{1,64}$/;
|
|
62
|
+
/** Whether a name can be a source build's registered name (UnrealVersionSelector writes a {GUID}). */
|
|
63
|
+
export const isBuildId = (id) => id === id.trim() && BUILD_ID.test(id);
|
|
64
|
+
/**
|
|
65
|
+
* A source build's registered name when it has none of its own: a GUID made from its folder, so it is the same every
|
|
66
|
+
* boot and a project that was switched to it keeps finding it.
|
|
67
|
+
*/
|
|
68
|
+
export function stableBuildId(dir) {
|
|
69
|
+
const h = crypto.createHash('sha1').update(win.normalize(dir).toLowerCase()).digest('hex').toUpperCase();
|
|
70
|
+
return ('{' +
|
|
71
|
+
h.slice(0, 8) +
|
|
72
|
+
'-' +
|
|
73
|
+
h.slice(8, 12) +
|
|
74
|
+
'-' +
|
|
75
|
+
h.slice(12, 16) +
|
|
76
|
+
'-' +
|
|
77
|
+
h.slice(16, 20) +
|
|
78
|
+
'-' +
|
|
79
|
+
h.slice(20, 32) +
|
|
80
|
+
'}');
|
|
81
|
+
}
|
|
82
|
+
/** The engine in a folder, or null when it is not one. `id`: a source build's registered name (else one is made). */
|
|
83
|
+
export function engineAt(dir, origin, writable, fs, id) {
|
|
84
|
+
if (!win.isAbsolute(dir) || /["\r\n]/.test(dir))
|
|
85
|
+
return null;
|
|
86
|
+
const version = readEngineVersion(dir, fs);
|
|
87
|
+
if (!version)
|
|
88
|
+
return null;
|
|
89
|
+
const build = isInstalledBuild(dir, fs) ? 'installed' : 'source';
|
|
90
|
+
const name = build === 'installed' ? launcherAppName(version) : id && isBuildId(id) ? id : stableBuildId(dir);
|
|
91
|
+
return { id: name, dir: win.normalize(dir), version, build, origin, writable };
|
|
92
|
+
}
|
|
93
|
+
/** At most this many folders are looked at in one place (a project root may hold many projects). */
|
|
94
|
+
const MAX_LOOK = 200;
|
|
95
|
+
/** At most this many engines are used. */
|
|
96
|
+
export const MAX_ENGINES = 16;
|
|
97
|
+
/** Every engine the sandbox can use, in a fixed order (this PC's, the data disk's, the project roots'), once each. */
|
|
98
|
+
export function findEngines(s, fs) {
|
|
99
|
+
const found = [];
|
|
100
|
+
const add = (e) => {
|
|
101
|
+
if (!e || found.length >= MAX_ENGINES)
|
|
102
|
+
return;
|
|
103
|
+
if (found.some((f) => f.dir.toLowerCase() === e.dir.toLowerCase()))
|
|
104
|
+
return;
|
|
105
|
+
found.push(e);
|
|
106
|
+
};
|
|
107
|
+
for (const m of s.mapped)
|
|
108
|
+
add(engineAt(m.guest, m.writable ? 'pc-folder' : 'pc', m.writable === true, fs, m.buildId));
|
|
109
|
+
const launched = new Set(s.launcherDirs.map((d) => win.normalize(d).toLowerCase()));
|
|
110
|
+
for (const name of fs.list(s.enginesRoot).slice(0, MAX_LOOK)) {
|
|
111
|
+
if (name.startsWith('.'))
|
|
112
|
+
continue;
|
|
113
|
+
const dir = win.join(s.enginesRoot, name);
|
|
114
|
+
add(engineAt(dir, launched.has(dir.toLowerCase()) ? 'launcher' : 'data', true, fs));
|
|
115
|
+
}
|
|
116
|
+
for (const root of s.projectRoots)
|
|
117
|
+
for (const name of fs.list(root).slice(0, MAX_LOOK)) {
|
|
118
|
+
const dir = win.join(root, name);
|
|
119
|
+
if (fs.exists(win.join(dir, 'Engine', 'Build', 'Build.version')))
|
|
120
|
+
add(engineAt(dir, 'project', true, fs));
|
|
121
|
+
}
|
|
122
|
+
return found;
|
|
123
|
+
}
|
|
124
|
+
/** What a .uproject's EngineAssociation names: "5.4" (an installed build), "{GUID}" or a name (a source build). */
|
|
125
|
+
export function parseEngineAssociation(value) {
|
|
126
|
+
if (typeof value !== 'string' || !value.trim())
|
|
127
|
+
return { kind: 'none' };
|
|
128
|
+
const text = value.trim();
|
|
129
|
+
const version = /^(\d{1,2}\.\d{1,2})(?:\.\d{1,3})?$/.exec(text);
|
|
130
|
+
if (version)
|
|
131
|
+
return { kind: 'version', version: version[1] };
|
|
132
|
+
return isBuildId(text) ? { kind: 'build', id: text } : { kind: 'none' };
|
|
133
|
+
}
|
|
134
|
+
/** The EngineAssociation of a .uproject's text (null when it is not JSON). */
|
|
135
|
+
export function uprojectAssociation(text) {
|
|
136
|
+
try {
|
|
137
|
+
const json = JSON.parse(stripBom(text));
|
|
138
|
+
if (!json || typeof json !== 'object' || Array.isArray(json))
|
|
139
|
+
return null;
|
|
140
|
+
return parseEngineAssociation(json.EngineAssociation);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/** A project's .uproject: in its folder, else one folder down (the first by name), or null. */
|
|
147
|
+
export function findUproject(workspace, fs) {
|
|
148
|
+
const own = (dir) => {
|
|
149
|
+
const hit = fs
|
|
150
|
+
.list(dir)
|
|
151
|
+
.filter((n) => /\.uproject$/i.test(n))
|
|
152
|
+
.sort()[0];
|
|
153
|
+
return hit ? win.join(dir, hit) : null;
|
|
154
|
+
};
|
|
155
|
+
const here = own(workspace);
|
|
156
|
+
if (here)
|
|
157
|
+
return here;
|
|
158
|
+
for (const name of fs.list(workspace).slice(0, MAX_LOOK).sort()) {
|
|
159
|
+
if (name.startsWith('.') || /^(node_modules|Engine|Binaries|Intermediate|Saved|DerivedDataCache)$/i.test(name))
|
|
160
|
+
continue;
|
|
161
|
+
const hit = own(win.join(workspace, name));
|
|
162
|
+
if (hit)
|
|
163
|
+
return hit;
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
/** Writable before read-only (the editor writes into a project's engine plugins), installed before source. */
|
|
168
|
+
const preference = (e) => (e.writable ? 0 : 2) + (e.build === 'installed' ? 0 : 1);
|
|
169
|
+
/**
|
|
170
|
+
* The engine a project builds with: the one its EngineAssociation names (a version: a writable one before this PC's
|
|
171
|
+
* read-only one; a build: that registered build), else the newest. Null when the project names one that is not here.
|
|
172
|
+
*/
|
|
173
|
+
export function chooseEngine(engines, association) {
|
|
174
|
+
const best = (list) => [...list].sort((a, b) => preference(a) - preference(b) || compareVersions(b.version, a.version))[0] ?? null;
|
|
175
|
+
if (association.kind === 'version') {
|
|
176
|
+
const engine = best(engines.filter((e) => majorMinor(e.version) === association.version));
|
|
177
|
+
return engine
|
|
178
|
+
? { engine, reason: 'the project asks for ' + association.version }
|
|
179
|
+
: { engine: null, reason: 'the project asks for Unreal Engine ' + association.version + ', which is not here' };
|
|
180
|
+
}
|
|
181
|
+
if (association.kind === 'build') {
|
|
182
|
+
const exact = engines.find((e) => e.id.toLowerCase() === association.id.toLowerCase());
|
|
183
|
+
if (exact)
|
|
184
|
+
return { engine: exact, reason: 'the project asks for the source build ' + association.id };
|
|
185
|
+
// The project names a source build of this PC's registry; here the source sits in a project root or on the data
|
|
186
|
+
// disk under another name: use the newest of those and register the project's name for it.
|
|
187
|
+
const source = [...engines]
|
|
188
|
+
.filter((e) => e.build === 'source' && e.writable)
|
|
189
|
+
.sort((a, b) => compareVersions(b.version, a.version))[0];
|
|
190
|
+
return source
|
|
191
|
+
? {
|
|
192
|
+
engine: source,
|
|
193
|
+
alias: association.id,
|
|
194
|
+
reason: 'the project names the source build ' +
|
|
195
|
+
association.id +
|
|
196
|
+
' (registered on your PC); using the source build at ' +
|
|
197
|
+
source.dir,
|
|
198
|
+
}
|
|
199
|
+
: { engine: null, reason: 'the project asks for the source build ' + association.id + ', which is not here' };
|
|
200
|
+
}
|
|
201
|
+
const newest = [...engines].sort((a, b) => compareVersions(b.version, a.version) || preference(a) - preference(b))[0];
|
|
202
|
+
return newest
|
|
203
|
+
? { engine: newest, reason: 'the newest engine here' }
|
|
204
|
+
: { engine: null, reason: 'no engine is installed yet' };
|
|
205
|
+
}
|
|
206
|
+
/** Installed builds: where an installer records them (UnrealVersionSelector and the editor read it). */
|
|
207
|
+
export const INSTALLED_KEY = 'SOFTWARE\\EpicGames\\Unreal Engine';
|
|
208
|
+
/** Source builds: UnrealVersionSelector's list (name = the build's id, value = its folder). */
|
|
209
|
+
export const BUILDS_KEY = 'Software\\Epic Games\\Unreal Engine\\Builds';
|
|
210
|
+
/**
|
|
211
|
+
* The installed build of each version that the editor and UnrealVersionSelector use: one per major.minor, chosen the
|
|
212
|
+
* way chooseEngine chooses (writable before read-only, then the newest).
|
|
213
|
+
*/
|
|
214
|
+
export function preferredInstalled(engines) {
|
|
215
|
+
const sorted = engines
|
|
216
|
+
.filter((e) => e.build === 'installed')
|
|
217
|
+
.sort((a, b) => preference(a) - preference(b) || compareVersions(b.version, a.version));
|
|
218
|
+
const seen = new Set();
|
|
219
|
+
return sorted.filter((e) => {
|
|
220
|
+
const v = majorMinor(e.version);
|
|
221
|
+
if (seen.has(v))
|
|
222
|
+
return false;
|
|
223
|
+
seen.add(v);
|
|
224
|
+
return true;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* The registry values that make Unreal find the engines: an installed build as HKLM\SOFTWARE\EpicGames\Unreal
|
|
229
|
+
* Engine\<5.4> InstalledDirectory (one per version: the one a project would pick), a source build as a value under
|
|
230
|
+
* HKCU\Software\Epic Games\Unreal Engine\Builds. `aliases`: names a project uses for a source build here (its GUID on
|
|
231
|
+
* this PC), registered for the folder they stand for, so the .uproject works unchanged.
|
|
232
|
+
*/
|
|
233
|
+
export function registryPlan(engines, aliases = []) {
|
|
234
|
+
const values = engines
|
|
235
|
+
.filter((e) => e.build === 'source')
|
|
236
|
+
.map((e) => ({ hive: 'HKCU', key: BUILDS_KEY, name: e.id, value: e.dir }));
|
|
237
|
+
for (const a of aliases)
|
|
238
|
+
values.push({ hive: 'HKCU', key: BUILDS_KEY, name: a.id, value: a.dir });
|
|
239
|
+
for (const e of preferredInstalled(engines))
|
|
240
|
+
values.push({
|
|
241
|
+
hive: 'HKLM',
|
|
242
|
+
key: INSTALLED_KEY + '\\' + majorMinor(e.version),
|
|
243
|
+
name: 'InstalledDirectory',
|
|
244
|
+
value: e.dir,
|
|
245
|
+
});
|
|
246
|
+
return values;
|
|
247
|
+
}
|
|
248
|
+
/** UnrealVersionSelector inside an engine (it opens, switches and generates project files for .uproject files). */
|
|
249
|
+
export function versionSelectorIn(dir) {
|
|
250
|
+
return win.join(dir, 'Engine', 'Binaries', 'Win64', 'UnrealVersionSelector.exe');
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* The .uproject file association UnrealVersionSelector /fileassociations writes (per user): open in the editor, launch
|
|
254
|
+
* the game, generate Visual Studio project files, switch the engine version.
|
|
255
|
+
*/
|
|
256
|
+
export function fileAssociationPlan(selector) {
|
|
257
|
+
if (!win.isAbsolute(selector) || /["\r\n]/.test(selector))
|
|
258
|
+
throw new Error('not a program path: ' + selector);
|
|
259
|
+
const exe = '"' + selector + '"';
|
|
260
|
+
const classes = 'Software\\Classes';
|
|
261
|
+
const type = classes + '\\Unreal.ProjectFile';
|
|
262
|
+
const v = (key, value, name = '') => ({ hive: 'HKCU', key, name, value });
|
|
263
|
+
const verb = (id, label, args) => [
|
|
264
|
+
v(type + '\\shell\\' + id, label),
|
|
265
|
+
v(type + '\\shell\\' + id + '\\command', exe + ' ' + args + ' "%1"'),
|
|
266
|
+
];
|
|
267
|
+
return [
|
|
268
|
+
v(classes + '\\.uproject', 'Unreal.ProjectFile'),
|
|
269
|
+
v(type, 'Unreal Engine Project File'),
|
|
270
|
+
v(type + '\\DefaultIcon', exe),
|
|
271
|
+
...verb('open', 'Open', '/editor'),
|
|
272
|
+
...verb('run', 'Launch game', '/game'),
|
|
273
|
+
...verb('rungenproj', 'Generate Visual Studio project files', '/projectfiles'),
|
|
274
|
+
...verb('switchversion', 'Switch Unreal Engine version...', '/switchversion'),
|
|
275
|
+
];
|
|
276
|
+
}
|
|
277
|
+
// ---- Environment, PATH, derived data cache ---------------------------------------------------------------------
|
|
278
|
+
/** The folders of an engine an agent runs its tools from (those that exist), in PATH order. */
|
|
279
|
+
export function engineToolDirs(dir, fs) {
|
|
280
|
+
return [win.join(dir, 'Engine', 'Build', 'BatchFiles')].filter((d) => fs.exists(d));
|
|
281
|
+
}
|
|
282
|
+
/** The engine folders put at the END of PATH: they hold many DLLs, which every program started from PATH would load. */
|
|
283
|
+
export function engineExtraDirs(dir, fs) {
|
|
284
|
+
return [
|
|
285
|
+
win.join(dir, 'Engine', 'Binaries', 'Win64'),
|
|
286
|
+
win.join(dir, 'Engine', 'Binaries', 'DotNET', 'UnrealBuildTool'),
|
|
287
|
+
].filter((d) => fs.exists(d));
|
|
288
|
+
}
|
|
289
|
+
/** Variables for the project's engine: UE_ROOT (the engine folder) and UE_ENGINE_DIR (its Engine folder). */
|
|
290
|
+
export function engineVars(engine) {
|
|
291
|
+
return { UE_ROOT: engine.dir, UE_ENGINE_DIR: win.join(engine.dir, 'Engine') };
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* The derived data cache: UE-LocalDataCachePath moves the local cache (and from 5.4 the Zen store, in its Zen
|
|
295
|
+
* subfolder) out of the engine and off the sandbox's disk. "local" leaves Unreal's own place (the sandbox's disk).
|
|
296
|
+
*/
|
|
297
|
+
export function ddcVars(mode, ddcDir) {
|
|
298
|
+
return mode === 'persisted' ? { 'UE-LocalDataCachePath': ddcDir } : {};
|
|
299
|
+
}
|
|
300
|
+
/** The editor's MCP server (Unreal 5.8's ModelContextProtocol plugin): its default address. */
|
|
301
|
+
export const UNREAL_MCP_URL = 'http://127.0.0.1:8000/mcp';
|
|
302
|
+
const MCP_PLUGIN = ['Experimental', 'ModelContextProtocol', 'ModelContextProtocol.uplugin'];
|
|
303
|
+
/** Whether an engine ships the ModelContextProtocol plugin (5.8 and later). */
|
|
304
|
+
export function hasMcpPlugin(engine, fs) {
|
|
305
|
+
if (engine.version.major < 5 || (engine.version.major === 5 && engine.version.minor < 8))
|
|
306
|
+
return false;
|
|
307
|
+
return fs.exists(win.join(engine.dir, 'Engine', 'Plugins', ...MCP_PLUGIN));
|
|
308
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { UnrealStatus } from './status.js';
|
|
2
|
+
export interface UnrealGuideProject {
|
|
3
|
+
/** The project's folder in the sandbox. */
|
|
4
|
+
readonly workspace: string;
|
|
5
|
+
/** Its .uproject, when it has one. */
|
|
6
|
+
readonly uproject: string | null;
|
|
7
|
+
}
|
|
8
|
+
/** Optional extra guide sections. */
|
|
9
|
+
export interface UnrealGuideExtras {
|
|
10
|
+
/** The Mixamo lines (where downloads land, the import script when one ships). */
|
|
11
|
+
readonly mixamo?: {
|
|
12
|
+
readonly downloads: string;
|
|
13
|
+
readonly script: string | null;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** The guide's Unreal lines for these projects (none when Unreal is not set up). */
|
|
17
|
+
export declare function unrealGuideLines(status: UnrealStatus, projects: readonly UnrealGuideProject[], extras?: UnrealGuideExtras): string[];
|
|
18
|
+
/** The short tool-summary line of a project's engine. */
|
|
19
|
+
export declare function unrealSummaryLine(status: UnrealStatus, workspace: string): string;
|