// 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 ] [--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); }