diff options
Diffstat (limited to 'libs')
| -rw-r--r-- | libs/game_bethesda/src/gamebryo/gamebryolocalsavegames.cpp | 21 | ||||
| -rw-r--r-- | libs/game_bethesda/src/gamebryo/gamegamebryo.cpp | 21 | ||||
| -rw-r--r-- | libs/nak/src/installers/prefix_setup.rs | 20 | ||||
| -rw-r--r-- | libs/nak/src/runtime_wrap.rs | 39 | ||||
| -rw-r--r-- | libs/uibase/include/uibase/registry.h | 6 | ||||
| -rw-r--r-- | libs/uibase/src/registry.cpp | 81 | ||||
| -rw-r--r-- | libs/uibase/src/utility.cpp | 41 |
7 files changed, 148 insertions, 81 deletions
diff --git a/libs/game_bethesda/src/gamebryo/gamebryolocalsavegames.cpp b/libs/game_bethesda/src/gamebryo/gamebryolocalsavegames.cpp index 4c1199c..2daf2c3 100644 --- a/libs/game_bethesda/src/gamebryo/gamebryolocalsavegames.cpp +++ b/libs/game_bethesda/src/gamebryo/gamebryolocalsavegames.cpp @@ -23,21 +23,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #include <stddef.h> #include <string> -#include <QSettings> - #include "gamegamebryo.h" -// Helper to remove a key from a Bethesda-style INI file -static void removeIniValue(const QString& iniFile, const QString& section, - const QString& key) -{ - QSettings settings(iniFile, QSettings::IniFormat); - settings.beginGroup(section); - settings.remove(key); - settings.endGroup(); - settings.sync(); -} - GamebryoLocalSavegames::GamebryoLocalSavegames(const GameGamebryo* game, const QString& iniFileName) : m_Game{game}, m_IniFileName(iniFileName) @@ -119,20 +106,20 @@ bool GamebryoLocalSavegames::prepareProfile(MOBase::IProfile* profile) if (savedPath != "DELETE_ME") { MOBase::WriteRegistryValue("General", "sLocalSavePath", savedPath, iniFilePath); } else { - removeIniValue(iniFilePath, "General", "sLocalSavePath"); + MOBase::RemoveRegistryValue("General", "sLocalSavePath", iniFilePath); } if (savedMyGames != "DELETE_ME") { MOBase::WriteRegistryValue("General", "bUseMyGamesDirectory", savedMyGames, iniFilePath); } else { - removeIniValue(iniFilePath, "General", "bUseMyGamesDirectory"); + MOBase::RemoveRegistryValue("General", "bUseMyGamesDirectory", iniFilePath); } QFile::remove(saveIni); } // Otherwise just delete the setting else { - removeIniValue(iniFilePath, "General", "sLocalSavePath"); - removeIniValue(iniFilePath, "General", "bUseMyGamesDirectory"); + MOBase::RemoveRegistryValue("General", "sLocalSavePath", iniFilePath); + MOBase::RemoveRegistryValue("General", "bUseMyGamesDirectory", iniFilePath); } } diff --git a/libs/game_bethesda/src/gamebryo/gamegamebryo.cpp b/libs/game_bethesda/src/gamebryo/gamegamebryo.cpp index 4a92440..2fd45cd 100644 --- a/libs/game_bethesda/src/gamebryo/gamegamebryo.cpp +++ b/libs/game_bethesda/src/gamebryo/gamegamebryo.cpp @@ -357,6 +357,15 @@ void GameGamebryo::ensureIniFilesExist(const QString& basePath) } #ifndef _WIN32 + // Remove broken symlinks — QFileInfo::exists() follows symlinks and + // returns false for dangling ones, but QFile::copy() fails with + // "File exists" because the symlink inode is still there. + if (targetInfo.isSymLink() && !targetInfo.exists()) { + QFile::remove(targetPath); + } +#endif + +#ifndef _WIN32 // On Linux, search for a differently-cased version of this INI file // (e.g., FalloutPrefs.ini when we expect falloutprefs.ini). // We can't use resolveFileCaseInsensitive here because the exact-case @@ -413,15 +422,21 @@ void GameGamebryo::ensureIniFilesExist(const QString& basePath) if (targetInfo.exists()) { QFile::remove(targetPath); } - if (QFile::copy(defaultIniPath, targetPath)) { + QFile srcFile(defaultIniPath); + if (srcFile.copy(targetPath)) { // Make the copy writable QFile::setPermissions( targetPath, QFile::permissions(targetPath) | QFile::WriteUser | QFile::WriteOwner); MOBase::log::info("Seeded '{}' from default INI '{}'", iniFile, defaultIniPath); } else { - MOBase::log::warn("Failed to copy default INI '{}' -> '{}'", - defaultIniPath, targetPath); + MOBase::log::warn("Failed to copy default INI '{}' -> '{}': {} " + "(srcExists={}, srcSize={}, targetExists={}, dirExists={})", + defaultIniPath, targetPath, srcFile.errorString(), + QFileInfo::exists(defaultIniPath), + QFileInfo(defaultIniPath).size(), + QFileInfo::exists(targetPath), + QFileInfo(targetPath).dir().exists()); } } diff --git a/libs/nak/src/installers/prefix_setup.rs b/libs/nak/src/installers/prefix_setup.rs index 4f27606..468aaa2 100644 --- a/libs/nak/src/installers/prefix_setup.rs +++ b/libs/nak/src/installers/prefix_setup.rs @@ -297,23 +297,9 @@ fn initialize_prefix_with_proton( ("WINEDLLOVERRIDES", "msdia80.dll=n;conhost.exe=d;cmd.exe=d".to_string()), ]; - let (exe, args): (std::path::PathBuf, Vec<&str>) = if runtime_wrap::use_umu_for_prefix() { - if let Some(umu_run) = runtime_wrap::resolve_umu_run() { - log_install(&format!("Initializing prefix with umu-run: {:?}", umu_run)); - envs.push(("PROTONPATH", proton.path.display().to_string())); - envs.push(("WINEPREFIX", prefix_root.display().to_string())); - envs.push(("GAMEID", app_id.to_string())); - (umu_run, vec!["wineboot", "-u"]) - } else { - log_warning( - "UMU prefix mode enabled but no umu-run was found; falling back to proton wrapper", - ); - (proton_script.clone(), vec!["run", "wineboot", "-u"]) - } - } else { - log_install(&format!("Initializing prefix with proton wrapper: {:?}", proton_script)); - (proton_script.clone(), vec!["run", "wineboot", "-u"]) - }; + log_install(&format!("Initializing prefix with proton wrapper: {:?}", proton_script)); + let (exe, args): (std::path::PathBuf, Vec<&str>) = + (proton_script.clone(), vec!["run", "wineboot", "-u"]); let mut cmd = runtime_wrap::build_command(&exe, &envs); cmd.args(&args); diff --git a/libs/nak/src/runtime_wrap.rs b/libs/nak/src/runtime_wrap.rs index 9b5ff89..69f693e 100644 --- a/libs/nak/src/runtime_wrap.rs +++ b/libs/nak/src/runtime_wrap.rs @@ -1,6 +1,5 @@ use std::env; use std::ffi::OsStr; -use std::path::{Path, PathBuf}; use std::process::Command; fn env_flag(name: &str) -> bool { @@ -18,35 +17,6 @@ pub fn use_steam_run() -> bool { env_flag("NAK_USE_STEAM_RUN") } -pub fn use_umu_for_prefix() -> bool { - env_flag("NAK_USE_UMU_FOR_PREFIX") -} - -pub fn prefer_system_umu() -> bool { - env_flag("NAK_PREFER_SYSTEM_UMU") -} - -fn find_in_path(binary: &str) -> Option<PathBuf> { - let path = env::var_os("PATH")?; - env::split_paths(&path) - .map(|entry| entry.join(binary)) - .find(|candidate| candidate.exists()) -} - -pub fn resolve_umu_run() -> Option<PathBuf> { - let bundled = env::var("NAK_BUNDLED_UMU_RUN") - .ok() - .map(PathBuf::from) - .filter(|p| p.exists()); - let system = find_in_path("umu-run"); - - if prefer_system_umu() { - system.or(bundled) - } else { - bundled.or(system) - } -} - /// Build a command to run `exe` with the given environment variables. /// /// In steam-run mode, the command is wrapped with `steam-run`. @@ -74,12 +44,3 @@ pub fn build_command<S: AsRef<OsStr>>( pub fn command_for(exe: impl AsRef<OsStr>) -> Command { build_command::<&str>(exe, &[]) } - -pub fn bundled_umu_path_from_appdir(appdir: &Path) -> Option<PathBuf> { - let path = appdir.join("umu-run"); - if path.exists() { - Some(path) - } else { - None - } -} diff --git a/libs/uibase/include/uibase/registry.h b/libs/uibase/include/uibase/registry.h index 00013df..6a93f9d 100644 --- a/libs/uibase/include/uibase/registry.h +++ b/libs/uibase/include/uibase/registry.h @@ -28,6 +28,12 @@ namespace MOBase QDLLEXPORT bool WriteRegistryValue(const QString& appName, const QString& keyName, const QString& value, const QString& fileName); +// Removes a key from a Bethesda-style INI file without QSettings. +// Uses a safe line-by-line approach that does not corrupt backslashes +// or URL-encode spaces in key names. +QDLLEXPORT bool RemoveRegistryValue(const QString& section, const QString& key, + const QString& fileName); + #ifdef _WIN32 // Windows-specific overload using wide strings QDLLEXPORT bool WriteRegistryValue(const wchar_t* appName, const wchar_t* keyName, diff --git a/libs/uibase/src/registry.cpp b/libs/uibase/src/registry.cpp index 0b6834f..5832075 100644 --- a/libs/uibase/src/registry.cpp +++ b/libs/uibase/src/registry.cpp @@ -174,6 +174,87 @@ bool WriteRegistryValue(const QString& appName, const QString& keyName, return false; } +bool RemoveRegistryValue(const QString& section, const QString& key, + const QString& fileName) +{ + if (!QFileInfo::exists(fileName)) { + return true; // nothing to remove + } + + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return false; + } + QStringList lines; + QTextStream in(&file); + while (!in.atEnd()) { + lines.append(in.readLine()); + } + file.close(); + + const QString sectionHeader = "[" + section + "]"; + int sectionStart = -1; + int sectionEnd = lines.size(); + + for (int i = 0; i < lines.size(); ++i) { + QString trimmed = lines[i].trimmed(); + if (trimmed.compare(sectionHeader, Qt::CaseInsensitive) == 0) { + sectionStart = i; + for (int j = i + 1; j < lines.size(); ++j) { + QString t = lines[j].trimmed(); + if (t.startsWith('[') && t.endsWith(']')) { + sectionEnd = j; + break; + } + } + break; + } + } + + if (sectionStart < 0) { + return true; // section not found, nothing to remove + } + + // Find and remove the key line + bool found = false; + for (int i = sectionStart + 1; i < sectionEnd; ++i) { + QString trimmed = lines[i].trimmed(); + if (trimmed.isEmpty() || trimmed.startsWith(';') || trimmed.startsWith('#')) { + continue; + } + int eqPos = trimmed.indexOf('='); + if (eqPos > 0) { + QString existingKey = trimmed.left(eqPos).trimmed(); + if (existingKey.compare(key, Qt::CaseInsensitive) == 0) { + lines.removeAt(i); + found = true; + break; + } + } + } + + if (!found) { + return true; // key not found, nothing to remove + } + + // Write back + QFile outFile(fileName); + if (!outFile.open(QIODevice::WriteOnly | QIODevice::Text)) { + return false; + } + QTextStream out(&outFile); + for (int i = 0; i < lines.size(); ++i) { + out << lines[i]; + if (i < lines.size() - 1) { + out << '\n'; + } + } + out << '\n'; + outFile.close(); + + return true; +} + #ifdef _WIN32 bool WriteRegistryValue(const wchar_t* appName, const wchar_t* keyName, const wchar_t* value, const wchar_t* fileName) diff --git a/libs/uibase/src/utility.cpp b/libs/uibase/src/utility.cpp index e6f53b8..33f227d 100644 --- a/libs/uibase/src/utility.cpp +++ b/libs/uibase/src/utility.cpp @@ -388,11 +388,42 @@ namespace shell } } + // Launch an external host process with AppImage environment variables + // cleaned up, so tools like xdg-open/kde-open use the host's own + // libraries and Qt plugins instead of the bundled ones. + static bool startDetachedHostProcess(const QString& program, + const QStringList& args) + { + QProcess proc; + auto env = QProcessEnvironment::systemEnvironment(); + + // Restore original LD_LIBRARY_PATH (saved by AppRun.sh) + if (env.contains(QStringLiteral("FLUORINE_ORIG_LD_LIBRARY_PATH"))) { + const auto orig = env.value(QStringLiteral("FLUORINE_ORIG_LD_LIBRARY_PATH")); + if (orig.isEmpty()) + env.remove(QStringLiteral("LD_LIBRARY_PATH")); + else + env.insert(QStringLiteral("LD_LIBRARY_PATH"), orig); + } + + // Remove AppImage-specific Qt/path variables that would confuse host apps + env.remove(QStringLiteral("QT_PLUGIN_PATH")); + env.remove(QStringLiteral("QT_QPA_PLATFORM_PLUGIN_PATH")); + env.remove(QStringLiteral("QTWEBENGINEPROCESS_PATH")); + env.remove(QStringLiteral("QTWEBENGINE_RESOURCES_PATH")); + env.remove(QStringLiteral("QTWEBENGINE_LOCALES_PATH")); + + proc.setProgram(program); + proc.setArguments(args); + proc.setProcessEnvironment(env); + return proc.startDetached(); + } + Result ExploreDirectory(const QFileInfo& info) { const auto path = info.absoluteFilePath(); // Use xdg-open on Linux - if (QProcess::startDetached("xdg-open", {path})) { + if (startDetachedHostProcess("xdg-open", {path})) { return Result::makeSuccess(); } return Result::makeFailure(ERROR_FILE_NOT_FOUND, QObject::tr("Failed to open directory")); @@ -403,7 +434,7 @@ namespace shell // Try to use the system file manager to highlight the file // dbus method for Nautilus/Files, fallback to xdg-open on parent dir const auto dir = info.absolutePath(); - if (QProcess::startDetached("xdg-open", {dir})) { + if (startDetachedHostProcess("xdg-open", {dir})) { return Result::makeSuccess(); } return Result::makeFailure(ERROR_FILE_NOT_FOUND, QObject::tr("Failed to open file manager")); @@ -437,7 +468,7 @@ namespace shell Result Open(const QString& path) { - if (QProcess::startDetached("xdg-open", {path})) { + if (startDetachedHostProcess("xdg-open", {path})) { return Result::makeSuccess(); } return Result::makeFailure(ERROR_FILE_NOT_FOUND, QObject::tr("Failed to open file")); @@ -448,7 +479,7 @@ namespace shell log::debug("opening url '{}'", url.toString()); if (g_urlHandler.isEmpty()) { - if (QProcess::startDetached("xdg-open", {url.toString()})) { + if (startDetachedHostProcess("xdg-open", {url.toString()})) { return Result::makeSuccess(); } return Result::makeFailure(ERROR_FILE_NOT_FOUND, QObject::tr("Failed to open URL")); @@ -456,7 +487,7 @@ namespace shell // Custom URL handler QString cmd = g_urlHandler; cmd.replace("%1", url.toString()); - if (QProcess::startDetached("/bin/sh", {"-c", cmd})) { + if (startDetachedHostProcess("/bin/sh", {"-c", cmd})) { return Result::makeSuccess(); } return Result::makeFailure(ERROR_FILE_NOT_FOUND, |
