From cc9827cdda5776a4fd540a2ab0fb8a08ec9f75a7 Mon Sep 17 00:00:00 2001 From: Tannin Date: Thu, 17 Jul 2014 22:02:15 +0200 Subject: normalized eol style (all files should now have windows line endings) --- src/profile.cpp | 1564 +++++++++++++++++++++++++++---------------------------- 1 file changed, 782 insertions(+), 782 deletions(-) (limited to 'src/profile.cpp') diff --git a/src/profile.cpp b/src/profile.cpp index 2bbdea66..fbfb3266 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -1,782 +1,782 @@ -/* -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 "report.h" -#include "gameinfo.h" -#include "windows_error.h" -#include "dummybsa.h" -#include "modinfo.h" -#include "safewritefile.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#define WIN32_LEAN_AND_MEAN -#include -#include -#include - -using namespace MOBase; -using namespace MOShared; - -Profile::Profile() - : m_SaveTimer(NULL) -{ - initTimer(); -} - -Profile::Profile(const QString &name, bool useDefaultSettings) - : m_SaveTimer(NULL) -{ - initTimer(); - QString profilesDir = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())); - QDir profileBase(profilesDir); - - QString fixedName = name; - if (!fixDirectoryName(fixedName)) { - throw MyException(tr("invalid profile name %1").arg(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); - QFile modList(m_Directory.filePath("modlist.txt")); - if (!modList.open(QIODevice::ReadWrite)) { - profileBase.rmdir(fixedName); - throw std::runtime_error(QObject::tr("failed to create %1").arg(m_Directory.filePath("modlist.txt")).toUtf8().constData()); - } - modList.close(); - - try { - GameInfo::instance().createProfile(ToWString(fullPath), useDefaultSettings); - } catch (...) { - // clean up in case of an error - shellDelete(QStringList(profileBase.absoluteFilePath(fixedName))); - throw; - } - refreshModStatus(); -} - - -Profile::Profile(const QDir& directory) - : m_Directory(directory), m_SaveTimer(NULL) -{ - initTimer(); - if (!QFile::exists(m_Directory.filePath("modlist.txt"))) { - qWarning("missing modlist.txt in %s", qPrintable(directory.path())); - } - - GameInfo::instance().repairProfile(ToWString(m_Directory.absolutePath())); - - if (!QFile::exists(getIniFileName())) { - reportError(QObject::tr("\"%1\" is missing or inaccessible").arg(getIniFileName())); - } - refreshModStatus(); -} - - -Profile::Profile(const Profile& reference) - : m_Directory(reference.m_Directory), m_SaveTimer(NULL) -{ - initTimer(); - refreshModStatus(); -} - - -Profile::~Profile() -{ - writeModlistNow(); -} - - -void Profile::initTimer() -{ - m_SaveTimer = new QTimer(this); - m_SaveTimer->setSingleShot(true); - connect(m_SaveTimer, SIGNAL(timeout()), this, SLOT(writeModlistNow())); -} - - -bool Profile::exists() const -{ - return m_Directory.exists(); -} - - -void Profile::writeModlist() const -{ - if (!m_SaveTimer->isActive()) { - m_SaveTimer->start(2000); - } -} - - -void Profile::cancelWriteModlist() const -{ - m_SaveTimer->stop(); -} - - -void Profile::writeModlistNow(bool onlyOnTimer) const -{ - if (onlyOnTimer && !m_SaveTimer->isActive()) return; - - m_SaveTimer->stop(); - 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 (int i = m_ModStatus.size() - 1; i >= 0; --i) { - // the priority order was inverted on load so it has to be inverted again - unsigned int index = m_ModIndexByPriority[i]; - 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", QDir::toNativeSeparators(fileName).toUtf8().constData()); - } - } 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 (unsigned int i = 0; i < m_ModStatus.size(); ++i) { - if (m_ModStatus[i].m_Enabled) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - mergeTweaks(modInfo, tweakedIni); - } - } - - mergeTweak(getProfileTweaks(), tweakedIni); - - bool error = false; - if (!::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", ToWString(tweakedIni).c_str())) { - error = true; - } - - if (localSavesEnabled()) { - if (!::WritePrivateProfileStringW(L"General", L"bUseMyGamesDirectory", L"1", ToWString(tweakedIni).c_str())) { - error = true; - } - - if (!::WritePrivateProfileStringW(L"General", L"SLocalSavePath", - AppConfig::localSavePlaceholder(), - ToWString(tweakedIni).c_str())) { - error = true; - } - } - - if (error) { - reportError(tr("failed to create tweaked ini: %1").arg(getCurrentErrorStringA().c_str())); - } - qDebug("%s saved", qPrintable(QDir::toNativeSeparators(tweakedIni))); -} - - -void Profile::refreshModStatus() -{ - QFile file(getModlistFileName()); - if (!file.exists()) { - 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 - file.open(QIODevice::ReadOnly); - int index = 0; - while (!file.atEnd()) { - QByteArray line = file.readLine(); - 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) { - if (namesRead.find(modName) != namesRead.end()) { - continue; - } else { - namesRead.insert(modName); - } - unsigned int modindex = ModInfo::getIndex(modName); - 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 || info->alwaysEnabled(); - if (m_ModStatus[modindex].m_Priority == -1) { - if (static_cast(index) >= m_ModStatus.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } - m_ModStatus[modindex].m_Priority = index++; - } - } else { - qDebug("mod \"%s\" (profile \"%s\") not found", - modName.toUtf8().constData(), m_Directory.path().toUtf8().constData()); - // need to rewrite the modlist to fix this - modStatusModified = true; - } - } - } else { - // line was empty after trimming - } - } - - 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(i); - 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 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(i); - if (modInfo->getFixedPriority() == INT_MAX) { - continue; - } - - m_ModStatus[i].m_Priority += offset; - } - } - - file.close(); - updateIndices(); - if (modStatusModified) { - writeModlist(); - } -} - - -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(); - m_ModIndexByPriority.resize(m_ModStatus.size(), UINT_MAX); - for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { - int priority = m_ModStatus[i].m_Priority; - if (priority < 0) { - // don't assign this to mapping at all, it's probably the overwrite mod - continue; - } else if (priority >= static_cast(m_ModIndexByPriority.size())) { - qCritical("invalid priority %d for mod", priority); - continue; - } else { - ++m_NumRegularMods; - m_ModIndexByPriority.at(priority) = i; - } - } -} - - -std::vector > Profile::getActiveMods() -{ - std::vector > result; - for (std::vector::const_iterator iter = m_ModIndexByPriority.begin(); - iter != m_ModIndexByPriority.end(); ++iter) { - if ((*iter != UINT_MAX) && m_ModStatus[*iter].m_Enabled) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(*iter); - result.push_back(std::make_tuple(modInfo->name(), modInfo->absolutePath(), m_ModStatus[*iter].m_Priority)); - } - } - - unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { - std::vector flags = mod->getFlags(); - return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); }); - - if (overwriteIndex != UINT_MAX) { - ModInfo::Ptr overwriteInfo = ModInfo::getByIndex(overwriteIndex); - result.push_back(std::make_tuple(overwriteInfo->name(), overwriteInfo->absolutePath(), UINT_MAX)); - } else { - reportError(tr("Overwrite directory couldn't be parsed")); - } - return result; -} - - -unsigned int Profile::modIndexByPriority(unsigned int priority) const -{ - if (priority >= m_ModStatus.size()) { - throw MyException(tr("invalid priority %1").arg(priority)); - } - - return m_ModIndexByPriority[priority]; -} - - -void Profile::setModEnabled(unsigned int index, bool enabled) -{ - if (index >= m_ModStatus.size()) { - throw MyException(tr("invalid 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); - } -} - - -bool Profile::modEnabled(unsigned int index) const -{ - if (index >= m_ModStatus.size()) { - throw MyException(tr("invalid 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 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 newPriorityTemp = (std::max)(0, (std::min)(m_ModStatus.size() - 1, newPriority)); - - // don't try to place below overwrite - while ((m_ModIndexByPriority.at(newPriorityTemp) >= m_ModStatus.size()) || - m_ModStatus.at(m_ModIndexByPriority.at(newPriorityTemp)).m_Overwrite) { - --newPriorityTemp; - } - - int oldPriority = m_ModStatus.at(index).m_Priority; - if (newPriorityTemp > oldPriority) { - // priority is higher than the old, so the gap we left is in lower priorities - for (int i = oldPriority + 1; i <= newPriorityTemp; ++i) { - --m_ModStatus.at(m_ModIndexByPriority.at(i)).m_Priority; - } - } else { - for (int i = newPriorityTemp; i < oldPriority; ++i) { - ++m_ModStatus.at(m_ModIndexByPriority.at(i)).m_Priority; - } - ++newPriority; - } - - m_ModStatus.at(index).m_Priority = newPriorityTemp; - - updateIndices(); - writeModlist(); -} - - -Profile Profile::createFrom(const QString &name, const Profile &reference) -{ - QString profileDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())).append("/").append(name); - reference.copyFilesTo(profileDirectory); - return Profile(QDir(profileDirectory)); -} - - -Profile *Profile::createPtrFrom(const QString &name, const Profile &reference) -{ - QString profileDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())).append("/").append(name); - reference.copyFilesTo(profileDirectory); - return new Profile(QDir(profileDirectory)); -} - - -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(), NULL, NULL, 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(), - NULL, buffer.data(), bufferSize, ToWString(tweakName).c_str()); - ::WritePrivateProfileStringW(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 -{ - if (GameInfo::instance().requiresBSAInvalidation()) { - *supported = true; - wchar_t buffer[1024]; - std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); - // epic ms fail: GetPrivateProfileString uses errno (for whatever reason) to signal a fail since the return value - // has a different meaning (number of bytes copied). HOWEVER, it will not set errno to 0 if NO error occured - errno = 0; - if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - L"", buffer, 1024, iniFileName.c_str()) == 0) { - if (errno != 0x02) { - if (supported != NULL) { - *supported = false; - } - return false; - } else { - QString errorMessage = tr("failed to parse ini file (%1)").arg(ToQString(iniFileName)); - throw windows_error(errorMessage.toUtf8().constData()); - } - } - QStringList archives = ToQString(buffer).split(','); - - for (int i = 0; i < archives.count(); ++i) { - QString bsaName = archives.at(i).trimmed(); - if (GameInfo::instance().isInvalidationBSA(ToWString(bsaName))) { - return true; - } - } - } else { - *supported = false; - } - return false; -} - - -void Profile::deactivateInvalidation() const -{ - if (GameInfo::instance().requiresBSAInvalidation()) { - wchar_t buffer[1024]; - std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); - errno = 0; - if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - L"", buffer, 1024, iniFileName.c_str()) == 0) { - if (errno == 0x02) { - QString errorMessage = tr("failed to parse ini file (%1): %2").arg(QDir::toNativeSeparators(getIniFileName())).arg(::GetLastError()); - throw windows_error(errorMessage.toUtf8().constData()); - } else { - return; - } - } - QStringList archives = ToQString(buffer).split(", "); - - for (int i = 0; i < archives.count();) { - QString bsaName = archives.at(i).trimmed(); - if (GameInfo::instance().isInvalidationBSA(ToWString(bsaName))) { - archives.removeAt(i); - } else { - ++i; - } - } - - // just to be safe... - ::SetFileAttributesW(iniFileName.c_str(), FILE_ATTRIBUTE_NORMAL); - - if (!::WritePrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - ToWString(archives.join(", ").toUtf8()).c_str(), iniFileName.c_str()) || - !::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"0", iniFileName.c_str()) || - !::WritePrivateProfileStringW(L"Archive", L"SInvalidationFile", L"ArchiveInvalidation.txt", iniFileName.c_str())) { - QString errorMessage = tr("failed to modify \"%1\"").arg(ToQString(iniFileName)); - throw windows_error(errorMessage.toUtf8().constData()); - } - } -} - - -void Profile::activateInvalidation(const QString& dataDirectory) const -{ - if (GameInfo::instance().requiresBSAInvalidation()) { - wchar_t buffer[1024]; - std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); - errno = 0; - if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - L"", buffer, 1024, iniFileName.c_str()) == 0) { - if (errno == 0x02) { - throw windows_error("failed to parse ini file"); - } else { - // ignore. shouldn't have gotten here anyway - return; - } - } - QStringList archives = ToQString(buffer).split(", "); - - QString invalidationBSA = ToQString(GameInfo::instance().getInvalidationBSA()); - - if (!archives.contains(invalidationBSA)) { - archives.insert(0, invalidationBSA); - } - - // just to be safe... - ::SetFileAttributesW(iniFileName.c_str(), FILE_ATTRIBUTE_NORMAL); - - if (!::WritePrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - ToWString(archives.join(", ").toUtf8()).c_str(), iniFileName.c_str()) || - !::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", iniFileName.c_str()) || - !::WritePrivateProfileStringW(L"Archive", L"SInvalidationFile", L"", iniFileName.c_str())) { - QString errorMessage = tr("failed to modify \"%1\"").arg(ToQString(iniFileName)); - throw windows_error(errorMessage.toUtf8().constData()); - } - - QString bsaFile = dataDirectory + "/" + invalidationBSA; - if (!QFile::exists(bsaFile)) { - DummyBSA bsa; - bsa.write(bsaFile); - } - } -} - - -bool Profile::localSavesEnabled() const -{ - return m_Directory.exists("saves"); -} - - -bool Profile::enableLocalSaves(bool enable) -{ - if (enable) { - if (m_Directory.exists("_saves")) { - m_Directory.rename("_saves", "saves"); - } else { - m_Directory.mkdir("saves"); - } - } else { - QMessageBox::StandardButton res = QMessageBox::question(QApplication::activeModalWidget(), tr("Delete savegames?"), - tr("Do you want to delete local savegames? (If you select \"No\", the save games " - "will show up again if you re-enable local savegames)"), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, - QMessageBox::Cancel); - if (res == QMessageBox::Yes) { - shellDelete(QStringList(m_Directory.absoluteFilePath("saves"))); - } else if (res == QMessageBox::No) { - m_Directory.rename("saves", "_saves"); - } else { - return false; - } - } - - // default: assume success - 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 -{ - std::wstring primaryIniFile = *(GameInfo::instance().getIniFileNames().begin()); - return m_Directory.absoluteFilePath(ToQString(primaryIniFile)); -} - -QString Profile::getProfileTweaks() const -{ - return QDir::cleanPath(m_Directory.absoluteFilePath(ToQString(AppConfig::profileTweakIni()))); -} - -QString Profile::getPath() const -{ - return QDir::cleanPath(m_Directory.absolutePath()); -} - -void Profile::rename(const QString &newName) -{ - QDir profileDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir()))); - profileDir.rename(getName(), newName); - m_Directory = profileDir.absoluteFilePath(newName); -} +/* +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 "report.h" +#include "gameinfo.h" +#include "windows_error.h" +#include "dummybsa.h" +#include "modinfo.h" +#include "safewritefile.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + +using namespace MOBase; +using namespace MOShared; + +Profile::Profile() + : m_SaveTimer(NULL) +{ + initTimer(); +} + +Profile::Profile(const QString &name, bool useDefaultSettings) + : m_SaveTimer(NULL) +{ + initTimer(); + QString profilesDir = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())); + QDir profileBase(profilesDir); + + QString fixedName = name; + if (!fixDirectoryName(fixedName)) { + throw MyException(tr("invalid profile name %1").arg(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); + QFile modList(m_Directory.filePath("modlist.txt")); + if (!modList.open(QIODevice::ReadWrite)) { + profileBase.rmdir(fixedName); + throw std::runtime_error(QObject::tr("failed to create %1").arg(m_Directory.filePath("modlist.txt")).toUtf8().constData()); + } + modList.close(); + + try { + GameInfo::instance().createProfile(ToWString(fullPath), useDefaultSettings); + } catch (...) { + // clean up in case of an error + shellDelete(QStringList(profileBase.absoluteFilePath(fixedName))); + throw; + } + refreshModStatus(); +} + + +Profile::Profile(const QDir& directory) + : m_Directory(directory), m_SaveTimer(NULL) +{ + initTimer(); + if (!QFile::exists(m_Directory.filePath("modlist.txt"))) { + qWarning("missing modlist.txt in %s", qPrintable(directory.path())); + } + + GameInfo::instance().repairProfile(ToWString(m_Directory.absolutePath())); + + if (!QFile::exists(getIniFileName())) { + reportError(QObject::tr("\"%1\" is missing or inaccessible").arg(getIniFileName())); + } + refreshModStatus(); +} + + +Profile::Profile(const Profile& reference) + : m_Directory(reference.m_Directory), m_SaveTimer(NULL) +{ + initTimer(); + refreshModStatus(); +} + + +Profile::~Profile() +{ + writeModlistNow(); +} + + +void Profile::initTimer() +{ + m_SaveTimer = new QTimer(this); + m_SaveTimer->setSingleShot(true); + connect(m_SaveTimer, SIGNAL(timeout()), this, SLOT(writeModlistNow())); +} + + +bool Profile::exists() const +{ + return m_Directory.exists(); +} + + +void Profile::writeModlist() const +{ + if (!m_SaveTimer->isActive()) { + m_SaveTimer->start(2000); + } +} + + +void Profile::cancelWriteModlist() const +{ + m_SaveTimer->stop(); +} + + +void Profile::writeModlistNow(bool onlyOnTimer) const +{ + if (onlyOnTimer && !m_SaveTimer->isActive()) return; + + m_SaveTimer->stop(); + 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 (int i = m_ModStatus.size() - 1; i >= 0; --i) { + // the priority order was inverted on load so it has to be inverted again + unsigned int index = m_ModIndexByPriority[i]; + 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", QDir::toNativeSeparators(fileName).toUtf8().constData()); + } + } 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 (unsigned int i = 0; i < m_ModStatus.size(); ++i) { + if (m_ModStatus[i].m_Enabled) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + mergeTweaks(modInfo, tweakedIni); + } + } + + mergeTweak(getProfileTweaks(), tweakedIni); + + bool error = false; + if (!::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", ToWString(tweakedIni).c_str())) { + error = true; + } + + if (localSavesEnabled()) { + if (!::WritePrivateProfileStringW(L"General", L"bUseMyGamesDirectory", L"1", ToWString(tweakedIni).c_str())) { + error = true; + } + + if (!::WritePrivateProfileStringW(L"General", L"SLocalSavePath", + AppConfig::localSavePlaceholder(), + ToWString(tweakedIni).c_str())) { + error = true; + } + } + + if (error) { + reportError(tr("failed to create tweaked ini: %1").arg(getCurrentErrorStringA().c_str())); + } + qDebug("%s saved", qPrintable(QDir::toNativeSeparators(tweakedIni))); +} + + +void Profile::refreshModStatus() +{ + QFile file(getModlistFileName()); + if (!file.exists()) { + 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 + file.open(QIODevice::ReadOnly); + int index = 0; + while (!file.atEnd()) { + QByteArray line = file.readLine(); + 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) { + if (namesRead.find(modName) != namesRead.end()) { + continue; + } else { + namesRead.insert(modName); + } + unsigned int modindex = ModInfo::getIndex(modName); + 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 || info->alwaysEnabled(); + if (m_ModStatus[modindex].m_Priority == -1) { + if (static_cast(index) >= m_ModStatus.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + m_ModStatus[modindex].m_Priority = index++; + } + } else { + qDebug("mod \"%s\" (profile \"%s\") not found", + modName.toUtf8().constData(), m_Directory.path().toUtf8().constData()); + // need to rewrite the modlist to fix this + modStatusModified = true; + } + } + } else { + // line was empty after trimming + } + } + + 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(i); + 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 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(i); + if (modInfo->getFixedPriority() == INT_MAX) { + continue; + } + + m_ModStatus[i].m_Priority += offset; + } + } + + file.close(); + updateIndices(); + if (modStatusModified) { + writeModlist(); + } +} + + +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(); + m_ModIndexByPriority.resize(m_ModStatus.size(), UINT_MAX); + for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { + int priority = m_ModStatus[i].m_Priority; + if (priority < 0) { + // don't assign this to mapping at all, it's probably the overwrite mod + continue; + } else if (priority >= static_cast(m_ModIndexByPriority.size())) { + qCritical("invalid priority %d for mod", priority); + continue; + } else { + ++m_NumRegularMods; + m_ModIndexByPriority.at(priority) = i; + } + } +} + + +std::vector > Profile::getActiveMods() +{ + std::vector > result; + for (std::vector::const_iterator iter = m_ModIndexByPriority.begin(); + iter != m_ModIndexByPriority.end(); ++iter) { + if ((*iter != UINT_MAX) && m_ModStatus[*iter].m_Enabled) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(*iter); + result.push_back(std::make_tuple(modInfo->name(), modInfo->absolutePath(), m_ModStatus[*iter].m_Priority)); + } + } + + unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { + std::vector flags = mod->getFlags(); + return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); }); + + if (overwriteIndex != UINT_MAX) { + ModInfo::Ptr overwriteInfo = ModInfo::getByIndex(overwriteIndex); + result.push_back(std::make_tuple(overwriteInfo->name(), overwriteInfo->absolutePath(), UINT_MAX)); + } else { + reportError(tr("Overwrite directory couldn't be parsed")); + } + return result; +} + + +unsigned int Profile::modIndexByPriority(unsigned int priority) const +{ + if (priority >= m_ModStatus.size()) { + throw MyException(tr("invalid priority %1").arg(priority)); + } + + return m_ModIndexByPriority[priority]; +} + + +void Profile::setModEnabled(unsigned int index, bool enabled) +{ + if (index >= m_ModStatus.size()) { + throw MyException(tr("invalid 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); + } +} + + +bool Profile::modEnabled(unsigned int index) const +{ + if (index >= m_ModStatus.size()) { + throw MyException(tr("invalid 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 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 newPriorityTemp = (std::max)(0, (std::min)(m_ModStatus.size() - 1, newPriority)); + + // don't try to place below overwrite + while ((m_ModIndexByPriority.at(newPriorityTemp) >= m_ModStatus.size()) || + m_ModStatus.at(m_ModIndexByPriority.at(newPriorityTemp)).m_Overwrite) { + --newPriorityTemp; + } + + int oldPriority = m_ModStatus.at(index).m_Priority; + if (newPriorityTemp > oldPriority) { + // priority is higher than the old, so the gap we left is in lower priorities + for (int i = oldPriority + 1; i <= newPriorityTemp; ++i) { + --m_ModStatus.at(m_ModIndexByPriority.at(i)).m_Priority; + } + } else { + for (int i = newPriorityTemp; i < oldPriority; ++i) { + ++m_ModStatus.at(m_ModIndexByPriority.at(i)).m_Priority; + } + ++newPriority; + } + + m_ModStatus.at(index).m_Priority = newPriorityTemp; + + updateIndices(); + writeModlist(); +} + + +Profile Profile::createFrom(const QString &name, const Profile &reference) +{ + QString profileDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())).append("/").append(name); + reference.copyFilesTo(profileDirectory); + return Profile(QDir(profileDirectory)); +} + + +Profile *Profile::createPtrFrom(const QString &name, const Profile &reference) +{ + QString profileDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())).append("/").append(name); + reference.copyFilesTo(profileDirectory); + return new Profile(QDir(profileDirectory)); +} + + +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(), NULL, NULL, 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(), + NULL, buffer.data(), bufferSize, ToWString(tweakName).c_str()); + ::WritePrivateProfileStringW(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 +{ + if (GameInfo::instance().requiresBSAInvalidation()) { + *supported = true; + wchar_t buffer[1024]; + std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); + // epic ms fail: GetPrivateProfileString uses errno (for whatever reason) to signal a fail since the return value + // has a different meaning (number of bytes copied). HOWEVER, it will not set errno to 0 if NO error occured + errno = 0; + if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + L"", buffer, 1024, iniFileName.c_str()) == 0) { + if (errno != 0x02) { + if (supported != NULL) { + *supported = false; + } + return false; + } else { + QString errorMessage = tr("failed to parse ini file (%1)").arg(ToQString(iniFileName)); + throw windows_error(errorMessage.toUtf8().constData()); + } + } + QStringList archives = ToQString(buffer).split(','); + + for (int i = 0; i < archives.count(); ++i) { + QString bsaName = archives.at(i).trimmed(); + if (GameInfo::instance().isInvalidationBSA(ToWString(bsaName))) { + return true; + } + } + } else { + *supported = false; + } + return false; +} + + +void Profile::deactivateInvalidation() const +{ + if (GameInfo::instance().requiresBSAInvalidation()) { + wchar_t buffer[1024]; + std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); + errno = 0; + if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + L"", buffer, 1024, iniFileName.c_str()) == 0) { + if (errno == 0x02) { + QString errorMessage = tr("failed to parse ini file (%1): %2").arg(QDir::toNativeSeparators(getIniFileName())).arg(::GetLastError()); + throw windows_error(errorMessage.toUtf8().constData()); + } else { + return; + } + } + QStringList archives = ToQString(buffer).split(", "); + + for (int i = 0; i < archives.count();) { + QString bsaName = archives.at(i).trimmed(); + if (GameInfo::instance().isInvalidationBSA(ToWString(bsaName))) { + archives.removeAt(i); + } else { + ++i; + } + } + + // just to be safe... + ::SetFileAttributesW(iniFileName.c_str(), FILE_ATTRIBUTE_NORMAL); + + if (!::WritePrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + ToWString(archives.join(", ").toUtf8()).c_str(), iniFileName.c_str()) || + !::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"0", iniFileName.c_str()) || + !::WritePrivateProfileStringW(L"Archive", L"SInvalidationFile", L"ArchiveInvalidation.txt", iniFileName.c_str())) { + QString errorMessage = tr("failed to modify \"%1\"").arg(ToQString(iniFileName)); + throw windows_error(errorMessage.toUtf8().constData()); + } + } +} + + +void Profile::activateInvalidation(const QString& dataDirectory) const +{ + if (GameInfo::instance().requiresBSAInvalidation()) { + wchar_t buffer[1024]; + std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); + errno = 0; + if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + L"", buffer, 1024, iniFileName.c_str()) == 0) { + if (errno == 0x02) { + throw windows_error("failed to parse ini file"); + } else { + // ignore. shouldn't have gotten here anyway + return; + } + } + QStringList archives = ToQString(buffer).split(", "); + + QString invalidationBSA = ToQString(GameInfo::instance().getInvalidationBSA()); + + if (!archives.contains(invalidationBSA)) { + archives.insert(0, invalidationBSA); + } + + // just to be safe... + ::SetFileAttributesW(iniFileName.c_str(), FILE_ATTRIBUTE_NORMAL); + + if (!::WritePrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + ToWString(archives.join(", ").toUtf8()).c_str(), iniFileName.c_str()) || + !::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", iniFileName.c_str()) || + !::WritePrivateProfileStringW(L"Archive", L"SInvalidationFile", L"", iniFileName.c_str())) { + QString errorMessage = tr("failed to modify \"%1\"").arg(ToQString(iniFileName)); + throw windows_error(errorMessage.toUtf8().constData()); + } + + QString bsaFile = dataDirectory + "/" + invalidationBSA; + if (!QFile::exists(bsaFile)) { + DummyBSA bsa; + bsa.write(bsaFile); + } + } +} + + +bool Profile::localSavesEnabled() const +{ + return m_Directory.exists("saves"); +} + + +bool Profile::enableLocalSaves(bool enable) +{ + if (enable) { + if (m_Directory.exists("_saves")) { + m_Directory.rename("_saves", "saves"); + } else { + m_Directory.mkdir("saves"); + } + } else { + QMessageBox::StandardButton res = QMessageBox::question(QApplication::activeModalWidget(), tr("Delete savegames?"), + tr("Do you want to delete local savegames? (If you select \"No\", the save games " + "will show up again if you re-enable local savegames)"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, + QMessageBox::Cancel); + if (res == QMessageBox::Yes) { + shellDelete(QStringList(m_Directory.absoluteFilePath("saves"))); + } else if (res == QMessageBox::No) { + m_Directory.rename("saves", "_saves"); + } else { + return false; + } + } + + // default: assume success + 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 +{ + std::wstring primaryIniFile = *(GameInfo::instance().getIniFileNames().begin()); + return m_Directory.absoluteFilePath(ToQString(primaryIniFile)); +} + +QString Profile::getProfileTweaks() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath(ToQString(AppConfig::profileTweakIni()))); +} + +QString Profile::getPath() const +{ + return QDir::cleanPath(m_Directory.absolutePath()); +} + +void Profile::rename(const QString &newName) +{ + QDir profileDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir()))); + profileDir.rename(getName(), newName); + m_Directory = profileDir.absoluteFilePath(newName); +} -- cgit v1.3.1