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,292 @@
|
|
|
1
|
+
// What the Epic Games Launcher keeps about installed apps, pure apart from the files it is pointed at:
|
|
2
|
+
// LauncherInstalled.dat (%ProgramData%\Epic\UnrealEngineLauncher: the InstallationList the editor and
|
|
3
|
+
// UnrealVersionSelector read), the .item manifests (%ProgramData%\Epic\EpicGamesLauncher\Data\Manifests: what the
|
|
4
|
+
// launcher itself lists as installed) and GameUserSettings.ini (its DefaultAppInstallLocation). Entries and manifests
|
|
5
|
+
// that are not ours (other games, engines elsewhere) are never dropped or changed. Every file is read bounded.
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { readBoundedFile } from '../io/bounded.js';
|
|
10
|
+
import { decodeText, encodeText } from '../io/text.js';
|
|
11
|
+
import { launcherAppName, preferredInstalled } from './engines.js';
|
|
12
|
+
const win = path.win32;
|
|
13
|
+
/** "5.4.4-33043543+++UE5+Release-5.4": the launcher's AppVersion of an engine version. */
|
|
14
|
+
export function appVersionText(v) {
|
|
15
|
+
return v.major + '.' + v.minor + '.' + v.patch + '-' + v.changelist + '+' + v.branch.replace(/^\+*/, '++');
|
|
16
|
+
}
|
|
17
|
+
export const MAX_LAUNCHER_FILE_BYTES = 4 * 1024 * 1024;
|
|
18
|
+
const MAX_ITEM_BYTES = 1024 * 1024;
|
|
19
|
+
const MAX_ENTRIES = 2000;
|
|
20
|
+
const stripBom = (text) => text.replace(/^\uFEFF/, '');
|
|
21
|
+
/** %ProgramData%\Epic\UnrealEngineLauncher\LauncherInstalled.dat */
|
|
22
|
+
export function launcherInstalledFile(programData) {
|
|
23
|
+
return win.join(programData, 'Epic', 'UnrealEngineLauncher', 'LauncherInstalled.dat');
|
|
24
|
+
}
|
|
25
|
+
/** %ProgramData%\Epic\EpicGamesLauncher\Data\Manifests */
|
|
26
|
+
export function launcherManifestsDir(programData) {
|
|
27
|
+
return win.join(programData, 'Epic', 'EpicGamesLauncher', 'Data', 'Manifests');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The launcher's settings files under %LOCALAPPDATA%\EpicGamesLauncher\Saved\Config: WindowsEditor\ (what launcher 17+
|
|
31
|
+
* writes; 20.3 checked) and Windows\ (older launchers).
|
|
32
|
+
*/
|
|
33
|
+
export function launcherSettingsFiles(localAppData) {
|
|
34
|
+
const config = win.join(localAppData, 'EpicGamesLauncher', 'Saved', 'Config');
|
|
35
|
+
return [
|
|
36
|
+
win.join(config, 'WindowsEditor', 'GameUserSettings.ini'),
|
|
37
|
+
win.join(config, 'Windows', 'GameUserSettings.ini'),
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
const EntrySchema = z.looseObject({
|
|
41
|
+
InstallLocation: z.string().max(1024),
|
|
42
|
+
AppName: z.string().max(200),
|
|
43
|
+
NamespaceId: z.string().max(200).optional(),
|
|
44
|
+
ItemId: z.string().max(200).optional(),
|
|
45
|
+
ArtifactId: z.string().max(200).optional(),
|
|
46
|
+
AppVersion: z.string().max(200).optional(),
|
|
47
|
+
});
|
|
48
|
+
const InstalledFileSchema = z.looseObject({ InstallationList: z.array(z.unknown()).max(MAX_ENTRIES) });
|
|
49
|
+
/** The InstallationList of a LauncherInstalled.dat text ([] when it is empty or not one). */
|
|
50
|
+
export function launcherInstallations(text) {
|
|
51
|
+
let list;
|
|
52
|
+
try {
|
|
53
|
+
list = InstalledFileSchema.parse(JSON.parse(stripBom(text))).InstallationList;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
return list.flatMap((raw) => {
|
|
59
|
+
const e = EntrySchema.safeParse(raw);
|
|
60
|
+
if (!e.success || !win.isAbsolute(e.data.InstallLocation) || /["\r\n]/.test(e.data.InstallLocation))
|
|
61
|
+
return [];
|
|
62
|
+
const { NamespaceId, ItemId, ArtifactId, AppVersion } = e.data;
|
|
63
|
+
return [
|
|
64
|
+
{
|
|
65
|
+
appName: e.data.AppName,
|
|
66
|
+
installLocation: e.data.InstallLocation,
|
|
67
|
+
ids: NamespaceId && ItemId && ArtifactId && AppVersion
|
|
68
|
+
? { namespaceId: NamespaceId, itemId: ItemId, artifactId: ArtifactId, appVersion: AppVersion }
|
|
69
|
+
: null,
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** The entry that lists an engine folder as an installed engine (with the launcher's own ids when they are known). */
|
|
75
|
+
export function launcherEntry(engine, ids = null) {
|
|
76
|
+
const name = launcherAppName(engine.version);
|
|
77
|
+
return {
|
|
78
|
+
InstallLocation: engine.dir,
|
|
79
|
+
NamespaceId: ids?.namespaceId ?? 'ue',
|
|
80
|
+
ItemId: ids?.itemId ?? name,
|
|
81
|
+
ArtifactId: ids?.artifactId ?? name,
|
|
82
|
+
AppVersion: ids?.appVersion ?? appVersionText(engine.version),
|
|
83
|
+
AppName: name,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The LauncherInstalled.dat entries for the installed builds: only the one preferred for each version (Unreal keeps
|
|
88
|
+
* the LAST entry per app name, so a second UE_5.4 would shadow the first), with the launcher's own saved entry when
|
|
89
|
+
* there is one, else the ids copied from this PC, else made up.
|
|
90
|
+
*/
|
|
91
|
+
export function launcherEntriesFor(engines, saved, pcItems) {
|
|
92
|
+
return preferredInstalled(engines).map((e) => {
|
|
93
|
+
const own = saved.find((s) => s.InstallLocation.toLowerCase() === e.dir.toLowerCase());
|
|
94
|
+
if (own)
|
|
95
|
+
return own;
|
|
96
|
+
const pc = pcItems.find((i) => i.kind === 'unreal' && i.guest.toLowerCase() === e.dir.toLowerCase());
|
|
97
|
+
return launcherEntry(e, pc?.launcher ?? null);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* LauncherInstalled.dat with these entries in it. Every entry that is not one of them (another game, an engine at
|
|
102
|
+
* another place) stays as it was; an entry for the same folder is replaced. A file that is not a LauncherInstalled.dat
|
|
103
|
+
* throws (it is never overwritten); a missing or empty one starts a new list.
|
|
104
|
+
*/
|
|
105
|
+
export function mergeLauncherInstalled(existing, entries) {
|
|
106
|
+
let list = [];
|
|
107
|
+
let rest = {};
|
|
108
|
+
if (existing && existing.trim()) {
|
|
109
|
+
const { InstallationList, ...others } = InstalledFileSchema.parse(JSON.parse(stripBom(existing)));
|
|
110
|
+
list = InstallationList;
|
|
111
|
+
rest = others;
|
|
112
|
+
}
|
|
113
|
+
const same = (raw, mine) => {
|
|
114
|
+
const e = EntrySchema.safeParse(raw);
|
|
115
|
+
return (e.success &&
|
|
116
|
+
win.normalize(e.data.InstallLocation).toLowerCase() === win.normalize(mine.InstallLocation).toLowerCase());
|
|
117
|
+
};
|
|
118
|
+
const kept = list.filter((raw) => !entries.some((mine) => same(raw, mine)));
|
|
119
|
+
return JSON.stringify({ ...rest, InstallationList: [...kept, ...entries] }, null, '\t') + '\n';
|
|
120
|
+
}
|
|
121
|
+
/** Whether `child` is `parent` or inside it (case-insensitive, Windows paths). */
|
|
122
|
+
export function isWithin(parent, child) {
|
|
123
|
+
const rel = win.relative(win.resolve(parent).toLowerCase(), win.resolve(child).toLowerCase());
|
|
124
|
+
return rel === '' || (!rel.startsWith('..') && !win.isAbsolute(rel));
|
|
125
|
+
}
|
|
126
|
+
/** The entries of a LauncherInstalled.dat text whose folder is under `root` (the engines kept on the data disk). */
|
|
127
|
+
export function entriesUnder(text, root) {
|
|
128
|
+
return launcherInstallations(text)
|
|
129
|
+
.filter((i) => i.ids && isWithin(root, i.installLocation) && win.resolve(i.installLocation) !== win.resolve(root))
|
|
130
|
+
.map((i) => ({
|
|
131
|
+
InstallLocation: i.installLocation,
|
|
132
|
+
NamespaceId: i.ids.namespaceId,
|
|
133
|
+
ItemId: i.ids.itemId,
|
|
134
|
+
ArtifactId: i.ids.artifactId,
|
|
135
|
+
AppVersion: i.ids.appVersion,
|
|
136
|
+
AppName: i.appName,
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
// ---- .item manifests ------------------------------------------------------------------------------------------
|
|
140
|
+
const ItemSchema = z.looseObject({
|
|
141
|
+
InstallLocation: z.string().max(1024).optional(),
|
|
142
|
+
AppName: z.string().max(200).optional(),
|
|
143
|
+
InstallSize: z.number().int().min(0).optional(),
|
|
144
|
+
bIsIncompleteInstall: z.boolean().optional(),
|
|
145
|
+
});
|
|
146
|
+
/** The folder a launcher manifest says its app is installed in, or null. */
|
|
147
|
+
export function itemInstallLocation(text) {
|
|
148
|
+
return itemInfo(text)?.installLocation ?? null;
|
|
149
|
+
}
|
|
150
|
+
/** A launcher manifest's folder and size, or null when it is not one we can read. */
|
|
151
|
+
export function itemInfo(text) {
|
|
152
|
+
try {
|
|
153
|
+
const item = ItemSchema.parse(JSON.parse(stripBom(text)));
|
|
154
|
+
const at = item.InstallLocation;
|
|
155
|
+
return at && win.isAbsolute(at) ? { installLocation: at, sizeBytes: item.InstallSize ?? null } : null;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return null; // Not a manifest we can read: left alone.
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The folders (lower-case, normalized) the manifests in these folders mark as a launcher install still in progress
|
|
163
|
+
* (bIsIncompleteInstall): not engines yet.
|
|
164
|
+
*/
|
|
165
|
+
export function incompleteDirs(...manifestDirs) {
|
|
166
|
+
const dirs = new Set();
|
|
167
|
+
for (const dir of manifestDirs)
|
|
168
|
+
for (const name of itemFiles(dir)) {
|
|
169
|
+
const bytes = readBoundedFile(win.join(dir, name), MAX_ITEM_BYTES);
|
|
170
|
+
if (!bytes)
|
|
171
|
+
continue;
|
|
172
|
+
try {
|
|
173
|
+
const item = ItemSchema.parse(JSON.parse(stripBom(bytes.toString('utf8'))));
|
|
174
|
+
if (item.bIsIncompleteInstall === true && item.InstallLocation && win.isAbsolute(item.InstallLocation))
|
|
175
|
+
dirs.add(win.normalize(item.InstallLocation).toLowerCase());
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
// Not a manifest we can read: it marks nothing.
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return dirs;
|
|
182
|
+
}
|
|
183
|
+
/** The sizes the saved manifests give their engine folders (lower-case folder -> bytes). */
|
|
184
|
+
export function itemSizes(saved) {
|
|
185
|
+
const sizes = new Map();
|
|
186
|
+
for (const name of itemFiles(saved)) {
|
|
187
|
+
const bytes = readBoundedFile(win.join(saved, name), MAX_ITEM_BYTES);
|
|
188
|
+
const info = bytes ? itemInfo(bytes.toString('utf8')) : null;
|
|
189
|
+
if (info?.sizeBytes !== null && info?.sizeBytes !== undefined)
|
|
190
|
+
sizes.set(win.normalize(info.installLocation).toLowerCase(), info.sizeBytes);
|
|
191
|
+
}
|
|
192
|
+
return sizes;
|
|
193
|
+
}
|
|
194
|
+
/** Whether the text is a launcher manifest of an app installed under `root`. */
|
|
195
|
+
export function isItemUnder(text, root) {
|
|
196
|
+
const at = itemInstallLocation(text);
|
|
197
|
+
return at !== null && isWithin(root, at) && win.resolve(at) !== win.resolve(root);
|
|
198
|
+
}
|
|
199
|
+
function itemFiles(dir) {
|
|
200
|
+
try {
|
|
201
|
+
return fs.readdirSync(dir).filter((f) => /^[A-Za-z0-9_-]{1,80}\.item$/.test(f));
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/** Copies the launcher's manifests of apps under `root` into `saved` (changed ones only). Returns the file names. */
|
|
208
|
+
export function saveItems(manifests, saved, root) {
|
|
209
|
+
const done = [];
|
|
210
|
+
for (const name of itemFiles(manifests)) {
|
|
211
|
+
const bytes = readBoundedFile(win.join(manifests, name), MAX_ITEM_BYTES);
|
|
212
|
+
if (!bytes || !isItemUnder(bytes.toString('utf8'), root))
|
|
213
|
+
continue;
|
|
214
|
+
const old = readBoundedFile(win.join(saved, name), MAX_ITEM_BYTES);
|
|
215
|
+
if (old && old.equals(bytes))
|
|
216
|
+
continue;
|
|
217
|
+
fs.mkdirSync(saved, { recursive: true });
|
|
218
|
+
fs.writeFileSync(win.join(saved, name), bytes);
|
|
219
|
+
done.push(name);
|
|
220
|
+
}
|
|
221
|
+
return done;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Puts saved manifests back where the launcher reads them, for the apps whose folder is still there. A manifest the
|
|
225
|
+
* launcher already has is never replaced. Returns the folders they name.
|
|
226
|
+
*/
|
|
227
|
+
export function restoreItems(saved, manifests, root, exists) {
|
|
228
|
+
const dirs = [];
|
|
229
|
+
for (const name of itemFiles(saved)) {
|
|
230
|
+
const bytes = readBoundedFile(win.join(saved, name), MAX_ITEM_BYTES);
|
|
231
|
+
const at = bytes ? itemInstallLocation(bytes.toString('utf8')) : null;
|
|
232
|
+
if (!bytes || !at || !isItemUnder(bytes.toString('utf8'), root) || !exists(at))
|
|
233
|
+
continue;
|
|
234
|
+
dirs.push(win.normalize(at));
|
|
235
|
+
if (fs.existsSync(win.join(manifests, name)))
|
|
236
|
+
continue;
|
|
237
|
+
fs.mkdirSync(manifests, { recursive: true });
|
|
238
|
+
fs.writeFileSync(win.join(manifests, name), bytes);
|
|
239
|
+
}
|
|
240
|
+
return dirs;
|
|
241
|
+
}
|
|
242
|
+
// ---- GameUserSettings.ini -------------------------------------------------------------------------------------
|
|
243
|
+
/** The ini text with [Launcher] DefaultAppInstallLocation set (other lines, comments and line ends untouched). */
|
|
244
|
+
export function withInstallLocation(ini, dir) {
|
|
245
|
+
if (/[\r\n]/.test(dir))
|
|
246
|
+
throw new Error('not a folder: ' + dir);
|
|
247
|
+
const eol = ini.includes('\r\n') || !ini.includes('\n') ? '\r\n' : '\n';
|
|
248
|
+
const lines = ini.length ? ini.split(/\r?\n/) : [];
|
|
249
|
+
if (lines.length && lines[lines.length - 1] === '')
|
|
250
|
+
lines.pop();
|
|
251
|
+
const entry = 'DefaultAppInstallLocation=' + dir;
|
|
252
|
+
const start = lines.findIndex((l) => /^\s*\[Launcher\]\s*$/i.test(l));
|
|
253
|
+
if (start < 0)
|
|
254
|
+
return [...lines, ...(lines.length ? [''] : []), '[Launcher]', entry].join(eol) + eol;
|
|
255
|
+
let end = lines.findIndex((l, i) => i > start && /^\s*\[/.test(l));
|
|
256
|
+
if (end < 0)
|
|
257
|
+
end = lines.length;
|
|
258
|
+
const at = lines.findIndex((l, i) => i > start && i < end && /^\s*DefaultAppInstallLocation\s*=/i.test(l));
|
|
259
|
+
if (at >= 0)
|
|
260
|
+
lines[at] = entry;
|
|
261
|
+
else
|
|
262
|
+
lines.splice(start + 1, 0, entry);
|
|
263
|
+
return lines.join(eol) + eol;
|
|
264
|
+
}
|
|
265
|
+
/** The [Launcher] DefaultAppInstallLocation of an ini text, or null. */
|
|
266
|
+
export function installLocationOf(ini) {
|
|
267
|
+
let inLauncher = false;
|
|
268
|
+
for (const raw of ini.split(/\r?\n/)) {
|
|
269
|
+
const line = raw.trim();
|
|
270
|
+
if (line.startsWith('[')) {
|
|
271
|
+
inLauncher = /^\[Launcher\]$/i.test(line);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const m = inLauncher ? /^DefaultAppInstallLocation\s*=\s*(.*)$/i.exec(line) : null;
|
|
275
|
+
if (m)
|
|
276
|
+
return m[1].trim() || null;
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
/** Sets the launcher's default install folder in a settings file, keeping its encoding. Returns whether it changed. */
|
|
281
|
+
export function setLauncherInstallLocation(file, dir) {
|
|
282
|
+
const bytes = readBoundedFile(file, MAX_LAUNCHER_FILE_BYTES);
|
|
283
|
+
if (!bytes && fs.existsSync(file))
|
|
284
|
+
throw new Error('could not read ' + file + ' (too big or not a file)');
|
|
285
|
+
const decoded = bytes ? decodeText(bytes) : { text: '', encoding: 'utf8' };
|
|
286
|
+
const next = withInstallLocation(decoded.text, dir);
|
|
287
|
+
if (bytes && next === decoded.text)
|
|
288
|
+
return false;
|
|
289
|
+
fs.mkdirSync(win.dirname(file), { recursive: true });
|
|
290
|
+
fs.writeFileSync(file, encodeText(next, decoded.encoding));
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// The guide's Mixamo lines (free rigged characters and animations, downloaded in the sandbox's browser) and how
|
|
2
|
+
// to bring them into Unreal. Pure: the guide adds them when an Unreal engine is set up (guide.ts).
|
|
3
|
+
/** The Mixamo lines: where downloads land, the export settings, the import, and the licence. */
|
|
4
|
+
export function mixamoGuideLines(o) {
|
|
5
|
+
const lines = [
|
|
6
|
+
"- Mixamo (rigged characters and animations, free with an Adobe sign-in): the user downloads them in this sandbox's browser from https://www.mixamo.com; downloads land in `" +
|
|
7
|
+
o.downloads +
|
|
8
|
+
'` (kept between sandboxes). Ask for the character as FBX Binary (.fbx), T-pose, With Skin; each animation as FBX Binary, Without Skin, 30 fps, In Place for walk and run loops.',
|
|
9
|
+
"- Mixamo into Unreal: import the character first (Skeletal Mesh, Import Uniform Scale 1.0; `mixamorig:Hips` is the root, so there is no root motion), then each animation with Skeleton = that character's skeleton. To use the UE5 mannequin's animations, retarget with an IK Rig per skeleton and an IK Retargeter. Licence: use them in the game; never ship or share the raw FBX files.",
|
|
10
|
+
];
|
|
11
|
+
if (o.script)
|
|
12
|
+
lines.push('- Mixamo import script: `UnrealEditor-Cmd.exe "<Project>.uproject" -run=pythonscript -script="' +
|
|
13
|
+
o.script +
|
|
14
|
+
' --fbx <file.fbx> --dest /Game/Characters/<Name> [--skeleton /Game/Characters/<Name>/<Name>_Skeleton]" -unattended -nullrhi -nosplash` (needs the Python Editor Script Plugin on in the .uproject).');
|
|
15
|
+
return lines;
|
|
16
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type StatFs } from '../io/disk.js';
|
|
2
|
+
/** What one engine takes while the launcher installs it (download and unpacked, 40-60 GB). */
|
|
3
|
+
export declare const ENGINE_NEED_BYTES: number;
|
|
4
|
+
export interface LauncherPlace {
|
|
5
|
+
/** The launcher's install folder was set to the data disk. */
|
|
6
|
+
readonly set: boolean;
|
|
7
|
+
/** Why it could not be (the file could not be written), or that persistEngines is off. */
|
|
8
|
+
readonly error?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The loud line for the boot summary and the Unreal page when an engine would not go to the data disk, or would not
|
|
12
|
+
* fit there; null when all is well.
|
|
13
|
+
*/
|
|
14
|
+
export declare function enginePlaceProblem(place: LauncherPlace, enginesDir: string, statfs?: StatFs): {
|
|
15
|
+
problem: string | null;
|
|
16
|
+
freeBytes: number | null;
|
|
17
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Where the Epic Games Launcher puts an engine, and whether there is room: engines go to the data disk only (never the
|
|
2
|
+
// sandbox's own disk, which holds 8-14 GB free and the paging file), and one needs 60 GB free there. Pure apart from
|
|
3
|
+
// the disk check it is given.
|
|
4
|
+
import { freeBytesAt } from '../io/disk.js';
|
|
5
|
+
const GB = 1024 ** 3;
|
|
6
|
+
/** What one engine takes while the launcher installs it (download and unpacked, 40-60 GB). */
|
|
7
|
+
export const ENGINE_NEED_BYTES = 60 * GB;
|
|
8
|
+
const gb = (bytes) => (bytes / GB).toFixed(0) + ' GB';
|
|
9
|
+
/**
|
|
10
|
+
* The loud line for the boot summary and the Unreal page when an engine would not go to the data disk, or would not
|
|
11
|
+
* fit there; null when all is well.
|
|
12
|
+
*/
|
|
13
|
+
export function enginePlaceProblem(place, enginesDir, statfs) {
|
|
14
|
+
if (!place.set)
|
|
15
|
+
return {
|
|
16
|
+
problem: 'ENGINES WOULD GO ON THE SANDBOX DISK: ' +
|
|
17
|
+
(place.error ?? "the Epic Games Launcher's install folder could not be set") +
|
|
18
|
+
'. Do not install an engine with the launcher in this sandbox (it would fill C: and be lost); install it on your PC instead (the next sandbox maps it read-only).',
|
|
19
|
+
freeBytes: null,
|
|
20
|
+
};
|
|
21
|
+
const free = freeBytesAt(enginesDir, statfs);
|
|
22
|
+
if (free !== null && free < ENGINE_NEED_BYTES)
|
|
23
|
+
return {
|
|
24
|
+
problem: 'Only ' +
|
|
25
|
+
gb(free) +
|
|
26
|
+
' free on the data disk (' +
|
|
27
|
+
enginesDir +
|
|
28
|
+
'); an engine from the Epic Games Launcher needs ' +
|
|
29
|
+
gb(ENGINE_NEED_BYTES) +
|
|
30
|
+
'. Free room on that disk first, or install the engine on your PC (the next sandbox maps it read-only).',
|
|
31
|
+
freeBytes: free,
|
|
32
|
+
};
|
|
33
|
+
return { problem: null, freeBytes: free };
|
|
34
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** The file in tools\unreal. */
|
|
3
|
+
export declare const UNREAL_STATUS_FILE = "status.json";
|
|
4
|
+
export declare const UnrealStatusSchema: z.ZodObject<{
|
|
5
|
+
updatedAt: z.ZodString;
|
|
6
|
+
engines: z.ZodArray<z.ZodObject<{
|
|
7
|
+
id: z.ZodString;
|
|
8
|
+
dir: z.ZodString;
|
|
9
|
+
version: z.ZodString;
|
|
10
|
+
build: z.ZodEnum<{
|
|
11
|
+
installed: "installed";
|
|
12
|
+
source: "source";
|
|
13
|
+
}>;
|
|
14
|
+
origin: z.ZodEnum<{
|
|
15
|
+
pc: "pc";
|
|
16
|
+
project: "project";
|
|
17
|
+
launcher: "launcher";
|
|
18
|
+
"pc-folder": "pc-folder";
|
|
19
|
+
data: "data";
|
|
20
|
+
}>;
|
|
21
|
+
writable: z.ZodBoolean;
|
|
22
|
+
sizeBytes: z.ZodNullable<z.ZodNumber>;
|
|
23
|
+
}, z.core.$strip>>;
|
|
24
|
+
projects: z.ZodArray<z.ZodObject<{
|
|
25
|
+
workspace: z.ZodString;
|
|
26
|
+
engine: z.ZodNullable<z.ZodString>;
|
|
27
|
+
reason: z.ZodString;
|
|
28
|
+
}, z.core.$strip>>;
|
|
29
|
+
launcher: z.ZodObject<{
|
|
30
|
+
installed: z.ZodBoolean;
|
|
31
|
+
installLocation: z.ZodNullable<z.ZodString>;
|
|
32
|
+
problem: z.ZodNullable<z.ZodString>;
|
|
33
|
+
freeBytes: z.ZodNullable<z.ZodNumber>;
|
|
34
|
+
needBytes: z.ZodNumber;
|
|
35
|
+
}, z.core.$strip>;
|
|
36
|
+
visualStudio: z.ZodObject<{
|
|
37
|
+
version: z.ZodString;
|
|
38
|
+
path: z.ZodString;
|
|
39
|
+
how: z.ZodString;
|
|
40
|
+
check: z.ZodString;
|
|
41
|
+
ok: z.ZodBoolean;
|
|
42
|
+
}, z.core.$strip>;
|
|
43
|
+
debugger: z.ZodNullable<z.ZodString>;
|
|
44
|
+
ddc: z.ZodObject<{
|
|
45
|
+
mode: z.ZodEnum<{
|
|
46
|
+
persisted: "persisted";
|
|
47
|
+
local: "local";
|
|
48
|
+
}>;
|
|
49
|
+
dir: z.ZodNullable<z.ZodString>;
|
|
50
|
+
}, z.core.$strip>;
|
|
51
|
+
vaults: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
52
|
+
mcpUrl: z.ZodNullable<z.ZodString>;
|
|
53
|
+
}, z.core.$strip>;
|
|
54
|
+
export type UnrealStatus = z.output<typeof UnrealStatusSchema>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// What the sandbox last found for Unreal (tools\unreal\status.json): its engines, which engine each project builds
|
|
2
|
+
// with, the Epic Games Launcher's install folder, Visual Studio, the debuggers and the derived data cache. The sandbox
|
|
3
|
+
// writes it every boot and when a project joins; the Unreal page on this PC reads it. It is written by the guest, so
|
|
4
|
+
// the host reads it with this schema (bounded strings and lists) and prints its text through safeText.
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
/** The file in tools\unreal. */
|
|
7
|
+
export const UNREAL_STATUS_FILE = 'status.json';
|
|
8
|
+
const Text = (max = 400) => z.string().max(max);
|
|
9
|
+
export const UnrealStatusSchema = z.object({
|
|
10
|
+
updatedAt: Text(40),
|
|
11
|
+
engines: z
|
|
12
|
+
.array(z.object({
|
|
13
|
+
id: Text(100),
|
|
14
|
+
dir: Text(),
|
|
15
|
+
version: Text(40),
|
|
16
|
+
build: z.enum(['installed', 'source']),
|
|
17
|
+
origin: z.enum(['pc', 'pc-folder', 'launcher', 'data', 'project']),
|
|
18
|
+
writable: z.boolean(),
|
|
19
|
+
/** Its size on disk, when it was counted (the launcher's manifest says it). */
|
|
20
|
+
sizeBytes: z.number().int().min(0).nullable(),
|
|
21
|
+
}))
|
|
22
|
+
.max(32),
|
|
23
|
+
projects: z
|
|
24
|
+
.array(z.object({
|
|
25
|
+
workspace: Text(),
|
|
26
|
+
/** The engine's folder, null when there is none for it. */
|
|
27
|
+
engine: Text().nullable(),
|
|
28
|
+
reason: Text(),
|
|
29
|
+
}))
|
|
30
|
+
.max(64),
|
|
31
|
+
launcher: z.object({
|
|
32
|
+
installed: z.boolean(),
|
|
33
|
+
/** Where the launcher installs engines; null when it could not be set. */
|
|
34
|
+
installLocation: Text().nullable(),
|
|
35
|
+
/** Why engines would not go to the data disk (shown loudly), null when they do. */
|
|
36
|
+
problem: Text(600).nullable(),
|
|
37
|
+
freeBytes: z.number().min(0).nullable(),
|
|
38
|
+
needBytes: z.number().min(0),
|
|
39
|
+
}),
|
|
40
|
+
visualStudio: z.object({
|
|
41
|
+
version: Text(60),
|
|
42
|
+
path: Text(),
|
|
43
|
+
/** "kept on the data disk", "installed" ... ('' when there is none). */
|
|
44
|
+
how: Text(100),
|
|
45
|
+
/** What UnrealBuildTool's way of looking finds (the Setup API, MSVC, the Windows SDK), in plain words. */
|
|
46
|
+
check: Text(600),
|
|
47
|
+
/** Whether that check found everything a build needs. */
|
|
48
|
+
ok: z.boolean(),
|
|
49
|
+
}),
|
|
50
|
+
/** cdb.exe of the Windows SDK's debuggers, null when they are missing. */
|
|
51
|
+
debugger: Text().nullable(),
|
|
52
|
+
ddc: z.object({ mode: z.enum(['persisted', 'local']), dir: Text().nullable() }),
|
|
53
|
+
/** This PC's vault cache folders (Fab and Marketplace packs), read-only in the sandbox. */
|
|
54
|
+
vaults: z.array(Text()).max(4).default([]),
|
|
55
|
+
/** The editor's MCP server address when the project's engine has the plugin. */
|
|
56
|
+
mcpUrl: Text(200).nullable(),
|
|
57
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** The default vault cache under a ProgramData folder. */
|
|
2
|
+
export declare function defaultVaultDir(programData: string): string;
|
|
3
|
+
/** The vault cache folders a launcher settings text names (VaultCacheDirectories / VaultCacheLocation lines). */
|
|
4
|
+
export declare function vaultDirsFromIni(ini: string): string[];
|
|
5
|
+
export interface VaultFs {
|
|
6
|
+
exists(file: string): boolean;
|
|
7
|
+
list(dir: string): string[];
|
|
8
|
+
/** A file's size, or null when it is a folder or missing. */
|
|
9
|
+
size(file: string): number | null;
|
|
10
|
+
isDir(dir: string): boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface VaultPack {
|
|
13
|
+
readonly name: string;
|
|
14
|
+
/** Bytes counted (at least this much when `partial`). */
|
|
15
|
+
readonly sizeBytes: number;
|
|
16
|
+
readonly partial: boolean;
|
|
17
|
+
/** "content" (data\Content: copy it into a project's Content) or "plugin" (a code plugin: into Plugins). */
|
|
18
|
+
readonly kind: 'content' | 'plugin' | 'other';
|
|
19
|
+
}
|
|
20
|
+
/** The packs in a vault cache folder, by name, with their size (bounded: very big packs read "at least"). */
|
|
21
|
+
export declare function vaultPacks(vault: string, fs: VaultFs): VaultPack[];
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// The Epic Games Launcher's vault cache: the Fab and Marketplace packs it downloaded but has not added to a project
|
|
2
|
+
// yet, one folder per pack (<VaultCache>\<Pack>\data\Content\..., or data\<Plugin>.uplugin for a code plugin). Where
|
|
3
|
+
// it is comes from the launcher's GameUserSettings.ini (VaultCacheDirectories in launcher 20.3; older files may say
|
|
4
|
+
// VaultCacheLocation), else its default %ProgramData%\Epic\EpicGamesLauncher\VaultCache. Pure apart from the
|
|
5
|
+
// EngineFs-style reads it is given; every walk is bounded.
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
const win = path.win32;
|
|
8
|
+
const MAX_PACKS = 500;
|
|
9
|
+
const MAX_FILES_PER_PACK = 5_000;
|
|
10
|
+
const MAX_FILES = 50_000;
|
|
11
|
+
/** The default vault cache under a ProgramData folder. */
|
|
12
|
+
export function defaultVaultDir(programData) {
|
|
13
|
+
return win.join(programData, 'Epic', 'EpicGamesLauncher', 'VaultCache');
|
|
14
|
+
}
|
|
15
|
+
/** The vault cache folders a launcher settings text names (VaultCacheDirectories / VaultCacheLocation lines). */
|
|
16
|
+
export function vaultDirsFromIni(ini) {
|
|
17
|
+
const found = [];
|
|
18
|
+
for (const raw of ini.split(/\r?\n/).slice(0, 5000)) {
|
|
19
|
+
const m = /^\s*[+.-]?\s*(VaultCacheDirectories|VaultCacheLocation)\s*=\s*(.{0,2000})$/i.exec(raw);
|
|
20
|
+
if (!m)
|
|
21
|
+
continue;
|
|
22
|
+
for (const hit of m[2].matchAll(/[A-Za-z]:[\\/][^"\r\n,()]{0,400}/g)) {
|
|
23
|
+
const dir = win.normalize(hit[0].trim().replace(/\//g, '\\')).replace(/\\+$/, '');
|
|
24
|
+
if (dir.length > 3 && !found.some((f) => f.toLowerCase() === dir.toLowerCase()))
|
|
25
|
+
found.push(dir);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return found;
|
|
29
|
+
}
|
|
30
|
+
function walk(dir, fs, budget) {
|
|
31
|
+
let bytes = 0;
|
|
32
|
+
let files = 0;
|
|
33
|
+
const stack = [dir];
|
|
34
|
+
while (stack.length) {
|
|
35
|
+
const at = stack.pop();
|
|
36
|
+
for (const name of fs.list(at)) {
|
|
37
|
+
if (++files > MAX_FILES_PER_PACK || --budget.left < 0)
|
|
38
|
+
return { bytes, partial: true };
|
|
39
|
+
const full = win.join(at, name);
|
|
40
|
+
const size = fs.size(full);
|
|
41
|
+
if (size === null) {
|
|
42
|
+
if (fs.isDir(full))
|
|
43
|
+
stack.push(full);
|
|
44
|
+
}
|
|
45
|
+
else
|
|
46
|
+
bytes += size;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { bytes, partial: false };
|
|
50
|
+
}
|
|
51
|
+
/** The packs in a vault cache folder, by name, with their size (bounded: very big packs read "at least"). */
|
|
52
|
+
export function vaultPacks(vault, fs) {
|
|
53
|
+
const budget = { left: MAX_FILES };
|
|
54
|
+
return fs
|
|
55
|
+
.list(vault)
|
|
56
|
+
.filter((n) => !n.startsWith('.') && fs.isDir(win.join(vault, n)))
|
|
57
|
+
.sort((a, b) => a.localeCompare(b))
|
|
58
|
+
.slice(0, MAX_PACKS)
|
|
59
|
+
.map((name) => {
|
|
60
|
+
const dir = win.join(vault, name);
|
|
61
|
+
const data = win.join(dir, 'data');
|
|
62
|
+
const plugin = fs.list(data).some((n) => /\.uplugin$/i.test(n));
|
|
63
|
+
const kind = plugin ? 'plugin' : fs.isDir(win.join(data, 'Content')) ? 'content' : 'other';
|
|
64
|
+
const { bytes, partial } = walk(dir, fs, budget);
|
|
65
|
+
return { name, sizeBytes: bytes, partial, kind };
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const VsSetupReportSchema: z.ZodObject<{
|
|
3
|
+
setupError: z.ZodString;
|
|
4
|
+
instances: z.ZodArray<z.ZodObject<{
|
|
5
|
+
instanceId: z.ZodString;
|
|
6
|
+
installationPath: z.ZodString;
|
|
7
|
+
installationVersion: z.ZodString;
|
|
8
|
+
productId: z.ZodString;
|
|
9
|
+
state: z.ZodNumber;
|
|
10
|
+
isComplete: z.ZodBoolean;
|
|
11
|
+
isLaunchable: z.ZodBoolean;
|
|
12
|
+
packages: z.ZodArray<z.ZodString>;
|
|
13
|
+
toolchains: z.ZodArray<z.ZodObject<{
|
|
14
|
+
version: z.ZodString;
|
|
15
|
+
cl: z.ZodBoolean;
|
|
16
|
+
link: z.ZodBoolean;
|
|
17
|
+
}, z.core.$strip>>;
|
|
18
|
+
}, z.core.$strip>>;
|
|
19
|
+
kitsRoots: z.ZodArray<z.ZodObject<{
|
|
20
|
+
where: z.ZodString;
|
|
21
|
+
path: z.ZodString;
|
|
22
|
+
}, z.core.$strip>>;
|
|
23
|
+
sdks: z.ZodArray<z.ZodObject<{
|
|
24
|
+
version: z.ZodString;
|
|
25
|
+
root: z.ZodString;
|
|
26
|
+
windowsH: z.ZodBoolean;
|
|
27
|
+
kernel32: z.ZodBoolean;
|
|
28
|
+
ucrt: z.ZodBoolean;
|
|
29
|
+
}, z.core.$strip>>;
|
|
30
|
+
}, z.core.$strip>;
|
|
31
|
+
export type VsSetupReport = z.output<typeof VsSetupReportSchema>;
|
|
32
|
+
/** The oldest MSVC Unreal 5.4+ builds with. */
|
|
33
|
+
export declare const MIN_MSVC: readonly [14, 38];
|
|
34
|
+
export interface VsVerdict {
|
|
35
|
+
readonly ok: boolean;
|
|
36
|
+
readonly text: string;
|
|
37
|
+
}
|
|
38
|
+
/** What UnrealBuildTool would find, in plain words. */
|
|
39
|
+
export declare function vsVerdict(r: VsSetupReport): VsVerdict;
|