windows-development-sandbox 1.27.0 → 1.27.3
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/dist/app/context.d.ts +5 -0
- package/dist/app/context.js +13 -0
- package/dist/app/features/accounts/accounts.def.d.ts +16 -0
- package/dist/app/features/accounts/accounts.run.d.ts +4 -0
- package/dist/app/features/accounts/services.def.d.ts +15 -1
- package/dist/app/features/accounts/services.def.js +67 -31
- package/dist/app/features/accounts/services.run.d.ts +7 -2
- package/dist/app/features/accounts/services.run.js +25 -6
- package/dist/app/features/defs.d.ts +17 -0
- package/dist/app/features/unreal/unreal.run.js +13 -3
- package/dist/app/registry.d.ts +1 -1
- package/dist/app/registry.js +15 -2
- package/dist/bin/agent-sandbox.js +2 -2
- package/dist/bin/{chunk-JXL6ZOO3.js → chunk-4M5SZZ5P.js} +46 -41
- package/dist/bin/{chunk-ZXCAVVS4.js → chunk-FA5VWLP4.js} +1 -1
- package/dist/bin/{chunk-6WWMBCT2.js → chunk-RU7XRN62.js} +303 -112
- 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 +2 -1
- package/dist/cli/menu/menu.js +6 -1
- package/dist/cli/menu/pages/accounts-page.js +9 -5
- package/dist/guest/apps/app-installer.js +7 -5
- package/dist/guest/apps/comet-unpack.js +2 -2
- package/dist/guest/runtime/pools.d.ts +4 -4
- package/dist/guest/runtime/pools.js +4 -15
- package/dist/guest/runtime/process.d.ts +5 -2
- package/dist/guest/runtime/process.js +23 -3
- package/dist/guest/setup/unity-agent.js +4 -1
- package/dist/guest/toolchains/engines/epic-launcher.d.ts +7 -1
- package/dist/guest/toolchains/engines/epic-launcher.js +30 -8
- package/dist/guest/toolchains/engines/unreal-engines.js +2 -1
- package/dist/guest/toolchains/engines/unreal-status.js +1 -1
- package/dist/guest/toolchains/installers.d.ts +3 -3
- package/dist/guest/toolchains/installers.js +5 -5
- package/dist/guest/toolchains/native/mesa.js +35 -11
- package/dist/guest/toolchains/native/unreal.js +1 -0
- package/dist/guest/toolchains/native/vcruntime.js +2 -2
- package/dist/guest/toolchains/native/vs-persist.d.ts +2 -2
- package/dist/guest/toolchains/signature.d.ts +2 -0
- package/dist/guest/toolchains/signature.js +9 -2
- package/dist/guest/toolchains/step-scheduler.js +1 -1
- package/dist/host/accounts/deliver.d.ts +3 -0
- package/dist/host/accounts/deliver.js +27 -0
- package/dist/host/accounts/source.d.ts +2 -0
- package/dist/host/accounts/source.js +2 -0
- package/dist/host/pc-installs/discover.js +8 -4
- package/dist/host/pc-installs/epic.js +3 -0
- package/dist/host/pc-installs/unity.d.ts +2 -0
- package/dist/host/pc-installs/unity.js +12 -3
- package/dist/host/pc-installs/unreal.d.ts +5 -1
- package/dist/host/pc-installs/unreal.js +51 -9
- package/dist/runtime/agent.js +34 -22
- package/dist/runtime/daemon.js +134 -65
- package/dist/runtime/db.js +32 -16
- package/dist/runtime/main.js +136 -67
- package/dist/runtime/netproxy.js +26 -12
- package/dist/runtime/tool.js +32 -15
- package/dist/runtime/view.js +26 -12
- package/dist/selftest/app.test.js +20 -2
- package/dist/selftest/boot-runner.test.js +15 -1
- package/dist/selftest/engines.test.js +52 -2
- package/dist/selftest/menu-screens.js +52 -1
- package/dist/selftest/menu.test.js +29 -3
- package/dist/selftest/mixamo.test.js +1 -1
- package/dist/selftest/service-accounts.test.js +88 -2
- package/dist/selftest/ui-text.test.js +4 -0
- package/dist/shared/accounts/kinds.js +9 -9
- package/dist/shared/apps/catalogue-gamedev.js +1 -0
- package/dist/shared/apps/catalogue.d.ts +2 -0
- package/dist/shared/concurrency/pools.js +1 -1
- package/dist/shared/download/archive.js +4 -2
- package/dist/shared/download/index.d.ts +1 -1
- package/dist/shared/download/index.js +1 -1
- package/dist/shared/download/spawn.d.ts +7 -1
- package/dist/shared/download/spawn.js +16 -1
- package/dist/shared/paths/pc-installs.d.ts +8 -0
- package/dist/shared/unreal/launcher-files.d.ts +7 -0
- package/dist/shared/unreal/launcher-files.js +32 -1
- package/dist/shared/unreal/status.d.ts +1 -0
- package/dist/shared/unreal/status.js +2 -0
- package/dist/web/index.html +1 -1
- package/dist/web/{main-VPMYTSUL.js → main-NKM7ECSB.js} +59 -59
- 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/package.json +1 -1
- package/schemas/config.schema.json +9 -9
- package/schemas/sandbox-config.schema.json +9 -9
|
@@ -6,7 +6,7 @@ import { dryRun } from '../../runtime/paths.js';
|
|
|
6
6
|
import { exists } from '../../runtime/fs.js';
|
|
7
7
|
import { log } from '../../runtime/log.js';
|
|
8
8
|
import { downloadFile } from '../install.js';
|
|
9
|
-
import { SIGNERS,
|
|
9
|
+
import { SIGNERS, signedByAsync } from '../installers.js';
|
|
10
10
|
import { tool } from '../tool-env.js';
|
|
11
11
|
const VC_REDIST = 'https://aka.ms/vs/17/release/vc_redist.x64.exe';
|
|
12
12
|
/** 1638: a newer version is already installed; 3010: installed, a restart would finish it (not needed for the DLLs). */
|
|
@@ -37,7 +37,7 @@ export async function setupVcRuntime(env, summary = true) {
|
|
|
37
37
|
log('Downloading the Visual C++ runtime (~25 MB once) ...');
|
|
38
38
|
await downloadFile(VC_REDIST, installer);
|
|
39
39
|
}
|
|
40
|
-
if (!
|
|
40
|
+
if (!(await signedByAsync(installer, SIGNERS.microsoft))) {
|
|
41
41
|
fs.rmSync(installer, { force: true });
|
|
42
42
|
throw new Error('vc_redist.x64.exe is not signed by Microsoft; deleted it (it is downloaded again next boot)');
|
|
43
43
|
}
|
|
@@ -71,6 +71,7 @@ export declare const VsPersistMarkerSchema: z.ZodObject<{
|
|
|
71
71
|
installationPath: z.ZodString;
|
|
72
72
|
sdkVersion: z.ZodString;
|
|
73
73
|
folders: z.ZodArray<z.ZodEnum<{
|
|
74
|
+
Shared: "Shared";
|
|
74
75
|
Community: "Community";
|
|
75
76
|
BuildTools: "BuildTools";
|
|
76
77
|
WindowsKits10: "WindowsKits10";
|
|
@@ -78,7 +79,6 @@ export declare const VsPersistMarkerSchema: z.ZodObject<{
|
|
|
78
79
|
"MicrosoftSDKs-v10.0A": "MicrosoftSDKs-v10.0A";
|
|
79
80
|
ReferenceAssemblies: "ReferenceAssemblies";
|
|
80
81
|
Installer: "Installer";
|
|
81
|
-
Shared: "Shared";
|
|
82
82
|
Setup: "Setup";
|
|
83
83
|
instance: "instance";
|
|
84
84
|
}>>;
|
|
@@ -102,6 +102,7 @@ export type VsPersistMarker = z.infer<typeof VsPersistMarkerSchema>;
|
|
|
102
102
|
/** What save-visual-studio.ps1 writes to its result file. */
|
|
103
103
|
export declare const SaveResultSchema: z.ZodObject<{
|
|
104
104
|
copied: z.ZodArray<z.ZodEnum<{
|
|
105
|
+
Shared: "Shared";
|
|
105
106
|
Community: "Community";
|
|
106
107
|
BuildTools: "BuildTools";
|
|
107
108
|
WindowsKits10: "WindowsKits10";
|
|
@@ -109,7 +110,6 @@ export declare const SaveResultSchema: z.ZodObject<{
|
|
|
109
110
|
"MicrosoftSDKs-v10.0A": "MicrosoftSDKs-v10.0A";
|
|
110
111
|
ReferenceAssemblies: "ReferenceAssemblies";
|
|
111
112
|
Installer: "Installer";
|
|
112
|
-
Shared: "Shared";
|
|
113
113
|
Setup: "Setup";
|
|
114
114
|
instance: "instance";
|
|
115
115
|
}>>;
|
|
@@ -3,3 +3,5 @@ import { type AppSigner, type Signature } from '../../shared/signer.js';
|
|
|
3
3
|
export declare function signatureOf(file: string): Signature | null;
|
|
4
4
|
/** Whether a file's valid signature is the expected signer's. */
|
|
5
5
|
export declare function signedBy(file: string, signer: AppSigner): boolean;
|
|
6
|
+
/** signedBy without blocking the event loop: a boot check of a big download must not stall the other tasks. */
|
|
7
|
+
export declare function signedByAsync(file: string, signer: AppSigner): Promise<boolean>;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
// A file's Authenticode signature (sandbox\scripts\get-signature.ps1) checked against the signer a download names.
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { signerMatches } from '../../shared/signer.js';
|
|
4
|
-
import { scriptOutput } from '../runtime/process.js';
|
|
4
|
+
import { scriptOutput, scriptOutputAsync } from '../runtime/process.js';
|
|
5
5
|
const SignatureSchema = z.object({ subject: z.string().max(4096), issuer: z.string().max(4096) });
|
|
6
6
|
/** The names on a file's valid signature, or null when it is unsigned, tampered with, or cannot be read. */
|
|
7
7
|
export function signatureOf(file) {
|
|
8
|
-
|
|
8
|
+
return parseSignature(scriptOutput('get-signature.ps1', ['-Path', file]));
|
|
9
|
+
}
|
|
10
|
+
function parseSignature(output) {
|
|
11
|
+
const text = output.trim();
|
|
9
12
|
if (!text)
|
|
10
13
|
return null;
|
|
11
14
|
try {
|
|
@@ -20,3 +23,7 @@ export function signatureOf(file) {
|
|
|
20
23
|
export function signedBy(file, signer) {
|
|
21
24
|
return signerMatches(signatureOf(file), signer);
|
|
22
25
|
}
|
|
26
|
+
/** signedBy without blocking the event loop: a boot check of a big download must not stall the other tasks. */
|
|
27
|
+
export async function signedByAsync(file, signer) {
|
|
28
|
+
return signerMatches(parseSignature(await scriptOutputAsync('get-signature.ps1', ['-Path', file])), signer);
|
|
29
|
+
}
|
|
@@ -5,7 +5,7 @@ import { downloader } from '../../shared/download/index.js';
|
|
|
5
5
|
import { errMsg } from '../../core/util/errors.js';
|
|
6
6
|
import { emptyEnv } from './tool-env.js';
|
|
7
7
|
/** How many installers run at once: enough to overlap downloads with installs, few enough not to thrash the disk. */
|
|
8
|
-
const PARALLEL =
|
|
8
|
+
const PARALLEL = 3;
|
|
9
9
|
/** Runs one installer; a failure is reported and skipped so it never blocks the other toolchains. */
|
|
10
10
|
async function runReported(sink, name, env, work) {
|
|
11
11
|
try {
|
|
@@ -2,6 +2,7 @@ import type { BootWant } from '../../core/local-config/index.js';
|
|
|
2
2
|
import type { Config } from '../../core/types.js';
|
|
3
3
|
import { type Payloads, type ServiceKind, type UsedBy } from '../../shared/accounts/index.js';
|
|
4
4
|
import { AccountLeases } from './leases.js';
|
|
5
|
+
import type { PcEnv } from './source.js';
|
|
5
6
|
import { ServiceAccountStore } from './store.js';
|
|
6
7
|
export interface AccountDelivery {
|
|
7
8
|
readonly payloads: Payloads;
|
|
@@ -22,5 +23,7 @@ export interface DeliveryInput {
|
|
|
22
23
|
readonly isRunning: (dataSet: string) => boolean;
|
|
23
24
|
readonly store?: ServiceAccountStore;
|
|
24
25
|
readonly leases?: AccountLeases;
|
|
26
|
+
/** This PC's folders: a shared kind with nothing saved takes this PC's own sign-in (left out: never). */
|
|
27
|
+
readonly pc?: PcEnv;
|
|
25
28
|
}
|
|
26
29
|
export declare function prepareServiceAccounts(i: DeliveryInput): AccountDelivery;
|
|
@@ -2,6 +2,7 @@ import { fingerprint, SERVICE_KINDS, SERVICE_SPECS, serviceWanted, } from '../..
|
|
|
2
2
|
import { setNameOf } from '../storage/index.js';
|
|
3
3
|
import { takeHandoffs } from './handoff.js';
|
|
4
4
|
import { AccountLeases } from './leases.js';
|
|
5
|
+
import { HOST_SOURCES } from './sources.js';
|
|
5
6
|
import { ServiceAccountStore } from './store.js';
|
|
6
7
|
/** How to get a first sign-in of a kind, for the note when none is saved. */
|
|
7
8
|
function howToSave(spec) {
|
|
@@ -21,6 +22,30 @@ function howToSave(spec) {
|
|
|
21
22
|
? 'save one: sandbox account login ' + spec.kind
|
|
22
23
|
: 'save one: sandbox account import ' + spec.kind + ' --from <file>';
|
|
23
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* This PC's own sign-in, saved like `sandbox account import`: when nothing is saved, and when the saved one was taken
|
|
27
|
+
* from this PC and the PC's has changed since (Epic replaces its token whenever the launcher here renews it, so the
|
|
28
|
+
* old copy no longer signs in). One a sandbox handed back is newer than the PC's and is kept.
|
|
29
|
+
*/
|
|
30
|
+
function takeFromPc(kind, pc, store, saved) {
|
|
31
|
+
const source = HOST_SOURCES[kind];
|
|
32
|
+
if (!pc || !source.fromPc)
|
|
33
|
+
return saved;
|
|
34
|
+
if (saved && store.info(kind)?.source !== 'pc')
|
|
35
|
+
return saved;
|
|
36
|
+
let payload = null;
|
|
37
|
+
try {
|
|
38
|
+
payload = source.fromPc(pc);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return saved; // This PC's program has no usable sign-in: the saved one (or the note) stands.
|
|
42
|
+
}
|
|
43
|
+
if (!payload)
|
|
44
|
+
return saved;
|
|
45
|
+
if (!saved || fingerprint(payload) !== fingerprint(saved))
|
|
46
|
+
store.save(kind, payload, 'pc', source.summary(payload));
|
|
47
|
+
return payload;
|
|
48
|
+
}
|
|
24
49
|
export function prepareServiceAccounts(i) {
|
|
25
50
|
const store = i.store ?? new ServiceAccountStore(i.storageRoot);
|
|
26
51
|
const leases = i.leases ?? new AccountLeases(i.storageRoot);
|
|
@@ -53,6 +78,8 @@ export function prepareServiceAccounts(i) {
|
|
|
53
78
|
catch {
|
|
54
79
|
unreadable = true;
|
|
55
80
|
}
|
|
81
|
+
if (!unreadable)
|
|
82
|
+
payload = takeFromPc(kind, i.pc, store, payload);
|
|
56
83
|
if (spec.singleHolder) {
|
|
57
84
|
const claim = leases.claim(kind, dataSet, i.launchId, payload ? fingerprint(payload) : null, i.isRunning);
|
|
58
85
|
if (!claim.ok) {
|
|
@@ -10,6 +10,8 @@ export interface PcEnv {
|
|
|
10
10
|
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
11
11
|
/** A variable of this Windows user's saved environment (HKCU\Environment), or null when it is not set. */
|
|
12
12
|
readonly userVariable?: (name: string) => string | null;
|
|
13
|
+
/** This PC's local drives ("D:\\"), asked only by the searches that look on every drive. */
|
|
14
|
+
readonly drives?: () => readonly string[];
|
|
13
15
|
}
|
|
14
16
|
/** The value of `name` in `reg query <key> /v <name>` output, or null. */
|
|
15
17
|
export declare function parseRegValue(text: string, name: string): string | null;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// (fromFile, `sandbox account import <kind> --from <file>`), or typed (fromAnswers, `sandbox account login <kind>`).
|
|
3
3
|
// One module per kind under kinds/; sources.ts lists them.
|
|
4
4
|
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { diskUsage } from '../../core/disk.js';
|
|
5
6
|
import { fail } from '../../core/util/index.js';
|
|
6
7
|
import { SYSTEM } from '../../shared/system.js';
|
|
7
8
|
import { refuseInSelfTest } from '../test-guard.js';
|
|
@@ -35,6 +36,7 @@ export function pcEnv() {
|
|
|
35
36
|
programFiles: env.ProgramFiles ?? 'C:\\Program Files',
|
|
36
37
|
env,
|
|
37
38
|
userVariable: savedUserVariable,
|
|
39
|
+
drives: () => diskUsage(env.ProgramData ?? 'C:\\').map((d) => d.drive),
|
|
38
40
|
};
|
|
39
41
|
}
|
|
40
42
|
/** What a kind whose host side is not written yet says. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { assertSafeMapping } from '../../core/safe-paths.js';
|
|
2
2
|
import { dirExists, errMsg } from '../../core/util/index.js';
|
|
3
3
|
import { pcInstallGuest } from '../../shared/paths/index.js';
|
|
4
|
+
import { relocateItem } from '../../shared/unreal/index.js';
|
|
4
5
|
import { pcEnv } from '../accounts/index.js';
|
|
5
6
|
import { epicInstalls } from './epic.js';
|
|
6
7
|
import { steamInstalls } from './steam.js';
|
|
@@ -10,12 +11,13 @@ import { unrealFolders, unrealInstalls } from './unreal.js';
|
|
|
10
11
|
import { unrealVault } from './unreal-vault.js';
|
|
11
12
|
/** At most this many installs are mapped (each one is a mapping in the .wsb). */
|
|
12
13
|
export const MAX_PC_INSTALLS = 40;
|
|
14
|
+
// Engines first: a folder is mapped once, as the first source that finds it.
|
|
13
15
|
export const PC_INSTALL_SOURCES = [
|
|
14
|
-
steamInstalls,
|
|
15
|
-
epicInstalls,
|
|
16
16
|
unrealFolders,
|
|
17
17
|
unrealInstalls,
|
|
18
18
|
unrealVault,
|
|
19
|
+
steamInstalls,
|
|
20
|
+
epicInstalls,
|
|
19
21
|
unityInstalls,
|
|
20
22
|
unityAssets,
|
|
21
23
|
];
|
|
@@ -73,8 +75,10 @@ export function discoverPcInstalls(s, o, pc = pcEnv()) {
|
|
|
73
75
|
readOnly: !writable,
|
|
74
76
|
label: writable ? 'this PC, writable' : 'this PC, read-only',
|
|
75
77
|
});
|
|
76
|
-
const { host: _host, ...shown } = install;
|
|
77
|
-
|
|
78
|
+
const { host: _host, launcherItem, ...shown } = install;
|
|
79
|
+
// The manifest names the sandbox's folder, or it is left out: no path of this PC goes to the sandbox.
|
|
80
|
+
const moved = launcherItem ? relocateItem(launcherItem.text, install.host, guest) : null;
|
|
81
|
+
items.push({ ...shown, guest, ...(moved ? { launcherItem: { name: launcherItem.name, text: moved } } : {}) });
|
|
78
82
|
}
|
|
79
83
|
catch (e) {
|
|
80
84
|
notes.push('pcInstalls: ' + install.name + ' is not mapped: ' + errMsg(e));
|
|
@@ -11,6 +11,9 @@ export function findEpicGames(text, wanted) {
|
|
|
11
11
|
for (const i of text ? launcherInstallations(text) : []) {
|
|
12
12
|
if (!APP.test(i.appName) || /^\.+$/.test(i.appName) || /^UE_\d/.test(i.appName))
|
|
13
13
|
continue;
|
|
14
|
+
// Engines and what the launcher puts into one (the Fab plugin lists the engine's folder) are unreal.ts's.
|
|
15
|
+
if (i.ids?.namespaceId.toLowerCase() === 'ue' || /^UE_\d/i.test(path.win32.basename(i.installLocation)))
|
|
16
|
+
continue;
|
|
14
17
|
if (names && !names.has(i.appName.toLowerCase()))
|
|
15
18
|
continue;
|
|
16
19
|
if (found.some((f) => f.id.toLowerCase() === i.appName.toLowerCase()))
|
|
@@ -10,6 +10,8 @@ export interface UnityPcFiles {
|
|
|
10
10
|
list(dir: string): string[];
|
|
11
11
|
readonly appData: string;
|
|
12
12
|
readonly programFiles: string;
|
|
13
|
+
/** Hub's folder on every other drive (D:\Program Files\Unity\Hub\Editor, D:\Unity\Hub\Editor ...). */
|
|
14
|
+
readonly otherHubFolders?: readonly string[];
|
|
13
15
|
}
|
|
14
16
|
/** The editors of this PC: all of them (true) or the listed versions. Each version once, each folder once. */
|
|
15
17
|
export declare function findUnityEditorsOnPc(files: UnityPcFiles, wanted: true | readonly string[]): PcInstall[];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
// Unity editors of this PC (pcInstalls.unityEditors): Unity Hub's default folder
|
|
1
|
+
// Unity editors of this PC (pcInstalls.unityEditors): Unity Hub's default folder on every drive
|
|
2
|
+
// (<drive>\Program Files\Unity\Hub\Editor, <drive>\Unity\Hub\Editor),
|
|
2
3
|
// the folder named by %APPDATA%\UnityHub\secondaryInstallPath.json and the editors in editors-v2.json. An editor counts
|
|
3
4
|
// only with Editor\Unity.exe; its folder is mapped read-only at C:\PcInstalls\unity\<version>. Every file is read
|
|
4
5
|
// bounded and every name is checked: what Hub wrote is data, never trusted.
|
|
@@ -67,8 +68,12 @@ function listedEditors(files) {
|
|
|
67
68
|
/** The editors of this PC: all of them (true) or the listed versions. Each version once, each folder once. */
|
|
68
69
|
export function findUnityEditorsOnPc(files, wanted) {
|
|
69
70
|
const candidates = [];
|
|
70
|
-
const hubFolders = [
|
|
71
|
-
|
|
71
|
+
const hubFolders = [
|
|
72
|
+
win.join(files.programFiles, 'Unity', 'Hub', 'Editor'),
|
|
73
|
+
secondaryRoot(files),
|
|
74
|
+
...(files.otherHubFolders ?? []),
|
|
75
|
+
].filter((d) => d !== null);
|
|
76
|
+
for (const folder of new Set(hubFolders.map((d) => win.resolve(d))))
|
|
72
77
|
for (const name of files.list(folder).slice(0, MAX_LISTED).sort())
|
|
73
78
|
if (UNITY_VERSION.test(name))
|
|
74
79
|
candidates.push({ version: name, root: win.join(folder, name) });
|
|
@@ -125,6 +130,10 @@ export function unityPcFiles(pc) {
|
|
|
125
130
|
list: listDir,
|
|
126
131
|
appData: pc.appData,
|
|
127
132
|
programFiles: pc.programFiles ?? 'C:\\Program Files',
|
|
133
|
+
otherHubFolders: (pc.drives?.() ?? []).flatMap((d) => [
|
|
134
|
+
win.join(d, 'Program Files', 'Unity', 'Hub', 'Editor'),
|
|
135
|
+
win.join(d, 'Unity', 'Hub', 'Editor'),
|
|
136
|
+
]),
|
|
128
137
|
};
|
|
129
138
|
}
|
|
130
139
|
/** The cache root the environment names: this terminal's variable, else the saved user one. */
|
|
@@ -11,6 +11,10 @@ export interface UnrealPcFiles {
|
|
|
11
11
|
readonly builds: string;
|
|
12
12
|
/** The launcher's default engines folder (<Program Files>\Epic Games). */
|
|
13
13
|
readonly defaultRoot: string;
|
|
14
|
+
/** More folders that may hold UE_x.y folders: the same places on every drive (D:\Program Files\Epic Games ...). */
|
|
15
|
+
readonly otherRoots?: readonly string[];
|
|
16
|
+
/** The launcher's Data\Manifests folder (its .item files), or none. */
|
|
17
|
+
readonly manifests?: string;
|
|
14
18
|
readonly list: (dir: string) => string[];
|
|
15
19
|
readonly read: (file: string, max: number) => Buffer | null;
|
|
16
20
|
}
|
|
@@ -31,7 +35,7 @@ export interface PcSourceBuild {
|
|
|
31
35
|
export declare function pcSourceBuilds(files: Pick<UnrealPcFiles, 'read' | 'builds'>): PcSourceBuild[];
|
|
32
36
|
/** The engine folders you registered (pcInstalls.unrealEngineFolders), mapped writable. A folder that is a source
|
|
33
37
|
* build of this PC keeps the PC's build id. */
|
|
34
|
-
export declare function findEngineFolders(files: Pick<UnrealPcFiles, 'read' | 'builds'
|
|
38
|
+
export declare function findEngineFolders(files: Pick<UnrealPcFiles, 'read' | 'builds'> & Partial<Pick<UnrealPcFiles, 'launcherInstalled' | 'manifests' | 'list'>>, folders: readonly (string | number)[]): PcInstall[];
|
|
35
39
|
/** This PC's files (reg.exe through system32, bounded reads). */
|
|
36
40
|
export declare function unrealPcFiles(pc: PcEnv): UnrealPcFiles;
|
|
37
41
|
export declare const unrealInstalls: PcInstallSource;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Unreal Engine versions of this PC (pcInstalls.unrealEngines): the launcher's LauncherInstalled.dat (engines it
|
|
2
|
-
// installed, with its own ids) and the launcher's default
|
|
3
|
-
//
|
|
2
|
+
// installed, with its own ids) and folders named UE_x.y in the launcher's default places on every drive
|
|
3
|
+
// (<drive>\Program Files\Epic Games, <drive>\Epic Games) and next to what it lists (an install whose entry is gone).
|
|
4
|
+
// Each must hold Engine\Build\Build.version; it is mapped read-only at
|
|
4
5
|
// C:\PcInstalls\unreal\UE_5.4. Source builds (HKCU\Software\Epic Games\Unreal Engine\Builds) are NOT mapped by
|
|
5
6
|
// themselves, as UBT writes inside an engine. pcInstalls.unrealEngineFolders: folders you registered, mapped writable
|
|
6
7
|
// (dir-<name>; a source build of this PC keeps its build id). Every file is read bounded.
|
|
@@ -9,7 +10,7 @@ import fs from 'node:fs';
|
|
|
9
10
|
import path from 'node:path';
|
|
10
11
|
import { readBoundedFile } from '../../shared/io/index.js';
|
|
11
12
|
import { SYSTEM } from '../../shared/system.js';
|
|
12
|
-
import { BUILDS_KEY, launcherInstallations, launcherInstalledFile, MAX_LAUNCHER_FILE_BYTES, parseBuildVersion, versionText, } from '../../shared/unreal/index.js';
|
|
13
|
+
import { BUILDS_KEY, itemInstallLocation, ITEM_NAME, launcherInstallations, launcherInstalledFile, launcherManifestsDir, MAX_LAUNCHER_FILE_BYTES, parseBuildVersion, versionText, } from '../../shared/unreal/index.js';
|
|
13
14
|
const win = path.win32;
|
|
14
15
|
/** At most this many engines of this PC are mapped. */
|
|
15
16
|
export const MAX_PC_ENGINES = 8;
|
|
@@ -28,6 +29,27 @@ const versionOf = (dir, files) => {
|
|
|
28
29
|
const bytes = files.read(win.join(dir, 'Engine', 'Build', 'Build.version'), 64 * 1024);
|
|
29
30
|
return bytes ? parseBuildVersion(bytes.toString('utf8')) : null;
|
|
30
31
|
};
|
|
32
|
+
/** At most this much of a manifest goes into session.json (an engine's is a few KB). */
|
|
33
|
+
const MAX_ITEM_BYTES = 64 * 1024;
|
|
34
|
+
/** The launcher's manifest of the app in `dir` (with this PC's paths), or nothing. */
|
|
35
|
+
function launcherItemOf(dir, files) {
|
|
36
|
+
if (!files.manifests)
|
|
37
|
+
return {};
|
|
38
|
+
const want = win.resolve(dir).toLowerCase();
|
|
39
|
+
for (const name of files.list(files.manifests).filter((n) => ITEM_NAME.test(n))) {
|
|
40
|
+
const bytes = files.read(win.join(files.manifests, name), MAX_ITEM_BYTES);
|
|
41
|
+
const at = bytes ? itemInstallLocation(bytes.toString('utf8')) : null;
|
|
42
|
+
if (bytes && at && win.resolve(at).toLowerCase() === want)
|
|
43
|
+
return { launcherItem: { name, text: bytes.toString('utf8') } };
|
|
44
|
+
}
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
/** The launcher's ids for the app in `dir` (from LauncherInstalled.dat), or nothing. */
|
|
48
|
+
function launcherIdsOf(dir, files) {
|
|
49
|
+
const want = win.resolve(dir).toLowerCase();
|
|
50
|
+
const entry = (files.launcherInstalled ? launcherInstallations(files.launcherInstalled) : []).find((i) => APP_NAME.test(i.appName) && i.ids && win.resolve(i.installLocation).toLowerCase() === want);
|
|
51
|
+
return entry?.ids ? { launcher: entry.ids } : {};
|
|
52
|
+
}
|
|
31
53
|
const isWanted = (wanted, v) => wanted === true || wanted.map((w) => String(w).replace(/^UE_/i, '')).includes(v.major + '.' + v.minor);
|
|
32
54
|
/** The engines of this PC that `wanted` maps, each folder once, at most MAX_PC_ENGINES. */
|
|
33
55
|
export function findUnrealInstalls(files, wanted) {
|
|
@@ -52,16 +74,28 @@ export function findUnrealInstalls(files, wanted) {
|
|
|
52
74
|
host: win.normalize(dir),
|
|
53
75
|
guest: '',
|
|
54
76
|
...(ids ? { launcher: ids } : {}),
|
|
77
|
+
...launcherItemOf(dir, files),
|
|
55
78
|
});
|
|
56
79
|
};
|
|
57
|
-
|
|
80
|
+
const listed = files.launcherInstalled ? launcherInstallations(files.launcherInstalled) : [];
|
|
81
|
+
for (const i of listed)
|
|
58
82
|
if (APP_NAME.test(i.appName))
|
|
59
83
|
installed(i.installLocation, i.ids ?? undefined);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
84
|
+
// Folders named like an engine: the default places, and the folders that hold what the launcher installed (an
|
|
85
|
+
// engine whose entry is gone often sits next to one that is listed, or next to a game).
|
|
86
|
+
const roots = [files.defaultRoot, ...(files.otherRoots ?? []), ...listed.map((i) => win.dirname(i.installLocation))];
|
|
87
|
+
const seen = new Set();
|
|
88
|
+
for (const root of roots) {
|
|
89
|
+
const key = win.resolve(root).toLowerCase();
|
|
90
|
+
if (seen.has(key) || seen.size >= 32)
|
|
91
|
+
continue;
|
|
92
|
+
seen.add(key);
|
|
93
|
+
for (const name of files
|
|
94
|
+
.list(root)
|
|
95
|
+
.filter((n) => APP_NAME.test(n))
|
|
96
|
+
.sort())
|
|
97
|
+
installed(win.join(root, name));
|
|
98
|
+
}
|
|
65
99
|
return found;
|
|
66
100
|
}
|
|
67
101
|
/** Source builds this PC registered (the Builds key). They are not mapped by themselves: UBT writes inside them. */
|
|
@@ -105,6 +139,9 @@ export function findEngineFolders(files, folders) {
|
|
|
105
139
|
guest: '',
|
|
106
140
|
writable: true,
|
|
107
141
|
...(source ? { buildId: source.buildId } : {}),
|
|
142
|
+
// An engine the launcher installed keeps its ids and manifest, so the sandbox's launcher lists it too.
|
|
143
|
+
...launcherIdsOf(dir, { launcherInstalled: files.launcherInstalled ?? null }),
|
|
144
|
+
...(files.list ? launcherItemOf(dir, { manifests: files.manifests, list: files.list, read: files.read }) : {}),
|
|
108
145
|
});
|
|
109
146
|
}
|
|
110
147
|
return found;
|
|
@@ -130,6 +167,11 @@ export function unrealPcFiles(pc) {
|
|
|
130
167
|
launcherInstalled: dat ? dat.toString('utf8') : null,
|
|
131
168
|
builds: query.status === 0 ? String(query.stdout ?? '') : '',
|
|
132
169
|
defaultRoot: win.join(pc.programFiles ?? 'C:\\Program Files', 'Epic Games'),
|
|
170
|
+
otherRoots: (pc.drives?.() ?? []).flatMap((d) => [
|
|
171
|
+
win.join(d, 'Program Files', 'Epic Games'),
|
|
172
|
+
win.join(d, 'Epic Games'),
|
|
173
|
+
]),
|
|
174
|
+
manifests: launcherManifestsDir(pc.programData),
|
|
133
175
|
list: listDir,
|
|
134
176
|
read: readBoundedFile,
|
|
135
177
|
};
|
package/dist/runtime/agent.js
CHANGED
|
@@ -26093,6 +26093,7 @@ var GAMEDEV_APPS = [
|
|
|
26093
26093
|
size: "100 MB",
|
|
26094
26094
|
source: { github: "f3d-app/f3d", asset: "^F3D-[\\d.]+-Windows-x86_64\\.zip$" },
|
|
26095
26095
|
exe: "f3d.exe",
|
|
26096
|
+
onPath: true,
|
|
26096
26097
|
needs: ["mesa"]
|
|
26097
26098
|
},
|
|
26098
26099
|
{
|
|
@@ -27192,7 +27193,7 @@ var SERVICE_SPECS = {
|
|
|
27192
27193
|
},
|
|
27193
27194
|
steam: {
|
|
27194
27195
|
kind: "steam",
|
|
27195
|
-
label: "Steam
|
|
27196
|
+
label: "Steam",
|
|
27196
27197
|
usedBy: ["steam"],
|
|
27197
27198
|
sources: ["file", "sandbox"],
|
|
27198
27199
|
singleHolder: true,
|
|
@@ -27228,7 +27229,7 @@ var SERVICE_SPECS = {
|
|
|
27228
27229
|
},
|
|
27229
27230
|
cfx: {
|
|
27230
27231
|
kind: "cfx",
|
|
27231
|
-
label: "
|
|
27232
|
+
label: "FiveM key",
|
|
27232
27233
|
usedBy: ["fivem"],
|
|
27233
27234
|
sources: ["typed", "file"],
|
|
27234
27235
|
singleHolder: false,
|
|
@@ -27257,7 +27258,7 @@ var SERVICE_SPECS = {
|
|
|
27257
27258
|
},
|
|
27258
27259
|
keystore: {
|
|
27259
27260
|
kind: "keystore",
|
|
27260
|
-
label: "Android
|
|
27261
|
+
label: "Android keystore",
|
|
27261
27262
|
usedBy: ["android"],
|
|
27262
27263
|
sources: ["file"],
|
|
27263
27264
|
singleHolder: false,
|
|
@@ -27272,7 +27273,7 @@ var SERVICE_SPECS = {
|
|
|
27272
27273
|
},
|
|
27273
27274
|
play: {
|
|
27274
27275
|
kind: "play",
|
|
27275
|
-
label: "Google Play
|
|
27276
|
+
label: "Google Play",
|
|
27276
27277
|
usedBy: ["android"],
|
|
27277
27278
|
sources: ["file"],
|
|
27278
27279
|
singleHolder: false,
|
|
@@ -27283,7 +27284,7 @@ var SERVICE_SPECS = {
|
|
|
27283
27284
|
},
|
|
27284
27285
|
expo: {
|
|
27285
27286
|
kind: "expo",
|
|
27286
|
-
label: "Expo
|
|
27287
|
+
label: "Expo",
|
|
27287
27288
|
usedBy: ["reactNative"],
|
|
27288
27289
|
sources: ["typed"],
|
|
27289
27290
|
singleHolder: false,
|
|
@@ -27294,7 +27295,7 @@ var SERVICE_SPECS = {
|
|
|
27294
27295
|
},
|
|
27295
27296
|
appstore: {
|
|
27296
27297
|
kind: "appstore",
|
|
27297
|
-
label: "App Store Connect
|
|
27298
|
+
label: "App Store Connect",
|
|
27298
27299
|
usedBy: ["reactNative", "flutter"],
|
|
27299
27300
|
sources: ["file", "typed"],
|
|
27300
27301
|
singleHolder: false,
|
|
@@ -27308,7 +27309,7 @@ var SERVICE_SPECS = {
|
|
|
27308
27309
|
},
|
|
27309
27310
|
perplexity: {
|
|
27310
27311
|
kind: "perplexity",
|
|
27311
|
-
label: "Perplexity API
|
|
27312
|
+
label: "Perplexity API",
|
|
27312
27313
|
usedBy: ["perplexity"],
|
|
27313
27314
|
sources: ["pc", "file", "typed"],
|
|
27314
27315
|
singleHolder: false,
|
|
@@ -27332,7 +27333,7 @@ var SERVICE_SPECS = {
|
|
|
27332
27333
|
},
|
|
27333
27334
|
comet: {
|
|
27334
27335
|
kind: "comet",
|
|
27335
|
-
label: "Comet
|
|
27336
|
+
label: "Comet",
|
|
27336
27337
|
usedBy: ["comet"],
|
|
27337
27338
|
sources: ["sandbox"],
|
|
27338
27339
|
singleHolder: false,
|
|
@@ -27348,7 +27349,7 @@ var SERVICE_SPECS = {
|
|
|
27348
27349
|
},
|
|
27349
27350
|
perplexityapp: {
|
|
27350
27351
|
kind: "perplexityapp",
|
|
27351
|
-
label: "Perplexity
|
|
27352
|
+
label: "Perplexity app",
|
|
27352
27353
|
usedBy: ["perplexityapp"],
|
|
27353
27354
|
sources: ["sandbox"],
|
|
27354
27355
|
singleHolder: false,
|
|
@@ -28074,7 +28075,7 @@ import path12 from "node:path";
|
|
|
28074
28075
|
|
|
28075
28076
|
// src/guest/runtime/process.ts
|
|
28076
28077
|
import fs10 from "node:fs";
|
|
28077
|
-
import
|
|
28078
|
+
import os4 from "node:os";
|
|
28078
28079
|
import path11 from "node:path";
|
|
28079
28080
|
import { spawn as spawn2, spawnSync } from "node:child_process";
|
|
28080
28081
|
|
|
@@ -28134,10 +28135,20 @@ function scriptArgs(name, args = []) {
|
|
|
28134
28135
|
}
|
|
28135
28136
|
|
|
28136
28137
|
// src/shared/download/spawn.ts
|
|
28138
|
+
import os3 from "node:os";
|
|
28137
28139
|
import { spawn } from "node:child_process";
|
|
28140
|
+
function lowerPriority(pid) {
|
|
28141
|
+
if (pid === void 0) return;
|
|
28142
|
+
try {
|
|
28143
|
+
os3.setPriority(pid, os3.constants.priority.PRIORITY_BELOW_NORMAL);
|
|
28144
|
+
} catch {
|
|
28145
|
+
}
|
|
28146
|
+
}
|
|
28138
28147
|
function spawnAsync(file2, args, options = {}) {
|
|
28148
|
+
const { lowPriority, ...spawnOptions } = options;
|
|
28139
28149
|
return new Promise((resolve, reject) => {
|
|
28140
|
-
const child = spawn(file2, args, { stdio: "ignore", windowsHide: true, ...
|
|
28150
|
+
const child = spawn(file2, args, { stdio: "ignore", windowsHide: true, ...spawnOptions });
|
|
28151
|
+
if (lowPriority) lowerPriority(child.pid);
|
|
28141
28152
|
child.once("error", reject);
|
|
28142
28153
|
child.once("close", (code) => resolve(code ?? -1));
|
|
28143
28154
|
});
|
|
@@ -28158,13 +28169,16 @@ function extractArchive(archive, destination, members2 = []) {
|
|
|
28158
28169
|
}
|
|
28159
28170
|
async function extract(archive, destination, members2) {
|
|
28160
28171
|
fs8.mkdirSync(destination, { recursive: true });
|
|
28161
|
-
let status = await spawnAsync(SYSTEM.tar(), ["-xf", archive, "-C", destination, ...members2]
|
|
28172
|
+
let status = await spawnAsync(SYSTEM.tar(), ["-xf", archive, "-C", destination, ...members2], {
|
|
28173
|
+
lowPriority: true
|
|
28174
|
+
});
|
|
28162
28175
|
if (status !== 0 && /\.(zip|nupkg)$/i.test(archive)) {
|
|
28163
28176
|
const zip = archive.endsWith(".zip") ? archive : archive + ".zip";
|
|
28164
28177
|
if (zip !== archive) fs8.copyFileSync(archive, zip);
|
|
28165
28178
|
status = await spawnAsync(
|
|
28166
28179
|
SYSTEM.powershell(),
|
|
28167
|
-
scriptArgs("expand-archive.ps1", ["-Archive", zip, "-Destination", destination])
|
|
28180
|
+
scriptArgs("expand-archive.ps1", ["-Archive", zip, "-Destination", destination]),
|
|
28181
|
+
{ lowPriority: true }
|
|
28168
28182
|
);
|
|
28169
28183
|
}
|
|
28170
28184
|
if (status !== 0) throw new Error("Could not extract " + archive);
|
|
@@ -28381,13 +28395,15 @@ function logLines(stream) {
|
|
|
28381
28395
|
stream.on("end", () => logOutput(partial2));
|
|
28382
28396
|
}
|
|
28383
28397
|
async function execAsync(file2, args, options = {}) {
|
|
28398
|
+
const { lowPriority, ...spawnOptions } = options;
|
|
28384
28399
|
const code = captureOutput() ? await new Promise((resolve, reject) => {
|
|
28385
|
-
const child = spawn2(file2, args, { stdio: ["inherit", "pipe", "pipe"], windowsHide: true, ...
|
|
28400
|
+
const child = spawn2(file2, args, { stdio: ["inherit", "pipe", "pipe"], windowsHide: true, ...spawnOptions });
|
|
28401
|
+
if (lowPriority) lowerPriority(child.pid);
|
|
28386
28402
|
logLines(child.stdout);
|
|
28387
28403
|
logLines(child.stderr);
|
|
28388
28404
|
child.once("error", reject);
|
|
28389
28405
|
child.once("close", (c) => resolve(c ?? -1));
|
|
28390
|
-
}) : await spawnAsync(file2, args, { stdio: "inherit", ...
|
|
28406
|
+
}) : await spawnAsync(file2, args, { stdio: "inherit", ...spawnOptions, ...lowPriority ? { lowPriority } : {} });
|
|
28391
28407
|
if (code !== 0)
|
|
28392
28408
|
throw new Error(path11.basename(file2) + " " + args.slice(0, 3).join(" ") + " failed with exit code " + code);
|
|
28393
28409
|
}
|
|
@@ -28416,7 +28432,7 @@ function runScript(name, args = []) {
|
|
|
28416
28432
|
throw new Error(name + " failed with exit code " + result.status + (errors ? ": " + errors.slice(0, 400) : ""));
|
|
28417
28433
|
}
|
|
28418
28434
|
function runScriptWithJson(name, param, data, args = []) {
|
|
28419
|
-
const dir = fs10.mkdtempSync(path11.join(
|
|
28435
|
+
const dir = fs10.mkdtempSync(path11.join(os4.tmpdir(), "sbx-"));
|
|
28420
28436
|
const file2 = path11.join(dir, "input.json");
|
|
28421
28437
|
try {
|
|
28422
28438
|
fs10.writeFileSync(file2, JSON.stringify(data), "utf8");
|
|
@@ -28492,9 +28508,6 @@ async function setupUv(env) {
|
|
|
28492
28508
|
env.prepend.push(mkdirp(tool("uv-bin")));
|
|
28493
28509
|
}
|
|
28494
28510
|
|
|
28495
|
-
// src/guest/runtime/pools.ts
|
|
28496
|
-
import os4 from "node:os";
|
|
28497
|
-
|
|
28498
28511
|
// src/shared/concurrency/limiter.ts
|
|
28499
28512
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
28500
28513
|
var held = new AsyncLocalStorage2();
|
|
@@ -28554,14 +28567,13 @@ var Limiter = class {
|
|
|
28554
28567
|
};
|
|
28555
28568
|
|
|
28556
28569
|
// src/shared/concurrency/pools.ts
|
|
28557
|
-
var net = new Limiter(
|
|
28570
|
+
var net = new Limiter(3, "net");
|
|
28558
28571
|
var heavy = new Limiter(2, "heavy");
|
|
28559
28572
|
var installer = new Limiter(1, "installer");
|
|
28560
28573
|
|
|
28561
28574
|
// src/guest/runtime/pools.ts
|
|
28562
|
-
var BELOW_NORMAL = os4.constants.priority.PRIORITY_BELOW_NORMAL;
|
|
28563
28575
|
function execHeavy(file2, args, options = {}) {
|
|
28564
|
-
return heavy.run(() => execAsync(file2, args, options));
|
|
28576
|
+
return heavy.run(() => execAsync(file2, args, { ...options, lowPriority: true }));
|
|
28565
28577
|
}
|
|
28566
28578
|
|
|
28567
28579
|
// src/guest/runtime/session.ts
|