/* Copyright (C) 2012 Sebastian Herbord. All rights reserved. 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 "profile.h" #include "modinfo.h" #include "settings.h" #include #include #include "appconfig.h" #include #include #include #include #include #include "util.h" #include "registry.h" #include #include #include // for QFile #include // for operator|, QFlags #include // for QIODevice, etc #include #include #include // for QStringList #include // for qDebug, qWarning, etc #include // for qUtf8Printable #include #include #include #include // for assert #include // for UINT_MAX, INT_MAX, etc #include // for size_t #include // for wcslen #include // for max, min #include // for exception #include #include // for set #include // for find #include using namespace MOBase; using namespace MOShared; void Profile::touchFile(QString fileName) { QFile modList(m_Directory.filePath(fileName)); if (!modList.open(QIODevice::ReadWrite)) { throw std::runtime_error(QObject::tr("failed to create %1").arg(m_Directory.filePath(fileName)).toUtf8().constData()); } } Profile::Profile(const QString &name, IPluginGame const *gamePlugin, bool useDefaultSettings) : m_ModListWriter(std::bind(&Profile::doWriteModlist, this)) , m_GamePlugin(gamePlugin) { QString profilesDir = Settings::instance().getProfileDirectory(); QDir profileBase(profilesDir); QString fixedName = name; if (!fixDirectoryName(fixedName)) { throw MyException(tr("invalid profile name: %1").arg(qUtf8Printable(name))); } if (!profileBase.exists() || !profileBase.mkdir(fixedName)) { throw MyException(tr("failed to create %1").arg(fixedName).toUtf8().constData()); } QString fullPath = profilesDir + "/" + fixedName; m_Directory = QDir(fullPath); m_Settings = new QSettings(m_Directory.absoluteFilePath("settings.ini"), QSettings::IniFormat); try { // create files. Needs to happen after m_Directory was set! touchFile("modlist.txt"); touchFile("archives.txt"); IPluginGame::ProfileSettings settings = IPluginGame::CONFIGURATION | IPluginGame::MODS | IPluginGame::SAVEGAMES; if (useDefaultSettings) { settings |= IPluginGame::PREFER_DEFAULTS; } gamePlugin->initializeProfile(fullPath, settings); findProfileSettings(); } catch (...) { // clean up in case of an error shellDelete(QStringList(profileBase.absoluteFilePath(fixedName))); throw; } refreshModStatus(); } Profile::Profile(const QDir &directory, IPluginGame const *gamePlugin) : m_Directory(directory) , m_GamePlugin(gamePlugin) , m_ModListWriter(std::bind(&Profile::doWriteModlist, this)) { assert(gamePlugin != nullptr); m_Settings = new QSettings(directory.absoluteFilePath("settings.ini"), QSettings::IniFormat); findProfileSettings(); if (!QFile::exists(m_Directory.filePath("modlist.txt"))) { qWarning("missing modlist.txt in %s", qUtf8Printable(directory.path())); touchFile(m_Directory.filePath("modlist.txt")); } IPluginGame::ProfileSettings settings = IPluginGame::MODS | IPluginGame::SAVEGAMES; gamePlugin->initializeProfile(directory, settings); refreshModStatus(); } Profile::Profile(const Profile &reference) : m_Directory(reference.m_Directory) , m_ModListWriter(std::bind(&Profile::doWriteModlist, this)) , m_GamePlugin(reference.m_GamePlugin) { m_Settings = new QSettings(m_Directory.absoluteFilePath("settings.ini"), QSettings::IniFormat); findProfileSettings(); refreshModStatus(); } Profile::~Profile() { delete m_Settings; m_ModListWriter.writeImmediately(true); } void Profile::findProfileSettings() { if (setting("LocalSaves") == QVariant()) { if (m_Directory.exists("saves")) { storeSetting("LocalSaves", true); } else { if (m_Directory.exists("_saves")) { m_Directory.rename("_saves", "saves"); } storeSetting("LocalSaves", false); } } if (setting("LocalSettings") == QVariant()) { QString backupFile = getIniFileName() + "_"; if (m_Directory.exists(backupFile)) { storeSetting("LocalSettings", false); m_Directory.rename(backupFile, getIniFileName()); } else { storeSetting("LocalSettings", true); } } if (setting("AutomaticArchiveInvalidation") == QVariant()) { BSAInvalidation *invalidation = m_GamePlugin->feature(); DataArchives *dataArchives = m_GamePlugin->feature(); bool found = false; if ((invalidation != nullptr) && (dataArchives != nullptr)) { for (const QString &archive : dataArchives->archives(this)) { if (invalidation->isInvalidationBSA(archive)) { storeSetting("AutomaticArchiveInvalidation", true); found = true; break; } } } if (!found) { storeSetting("AutomaticArchiveInvalidation", false); } } } bool Profile::exists() const { return m_Directory.exists(); } void Profile::writeModlist() { m_ModListWriter.write(); } void Profile::writeModlistNow(bool onlyIfPending) { m_ModListWriter.writeImmediately(onlyIfPending); } void Profile::cancelModlistWrite() { m_ModListWriter.cancel(); } void Profile::doWriteModlist() { if (!m_Directory.exists()) return; try { QString fileName = getModlistFileName(); SafeWriteFile file(fileName); file->write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); if (m_ModStatus.empty()) { return; } for (std::map::const_reverse_iterator iter = m_ModIndexByPriority.crbegin(); iter != m_ModIndexByPriority.crend(); iter++ ) { //qDebug(QString("write mod %1 to priority %2").arg(iter->first).arg(iter->second).toLocal8Bit()); // the priority order was inverted on load so it has to be inverted again unsigned int index = iter->second; if (index != UINT_MAX) { ModInfo::Ptr modInfo = ModInfo::getByIndex(index); std::vector flags = modInfo->getFlags(); if ((modInfo->getFixedPriority() == INT_MIN)) { if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) != flags.end()) { file->write("*"); } else if (m_ModStatus[index].m_Enabled) { file->write("+"); } else { file->write("-"); } file->write(modInfo->name().toUtf8()); file->write("\r\n"); } } } if (file.commitIfDifferent(m_LastModlistHash)) { qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(fileName))); } } catch (const std::exception &e) { reportError(tr("failed to write mod list: %1").arg(e.what())); return; } } void Profile::createTweakedIniFile() { QString tweakedIni = m_Directory.absoluteFilePath("initweaks.ini"); if (QFile::exists(tweakedIni) && !shellDeleteQuiet(tweakedIni)) { reportError(tr("failed to update tweaked ini file, wrong settings may be used: %1").arg(windowsErrorString(::GetLastError()))); return; } for (int i = getPriorityMinimum(); i < getPriorityMinimum() + (int)numRegularMods(); ++i) { unsigned int idx = modIndexByPriority(i); if (m_ModStatus[idx].m_Enabled) { ModInfo::Ptr modInfo = ModInfo::getByIndex(idx); mergeTweaks(modInfo, tweakedIni); } } mergeTweak(getProfileTweaks(), tweakedIni); bool error = false; if (!MOBase::WriteRegistryValue(L"Archive", L"bInvalidateOlderFiles", L"1", ToWString(tweakedIni).c_str())) { error = true; } if (error) { reportError(tr("failed to create tweaked ini: %1").arg(getCurrentErrorString().c_str())); } qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(tweakedIni))); } // static void Profile::renameModInAllProfiles(const QString& oldName, const QString& newName) { QDir profilesDir(Settings::instance().getProfileDirectory()); profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); QDirIterator profileIter(profilesDir); while (profileIter.hasNext()) { profileIter.next(); QFile modList(profileIter.filePath() + "/modlist.txt"); if (modList.exists()) renameModInList(modList, oldName, newName); else qWarning("Profile has no modlist.txt : %s", qUtf8Printable(profileIter.filePath())); } } // static void Profile::renameModInList(QFile &modList, const QString &oldName, const QString &newName) { if (!modList.open(QIODevice::ReadOnly)) { reportError(tr("failed to open %1").arg(modList.fileName())); return; } QBuffer outBuffer; outBuffer.open(QIODevice::WriteOnly); int renamed = 0; while (!modList.atEnd()) { QByteArray line = modList.readLine(); if (line.length() == 0) { // ignore empty lines qWarning("mod list contained invalid data: empty line"); continue; } char spec = line.at(0); if (spec == '#') { // don't touch comments outBuffer.write(line); continue; } QString modName = QString::fromUtf8(line).mid(1).trimmed(); if (modName.isEmpty()) { // file broken? qWarning("mod list contained invalid data: missing mod name"); continue; } outBuffer.write(QByteArray(1, spec)); if (modName == oldName) { modName = newName; ++renamed; } outBuffer.write(qUtf8Printable(modName)); outBuffer.write("\r\n"); } modList.close(); if (renamed) { modList.open(QIODevice::WriteOnly); modList.write(outBuffer.buffer()); modList.close(); } if (renamed) qDebug("Renamed %d \"%s\" mod to \"%s\" in %s", renamed, qUtf8Printable(oldName), qUtf8Printable(newName), qUtf8Printable(modList.fileName())); } void Profile::refreshModStatus() { writeModlistNow(true); // if there are pending changes write them first QFile file(getModlistFileName()); if (!file.open(QIODevice::ReadOnly)) { throw MyException(tr("\"%1\" is missing or inaccessible").arg(getModlistFileName())); } bool modStatusModified = false; m_ModStatus.clear(); m_ModStatus.resize(ModInfo::getNumMods()); std::set namesRead; // load mods from file and update enabled state and priority for them int index = 0; while (!file.atEnd()) { QByteArray line = file.readLine().trimmed(); bool enabled = true; QString modName; if (line.length() == 0) { // empty line continue; } else if (line.at(0) == '#') { // comment line continue; } else if (line.at(0) == '-') { enabled = false; modName = QString::fromUtf8(line.mid(1).trimmed().constData()); } else if ((line.at(0) == '+') || (line.at(0) == '*')) { modName = QString::fromUtf8(line.mid(1).trimmed().constData()); } else { modName = QString::fromUtf8(line.trimmed().constData()); } if (modName.size() > 0) { QString lookupName = modName; if (namesRead.find(lookupName) != namesRead.end()) { continue; } else { namesRead.insert(lookupName); } unsigned int modIndex = ModInfo::getIndex(lookupName); if (modIndex != UINT_MAX) { ModInfo::Ptr info = ModInfo::getByIndex(modIndex); if ((modIndex < m_ModStatus.size()) && (info->getFixedPriority() == INT_MIN)) { m_ModStatus[modIndex].m_Enabled = enabled; if (m_ModStatus[modIndex].m_Priority == -1) { if (static_cast(index) >= m_ModStatus.size()) { throw MyException(tr("invalid mod index: %1").arg(index)); } m_ModStatus[modIndex].m_Priority = index++; } } else { qWarning("no mod state for \"%s\" (profile \"%s\")", qUtf8Printable(modName), qUtf8Printable(m_Directory.path())); // need to rewrite the modlist to fix this modStatusModified = true; } } else { qDebug("mod not found: \"%s\" (profile \"%s\")", qUtf8Printable(modName), qUtf8Printable(m_Directory.path())); // need to rewrite the modlist to fix this modStatusModified = true; } } } int numKnownMods = index; int topInsert = 0; // invert priority order to match that of the pluginlist. Also // give priorities to mods not referenced in the profile for (size_t i = 0; i < m_ModStatus.size(); ++i) { ModInfo::Ptr modInfo = ModInfo::getByIndex(static_cast(i)); if (modInfo->alwaysEnabled()) { m_ModStatus[i].m_Enabled = true; } if (modInfo->getFixedPriority() == INT_MAX) { continue; } if (m_ModStatus[i].m_Priority != -1) { m_ModStatus[i].m_Priority = numKnownMods - m_ModStatus[i].m_Priority - 1; } else { if (static_cast(index) >= m_ModStatus.size()) { throw MyException(tr("invalid mod index: %1").arg(index)); } if (modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { m_ModStatus[i].m_Priority = --topInsert; } else { m_ModStatus[i].m_Priority = index++; } // also, mark the mod-list as changed modStatusModified = true; } } // to support insertion of new mods at the top we may now have mods with negative priority. shift them all up // to align priority with 0 if (topInsert < 0) { int offset = topInsert * -1; for (size_t i = 0; i < m_ModStatus.size(); ++i) { ModInfo::Ptr modInfo = ModInfo::getByIndex(static_cast(i)); if (modInfo->getFixedPriority() == INT_MAX) { continue; } m_ModStatus[i].m_Priority += offset; } } file.close(); updateIndices(); if (modStatusModified) { m_ModListWriter.write(); } } void Profile::dumpModStatus() const { for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { ModInfo::Ptr info = ModInfo::getByIndex(i); qWarning("%d: %s - %d (%s)", i, info->name().toUtf8().constData(), m_ModStatus[i].m_Priority, m_ModStatus[i].m_Enabled ? "enabled" : "disabled"); } } void Profile::updateIndices() { m_NumRegularMods = 0; m_ModIndexByPriority.clear(); for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { int priority = m_ModStatus[i].m_Priority; if (priority == INT_MIN) { // don't assign this to mapping at all, it's probably the overwrite mod continue; } else { ++m_NumRegularMods; m_ModIndexByPriority[priority] = i; } } } std::vector > Profile::getActiveMods() { std::vector > result; for (std::map::const_iterator iter = m_ModIndexByPriority.begin(); iter != m_ModIndexByPriority.end(); iter++ ) { if ((iter->second != UINT_MAX) && m_ModStatus[iter->second].m_Enabled) { ModInfo::Ptr modInfo = ModInfo::getByIndex(iter->second); if (modInfo->hasFlag(ModInfo::FLAG_OVERWRITE)) result.push_back(std::make_tuple(modInfo->internalName(), modInfo->absolutePath(), INT_MAX)); else result.push_back(std::make_tuple(modInfo->internalName(), modInfo->absolutePath(), m_ModStatus[iter->second].m_Priority)); } } return result; } unsigned int Profile::modIndexByPriority(int priority) const { try { return m_ModIndexByPriority.at(priority); } catch (std::out_of_range) { throw MyException(tr("invalid priority %1").arg(priority)); } } void Profile::setModEnabled(unsigned int index, bool enabled) { if (index >= m_ModStatus.size()) { throw MyException(tr("invalid mod index: %1").arg(index)); } ModInfo::Ptr modInfo = ModInfo::getByIndex(index); // we could quit in the following case, this shouldn't be a change anyway, // but at least this allows the situation to be fixed in case of an error if (modInfo->alwaysEnabled()) { enabled = true; } if (enabled != m_ModStatus[index].m_Enabled) { m_ModStatus[index].m_Enabled = enabled; emit modStatusChanged(index); } } void Profile::setModsEnabled(const QList &modsToEnable, const QList &modsToDisable) { QList dirtyMods; for (auto idx : modsToEnable) { if (idx >= m_ModStatus.size()) { qCritical() << tr("invalid mod index: %1").arg(idx); continue; } if (!m_ModStatus[idx].m_Enabled) { m_ModStatus[idx].m_Enabled = true; dirtyMods.append(idx); } } for (auto idx : modsToDisable) { if (idx >= m_ModStatus.size()) { qCritical() << tr("invalid mod index: %1").arg(idx); continue; } if (ModInfo::getByIndex(idx)->alwaysEnabled()) { continue; } if (m_ModStatus[idx].m_Enabled) { m_ModStatus[idx].m_Enabled = false; dirtyMods.append(idx); } } if (!dirtyMods.isEmpty()) { emit modStatusChanged(dirtyMods); } } bool Profile::modEnabled(unsigned int index) const { if (index >= m_ModStatus.size()) { throw MyException(tr("invalid mod index: %1").arg(index)); } return m_ModStatus[index].m_Enabled; } int Profile::getModPriority(unsigned int index) const { if (index >= m_ModStatus.size()) { throw MyException(tr("invalid mod index: %1").arg(index)); } return m_ModStatus[index].m_Priority; } void Profile::setModPriority(unsigned int index, int &newPriority) { if (m_ModStatus.at(index).m_Overwrite) { // can't change priority of the overwrite return; } int oldPriority = m_ModStatus.at(index).m_Priority; int lastPriority = INT_MIN; if (newPriority == oldPriority) { // nothing to do return; } for (std::map::iterator iter = m_ModIndexByPriority.begin(); iter != m_ModIndexByPriority.end(); iter++) { if (newPriority < oldPriority && iter->first >= newPriority && iter->first < oldPriority) { m_ModStatus.at(iter->second).m_Priority += 1; } else if (newPriority > oldPriority && iter->first <= newPriority && iter->first > oldPriority) { m_ModStatus.at(iter->second).m_Priority -= 1; } lastPriority = std::max(lastPriority, iter->first); } newPriority = std::min(newPriority, lastPriority); m_ModStatus.at(index).m_Priority = std::min(newPriority, lastPriority); updateIndices(); m_ModListWriter.write(); } Profile *Profile::createPtrFrom(const QString &name, const Profile &reference, MOBase::IPluginGame const *gamePlugin) { QString profileDirectory = Settings::instance().getProfileDirectory() + "/" + name; reference.copyFilesTo(profileDirectory); return new Profile(QDir(profileDirectory), gamePlugin); } void Profile::copyFilesTo(QString &target) const { copyDir(m_Directory.absolutePath(), target, false); } std::vector Profile::splitDZString(const wchar_t *buffer) const { std::vector result; const wchar_t *pos = buffer; size_t length = wcslen(pos); while (length != 0U) { result.push_back(pos); pos += length + 1; length = wcslen(pos); } return result; } void Profile::mergeTweak(const QString &tweakName, const QString &tweakedIni) const { static const int bufferSize = 32768; std::wstring tweakNameW = ToWString(tweakName); std::wstring tweakedIniW = ToWString(tweakedIni); QScopedArrayPointer buffer(new wchar_t[bufferSize]); // retrieve a list of sections DWORD size = ::GetPrivateProfileSectionNamesW( buffer.data(), bufferSize, tweakNameW.c_str()); if (size == bufferSize - 2) { // unfortunately there is no good way to find the required size // of the buffer throw MyException(QString("Buffer too small. Please report this as a bug. " "For now you might want to split up %1").arg(tweakName)); } std::vector sections = splitDZString(buffer.data()); // now iterate over all sections and retrieve a list of keys in each for (std::vector::iterator iter = sections.begin(); iter != sections.end(); ++iter) { // retrieve the names of all keys size = ::GetPrivateProfileStringW(iter->c_str(), nullptr, nullptr, buffer.data(), bufferSize, tweakNameW.c_str()); if (size == bufferSize - 2) { throw MyException(QString("Buffer too small. Please report this as a bug. " "For now you might want to split up %1").arg(tweakName)); } std::vector keys = splitDZString(buffer.data()); for (std::vector::iterator keyIter = keys.begin(); keyIter != keys.end(); ++keyIter) { //TODO this treats everything as strings but how could I differentiate the type? ::GetPrivateProfileStringW(iter->c_str(), keyIter->c_str(), nullptr, buffer.data(), bufferSize, ToWString(tweakName).c_str()); MOBase::WriteRegistryValue(iter->c_str(), keyIter->c_str(), buffer.data(), tweakedIniW.c_str()); } } } void Profile::mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const { std::vector iniTweaks = modInfo->getIniTweaks(); for (std::vector::iterator iter = iniTweaks.begin(); iter != iniTweaks.end(); ++iter) { mergeTweak(*iter, tweakedIni); } } bool Profile::invalidationActive(bool *supported) const { BSAInvalidation *invalidation = m_GamePlugin->feature(); DataArchives *dataArchives = m_GamePlugin->feature(); if (supported != nullptr) { *supported = ((invalidation != nullptr) && (dataArchives != nullptr)); } return setting("AutomaticArchiveInvalidation", false).toBool(); } void Profile::deactivateInvalidation() { BSAInvalidation *invalidation = m_GamePlugin->feature(); if (invalidation != nullptr) { invalidation->deactivate(this); } storeSetting("AutomaticArchiveInvalidation", false); } void Profile::activateInvalidation() { BSAInvalidation *invalidation = m_GamePlugin->feature(); if (invalidation != nullptr) { invalidation->activate(this); } storeSetting("AutomaticArchiveInvalidation", true); } bool Profile::localSavesEnabled() const { return setting("LocalSaves", false).toBool(); } bool Profile::enableLocalSaves(bool enable) { if (enable) { if (!m_Directory.exists("saves")) { m_Directory.mkdir("saves"); } } else { QDialogButtonBox::StandardButton res; res = QuestionBoxMemory::query(QApplication::activeModalWidget(), "deleteSavesQuery", tr("Delete profile-specific save games?"), tr("Do you want to delete the profile-specific save games? (If you select \"No\", the " "save games will show up again if you re-enable profile-specific save games)"), QDialogButtonBox::No | QDialogButtonBox::Yes | QDialogButtonBox::Cancel, QDialogButtonBox::No); if (res == QMessageBox::Yes) { shellDelete(QStringList(m_Directory.absoluteFilePath("saves"))); } else if (res == QMessageBox::No) { // No action } else { return false; } } storeSetting("LocalSaves", enable); return true; } bool Profile::localSettingsEnabled() const { bool enabled = setting("LocalSettings", false).toBool(); if (enabled) { QStringList missingFiles; for (QString file : m_GamePlugin->iniFiles()) { if (!QFile::exists(m_Directory.filePath(file))) { qWarning("missing %s in %s", qUtf8Printable(file), qUtf8Printable(m_Directory.path())); missingFiles << file; } } if (!missingFiles.empty()) { m_GamePlugin->initializeProfile(m_Directory, IPluginGame::CONFIGURATION); QMessageBox::StandardButton res = QMessageBox::warning( QApplication::activeModalWidget(), tr("Missing profile-specific game INI files!"), tr("Some of your profile-specific game INI files were missing. They will now be copied " "from the vanilla game folder. You might want to double-check your settings.\n\n" "Missing files:\n") + missingFiles.join("\n") ); } } return enabled; } bool Profile::enableLocalSettings(bool enable) { if (enable) { m_GamePlugin->initializeProfile(m_Directory.absolutePath(), IPluginGame::CONFIGURATION); } else { QDialogButtonBox::StandardButton res; res = QuestionBoxMemory::query(QApplication::activeModalWidget(), "deleteINIQuery", tr("Delete profile-specific game INI files?"), tr("Do you want to delete the profile-specific game INI files? (If you select \"No\", the " "INI files will be used again if you re-enable profile-specific game INI files.)"), QDialogButtonBox::No | QDialogButtonBox::Yes | QDialogButtonBox::Cancel, QDialogButtonBox::No); if (res == QMessageBox::Yes) { QStringList filesToDelete; for (QString file : m_GamePlugin->iniFiles()) { filesToDelete << m_Directory.absoluteFilePath(file); } shellDelete(filesToDelete, true); } else if (res == QMessageBox::No) { // No action } else { return false; } } storeSetting("LocalSettings", enable); return true; } QString Profile::getModlistFileName() const { return QDir::cleanPath(m_Directory.absoluteFilePath("modlist.txt")); } QString Profile::getPluginsFileName() const { return QDir::cleanPath(m_Directory.absoluteFilePath("plugins.txt")); } QString Profile::getLoadOrderFileName() const { return QDir::cleanPath(m_Directory.absoluteFilePath("loadorder.txt")); } QString Profile::getLockedOrderFileName() const { return QDir::cleanPath(m_Directory.absoluteFilePath("lockedorder.txt")); } QString Profile::getArchivesFileName() const { return QDir::cleanPath(m_Directory.absoluteFilePath("archives.txt")); } QString Profile::getDeleterFileName() const { return QDir::cleanPath(m_Directory.absoluteFilePath("hide_plugins.txt")); } QString Profile::getIniFileName() const { return m_Directory.absoluteFilePath(m_GamePlugin->iniFiles()[0]); } QString Profile::getProfileTweaks() const { return QDir::cleanPath(m_Directory.absoluteFilePath(ToQString(AppConfig::profileTweakIni()))); } QString Profile::absolutePath() const { return QDir::cleanPath(m_Directory.absolutePath()); } QString Profile::savePath() const { return QDir::cleanPath(m_Directory.absoluteFilePath("saves")); } void Profile::rename(const QString &newName) { QDir profileDir(Settings::instance().getProfileDirectory()); profileDir.rename(name(), newName); m_Directory = profileDir.absoluteFilePath(newName); } QVariant Profile::setting(const QString §ion, const QString &name, const QVariant &fallback) const { return m_Settings->value(section + "/" + name, fallback); } void Profile::storeSetting(const QString §ion, const QString &name, const QVariant &value) { m_Settings->setValue(section + "/" + name, value); } void Profile::storeSetting(const QString &name, const QVariant &value) { storeSetting("", name, value); } void Profile::removeSetting(const QString §ion, const QString &name) { m_Settings->remove(section + "/" + name); } void Profile::removeSetting(const QString &name) { removeSetting("", name); } QVariantMap Profile::settingsByGroup(const QString §ion) const { QVariantMap results; m_Settings->beginGroup(section); for (auto key : m_Settings->childKeys()) { results[key] = m_Settings->value(key); } m_Settings->endGroup(); return results; } void Profile::storeSettingsByGroup(const QString §ion, const QVariantMap &values) { m_Settings->beginGroup(section); for (auto key : values.keys()) { m_Settings->setValue(key, values[key]); } m_Settings->endGroup(); } QList Profile::settingsByArray(const QString &prefix) const { QList results; int size = m_Settings->beginReadArray(prefix); for (int i = 0; i < size; i++) { m_Settings->setArrayIndex(i); QVariantMap item; for (auto key : m_Settings->childKeys()) { item[key] = m_Settings->value(key); } results.append(item); } m_Settings->endArray(); return results; } void Profile::storeSettingsByArray(const QString &prefix, const QList &values) { m_Settings->beginWriteArray(prefix); for (int i = 0; i < values.length(); i++) { m_Settings->setArrayIndex(i); for (auto key : values.at(i).keys()) { m_Settings->setValue(key, values.at(i)[key]); } } m_Settings->endArray(); } int Profile::getPriorityMinimum() const { return m_ModIndexByPriority.begin()->first; } bool Profile::forcedLibrariesEnabled(const QString &executable) const { return setting("forced_libraries", executable + "/enabled", false).toBool(); } void Profile::setForcedLibrariesEnabled(const QString &executable, bool enabled) { storeSetting("forced_libraries", executable + "/enabled", enabled); } QList Profile::determineForcedLibraries(const QString &executable) const { QList results; auto rawSettings = settingsByArray("forced_libraries/" + executable); auto forcedLoads = m_GamePlugin->executableForcedLoads(); // look for enabled status on forced loads and add those for (auto forcedLoad : forcedLoads) { bool found = false; for (auto rawSetting : rawSettings) { if ((rawSetting.value("process").toString().compare(forcedLoad.process(), Qt::CaseInsensitive) == 0) && (rawSetting.value("library").toString().compare(forcedLoad.library(), Qt::CaseInsensitive) == 0)) { results.append(forcedLoad.withEnabled(rawSetting.value("enabled", false).toBool())); found = true; } } if (!found) { results.append(forcedLoad); } } // add everything else for (auto rawSetting : rawSettings) { bool add = true; for (auto forcedLoad : forcedLoads) { if ((rawSetting.value("process").toString().compare(forcedLoad.process(), Qt::CaseInsensitive) == 0) && (rawSetting.value("library").toString().compare(forcedLoad.library(), Qt::CaseInsensitive) == 0)) { add = false; } } if (add) { results.append( ExecutableForcedLoadSetting( rawSetting.value("process").toString(), rawSetting.value("library").toString() ).withEnabled(rawSetting.value("enabled", false).toBool()) ); } } return results; } void Profile::storeForcedLibraries(const QString &executable, const QList &values) { QList rawSettings; for (auto setting : values) { QVariantMap rawSetting; rawSetting["enabled"] = setting.enabled(); rawSetting["process"] = setting.process(); rawSetting["library"] = setting.library(); rawSettings.append(rawSetting); } storeSettingsByArray("forced_libraries/" + executable, rawSettings); } void Profile::removeForcedLibraries(const QString &executable) { m_Settings->remove("forced_libraries/" + executable); }