/* 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 . */ #include #include #include #include #include #include #include #include #include #include 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; } 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) { return WriteRegistryValue( QString::fromWCharArray(appName), QString::fromWCharArray(keyName), QString::fromWCharArray(value), QString::fromWCharArray(fileName)); } #endif } // namespace MOBase