diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-14 15:40:33 -0600 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-14 15:40:33 -0600 |
| commit | 6410929e17d642618f284d5c97d457f1ac653e6e (patch) | |
| tree | 5c0ee09e04f38a2dd70f1734d25c74e0b8ae5d43 /libs | |
| parent | 817e8f5cd26739c69d930d21cd9dc4c0b6e4984e (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')
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; } |
