fix double drive_c issues
This commit is contained in:
parent
3e054dd9de
commit
f2145d6678
7 changed files with 73 additions and 33 deletions
|
|
@ -45,7 +45,7 @@
|
|||
"import.prefix.title": "Import from prefix",
|
||||
"import.prefix.hint": "Pick the wine prefix the app lives in. Nothing is copied, the app is launched from the prefix, so Windows paths keep working (C:\\...).",
|
||||
"import.pickPrefixFolder": "Choose the app folder inside the prefix",
|
||||
"import.prefix.outside": "the selected folder is outside the prefix's drive_c",
|
||||
"import.prefix.outside": "the selected folder is outside the prefix's C: drive (drive_c or pfx/drive_c)",
|
||||
"import.prefix.noDriveC": "this prefix has no drive_c yet, run any app in it so wine creates one",
|
||||
"import.pickFolder": "Choose the app folder",
|
||||
"import.progress": "Importing {0}…",
|
||||
|
|
@ -59,6 +59,7 @@
|
|||
"import.done": "Import finished",
|
||||
"import.failed": "Import failed: {0}",
|
||||
"import.noExe": "no .exe file found in the selected folder",
|
||||
"import.prefixRoot": "this folder is a wine prefix (or its C: drive) - import an app from inside its drive_c instead",
|
||||
"config.title": "App configuration",
|
||||
"config.icon.auto": "Extract from exe",
|
||||
"config.icon.browse": "Choose image",
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@
|
|||
"import.prefix.title": "Import z prefiksu",
|
||||
"import.prefix.hint": "Wybierz prefiks wine, w którym jest aplikacja. Nic nie jest kopiowane, aplikacja odpalana jest z prefiksu, więc ścieżki windowsowe (C:\\...) dalej działają.",
|
||||
"import.pickPrefixFolder": "Wybierz folder aplikacji wewnątrz prefiksu",
|
||||
"import.prefix.outside": "wybrany folder jest poza drive_c prefiksu",
|
||||
"import.prefix.outside": "wybrany folder jest poza dyskiem C: prefiksu (drive_c lub pfx/drive_c)",
|
||||
"import.prefix.noDriveC": "ten prefiks nie ma jeszcze drive_c, uruchom w nim dowolną apkę, żeby wine go utworzył",
|
||||
"import.pickFolder": "Wybierz folder aplikacji",
|
||||
"import.progress": "Importowanie {0}…",
|
||||
|
|
@ -59,6 +59,7 @@
|
|||
"import.done": "Import zakończony",
|
||||
"import.failed": "Import nie powiódł się: {0}",
|
||||
"import.noExe": "nie znaleziono pliku .exe w wybranym folderze",
|
||||
"import.prefixRoot": "ten folder to prefiks wine (lub jego dysk C:) - zaimportuj aplikację z wnętrza jego drive_c",
|
||||
"config.title": "Konfiguracja aplikacji",
|
||||
"config.icon.auto": "Wyciągnij z exe",
|
||||
"config.icon.browse": "Wybierz obrazek",
|
||||
|
|
|
|||
|
|
@ -783,7 +783,10 @@ async function doImport(srcDir, mode) {
|
|||
closeModal()
|
||||
if (!game || game.ok === false) {
|
||||
const raw = (game && game.error) || ''
|
||||
const errText = raw === 'no-exe-found' ? t('import.noExe') : (raw || 'unknown error')
|
||||
const errText =
|
||||
raw === 'no-exe-found' ? t('import.noExe')
|
||||
: raw === 'prefix-root' ? t('import.prefixRoot')
|
||||
: (raw || 'unknown error')
|
||||
toast(t('import.failed', errText), 'error')
|
||||
return
|
||||
}
|
||||
|
|
@ -923,7 +926,8 @@ async function doPrefixImport(prefix, srcDir) {
|
|||
const errText =
|
||||
raw === 'no-exe-found' ? t('import.noExe')
|
||||
: raw === 'no-drive-c' ? t('import.prefix.noDriveC')
|
||||
: t('import.prefix.outside')
|
||||
: raw === 'outside-drive-c' ? t('import.prefix.outside')
|
||||
: (raw || 'unknown error')
|
||||
toast(t('import.failed', errText), 'error')
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ const path = require('path')
|
|||
const { DIRS, resolvePortablePath } = require('./paths')
|
||||
const { newGame, newGameId, addGame } = require('./library')
|
||||
const { extractIconFromExe } = require('./icons')
|
||||
const { listPrefixes, prefixDriveRoot } = require('./prefixes')
|
||||
const { listPrefixes, prefixDriveRoots, isPrefixRoot } = require('./prefixes')
|
||||
const { rewireSymlinks } = require('./symlinks')
|
||||
const { copyTree, treeSize } = require('./fsutil')
|
||||
|
||||
|
|
@ -22,6 +22,9 @@ function moveFolder(src, dst, onProgress) {
|
|||
}
|
||||
|
||||
async function importGame(srcDir, mode, onProgress) {
|
||||
// a prefix (or its drive_c / pfx) is not an app folder: copy/move would take
|
||||
// the prefix's C: drive with it and leave the prefix gutted
|
||||
if (isPrefixRoot(srcDir)) return { ok: false, error: 'prefix-root' }
|
||||
const name = path.basename(srcDir)
|
||||
// Validate before touching the user's files: refuse an import without any
|
||||
// .exe (and don't half-move a folder we'd then have to roll back).
|
||||
|
|
@ -71,17 +74,23 @@ async function importGame(srcDir, mode, onProgress) {
|
|||
// normally into C:\...
|
||||
async function importGameFromPrefix(prefix, srcDir, onProgress) {
|
||||
if (!listPrefixes().prefixes.some(p => p.name === prefix)) return { ok: false, error: 'prefix-not-found' }
|
||||
// the folder must live inside the prefix's C: drive so the C:\ mapping
|
||||
// holds - drive_c sits at the prefix top for plain wine, under pfx/ for
|
||||
// Proton-managed prefixes
|
||||
const driveRoot = prefixDriveRoot(prefix)
|
||||
if (!driveRoot) return { ok: false, error: 'no-drive-c' }
|
||||
const rel = path.relative(driveRoot, srcDir)
|
||||
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) return { ok: false, error: 'outside-drive-c' }
|
||||
const preExe = await findDefaultExe(srcDir)
|
||||
// The folder must live inside one of the prefix's C: drives so the C:\
|
||||
// mapping holds - drive_c sits at the prefix top for plain wine, under pfx/
|
||||
// for Proton-managed prefixes, and a prefix used with BOTH kinds has both.
|
||||
// Every existing drive_c counts; the stored path keeps its drive_c segment
|
||||
// either way, so it resolves in the layout the app will run in.
|
||||
const driveRoots = prefixDriveRoots(prefix)
|
||||
if (!driveRoots.length) return { ok: false, error: 'no-drive-c' }
|
||||
const src = path.resolve(String(srcDir))
|
||||
const insideDrive = driveRoots.some(root => {
|
||||
const rel = path.relative(root, src)
|
||||
return rel && !rel.startsWith('..') && !path.isAbsolute(rel)
|
||||
})
|
||||
if (!insideDrive) return { ok: false, error: 'outside-drive-c' }
|
||||
const preExe = await findDefaultExe(src)
|
||||
if (!preExe) return { ok: false, error: 'no-exe-found' }
|
||||
const id = newGameId()
|
||||
const game = newGame({ id, name: path.basename(srcDir), exePath: preExe, workdir: srcDir, mode: 'prefix' })
|
||||
const game = newGame({ id, name: path.basename(src), exePath: preExe, workdir: src, mode: 'prefix' })
|
||||
game.importSource = 'prefix'
|
||||
game.prefix = prefix
|
||||
// exe picked automatically -> grab its icon right away; a missing icon is
|
||||
|
|
|
|||
|
|
@ -260,11 +260,14 @@ function registerIpc({ getWindow, onGamesChanged }) {
|
|||
// shows up for wine as a regular C:\ install
|
||||
ipcMain.handle('games:pickPrefixFolder', async (e, prefix) => {
|
||||
// open the picker straight in the prefix's C: drive (plain wine: top
|
||||
// level, Proton: under pfx/)
|
||||
const driveC = prefixes.prefixDriveRoot(prefix)
|
||||
// level, Proton: under pfx/); a prefix used with BOTH kinds has two of
|
||||
// them, so it opens at the prefix folder - the only spot that keeps
|
||||
// drive_c and pfx/drive_c one click away
|
||||
const driveRoots = prefixes.prefixDriveRoots(prefix)
|
||||
const start = driveRoots.length === 1 ? driveRoots[0] : prefixes.prefixPath(prefix)
|
||||
const res = await dialog.showOpenDialog(getWindow(), {
|
||||
title: i18n.translate(library.loadSettings().language, 'import.pickPrefixFolder'),
|
||||
defaultPath: driveC || prefixes.prefixPath(prefix),
|
||||
defaultPath: start,
|
||||
properties: ['openDirectory']
|
||||
})
|
||||
return res.canceled || !res.filePaths[0] ? null : res.filePaths[0]
|
||||
|
|
|
|||
|
|
@ -41,22 +41,39 @@ function prefixExists(name) {
|
|||
}
|
||||
}
|
||||
|
||||
// Where the Windows C: drive of a prefix actually lives: plain wine keeps
|
||||
// it at the top (WINEPREFIX=<prefix>), Proton-managed prefixes nest it
|
||||
// under pfx/ (WINEPREFIX=<prefix>/pfx). system.reg marks the real prefix
|
||||
// root; a fresh prefix may not have it yet, so fall back to drive_c itself.
|
||||
function prefixDriveRoot(name) {
|
||||
// Every Windows C: drive a prefix has: plain wine keeps one at the top
|
||||
// (WINEPREFIX=<prefix>), Proton-managed prefixes nest one under pfx/
|
||||
// (WINEPREFIX=<prefix>/pfx). A prefix used with BOTH wine kinds has both,
|
||||
// and an import may come from either one - so callers that can accept
|
||||
// several get them all, top level first.
|
||||
function prefixDriveRoots(name) {
|
||||
const root = prefixPath(name)
|
||||
for (const pass of [true, false]) {
|
||||
for (const base of [root, path.join(root, 'pfx')]) {
|
||||
try {
|
||||
const driveC = path.join(base, 'drive_c')
|
||||
if (!fs.statSync(driveC).isDirectory()) continue
|
||||
if (!pass || fs.existsSync(path.join(base, 'system.reg'))) return driveC
|
||||
} catch {}
|
||||
}
|
||||
const found = []
|
||||
for (const base of [root, path.join(root, 'pfx')]) {
|
||||
try {
|
||||
const driveC = path.join(base, 'drive_c')
|
||||
if (fs.statSync(driveC).isDirectory()) found.push(driveC)
|
||||
} catch {}
|
||||
}
|
||||
return null
|
||||
return found
|
||||
}
|
||||
|
||||
// True when a path IS one of a prefix's own roots: the prefix folder, its
|
||||
// pfx/ or either drive_c. A copy/move import of such a folder would move the
|
||||
// prefix's C: drive (or the whole prefix) into files/apps, gutting it - the
|
||||
// "import from a prefix" flow is the one that works in place. Both drive
|
||||
// layouts are checked literally, not through prefixDriveRoots(): a prefix used
|
||||
// with wine AND Proton has a drive_c each and BOTH are off limits.
|
||||
function isPrefixRoot(p) {
|
||||
if (!p) return false
|
||||
const abs = path.resolve(String(p))
|
||||
if (path.resolve(DIRS.PREFIXES) === abs) return true
|
||||
for (const entry of listPrefixes().prefixes) {
|
||||
const root = prefixPath(entry.name)
|
||||
if (abs === root || abs === path.join(root, 'pfx')) return true
|
||||
if (abs === path.join(root, 'drive_c') || abs === path.join(root, 'pfx', 'drive_c')) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function createPrefix(name) {
|
||||
|
|
@ -136,4 +153,4 @@ function setDefaultPrefix(name) {
|
|||
return { ok: true }
|
||||
}
|
||||
|
||||
module.exports = { listPrefixes, savePrefixes, createPrefix, deletePrefix, renamePrefix, setDefaultPrefix, prefixPath, prefixExists, prefixDriveRoot, healPrefix, MAIN }
|
||||
module.exports = { listPrefixes, savePrefixes, createPrefix, deletePrefix, renamePrefix, setDefaultPrefix, prefixPath, prefixExists, prefixDriveRoots, isPrefixRoot, healPrefix, MAIN }
|
||||
|
|
|
|||
|
|
@ -95,8 +95,13 @@ function buildEnv({ game, wine }) {
|
|||
env.PROTON_LOG_DIR = path.join(DIRS.DATA, 'logs')
|
||||
fs.mkdirSync(env.PROTON_LOG_DIR, { recursive: true })
|
||||
linkPrefix(env.STEAM_COMPAT_DATA_PATH, prefixDir)
|
||||
// No silent fallback to a real directory: Proton would then set its prefix
|
||||
// up under files/data/compat/<id>/pfx, outside files/wineprefixes/ where
|
||||
// PLWL tracks it - rename, delete, the in-use guards and the LAN sync all
|
||||
// look at the launcher prefix, so its C: drive would be orphaned. Only a
|
||||
// missing path is refused; a populated one is linkPrefix's deliberate case.
|
||||
if (!fs.existsSync(env.STEAM_COMPAT_DATA_PATH)) {
|
||||
fs.mkdirSync(env.STEAM_COMPAT_DATA_PATH, { recursive: true })
|
||||
throw new Error('prefix-link-failed')
|
||||
}
|
||||
} else {
|
||||
// plain wine (system or imported build)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue