This commit is contained in:
2026-08-08 18:00:34 +02:00
commit 41e307b5c1
28 changed files with 4728 additions and 0 deletions
+118
View File
@@ -0,0 +1,118 @@
// About me: Writes the missing Steam libraryfolders.vdf into a Wine prefix from
// the Linux side, before Vortex is ever started. Vortex core scans Steam at
// startup and reads C:\Program Files (x86)\Steam\config\libraryfolders.vdf; when
// SteamTinkerLaunch has not created it, the unhandled ENOENT shows up as
// "unrecoverable error" and Vortex dies. Running this once repairs the prefix.
//
// Usage: node scripts/repair-prefix.mjs [--prefix <pfx>] [--force] [--dry-run]
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync } from 'node:fs';
import { homedir } from 'node:os';
import { resolve } from 'node:path';
import { findSteamLibraries, findSteamRoots } from '../src/discovery.ts';
import {
PREFIX_STEAM_DIR,
buildShimVdf,
dedupeByCanonicalPath,
ensureSteamShim,
} from '../src/steamShim.ts';
const DEFAULT_PREFIX = resolve(homedir(), '.config/steamtinkerlaunch/vortex/compatdata/pfx');
function parseArgs(argv) {
const args = { prefix: DEFAULT_PREFIX, force: false, dryRun: false };
for (let i = 0; i < argv.length; i += 1) {
if (argv[i] === '--prefix') {
args.prefix = resolve(argv[i + 1] ?? '');
i += 1;
} else if (argv[i] === '--force') {
args.force = true;
} else if (argv[i] === '--dry-run') {
args.dryRun = true;
} else {
throw new Error(`unknown argument: ${argv[i]}`);
}
}
return args;
}
/**
* Resolves the paths Vortex would see inside the prefix to real Linux paths:
* Z: is the Linux root, C: is the prefix's drive_c.
*/
function toRealPath(prefix, winPath) {
const normalised = winPath.replace(/\\/g, '/');
if (/^[zZ]:/.test(normalised)) {
return normalised.slice(2) || '/';
}
if (/^[cC]:/.test(normalised)) {
return resolve(prefix, 'drive_c', normalised.slice(3));
}
return normalised;
}
function prefixFileAccess(prefix) {
return {
exists: (p) => existsSync(toRealPath(prefix, p)),
readFile: (p) => readFileSync(toRealPath(prefix, p), 'utf8'),
readDir: (p) => {
try {
return readdirSync(toRealPath(prefix, p));
} catch {
return [];
}
},
};
}
const args = parseArgs(process.argv.slice(2));
if (!existsSync(args.prefix)) {
console.error(`prefix not found: ${args.prefix}`);
process.exit(1);
}
const files = prefixFileAccess(args.prefix);
// Only the Wine-side (Z:) view is useful here: the shim has to contain paths
// Vortex can open from inside the prefix.
const libraries = dedupeByCanonicalPath(
[
...new Set(
findSteamRoots(files)
.filter((root) => root.startsWith('Z:'))
.flatMap((root) => findSteamLibraries(files, root)),
),
],
(candidate) => realpathSync(toRealPath(args.prefix, candidate)),
);
console.log(`prefix: ${args.prefix}`);
console.log(`steam dir: ${toRealPath(args.prefix, PREFIX_STEAM_DIR)}`);
console.log(`libraries: ${libraries.length === 0 ? '(none found)' : ''}`);
for (const library of libraries) {
console.log(` ${library}`);
}
if (args.dryRun) {
console.log('--- shim that would be written ---');
console.log(buildShimVdf(libraries));
process.exit(0);
}
const writer = {
makeDir: (p) => mkdirSync(toRealPath(args.prefix, p), { recursive: true }),
writeFile: (p, contents) => writeFileSync(toRealPath(args.prefix, p), contents, 'utf8'),
};
const result = ensureSteamShim(files, writer, libraries, { overwrite: args.force });
console.log(`result: ${result.action} -> ${toRealPath(args.prefix, result.path)}`);
if (result.action === 'skipped') {
console.error(
libraries.length === 0
? 'no Steam libraries found under /home; pass --prefix for the right prefix or check your Steam install'
: `no Steam directory at ${toRealPath(args.prefix, PREFIX_STEAM_DIR)}; is this the Vortex prefix?`,
);
process.exit(1);
}