@oddessentials/agent-guild 1.29.0 → 1.30.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/bin/native/win32-arm64/agent-guild-report.exe +0 -0
- package/bin/native/win32-x64/agent-guild-report.exe +0 -0
- package/bin/windows/arm64/agent-guild-start.exe +0 -0
- package/bin/windows/x64/agent-guild-start.exe +0 -0
- package/docs/api.md +6 -2
- package/node_modules/@jsquash/jpeg/CHANGELOG.md +54 -0
- package/node_modules/@jsquash/jpeg/LICENSE +201 -0
- package/node_modules/@jsquash/jpeg/README.md +105 -0
- package/node_modules/@jsquash/jpeg/codec/LICENSE.codec.md +132 -0
- package/node_modules/@jsquash/jpeg/codec/dec/mozjpeg_dec.d.ts +7 -0
- package/node_modules/@jsquash/jpeg/codec/dec/mozjpeg_dec.js +15 -0
- package/node_modules/@jsquash/jpeg/codec/dec/mozjpeg_dec.wasm +0 -0
- package/node_modules/@jsquash/jpeg/codec/enc/mozjpeg_enc.d.ts +37 -0
- package/node_modules/@jsquash/jpeg/codec/enc/mozjpeg_enc.js +15 -0
- package/node_modules/@jsquash/jpeg/codec/enc/mozjpeg_enc.wasm +0 -0
- package/node_modules/@jsquash/jpeg/codec/pre.js +24 -0
- package/node_modules/@jsquash/jpeg/decode.d.ts +16 -0
- package/node_modules/@jsquash/jpeg/decode.js +36 -0
- package/node_modules/@jsquash/jpeg/emscripten-types.d.ts +137 -0
- package/node_modules/@jsquash/jpeg/encode.d.ts +21 -0
- package/node_modules/@jsquash/jpeg/encode.js +35 -0
- package/node_modules/@jsquash/jpeg/index.d.ts +3 -0
- package/node_modules/@jsquash/jpeg/index.js +2 -0
- package/node_modules/@jsquash/jpeg/meta.d.ts +24 -0
- package/node_modules/@jsquash/jpeg/meta.js +25 -0
- package/node_modules/@jsquash/jpeg/package.json +32 -0
- package/node_modules/@jsquash/jpeg/utils.d.ts +17 -0
- package/node_modules/@jsquash/jpeg/utils.js +31 -0
- package/node_modules/@jsquash/png/CHANGELOG.md +69 -0
- package/node_modules/@jsquash/png/LICENSE +201 -0
- package/node_modules/@jsquash/png/README.md +114 -0
- package/node_modules/@jsquash/png/codec/LICENSE.codec.md +29 -0
- package/node_modules/@jsquash/png/codec/README.md +5 -0
- package/node_modules/@jsquash/png/codec/pkg/README.md +5 -0
- package/node_modules/@jsquash/png/codec/pkg/squoosh_png.d.ts +71 -0
- package/node_modules/@jsquash/png/codec/pkg/squoosh_png.js +314 -0
- package/node_modules/@jsquash/png/codec/pkg/squoosh_png_bg.wasm +0 -0
- package/node_modules/@jsquash/png/codec/pkg/squoosh_png_bg.wasm.d.ts +13 -0
- package/node_modules/@jsquash/png/codec/pre.js +24 -0
- package/node_modules/@jsquash/png/decode.d.ts +29 -0
- package/node_modules/@jsquash/png/decode.js +35 -0
- package/node_modules/@jsquash/png/encode.d.ts +31 -0
- package/node_modules/@jsquash/png/encode.js +40 -0
- package/node_modules/@jsquash/png/index.d.ts +3 -0
- package/node_modules/@jsquash/png/index.js +2 -0
- package/node_modules/@jsquash/png/meta.d.ts +16 -0
- package/node_modules/@jsquash/png/meta.js +15 -0
- package/node_modules/@jsquash/png/package.json +34 -0
- package/node_modules/@jsquash/webp/CHANGELOG.md +48 -0
- package/node_modules/@jsquash/webp/LICENSE +201 -0
- package/node_modules/@jsquash/webp/README.md +100 -0
- package/node_modules/@jsquash/webp/codec/LICENSE.codec.md +29 -0
- package/node_modules/@jsquash/webp/codec/Makefile.animated.backup +88 -0
- package/node_modules/@jsquash/webp/codec/README.md +5 -0
- package/node_modules/@jsquash/webp/codec/dec/README.md +18 -0
- package/node_modules/@jsquash/webp/codec/dec/webp_dec.d.ts +7 -0
- package/node_modules/@jsquash/webp/codec/dec/webp_dec.js +16 -0
- package/node_modules/@jsquash/webp/codec/dec/webp_dec.wasm +0 -0
- package/node_modules/@jsquash/webp/codec/enc/README.md +22 -0
- package/node_modules/@jsquash/webp/codec/enc/webp_enc.d.ts +42 -0
- package/node_modules/@jsquash/webp/codec/enc/webp_enc.js +16 -0
- package/node_modules/@jsquash/webp/codec/enc/webp_enc.wasm +0 -0
- package/node_modules/@jsquash/webp/codec/enc/webp_enc_simd.d.ts +1 -0
- package/node_modules/@jsquash/webp/codec/enc/webp_enc_simd.js +16 -0
- package/node_modules/@jsquash/webp/codec/enc/webp_enc_simd.wasm +0 -0
- package/node_modules/@jsquash/webp/codec/pre.js +24 -0
- package/node_modules/@jsquash/webp/decode.d.ts +15 -0
- package/node_modules/@jsquash/webp/decode.js +34 -0
- package/node_modules/@jsquash/webp/emscripten-types.d.ts +137 -0
- package/node_modules/@jsquash/webp/encode.d.ts +23 -0
- package/node_modules/@jsquash/webp/encode.js +43 -0
- package/node_modules/@jsquash/webp/index.d.ts +3 -0
- package/node_modules/@jsquash/webp/index.js +2 -0
- package/node_modules/@jsquash/webp/meta.d.ts +19 -0
- package/node_modules/@jsquash/webp/meta.js +33 -0
- package/node_modules/@jsquash/webp/package.json +35 -0
- package/node_modules/@jsquash/webp/utils.d.ts +17 -0
- package/node_modules/@jsquash/webp/utils.js +31 -0
- package/node_modules/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
- package/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
- package/node_modules/node-pty/prebuilds/win32-x64/conpty.node +0 -0
- package/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
- package/node_modules/wasm-feature-detect/LICENSE +202 -0
- package/node_modules/wasm-feature-detect/README.md +109 -0
- package/node_modules/wasm-feature-detect/README.md.ejs +89 -0
- package/node_modules/wasm-feature-detect/dist/cjs/index.cjs +1 -0
- package/node_modules/wasm-feature-detect/dist/esm/index.js +1 -0
- package/node_modules/wasm-feature-detect/dist/index.d.cts +24 -0
- package/node_modules/wasm-feature-detect/dist/index.d.ts +24 -0
- package/node_modules/wasm-feature-detect/dist/umd/index.js +1 -0
- package/node_modules/wasm-feature-detect/package.json +53 -0
- package/package.json +4 -1
- package/src/manager/config.mjs +1 -0
- package/src/manager/folder-browser.mjs +31 -2
- package/src/manager/main.mjs +2 -0
- package/src/manager/server.mjs +19 -5
- package/src/manager/steam-workshop.mjs +64 -0
- package/src/manager/yard-piece-check.mjs +424 -0
- package/src/manager/yard-pieces.mjs +188 -0
- package/web/app.js +55 -17
- package/web/index.html +14 -1
- package/web/skins/guild/skin.css +2 -13
- package/web/styles.css +14 -0
- package/web/yard/art.json +1 -1
- package/web/yard/model.mjs +44 -2
- package/web/yard/renderer.js +136 -27
- package/web/yard/view.js +123 -11
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { readManifest } from './yard-pieces.mjs';
|
|
4
5
|
|
|
5
6
|
const failure = (status, code, message) => Object.assign(new Error(message), { status, code });
|
|
7
|
+
const PIECE_SCAN = 500;
|
|
6
8
|
const byName = (a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base', numeric: true }) || (a.name < b.name ? -1 : 1);
|
|
7
9
|
|
|
8
10
|
export function folderSegments(dir, paths = path) {
|
|
@@ -30,7 +32,7 @@ export function folderNameProblem(name, platform = process.platform) {
|
|
|
30
32
|
return null;
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
export function createFolderBrowser({ platform = process.platform, home = os.homedir(), limit = 2000, driveTimeoutMs = 500, fsp = fs.promises } = {}) {
|
|
35
|
+
export function createFolderBrowser({ platform = process.platform, home = os.homedir(), limit = 2000, driveTimeoutMs = 500, fsp = fs.promises, workshop = null } = {}) {
|
|
34
36
|
const paths = platform === 'win32' ? path.win32 : path.posix;
|
|
35
37
|
|
|
36
38
|
async function roots() {
|
|
@@ -79,6 +81,31 @@ export function createFolderBrowser({ platform = process.platform, home = os.hom
|
|
|
79
81
|
.map((entry) => ({ name: entry.name, path: paths.join(dir, entry.name) })).sort(byName);
|
|
80
82
|
}
|
|
81
83
|
|
|
84
|
+
async function pieceIn(dir, kind) {
|
|
85
|
+
const file = paths.join(dir, 'piece.json');
|
|
86
|
+
try {
|
|
87
|
+
const stat = await fsp.lstat(file);
|
|
88
|
+
if (!stat.isFile() || stat.size > 16 * 1024) return null;
|
|
89
|
+
const bytes = await fsp.readFile(file), folder = paths.basename(dir);
|
|
90
|
+
try {
|
|
91
|
+
const { title } = readManifest(bytes, kind);
|
|
92
|
+
return { title: title || folder, folder, path: dir };
|
|
93
|
+
} catch (err) {
|
|
94
|
+
const meta = err.newer ? JSON.parse(bytes.toString('utf8')) : null;
|
|
95
|
+
if (meta?.kind !== kind) return null;
|
|
96
|
+
const title = typeof meta.title === 'string' && meta.title.length <= 128 && !/\p{Cc}/u.test(meta.title) ? meta.title.trim() : '';
|
|
97
|
+
return { title: title || folder, folder, path: dir, problem: err.message };
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function piecesIn(dir, folders, kind) {
|
|
105
|
+
const found = await Promise.all([dir, ...folders.slice(0, PIECE_SCAN).map((entry) => entry.path)].map((folder) => pieceIn(folder, kind)));
|
|
106
|
+
return found.filter(Boolean).sort((a, b) => a.title.localeCompare(b.title, undefined, { sensitivity: 'base', numeric: true }) || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
107
|
+
}
|
|
108
|
+
|
|
82
109
|
const browser = {
|
|
83
110
|
async create(rawParent, rawName) {
|
|
84
111
|
if (typeof rawParent !== 'string' || !rawParent.trim() || rawParent.includes('\0')) throw failure(400, 'bad_path', 'The folder must be a path.');
|
|
@@ -103,7 +130,7 @@ export function createFolderBrowser({ platform = process.platform, home = os.hom
|
|
|
103
130
|
}
|
|
104
131
|
return browser.list(dir);
|
|
105
132
|
},
|
|
106
|
-
async list(raw, { extension = null } = {}) {
|
|
133
|
+
async list(raw, { extension = null, pieces = null } = {}) {
|
|
107
134
|
if (raw !== undefined && raw !== null && (typeof raw !== 'string' || raw.includes('\0'))) {
|
|
108
135
|
throw failure(400, 'bad_path', 'The folder must be a path.');
|
|
109
136
|
}
|
|
@@ -113,6 +140,7 @@ export function createFolderBrowser({ platform = process.platform, home = os.hom
|
|
|
113
140
|
const dir = await nearestFolder(wanted);
|
|
114
141
|
const [folders, drives, files] = await Promise.all([entries(dir), roots(), extension ? filesIn(dir, extension) : null]);
|
|
115
142
|
const parent = paths.dirname(dir);
|
|
143
|
+
const [found, steam] = pieces ? await Promise.all([piecesIn(dir, folders, pieces), workshop ? workshop() : null]) : [null, null];
|
|
116
144
|
return {
|
|
117
145
|
path: dir,
|
|
118
146
|
parent: parent === dir ? null : parent,
|
|
@@ -123,6 +151,7 @@ export function createFolderBrowser({ platform = process.platform, home = os.hom
|
|
|
123
151
|
truncated: folders.length > limit,
|
|
124
152
|
note: dir === wanted ? null : wanted,
|
|
125
153
|
...(files ? { files: files.slice(0, limit) } : {}),
|
|
154
|
+
...(found ? { pieces: found, workshop: steam } : {}),
|
|
126
155
|
};
|
|
127
156
|
},
|
|
128
157
|
};
|
package/src/manager/main.mjs
CHANGED
|
@@ -18,6 +18,7 @@ import { GitHub } from './github.mjs';
|
|
|
18
18
|
import { createManagerServer } from './server.mjs';
|
|
19
19
|
import { YardArt } from './yard-art.mjs';
|
|
20
20
|
import { YardWorlds } from './yard-worlds.mjs';
|
|
21
|
+
import { YardPieces } from './yard-pieces.mjs';
|
|
21
22
|
import { loadPty } from './pty.mjs';
|
|
22
23
|
import { createNotesStore } from './notes.mjs';
|
|
23
24
|
import { createCardNames } from './card-names.mjs';
|
|
@@ -188,6 +189,7 @@ export async function startManager({ port = resolvePort(), host = DEFAULT_HOST,
|
|
|
188
189
|
launcher: launcherPath(),
|
|
189
190
|
yardArt: new YardArt({ webDir, dir: paths.yardArt }),
|
|
190
191
|
yardWorlds: new YardWorlds({ dir: paths.yardWorlds }),
|
|
192
|
+
yardPieces: new YardPieces({ dir: paths.yardPieces }),
|
|
191
193
|
mcpConnect,
|
|
192
194
|
onShutdownRequest: ({ restart = false } = {}) => shutdown('requested via API', { restart }).then((code) => process.exit(code)),
|
|
193
195
|
});
|
package/src/manager/server.mjs
CHANGED
|
@@ -20,6 +20,8 @@ import { MODES as AUTOSTART_MODES } from './autostart.mjs';
|
|
|
20
20
|
import { ptyRestartProblem } from './pty.mjs';
|
|
21
21
|
import { YardArt } from './yard-art.mjs';
|
|
22
22
|
import { YardWorlds } from './yard-worlds.mjs';
|
|
23
|
+
import { YardPieces } from './yard-pieces.mjs';
|
|
24
|
+
import { createWorkshopFinder } from './steam-workshop.mjs';
|
|
23
25
|
import { spokenText } from './spoken-text.mjs';
|
|
24
26
|
import { MCP_ACTIONS } from './mcp-connect.mjs';
|
|
25
27
|
|
|
@@ -70,8 +72,8 @@ const SECURITY_HEADERS = {
|
|
|
70
72
|
'Referrer-Policy': 'no-referrer',
|
|
71
73
|
'Cross-Origin-Resource-Policy': 'same-origin',
|
|
72
74
|
'Content-Security-Policy':
|
|
73
|
-
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://i.ytimg.com; " +
|
|
74
|
-
"connect-src 'self' ws://127.0.0.1:* ws://localhost:*; frame-src https://www.youtube-nocookie.com; " +
|
|
75
|
+
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://i.ytimg.com; " +
|
|
76
|
+
"connect-src 'self' blob: ws://127.0.0.1:* ws://localhost:*; frame-src https://www.youtube-nocookie.com; " +
|
|
75
77
|
"frame-ancestors vscode-webview: vscode-file:; base-uri 'none'; form-action 'none'",
|
|
76
78
|
};
|
|
77
79
|
|
|
@@ -177,7 +179,7 @@ export function createManagerServer({
|
|
|
177
179
|
notes = createNotesStore(),
|
|
178
180
|
deviceSignIn = new DeviceSignIn({ token }),
|
|
179
181
|
folderOpener = createFolderOpener({ resolveCwd: (cwd) => manager.resolveCwd(cwd) }),
|
|
180
|
-
folderBrowser = createFolderBrowser(),
|
|
182
|
+
folderBrowser = createFolderBrowser({ workshop: createWorkshopFinder() }),
|
|
181
183
|
/** The double-click launcher file for this platform, or null when the package carries none. */
|
|
182
184
|
launcher = null,
|
|
183
185
|
/** @type {(opts: { restart: boolean }) => void} */
|
|
@@ -187,6 +189,7 @@ export function createManagerServer({
|
|
|
187
189
|
/** The Yard's art; without a data folder, only a checkout's. */
|
|
188
190
|
yardArt = new YardArt({ webDir }),
|
|
189
191
|
yardWorlds = new YardWorlds(),
|
|
192
|
+
yardPieces = new YardPieces(),
|
|
190
193
|
mcpConnect = null,
|
|
191
194
|
heartbeatMs = 30000,
|
|
192
195
|
}) {
|
|
@@ -300,6 +303,12 @@ export function createManagerServer({
|
|
|
300
303
|
if (!world) throw new HttpError(404, 'not found', 'not_found');
|
|
301
304
|
return serveFile(req, res, world);
|
|
302
305
|
}
|
|
306
|
+
if (rel.startsWith('yard/pieces/')) {
|
|
307
|
+
const [skin, slot, name, ...rest] = rel.slice('yard/pieces/'.length).split('/');
|
|
308
|
+
const piece = rest.length ? null : yardPieces.file(skin, slot, name);
|
|
309
|
+
if (!piece) throw new HttpError(404, 'not found', 'not_found');
|
|
310
|
+
return serveFile(req, res, piece);
|
|
311
|
+
}
|
|
303
312
|
let root = webDir;
|
|
304
313
|
if (rel.startsWith('yard/assets/')) {
|
|
305
314
|
root = yardArt.assetsDir();
|
|
@@ -420,6 +429,10 @@ export function createManagerServer({
|
|
|
420
429
|
const yardWorld = route.match(/^\/yard-worlds\/([a-z]+)$/);
|
|
421
430
|
if (yardWorld && method === 'PUT') return sendJson(res, 200, { yardWorlds: await yardWorlds.replace(yardWorld[1], (await readJsonBody(req)).path) });
|
|
422
431
|
if (yardWorld && method === 'DELETE') return sendJson(res, 200, { yardWorlds: await yardWorlds.restore(yardWorld[1]) });
|
|
432
|
+
if (route === '/yard-pieces' && method === 'GET') return sendJson(res, 200, { yardPieces: yardPieces.snapshot() });
|
|
433
|
+
const yardPiece = route.match(/^\/yard-pieces\/([a-z]+)\/([a-z]+)$/);
|
|
434
|
+
if (yardPiece && method === 'PUT') return sendJson(res, 200, { yardPieces: await yardPieces.replace(yardPiece[1], yardPiece[2], (await readJsonBody(req)).path) });
|
|
435
|
+
if (yardPiece && method === 'DELETE') return sendJson(res, 200, { yardPieces: await yardPieces.restore(yardPiece[1], yardPiece[2]) });
|
|
423
436
|
if (route === '/yard/fireworks' && method === 'POST') {
|
|
424
437
|
broadcast({ type: 'yard.fireworks' });
|
|
425
438
|
return sendJson(res, 200, { ok: true, pages: [...eventClients].filter((ws) => !ws.revoked && ws.readyState === ws.OPEN).length });
|
|
@@ -447,7 +460,7 @@ export function createManagerServer({
|
|
|
447
460
|
return sendJson(res, 200, { ok: true });
|
|
448
461
|
}
|
|
449
462
|
if (route === '/folders' && method === 'GET') {
|
|
450
|
-
return sendJson(res, 200, await folderBrowser.list(url.searchParams.get('path') ?? undefined, { extension: url.searchParams.get('files') === 'agworld' ? '.agworld' : null }));
|
|
463
|
+
return sendJson(res, 200, await folderBrowser.list(url.searchParams.get('path') ?? undefined, { extension: url.searchParams.get('files') === 'agworld' ? '.agworld' : null, pieces: ['character', 'companion'].includes(url.searchParams.get('pieces')) ? url.searchParams.get('pieces') : null }));
|
|
451
464
|
}
|
|
452
465
|
if (route === '/folders' && method === 'POST') {
|
|
453
466
|
const { path: parent, name } = await readJsonBody(req);
|
|
@@ -784,10 +797,11 @@ export function createManagerServer({
|
|
|
784
797
|
deviceSignIn.on('updated', () => broadcast({ type: 'sign-in.updated' }));
|
|
785
798
|
yardArt.on('updated', (status) => broadcast({ type: 'yard-art.updated', yardArt: status }));
|
|
786
799
|
yardWorlds.on('updated', (worlds) => broadcast({ type: 'yard-worlds.updated', yardWorlds: worlds }));
|
|
800
|
+
yardPieces.on('updated', (pieces) => broadcast({ type: 'yard-pieces.updated', yardPieces: pieces }));
|
|
787
801
|
|
|
788
802
|
function handleEvents(ws, req) {
|
|
789
803
|
eventClients.add(ws);
|
|
790
|
-
const hello = { type: 'hello', version, pid: process.pid, platform: process.platform, startedAt, launcher, folderOpener: folderOpenerFor(req), remoteAccess: remoteAccess ? { available: true } : null, upgrade: upgradeInfo(), yardArt: yardArt.snapshot(), yardWorlds: yardWorlds.snapshot(), sessions: manager.list(), heartbeat: true };
|
|
804
|
+
const hello = { type: 'hello', version, pid: process.pid, platform: process.platform, startedAt, launcher, folderOpener: folderOpenerFor(req), remoteAccess: remoteAccess ? { available: true } : null, upgrade: upgradeInfo(), yardArt: yardArt.snapshot(), yardWorlds: yardWorlds.snapshot(), yardPieces: yardPieces.snapshot(), sessions: manager.list(), heartbeat: true };
|
|
791
805
|
const notesHello = notes.helloRevision();
|
|
792
806
|
if (notesHello.known) hello.notesRevision = notesHello.revision;
|
|
793
807
|
else hello.notesUnreadable = true;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
export const WORLD_APP = '5462970';
|
|
7
|
+
|
|
8
|
+
export function parseVdf(text) {
|
|
9
|
+
const tokens = text.match(/"(?:[^"\\]|\\.)*"|[{}]/g) ?? [];
|
|
10
|
+
const root = {}, stack = [root];
|
|
11
|
+
let key = null;
|
|
12
|
+
for (const token of tokens) {
|
|
13
|
+
const top = stack[stack.length - 1];
|
|
14
|
+
if (token === '{') {
|
|
15
|
+
if (key === null) return root;
|
|
16
|
+
stack.push(top[key.toLowerCase()] = {});
|
|
17
|
+
key = null;
|
|
18
|
+
} else if (token === '}') {
|
|
19
|
+
if (stack.length > 1) stack.pop();
|
|
20
|
+
key = null;
|
|
21
|
+
} else {
|
|
22
|
+
const value = token.slice(1, -1).replace(/\\(.)/g, '$1');
|
|
23
|
+
if (key === null) key = value;
|
|
24
|
+
else { top[key.toLowerCase()] = value; key = null; }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return root;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function windowsSteam() {
|
|
31
|
+
return new Promise((resolve) => {
|
|
32
|
+
execFile('reg', ['query', 'HKCU\\Software\\Valve\\Steam', '/v', 'SteamPath'], { timeout: 5000, windowsHide: true }, (err, stdout) => {
|
|
33
|
+
resolve(err ? null : String(stdout).match(/SteamPath\s+REG_\w+\s+(.+)/)?.[1]?.trim() || null);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function steamRoots({ platform = process.platform, home = os.homedir(), registry = windowsSteam } = {}) {
|
|
39
|
+
if (platform === 'win32') return registry().then((root) => (root ? [root] : []));
|
|
40
|
+
if (platform === 'darwin') return Promise.resolve([path.join(home, 'Library', 'Application Support', 'Steam')]);
|
|
41
|
+
return Promise.resolve([path.join(home, '.local', 'share', 'Steam'), path.join(home, '.steam', 'steam'), path.join(home, '.var', 'app', 'com.valvesoftware.Steam', '.local', 'share', 'Steam')]);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function worldLibrary({ roots = steamRoots(), fsp = fs.promises } = {}) {
|
|
45
|
+
for (const root of await roots) {
|
|
46
|
+
let folders;
|
|
47
|
+
try { folders = parseVdf(await fsp.readFile(path.join(root, 'steamapps', 'libraryfolders.vdf'), 'utf8')).libraryfolders; } catch { continue; }
|
|
48
|
+
for (const entry of Object.values(folders ?? {})) {
|
|
49
|
+
if (typeof entry === 'object' && typeof entry.path === 'string' && entry.apps && Object.hasOwn(entry.apps, WORLD_APP)) return path.normalize(entry.path);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createWorkshopFinder({ library = () => worldLibrary(), fsp = fs.promises } = {}) {
|
|
56
|
+
let found = null;
|
|
57
|
+
return async () => {
|
|
58
|
+
found ??= library().catch(() => null);
|
|
59
|
+
const root = await found;
|
|
60
|
+
if (!root) { found = null; return null; }
|
|
61
|
+
const folder = path.join(root, 'steamapps', 'workshop', 'content', WORLD_APP);
|
|
62
|
+
return (await fsp.stat(folder).then((stat) => stat.isDirectory(), () => false)) ? folder : null;
|
|
63
|
+
};
|
|
64
|
+
}
|