diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-11 02:37:39 -0600 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-11 02:37:39 -0600 |
| commit | 7ee008e150bc5bcf76082d726f719ee0fdfda982 (patch) | |
| tree | 27fb39be241fdb5ac2734c574de678977d1856d0 /libs/uibase/src/registry.cpp | |
Fluorine Manager: full Linux port of Mod Organizer 2
Complete native Linux port with FUSE-based virtual filesystem,
Proton/umu-run integration, and Flatpak packaging.
Key features:
- FUSE VFS replacing Windows USVFS (in-process + standalone helper for Flatpak)
- Proton/GE-Proton/umu-run launcher with env var forwarding
- Flatpak support (sandbox-aware VFS, NXM handler, umu-run)
- Wine prefix management UI
- Case-insensitive path resolution for Linux filesystems
- QSettings-safe INI handling (avoids Bethesda INI corruption)
- Portable instance support with auto-generated launcher scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs/uibase/src/registry.cpp')
| -rw-r--r-- | libs/uibase/src/registry.cpp | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/libs/uibase/src/registry.cpp b/libs/uibase/src/registry.cpp new file mode 100644 index 0000000..0b6834f --- /dev/null +++ b/libs/uibase/src/registry.cpp @@ -0,0 +1,189 @@ +/* +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <uibase/registry.h> +#include <uibase/log.h> +#include <uibase/report.h> +#include <QApplication> +#include <QFile> +#include <QFileInfo> +#include <QList> +#include <QMessageBox> +#include <QString> +#include <QTextStream> + +namespace MOBase +{ + +// Line-by-line INI writer that preserves the file format. +// Unlike QSettings::IniFormat, this does NOT interpret backslashes as +// line continuations, does NOT URL-encode spaces in key names, and does +// NOT reorder keys. It only modifies the target key=value pair and +// leaves everything else untouched. +static bool writeIniValueDirect(const QString& section, const QString& key, + const QString& value, const QString& fileName) +{ + QStringList lines; + bool fileExists = QFileInfo::exists(fileName); + + if (fileExists) { + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return false; + } + QTextStream in(&file); + while (!in.atEnd()) { + lines.append(in.readLine()); + } + file.close(); + } + + // Find the target section and key + QString sectionHeader = "[" + section + "]"; + int sectionStart = -1; + int sectionEnd = lines.size(); // end of file if section is last + int keyLine = -1; + + for (int i = 0; i < lines.size(); ++i) { + QString trimmed = lines[i].trimmed(); + if (trimmed.compare(sectionHeader, Qt::CaseInsensitive) == 0) { + sectionStart = i; + // Find end of this section (next section header or EOF) + 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) { + // Section found, look for the key within it + for (int i = sectionStart + 1; i < sectionEnd; ++i) { + QString trimmed = lines[i].trimmed(); + // Skip comments and empty lines + 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) { + keyLine = i; + break; + } + } + } + + if (keyLine >= 0) { + // Key found, replace the line preserving indentation + QString original = lines[keyLine]; + int eqPos = original.indexOf('='); + // Preserve everything up to and including '=' + lines[keyLine] = original.left(eqPos + 1) + value; + } else { + // Key not found in section, insert it after the section header + lines.insert(sectionStart + 1, key + "=" + value); + } + } else { + // Section not found, append it + if (!lines.isEmpty() && !lines.last().trimmed().isEmpty()) { + lines.append(""); // blank line before new section + } + lines.append(sectionHeader); + lines.append(key + "=" + value); + } + + // 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'; + } + } + // Preserve trailing newline if original had one, or add one + out << '\n'; + outFile.close(); + + return true; +} + +bool WriteRegistryValue(const QString& appName, const QString& keyName, + const QString& value, const QString& fileName) +{ + if (writeIniValueDirect(appName, keyName, value, fileName)) { + return true; + } + + // Write failed, check if the file is read-only + QFileInfo fileInfo(fileName); + + QMessageBox::StandardButton result = + MOBase::TaskDialog(qApp->activeModalWidget(), + QObject::tr("INI file is read-only")) + .main(QObject::tr("INI file is read-only")) + .content(QObject::tr("Mod Organizer is attempting to write to \"%1\" " + "which is currently set to read-only.") + .arg(fileInfo.fileName())) + .icon(QMessageBox::Warning) + .button({QObject::tr("Clear the read-only flag"), QMessageBox::Yes}) + .button({QObject::tr("Allow the write once"), + QObject::tr("The file will be set to read-only again."), + QMessageBox::Ignore}) + .button({QObject::tr("Skip this file"), QMessageBox::No}) + .remember("clearReadOnly", fileInfo.fileName()) + .exec(); + + if (result & (QMessageBox::Yes | QMessageBox::Ignore)) { + // Make the file writable + QFile file(fileName); + file.setPermissions(file.permissions() | QFile::WriteUser | QFile::WriteOwner); + + bool ok = writeIniValueDirect(appName, keyName, value, fileName); + + if (result == QMessageBox::Ignore) { + // Set back to read-only + file.setPermissions(file.permissions() & ~(QFile::WriteUser | QFile::WriteOwner)); + } + + return ok; + } + + return false; +} + +#ifdef _WIN32 +bool WriteRegistryValue(const wchar_t* appName, const wchar_t* keyName, + const wchar_t* value, const wchar_t* fileName) +{ + return WriteRegistryValue( + QString::fromWCharArray(appName), + QString::fromWCharArray(keyName), + QString::fromWCharArray(value), + QString::fromWCharArray(fileName)); +} +#endif + +} // namespace MOBase |
