aboutsummaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-14 15:40:33 -0600
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-14 15:40:33 -0600
commit6410929e17d642618f284d5c97d457f1ac653e6e (patch)
tree5c0ee09e04f38a2dd70f1734d25c74e0b8ae5d43 /libs
parent817e8f5cd26739c69d930d21cd9dc4c0b6e4984e (diff)
Migrate data paths to ~/.local/share/fluorine/, add native build, fix %command% and system Proton scanning
- All data paths migrated from ~/.var/app/com.fluorine.manager/ to ~/.local/share/fluorine/ so native and Flatpak builds share the same instances, plugins, and configs - New fluorinepaths.h/.cpp with fluorineDataDir() helper and one-time migration from old path (writes MOVED.txt breadcrumb) - New libs/nak/src/paths.rs as Rust equivalent (data_dir()) - Strip %command% tokens from launch wrapper in protonlauncher.cpp - Always scan /usr/share/steam/compatibilitytools.d/ for system Proton packages (Arch installs Proton there); add Flatpak filesystem permission - Native build (build-native.sh) installs to ~/.local/share/fluorine/ with desktop entry and ~/.local/bin symlink instead of portable zip - build-flatpak.sh wrapper for Flatpak builds - Fix container locale (LANG=C.UTF-8) for AutoUic warnings - Fix prefixExists() to handle both compatdata and pfx directory layouts - Fix globalInstancesRootPath() to use fluorineDataDir() on Linux - Fix umu-run Flatpak lookup to use fluorineDataDir() - Update README with build instructions and Arch dependency list Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs')
-rw-r--r--libs/7zip/CMakeLists.txt2
-rw-r--r--libs/basic_games/basic_game.py8
-rw-r--r--libs/bsa_ffi/src/archive/mod.rs2
-rw-r--r--libs/bsapacker/src/qlibbsarch/QLibbsarch.h12
-rw-r--r--libs/bsatk/src/CMakeLists.txt3
-rw-r--r--libs/bsatk/src/bsaarchive.cpp12
-rw-r--r--libs/bsatk/src/bsafile.cpp1
-rw-r--r--libs/game_bethesda/src/gamebryo/dummybsa.cpp6
-rw-r--r--libs/game_bethesda/src/gamebryo/gamebryosavegame.cpp3
-rw-r--r--libs/game_bethesda/src/gamebryo/gamegamebryo.cpp6
-rw-r--r--libs/game_bethesda/src/games/enderal/enderalsavegame.cpp3
-rw-r--r--libs/game_bethesda/src/games/enderalse/enderalsesavegame.cpp3
-rw-r--r--libs/game_bethesda/src/games/fallout4/fallout4savegame.cpp3
-rw-r--r--libs/game_bethesda/src/games/fallout4london/fo4londonsavegame.cpp3
-rw-r--r--libs/game_bethesda/src/games/fallout4vr/fallout4vrsavegame.cpp3
-rw-r--r--libs/game_bethesda/src/games/fallout76/fallout76savegame.cpp3
-rw-r--r--libs/game_bethesda/src/games/skyrim/skyrimsavegame.cpp3
-rw-r--r--libs/game_bethesda/src/games/skyrimse/skyrimsesavegame.cpp3
-rw-r--r--libs/game_bethesda/src/games/skyrimvr/skyrimvrsavegame.cpp3
-rw-r--r--libs/game_bethesda/src/games/starfield/starfieldsavegame.cpp3
-rw-r--r--libs/installer_fomod/src/fomodinstallerdialog.cpp27
-rw-r--r--libs/installer_fomod_plus/installer/CMakeLists.txt2
-rw-r--r--libs/installer_fomod_plus/patchwizard/CMakeLists.txt2
-rw-r--r--libs/installer_fomod_plus/scanner/CMakeLists.txt2
-rw-r--r--libs/nak/src/deps/tools.rs9
-rw-r--r--libs/nak/src/dxvk.rs6
-rw-r--r--libs/nak/src/lib.rs1
-rw-r--r--libs/nak/src/logging.rs5
-rw-r--r--libs/nak/src/paths.rs12
-rw-r--r--libs/nak/src/steam/proton.rs9
-rw-r--r--libs/plugin_python/src/mobase/CMakeLists.txt7
-rw-r--r--libs/plugin_python/src/mobase/wrappers/basic_classes.cpp5
-rw-r--r--libs/plugin_python/src/pybind11-qt/CMakeLists.txt3
-rw-r--r--libs/plugin_python/src/pybind11-utils/CMakeLists.txt5
-rw-r--r--libs/plugin_python/src/runner/CMakeLists.txt3
-rw-r--r--libs/uibase/include/uibase/filterwidget.h6
-rw-r--r--libs/uibase/src/utility.cpp18
37 files changed, 129 insertions, 78 deletions
diff --git a/libs/7zip/CMakeLists.txt b/libs/7zip/CMakeLists.txt
index 915be26..5db6f0a 100644
--- a/libs/7zip/CMakeLists.txt
+++ b/libs/7zip/CMakeLists.txt
@@ -443,7 +443,7 @@ target_compile_options(7z PRIVATE
-Wno-unused-parameter
-Wno-sign-compare
-Wno-missing-field-initializers
- -Wno-reorder
+ $<$<COMPILE_LANGUAGE:CXX>:-Wno-reorder>
)
target_link_libraries(7z PRIVATE pthread dl)
diff --git a/libs/basic_games/basic_game.py b/libs/basic_games/basic_game.py
index b3ff73d..1047bbf 100644
--- a/libs/basic_games/basic_game.py
+++ b/libs/basic_games/basic_game.py
@@ -31,11 +31,11 @@ def _find_wine_userprofile() -> str | None:
candidates: list[str] = []
- # 1. Flatpak data prefix (most common for Fluorine)
- flatpak_pfx = os.path.expanduser(
- "~/.var/app/com.fluorine.manager/Prefix/pfx"
+ # 1. Fluorine data prefix (shared by native and Flatpak builds)
+ fluorine_pfx = os.path.expanduser(
+ "~/.local/share/fluorine/Prefix/pfx"
)
- candidates.append(flatpak_pfx)
+ candidates.append(fluorine_pfx)
# 2. Fluorine config prefix_path
try:
diff --git a/libs/bsa_ffi/src/archive/mod.rs b/libs/bsa_ffi/src/archive/mod.rs
index 247789e..27b1db1 100644
--- a/libs/bsa_ffi/src/archive/mod.rs
+++ b/libs/bsa_ffi/src/archive/mod.rs
@@ -201,6 +201,7 @@ pub enum GameVersion {
StarfieldV3,
}
+#[allow(dead_code)]
impl GameVersion {
/// Get display name for this game version
pub fn display_name(&self) -> &'static str {
@@ -353,6 +354,7 @@ impl GameVersion {
}
/// Detect game version from archive format
+#[allow(dead_code)]
pub fn detect_game_version(archive_path: &Path) -> Option<GameVersion> {
match detect_format(archive_path) {
Some(ArchiveFormat::Tes3Bsa) => Some(GameVersion::Morrowind),
diff --git a/libs/bsapacker/src/qlibbsarch/QLibbsarch.h b/libs/bsapacker/src/qlibbsarch/QLibbsarch.h
index 7af53f4..9e37e05 100644
--- a/libs/bsapacker/src/qlibbsarch/QLibbsarch.h
+++ b/libs/bsapacker/src/qlibbsarch/QLibbsarch.h
@@ -3,6 +3,7 @@
#include <libbsarch/libbsarch.h>
#include <string>
#include <stdexcept>
+#include <cstring>
#include <QDebug>
#include <QDir>
#include <QStringList>
@@ -23,7 +24,9 @@ namespace QLibBsarch
{
if (result.code == BSA_RESULT_EXCEPTION)
{
- const std::string &error = QLibBsarch::wcharToString(result.text);
+ wchar_t aligned_text[1024];
+ std::memcpy(aligned_text, result.text, sizeof(aligned_text));
+ const std::string error = QLibBsarch::wcharToString(aligned_text);
LOG_LIBBSARCH << QString::fromStdString(error);
throw std::runtime_error(error);
}
@@ -31,12 +34,7 @@ namespace QLibBsarch
inline void checkResult(const bsa_result_message_buffer_s &result)
{
- if (result.message.code == BSA_RESULT_EXCEPTION)
- {
- const std::string &error = QLibBsarch::wcharToString(result.message.text);
- LOG_LIBBSARCH << QString::fromStdString(error);
- throw std::runtime_error(error);
- }
+ checkResult(result.message);
}
} // namespace QLibBsarch
diff --git a/libs/bsatk/src/CMakeLists.txt b/libs/bsatk/src/CMakeLists.txt
index 99a0f26..d9b7be1 100644
--- a/libs/bsatk/src/CMakeLists.txt
+++ b/libs/bsatk/src/CMakeLists.txt
@@ -1,4 +1,7 @@
cmake_minimum_required(VERSION 3.16)
+if(POLICY CMP0167)
+ cmake_policy(SET CMP0167 NEW)
+endif()
find_package(Boost REQUIRED COMPONENTS thread)
find_package(ZLIB REQUIRED)
diff --git a/libs/bsatk/src/bsaarchive.cpp b/libs/bsatk/src/bsaarchive.cpp
index e1655c1..4ae94a6 100644
--- a/libs/bsatk/src/bsaarchive.cpp
+++ b/libs/bsatk/src/bsaarchive.cpp
@@ -446,11 +446,9 @@ EErrorCode Archive::write(const char* fileName)
writeHeader(outfile, determineFileFlags(fileNames),
static_cast<BSAULong>(folderNames.size()), folderNamesLength,
fileNamesLength);
-#pragma message("folders (and files?) need to be sorted by hash!")
// dummy-pass: before we can store the actual folder data
// prepare folder and file headers
-#pragma message("it's unnecessary to write actual data, placeholders are sufficient")
for (std::vector<Folder::Ptr>::const_iterator folderIter = folders.begin();
folderIter != folders.end(); ++folderIter) {
(*folderIter)->writeHeader(outfile);
@@ -673,7 +671,6 @@ EErrorCode Archive::extractDirect(File::Ptr file, std::ofstream& outFile) const
if (namePrefixed()) {
std::string fullName = readBString(m_File);
if (size <= fullName.length()) {
-#pragma message("report error!")
return result;
}
size -= fullName.length() + 1;
@@ -735,7 +732,6 @@ std::shared_ptr<unsigned char[]> Archive::decompress(unsigned char* inBuffer,
stream.next_out = reinterpret_cast<Bytef*>(outBuffer.get());
zlibRet = inflate(&stream, Z_NO_FLUSH);
if ((zlibRet != Z_OK) && (zlibRet != Z_STREAM_END) && (zlibRet != Z_BUF_ERROR)) {
-#pragma message("pass result code to caller")
throw std::runtime_error("invalid data");
}
} while (stream.avail_out == 0);
@@ -824,7 +820,6 @@ EErrorCode Archive::extractCompressed(File::Ptr file, std::ofstream& outFile) co
if (namePrefixed()) {
std::string fullName = readBString(m_File);
if (inSize <= fullName.length()) {
-#pragma message("report error!")
return result;
}
inSize -= fullName.length() + 1;
@@ -850,7 +845,6 @@ EErrorCode Archive::extractCompressed(File::Ptr file, std::ofstream& outFile) co
if (namePrefixed()) {
std::string fullName = readBString(m_File);
if (inSize <= fullName.length()) {
-#pragma message("report error!")
return result;
}
inSize -= fullName.length() + 1;
@@ -907,7 +901,6 @@ void Archive::readFiles(std::queue<FileInfo>& queue, boost::mutex& mutex,
if (namePrefixed()) {
std::string fullName = readBString(m_File);
if (size <= fullName.length()) {
-#pragma message("report error!")
continue;
}
size -= fullName.length() + 1;
@@ -954,7 +947,6 @@ void Archive::readFiles(std::queue<FileInfo>& queue, boost::mutex& mutex,
reinterpret_cast<char*>(unpackedChunk.get()), length);
unpackedChunk.reset();
} catch (const std::exception&) {
-#pragma message("report error!")
continue;
}
} else {
@@ -1029,7 +1021,6 @@ void Archive::extractFiles(const std::string& targetDirectory,
fstream::out | fstream::binary | fstream::trunc);
if (!outputFile.is_open()) {
-#pragma message("report error!")
continue;
// return ERROR_ACCESSFAILED;
}
@@ -1052,7 +1043,6 @@ void Archive::extractFiles(const std::string& targetDirectory,
buffer.reset();
}
} catch (const std::exception&) {
-#pragma message("report error!")
dataBuffer.first.reset();
fileInfo.data.first.reset();
continue;
@@ -1121,7 +1111,6 @@ void Archive::extractFiles(const std::string& targetDirectory,
dataBuffer.second);
}
} catch (const std::exception&) {
-#pragma message("report error!")
dataBuffer.first.reset();
fileInfo.data.first.reset();
continue;
@@ -1151,7 +1140,6 @@ EErrorCode Archive::extractAll(
const std::function<bool(int value, std::string fileName)>& progress,
bool overwrite)
{
-#pragma message("report errors")
createFolders(outputDirectory, m_RootFolder);
std::vector<File::Ptr> fileList;
diff --git a/libs/bsatk/src/bsafile.cpp b/libs/bsatk/src/bsafile.cpp
index db4e4e6..3179c63 100644
--- a/libs/bsatk/src/bsafile.cpp
+++ b/libs/bsatk/src/bsafile.cpp
@@ -104,7 +104,6 @@ EErrorCode File::writeData(fstream& sourceArchive, fstream& targetArchive) const
if (m_SourceFile.length() == 0) {
// copy from source archive
-#pragma message("we may have to compress/decompress!")
sourceArchive.seekg(m_DataOffset, fstream::beg);
try {
diff --git a/libs/game_bethesda/src/gamebryo/dummybsa.cpp b/libs/game_bethesda/src/gamebryo/dummybsa.cpp
index 7b0f877..3fbd2ef 100644
--- a/libs/game_bethesda/src/gamebryo/dummybsa.cpp
+++ b/libs/game_bethesda/src/gamebryo/dummybsa.cpp
@@ -19,6 +19,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "dummybsa.h"
#include <QFile>
+#include <stdexcept>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
@@ -184,7 +185,10 @@ void DummyBSA::writeFileRecordBlocks(QFile& file, const std::string& folderName)
void DummyBSA::write(const QString& fileName)
{
QFile file(fileName);
- file.open(QIODevice::WriteOnly);
+ if (!file.open(QIODevice::WriteOnly)) {
+ throw std::runtime_error(
+ QString("failed to open dummy BSA for writing: %1").arg(fileName).toStdString());
+ }
m_TotalFileNameLength = static_cast<unsigned long>(m_FileName.length() + 1);
diff --git a/libs/game_bethesda/src/gamebryo/gamebryosavegame.cpp b/libs/game_bethesda/src/gamebryo/gamebryosavegame.cpp
index 523df0d..7625af3 100644
--- a/libs/game_bethesda/src/gamebryo/gamebryosavegame.cpp
+++ b/libs/game_bethesda/src/gamebryo/gamebryosavegame.cpp
@@ -9,6 +9,7 @@
#include <QFileInfo>
#include <QScopedArrayPointer>
#include <QTime>
+#include <QTimeZone>
#ifdef _WIN32
#include <Windows.h>
@@ -90,7 +91,7 @@ void GamebryoSaveGame::setCreationTime(_SYSTEMTIME const& ctime)
QTime time;
time.setHMS(ctime.wHour, ctime.wMinute, ctime.wSecond, ctime.wMilliseconds);
- m_CreationTime = QDateTime(date, time, Qt::UTC);
+ m_CreationTime = QDateTime(date, time, QTimeZone::UTC);
}
GamebryoSaveGame::FileWrapper::FileWrapper(QString const& filepath,
diff --git a/libs/game_bethesda/src/gamebryo/gamegamebryo.cpp b/libs/game_bethesda/src/gamebryo/gamegamebryo.cpp
index caeeed0..1bcc2c0 100644
--- a/libs/game_bethesda/src/gamebryo/gamegamebryo.cpp
+++ b/libs/game_bethesda/src/gamebryo/gamegamebryo.cpp
@@ -495,7 +495,11 @@ void GameGamebryo::copyToProfile(QString const& sourcePath,
sourceFile = resolveFileCaseInsensitive(sourceFile);
if (!MOBase::shellCopy(sourceFile, filePath)) {
// if copy file fails, create the file empty
- QFile(filePath).open(QIODevice::WriteOnly);
+ QFile outputFile(filePath);
+ if (!outputFile.open(QIODevice::WriteOnly)) {
+ MOBase::log::warn("Failed to create fallback file '{}': {}", filePath,
+ outputFile.errorString());
+ }
}
}
}
diff --git a/libs/game_bethesda/src/games/enderal/enderalsavegame.cpp b/libs/game_bethesda/src/games/enderal/enderalsavegame.cpp
index cd9beb3..235bf1c 100644
--- a/libs/game_bethesda/src/games/enderal/enderalsavegame.cpp
+++ b/libs/game_bethesda/src/games/enderal/enderalsavegame.cpp
@@ -4,6 +4,7 @@
#include <Windows.h>
#else
#include <QDateTime>
+#include <QTimeZone>
using SYSTEMTIME = _SYSTEMTIME;
@@ -14,7 +15,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st)
const int64_t unixSecs = static_cast<int64_t>(ticks / 10000000ULL) - 11644473600LL;
const uint64_t remainderHns = ticks % 10000000ULL;
- const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC);
+ const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, QTimeZone::UTC);
const QDate d = dt.date();
const QTime t = dt.time();
diff --git a/libs/game_bethesda/src/games/enderalse/enderalsesavegame.cpp b/libs/game_bethesda/src/games/enderalse/enderalsesavegame.cpp
index 2662505..365ca37 100644
--- a/libs/game_bethesda/src/games/enderalse/enderalsesavegame.cpp
+++ b/libs/game_bethesda/src/games/enderalse/enderalsesavegame.cpp
@@ -4,6 +4,7 @@
#include <Windows.h>
#else
#include <QDateTime>
+#include <QTimeZone>
union _ULARGE_INTEGER {
struct {
@@ -22,7 +23,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st)
const int64_t unixSecs = static_cast<int64_t>(ticks / 10000000ULL) - 11644473600LL;
const uint64_t remainderHns = ticks % 10000000ULL;
- const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC);
+ const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, QTimeZone::UTC);
const QDate d = dt.date();
const QTime t = dt.time();
diff --git a/libs/game_bethesda/src/games/fallout4/fallout4savegame.cpp b/libs/game_bethesda/src/games/fallout4/fallout4savegame.cpp
index 2f88d1b..17863ad 100644
--- a/libs/game_bethesda/src/games/fallout4/fallout4savegame.cpp
+++ b/libs/game_bethesda/src/games/fallout4/fallout4savegame.cpp
@@ -4,6 +4,7 @@
#include <Windows.h>
#else
#include <QDateTime>
+#include <QTimeZone>
using SYSTEMTIME = _SYSTEMTIME;
@@ -14,7 +15,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st)
const int64_t unixSecs = static_cast<int64_t>(ticks / 10000000ULL) - 11644473600LL;
const uint64_t remainderHns = ticks % 10000000ULL;
- const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC);
+ const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, QTimeZone::UTC);
const QDate d = dt.date();
const QTime t = dt.time();
diff --git a/libs/game_bethesda/src/games/fallout4london/fo4londonsavegame.cpp b/libs/game_bethesda/src/games/fallout4london/fo4londonsavegame.cpp
index ead2d1a..c87d107 100644
--- a/libs/game_bethesda/src/games/fallout4london/fo4londonsavegame.cpp
+++ b/libs/game_bethesda/src/games/fallout4london/fo4londonsavegame.cpp
@@ -4,6 +4,7 @@
#include <Windows.h>
#else
#include <QDateTime>
+#include <QTimeZone>
using SYSTEMTIME = _SYSTEMTIME;
@@ -14,7 +15,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st)
const int64_t unixSecs = static_cast<int64_t>(ticks / 10000000ULL) - 11644473600LL;
const uint64_t remainderHns = ticks % 10000000ULL;
- const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC);
+ const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, QTimeZone::UTC);
const QDate d = dt.date();
const QTime t = dt.time();
diff --git a/libs/game_bethesda/src/games/fallout4vr/fallout4vrsavegame.cpp b/libs/game_bethesda/src/games/fallout4vr/fallout4vrsavegame.cpp
index cf29aae..d29c4fa 100644
--- a/libs/game_bethesda/src/games/fallout4vr/fallout4vrsavegame.cpp
+++ b/libs/game_bethesda/src/games/fallout4vr/fallout4vrsavegame.cpp
@@ -4,6 +4,7 @@
#include <Windows.h>
#else
#include <QDateTime>
+#include <QTimeZone>
using SYSTEMTIME = _SYSTEMTIME;
@@ -14,7 +15,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st)
const int64_t unixSecs = static_cast<int64_t>(ticks / 10000000ULL) - 11644473600LL;
const uint64_t remainderHns = ticks % 10000000ULL;
- const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC);
+ const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, QTimeZone::UTC);
const QDate d = dt.date();
const QTime t = dt.time();
diff --git a/libs/game_bethesda/src/games/fallout76/fallout76savegame.cpp b/libs/game_bethesda/src/games/fallout76/fallout76savegame.cpp
index 66e8a00..c070429 100644
--- a/libs/game_bethesda/src/games/fallout76/fallout76savegame.cpp
+++ b/libs/game_bethesda/src/games/fallout76/fallout76savegame.cpp
@@ -4,6 +4,7 @@
#include <Windows.h>
#else
#include <QDateTime>
+#include <QTimeZone>
using SYSTEMTIME = _SYSTEMTIME;
@@ -14,7 +15,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st)
const int64_t unixSecs = static_cast<int64_t>(ticks / 10000000ULL) - 11644473600LL;
const uint64_t remainderHns = ticks % 10000000ULL;
- const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC);
+ const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, QTimeZone::UTC);
const QDate d = dt.date();
const QTime t = dt.time();
diff --git a/libs/game_bethesda/src/games/skyrim/skyrimsavegame.cpp b/libs/game_bethesda/src/games/skyrim/skyrimsavegame.cpp
index bd16036..621692e 100644
--- a/libs/game_bethesda/src/games/skyrim/skyrimsavegame.cpp
+++ b/libs/game_bethesda/src/games/skyrim/skyrimsavegame.cpp
@@ -4,6 +4,7 @@
#include <Windows.h>
#else
#include <QDateTime>
+#include <QTimeZone>
// SYSTEMTIME is _SYSTEMTIME (defined in gamebryosavegame.h for Linux)
using SYSTEMTIME = _SYSTEMTIME;
@@ -16,7 +17,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st)
const int64_t unixSecs = static_cast<int64_t>(ticks / 10000000ULL) - 11644473600LL;
const uint64_t remainderHns = ticks % 10000000ULL; // leftover 100ns units
- const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC);
+ const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, QTimeZone::UTC);
const QDate d = dt.date();
const QTime t = dt.time();
diff --git a/libs/game_bethesda/src/games/skyrimse/skyrimsesavegame.cpp b/libs/game_bethesda/src/games/skyrimse/skyrimsesavegame.cpp
index d68ba19..59b9b6e 100644
--- a/libs/game_bethesda/src/games/skyrimse/skyrimsesavegame.cpp
+++ b/libs/game_bethesda/src/games/skyrimse/skyrimsesavegame.cpp
@@ -4,6 +4,7 @@
#include <Windows.h>
#else
#include <QDateTime>
+#include <QTimeZone>
// Windows ULARGE_INTEGER union for 64-bit FILETIME math
union _ULARGE_INTEGER {
@@ -29,7 +30,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st)
int64_t unixSecs = static_cast<int64_t>(ull.QuadPart / 10000000ULL) - 11644473600LL;
uint64_t remainderHns = ull.QuadPart % 10000000ULL; // leftover 100ns units
- QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC);
+ QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, QTimeZone::UTC);
QDate d = dt.date();
QTime t = dt.time();
diff --git a/libs/game_bethesda/src/games/skyrimvr/skyrimvrsavegame.cpp b/libs/game_bethesda/src/games/skyrimvr/skyrimvrsavegame.cpp
index 1670dc8..75b3683 100644
--- a/libs/game_bethesda/src/games/skyrimvr/skyrimvrsavegame.cpp
+++ b/libs/game_bethesda/src/games/skyrimvr/skyrimvrsavegame.cpp
@@ -4,6 +4,7 @@
#include <Windows.h>
#else
#include <QDateTime>
+#include <QTimeZone>
// Windows ULARGE_INTEGER union for 64-bit FILETIME math
union _ULARGE_INTEGER {
@@ -25,7 +26,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st)
const int64_t unixSecs = static_cast<int64_t>(ticks / 10000000ULL) - 11644473600LL;
const uint64_t remainderHns = ticks % 10000000ULL; // leftover 100ns units
- const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC);
+ const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, QTimeZone::UTC);
const QDate d = dt.date();
const QTime t = dt.time();
diff --git a/libs/game_bethesda/src/games/starfield/starfieldsavegame.cpp b/libs/game_bethesda/src/games/starfield/starfieldsavegame.cpp
index 49653cb..9640b75 100644
--- a/libs/game_bethesda/src/games/starfield/starfieldsavegame.cpp
+++ b/libs/game_bethesda/src/games/starfield/starfieldsavegame.cpp
@@ -4,6 +4,7 @@
#include <Windows.h>
#else
#include <QDateTime>
+#include <QTimeZone>
using SYSTEMTIME = _SYSTEMTIME;
@@ -14,7 +15,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st)
const int64_t unixSecs = static_cast<int64_t>(ticks / 10000000ULL) - 11644473600LL;
const uint64_t remainderHns = ticks % 10000000ULL;
- const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC);
+ const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, QTimeZone::UTC);
const QDate d = dt.date();
const QTime t = dt.time();
diff --git a/libs/installer_fomod/src/fomodinstallerdialog.cpp b/libs/installer_fomod/src/fomodinstallerdialog.cpp
index 0fa6bfc..f675e3d 100644
--- a/libs/installer_fomod/src/fomodinstallerdialog.cpp
+++ b/libs/installer_fomod/src/fomodinstallerdialog.cpp
@@ -127,11 +127,15 @@ int FomodInstallerDialog::bomOffset(const QByteArray& buffer)
static const unsigned char BOM_UTF8[] = {0xEF, 0xBB, 0xBF};
static const unsigned char BOM_UTF16BE[] = {0xFE, 0xFF};
static const unsigned char BOM_UTF16LE[] = {0xFF, 0xFE};
+ auto startsWithBytes = [&buffer](const unsigned char* bytes, qsizetype size) {
+ return buffer.startsWith(
+ QByteArrayView(reinterpret_cast<const char*>(bytes), size));
+ };
- if (buffer.startsWith(reinterpret_cast<const char*>(BOM_UTF8)))
+ if (startsWithBytes(BOM_UTF8, sizeof(BOM_UTF8)))
return 3;
- if (buffer.startsWith(reinterpret_cast<const char*>(BOM_UTF16BE)) ||
- buffer.startsWith(reinterpret_cast<const char*>(BOM_UTF16LE)))
+ if (startsWithBytes(BOM_UTF16BE, sizeof(BOM_UTF16BE)) ||
+ startsWithBytes(BOM_UTF16LE, sizeof(BOM_UTF16LE)))
return 2;
return 0;
@@ -150,25 +154,30 @@ QByteArray skipXmlHeader(QIODevice& file)
static const unsigned char UTF16LE[] = {0x3C, 0x00, 0x3F, 0x00};
static const unsigned char UTF16BE[] = {0x00, 0x3C, 0x00, 0x3F};
static const unsigned char UTF8[] = {0x3C, 0x3F, 0x78, 0x6D};
+ auto startsWithBytes = [](const QByteArray& bytes, const unsigned char* prefix,
+ qsizetype size) {
+ return bytes.startsWith(
+ QByteArrayView(reinterpret_cast<const char*>(prefix), size));
+ };
file.seek(0);
QByteArray rawBytes = file.read(4);
QTextStream stream(&file);
int bom = 0;
- if (rawBytes.startsWith((const char*)UTF16LE_BOM)) {
+ if (startsWithBytes(rawBytes, UTF16LE_BOM, sizeof(UTF16LE_BOM))) {
stream.setEncoding(QStringConverter::Encoding::Utf16LE);
bom = 2;
- } else if (rawBytes.startsWith((const char*)UTF16BE_BOM)) {
+ } else if (startsWithBytes(rawBytes, UTF16BE_BOM, sizeof(UTF16BE_BOM))) {
stream.setEncoding(QStringConverter::Encoding::Utf16BE);
bom = 2;
- } else if (rawBytes.startsWith((const char*)UTF8_BOM)) {
+ } else if (startsWithBytes(rawBytes, UTF8_BOM, sizeof(UTF8_BOM))) {
stream.setEncoding(QStringConverter::Encoding::Utf8);
bom = 3;
- } else if (rawBytes.startsWith(QByteArray((const char*)UTF16LE, 4))) {
+ } else if (startsWithBytes(rawBytes, UTF16LE, sizeof(UTF16LE))) {
stream.setEncoding(QStringConverter::Encoding::Utf16LE);
- } else if (rawBytes.startsWith(QByteArray((const char*)UTF16BE, 4))) {
+ } else if (startsWithBytes(rawBytes, UTF16BE, sizeof(UTF16BE))) {
stream.setEncoding(QStringConverter::Encoding::Utf16BE);
- } else if (rawBytes.startsWith(QByteArray((const char*)UTF8, 4))) {
+ } else if (startsWithBytes(rawBytes, UTF8, sizeof(UTF8))) {
stream.setEncoding(QStringConverter::Encoding::Utf8);
} // otherwise maybe the textstream knows the encoding?
diff --git a/libs/installer_fomod_plus/installer/CMakeLists.txt b/libs/installer_fomod_plus/installer/CMakeLists.txt
index 70018ff..07a670b 100644
--- a/libs/installer_fomod_plus/installer/CMakeLists.txt
+++ b/libs/installer_fomod_plus/installer/CMakeLists.txt
@@ -29,7 +29,7 @@ FetchContent_Declare(
-P ${CMAKE_CURRENT_LIST_DIR}/../cmake/patch_pugixml.cmake
)
-FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz)
+FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz DOWNLOAD_EXTRACT_TIMESTAMP TRUE)
FetchContent_MakeAvailable(pugixml json)
set(project_type plugin)
diff --git a/libs/installer_fomod_plus/patchwizard/CMakeLists.txt b/libs/installer_fomod_plus/patchwizard/CMakeLists.txt
index be46b32..2cb8ebb 100644
--- a/libs/installer_fomod_plus/patchwizard/CMakeLists.txt
+++ b/libs/installer_fomod_plus/patchwizard/CMakeLists.txt
@@ -14,7 +14,7 @@ file(GLOB SHARE_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/../share/**/*.cpp")
add_library(fomod_plus_patch_wizard SHARED ${PATCHWIZARD_SOURCES} ${SHARE_SOURCES})
-FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz)
+FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz DOWNLOAD_EXTRACT_TIMESTAMP TRUE)
FetchContent_Declare(pugixml GIT_REPOSITORY https://github.com/zeux/pugixml GIT_TAG v1.14)
FetchContent_MakeAvailable(pugixml json)
diff --git a/libs/installer_fomod_plus/scanner/CMakeLists.txt b/libs/installer_fomod_plus/scanner/CMakeLists.txt
index daea477..f50dea2 100644
--- a/libs/installer_fomod_plus/scanner/CMakeLists.txt
+++ b/libs/installer_fomod_plus/scanner/CMakeLists.txt
@@ -13,7 +13,7 @@ file(GLOB_RECURSE SCANNER_SOURCES CONFIGURE_DEPENDS
add_library(fomod_plus_scanner SHARED ${SCANNER_SOURCES})
-FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz)
+FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz DOWNLOAD_EXTRACT_TIMESTAMP TRUE)
FetchContent_Declare(pugixml GIT_REPOSITORY https://github.com/zeux/pugixml GIT_TAG v1.14)
FetchContent_MakeAvailable(pugixml json)
diff --git a/libs/nak/src/deps/tools.rs b/libs/nak/src/deps/tools.rs
index c97247c..0e3e356 100644
--- a/libs/nak/src/deps/tools.rs
+++ b/libs/nak/src/deps/tools.rs
@@ -1,7 +1,7 @@
//! Linux tool management (winetricks, cabextract)
//!
//! Handles downloading and managing Linux CLI tools.
-//! Tools are stored in ~/.var/app/com.fluorine.manager/bin/ for Fluorine Manager.
+//! Tools are stored in ~/.local/share/fluorine/bin/ for Fluorine Manager.
use std::error::Error;
use std::fs;
@@ -13,14 +13,13 @@ use std::process::Command;
use crate::logging::{log_error, log_info, log_warning};
// ============================================================================
-// NaK Bin Directory (~/.var/app/com.fluorine.manager/bin/)
+// NaK Bin Directory (~/.local/share/fluorine/bin/)
// ============================================================================
-/// Get the tool bin directory path (~/.var/app/com.fluorine.manager/bin/)
+/// Get the tool bin directory path (~/.local/share/fluorine/bin/)
/// This is accessible from both native and Flatpak environments.
pub fn get_nak_bin_path() -> PathBuf {
- let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
- PathBuf::from(home).join(".var/app/com.fluorine.manager/bin")
+ crate::paths::data_dir().join("bin")
}
/// Check if a command exists (either in system PATH or tool bin)
diff --git a/libs/nak/src/dxvk.rs b/libs/nak/src/dxvk.rs
index 97e4222..b335773 100644
--- a/libs/nak/src/dxvk.rs
+++ b/libs/nak/src/dxvk.rs
@@ -1,7 +1,7 @@
//! DXVK configuration management for Fluorine Manager.
//!
//! Downloads dxvk.conf from upstream, appends Fluorine-specific settings,
-//! and stores at `~/.var/app/com.fluorine.manager/config/dxvk.conf`.
+//! and stores at `~/.local/share/fluorine/config/dxvk.conf`.
use std::error::Error;
use std::fs;
@@ -20,9 +20,7 @@ dxvk.enableGraphicsPipelineLibrary = False
/// Get the path where the DXVK config will be stored.
pub fn get_dxvk_conf_path() -> PathBuf {
- let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
- PathBuf::from(home)
- .join(".var/app/com.fluorine.manager/config/dxvk.conf")
+ crate::paths::data_dir().join("config/dxvk.conf")
}
/// Ensure the dxvk.conf file exists, downloading if necessary.
diff --git a/libs/nak/src/lib.rs b/libs/nak/src/lib.rs
index c93f07b..59f41c6 100644
--- a/libs/nak/src/lib.rs
+++ b/libs/nak/src/lib.rs
@@ -7,6 +7,7 @@ pub mod config;
pub mod dxvk;
pub mod game_finder;
pub mod logging;
+pub mod paths;
pub mod runtime_wrap;
pub mod steam;
pub mod utils;
diff --git a/libs/nak/src/logging.rs b/libs/nak/src/logging.rs
index 8c0f91a..e506bf8 100644
--- a/libs/nak/src/logging.rs
+++ b/libs/nak/src/logging.rs
@@ -1,7 +1,7 @@
//! Logging for Fluorine Manager.
//!
//! All log messages are:
-//! 1. Written to `~/.var/app/com.fluorine.manager/logs/nak.log` (always)
+//! 1. Written to `~/.local/share/fluorine/logs/nak.log` (always)
//! 2. Forwarded to an optional callback set via `set_log_callback()` (for MOBase::log)
use std::fs;
@@ -25,8 +25,7 @@ pub fn set_log_callback(cb: impl Fn(&str, &str) + Send + Sync + 'static) {
/// Get the log directory path.
fn log_dir() -> PathBuf {
- let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
- PathBuf::from(home).join(".var/app/com.fluorine.manager/logs")
+ crate::paths::data_dir().join("logs")
}
/// Get the log file path.
diff --git a/libs/nak/src/paths.rs b/libs/nak/src/paths.rs
new file mode 100644
index 0000000..4b212b9
--- /dev/null
+++ b/libs/nak/src/paths.rs
@@ -0,0 +1,12 @@
+//! Shared data directory for Fluorine Manager.
+//!
+//! All data lives under `~/.local/share/fluorine/` — accessible from both
+//! native and Flatpak builds (the Flatpak has `--filesystem=home`).
+
+use std::path::PathBuf;
+
+/// Returns the Fluorine data directory (`~/.local/share/fluorine`).
+pub fn data_dir() -> PathBuf {
+ let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
+ PathBuf::from(home).join(".local/share/fluorine")
+}
diff --git a/libs/nak/src/steam/proton.rs b/libs/nak/src/steam/proton.rs
index 064fd96..4059dc4 100644
--- a/libs/nak/src/steam/proton.rs
+++ b/libs/nak/src/steam/proton.rs
@@ -56,8 +56,6 @@ pub fn find_steam_protons() -> Vec<SteamProton> {
return protons;
};
- let is_flatpak = steam_path.to_string_lossy().contains(".var/app/com.valvesoftware.Steam");
-
// 1. Steam's built-in Protons (steamapps/common/Proton*)
protons.extend(find_builtin_protons(&steam_path));
@@ -65,11 +63,8 @@ pub fn find_steam_protons() -> Vec<SteamProton> {
protons.extend(find_custom_protons(&steam_path));
// 3. System-level Protons in /usr/share/steam/compatibilitytools.d/
- if is_flatpak {
- crate::logging::log_info("Flatpak Steam detected - skipping system protons in /usr/share/steam/compatibilitytools.d/");
- } else {
- protons.extend(find_system_protons());
- }
+ // (Arch packages Proton here; Flatpak has --filesystem=/usr/share/steam:ro)
+ protons.extend(find_system_protons());
// Filter to only include Proton 10+ (required for Steam-native integration)
protons.retain(is_proton_10_or_newer);
diff --git a/libs/plugin_python/src/mobase/CMakeLists.txt b/libs/plugin_python/src/mobase/CMakeLists.txt
index 2fac52f..3e765dd 100644
--- a/libs/plugin_python/src/mobase/CMakeLists.txt
+++ b/libs/plugin_python/src/mobase/CMakeLists.txt
@@ -5,7 +5,7 @@ if(NOT TARGET mo2::uibase)
find_package(mo2-uibase CONFIG REQUIRED)
endif()
-pybind11_add_module(mobase MODULE)
+pybind11_add_module(mobase MODULE NO_EXTRAS)
mo2_default_source_group()
mo2_configure_target(mobase
NO_SOURCES
@@ -38,3 +38,8 @@ mo2_target_sources(mobase
./wrappers/wrappers.h
)
target_link_libraries(mobase PRIVATE pybind11::qt pybind11::utils mo2::uibase Qt6::Core)
+set_target_properties(mobase PROPERTIES INTERPROCEDURAL_OPTIMIZATION OFF)
+if(NOT MSVC)
+ target_compile_options(mobase PRIVATE -fno-lto)
+ target_link_options(mobase PRIVATE -fno-lto)
+endif()
diff --git a/libs/plugin_python/src/mobase/wrappers/basic_classes.cpp b/libs/plugin_python/src/mobase/wrappers/basic_classes.cpp
index ea69a46..f782099 100644
--- a/libs/plugin_python/src/mobase/wrappers/basic_classes.cpp
+++ b/libs/plugin_python/src/mobase/wrappers/basic_classes.cpp
@@ -575,10 +575,7 @@ namespace mo2::python {
mo2::python::show_deprecation_warning(
"appVersion", "IOrganizer::appVersion() is deprecated, use "
"IOrganizer::version() instead.");
-#pragma warning(push)
-#pragma warning(disable : 4996)
- return o.appVersion();
-#pragma warning(pop)
+ return o.version();
})
.def("version", &IOrganizer::version)
.def("createMod", &IOrganizer::createMod,
diff --git a/libs/plugin_python/src/pybind11-qt/CMakeLists.txt b/libs/plugin_python/src/pybind11-qt/CMakeLists.txt
index cb742ad..82793cb 100644
--- a/libs/plugin_python/src/pybind11-qt/CMakeLists.txt
+++ b/libs/plugin_python/src/pybind11-qt/CMakeLists.txt
@@ -56,5 +56,8 @@ add_custom_command(
add_dependencies(pybind11-qt PyQt6-siph)
target_include_directories(pybind11-qt PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
+if(NOT MSVC)
+ target_compile_options(pybind11-qt PRIVATE -Wno-attributes)
+endif()
add_library(pybind11::qt ALIAS pybind11-qt)
diff --git a/libs/plugin_python/src/pybind11-utils/CMakeLists.txt b/libs/plugin_python/src/pybind11-utils/CMakeLists.txt
index 77895b6..72b0707 100644
--- a/libs/plugin_python/src/pybind11-utils/CMakeLists.txt
+++ b/libs/plugin_python/src/pybind11-utils/CMakeLists.txt
@@ -17,6 +17,11 @@ mo2_configure_target(pybind11-utils
TRANSLATIONS OFF
)
mo2_default_source_group()
+set_target_properties(pybind11-utils PROPERTIES
+ AUTOMOC OFF
+ AUTOUIC OFF
+ AUTORCC OFF
+)
target_link_libraries(pybind11-utils PUBLIC pybind11::pybind11)
target_include_directories(pybind11-utils PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
diff --git a/libs/plugin_python/src/runner/CMakeLists.txt b/libs/plugin_python/src/runner/CMakeLists.txt
index d164460..9c90314 100644
--- a/libs/plugin_python/src/runner/CMakeLists.txt
+++ b/libs/plugin_python/src/runner/CMakeLists.txt
@@ -22,6 +22,9 @@ mo2_default_source_group()
target_link_libraries(runner PUBLIC mo2::uibase PRIVATE pybind11::embed pybind11::qt)
target_include_directories(runner PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(runner PRIVATE RUNNER_BUILD)
+if(NOT MSVC)
+ target_compile_options(runner PRIVATE -Wno-attributes)
+endif()
if(NOT WIN32 AND Python_SHARED_LIBRARY)
get_filename_component(_pylib_name "${Python_SHARED_LIBRARY}" NAME)
target_compile_definitions(runner PRIVATE
diff --git a/libs/uibase/include/uibase/filterwidget.h b/libs/uibase/include/uibase/filterwidget.h
index b6f09cd..eb1d00f 100644
--- a/libs/uibase/include/uibase/filterwidget.h
+++ b/libs/uibase/include/uibase/filterwidget.h
@@ -24,10 +24,12 @@ class QDLLEXPORT FilterWidgetProxyModel : public QSortFilterProxyModel
public:
FilterWidgetProxyModel(FilterWidget& fw, QWidget* parent = nullptr);
void refreshFilter() {
-#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
+#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
beginFilterChange();
-#endif
+ endFilterChange(QSortFilterProxyModel::Direction::Rows);
+#else
invalidateFilter();
+#endif
}
protected:
diff --git a/libs/uibase/src/utility.cpp b/libs/uibase/src/utility.cpp
index 0ad64ad..e6f53b8 100644
--- a/libs/uibase/src/utility.cpp
+++ b/libs/uibase/src/utility.cpp
@@ -64,7 +64,17 @@ bool removeDir(const QString& dirName)
dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden |
QDir::AllDirs | QDir::Files,
QDir::DirsFirst)) {
- if (info.isDir()) {
+ // Never recurse into symlinked directories: in Flatpak they can point to
+ // read-only runtime locations (e.g. /app), and we only want to remove the link.
+ if (info.isSymLink()) {
+ QFile file(info.absoluteFilePath());
+ if (!file.remove()) {
+ reportError(QObject::tr("removal of \"%1\" failed: %2")
+ .arg(info.absoluteFilePath())
+ .arg(file.errorString()));
+ return false;
+ }
+ } else if (info.isDir()) {
if (!removeDir(info.absoluteFilePath())) {
return false;
}
@@ -209,7 +219,11 @@ static bool shellOpDelete(const QStringList& fileNames, bool recycle)
// On Linux, "recycle" moves to trash using Qt; otherwise just delete
for (const auto& fileName : fileNames) {
QFileInfo fi(fileName);
- if (fi.isDir()) {
+ if (fi.isSymLink()) {
+ if (!QFile::remove(fileName)) {
+ return false;
+ }
+ } else if (fi.isDir()) {
if (!removeDir(fileName)) {
return false;
}