aboutsummaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/game_bethesda/src/gamebryo/gamebryolocalsavegames.cpp21
-rw-r--r--libs/game_bethesda/src/gamebryo/gamegamebryo.cpp21
-rw-r--r--libs/nak/src/installers/prefix_setup.rs20
-rw-r--r--libs/nak/src/runtime_wrap.rs39
-rw-r--r--libs/uibase/include/uibase/registry.h6
-rw-r--r--libs/uibase/src/registry.cpp81
-rw-r--r--libs/uibase/src/utility.cpp41
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,