/* 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 "executableslist.h" #include "iplugingame.h" #include "settings.h" #include "utility.h" #include #include #include #include #include #include using namespace MOBase; ExecutablesList::iterator ExecutablesList::begin() { return m_Executables.begin(); } ExecutablesList::const_iterator ExecutablesList::begin() const { return m_Executables.begin(); } ExecutablesList::iterator ExecutablesList::end() { return m_Executables.end(); } ExecutablesList::const_iterator ExecutablesList::end() const { return m_Executables.end(); } std::size_t ExecutablesList::size() const { return m_Executables.size(); } bool ExecutablesList::empty() const { return m_Executables.empty(); } void ExecutablesList::load(const MOBase::IPluginGame* game, const Settings& s) { log::debug("loading executables"); m_Executables.clear(); // whether the executable list in the .ini is still using the old custom // executables from 2.2.0, see upgradeFromCustom() bool needsUpgrade = false; for (auto& map : s.executables()) { Executable::Flags flags; if (map["toolbar"].toBool()) flags |= Executable::ShowInToolbar; if (map["ownicon"].toBool()) flags |= Executable::UseApplicationIcon; if (map["minimizeToSystemTray"].toBool()) flags |= Executable::MinimizeToSystemTray; if (map["hide"].toBool()) flags |= Executable::Hide; if (map.contains("custom")) { // the "custom" setting only exists in older versions needsUpgrade = true; } setExecutable(Executable() .title(map["title"].toString()) .binaryInfo(QFileInfo(map["binary"].toString())) .arguments(map["arguments"].toString()) .steamAppID(map["steamAppID"].toString()) .workingDirectory(map["workingDirectory"].toString()) .flags(flags)); } addFromPlugin(game, IgnoreExisting); if (needsUpgrade) upgradeFromCustom(game); dump(); } void ExecutablesList::store(Settings& s) { std::vector> v; for (const auto& item : *this) { std::map map; map["title"] = item.title(); map["toolbar"] = item.isShownOnToolbar(); map["ownicon"] = item.usesOwnIcon(); map["hide"] = item.hide(); map["binary"] = item.binaryInfo().filePath(); map["arguments"] = item.arguments(); map["workingDirectory"] = item.workingDirectory(); map["steamAppID"] = item.steamAppID(); map["minimizeToSystemTray"] = item.minimizeToSystemTray(); v.push_back(std::move(map)); } s.setExecutables(v); } std::vector ExecutablesList::getPluginExecutables(MOBase::IPluginGame const* game) const { Q_ASSERT(game != nullptr); std::vector v; for (const ExecutableInfo& info : game->executables()) { if (!info.isValid()) { continue; } v.push_back({info, Executable::UseApplicationIcon}); } const QFileInfo eppBin(QCoreApplication::applicationDirPath() + "/explorer++/Explorer++.exe"); if (eppBin.exists()) { const auto args = QString("\"%1\"").arg( QDir::toNativeSeparators(game->dataDirectory().absolutePath())); const auto exe = Executable() .title("Explore Virtual Folder") .binaryInfo(eppBin) .arguments(args) .workingDirectory(eppBin.absolutePath()) .flags(Executable::UseApplicationIcon); v.push_back(exe); } return v; } void ExecutablesList::resetFromPlugin(MOBase::IPluginGame const* game) { log::debug("resetting plugin executables"); Q_ASSERT(game != nullptr); for (const auto& exe : getPluginExecutables(game)) { setExecutable(exe, MoveExisting); } } void ExecutablesList::addFromPlugin(IPluginGame const* game, SetFlags flags) { Q_ASSERT(game != nullptr); for (const auto& exe : getPluginExecutables(game)) { setExecutable(exe, flags); } } const Executable& ExecutablesList::get(const QString& title) const { for (const auto& exe : m_Executables) { if (exe.title() == title) { return exe; } } throw std::runtime_error( QString("executable not found: %1").arg(title).toLocal8Bit().constData()); } Executable& ExecutablesList::get(const QString& title) { return const_cast(std::as_const(*this).get(title)); } Executable& ExecutablesList::getByBinary(const QFileInfo& info) { for (Executable& exe : m_Executables) { if (exe.binaryInfo() == info) { return exe; } } throw std::runtime_error("invalid info"); } ExecutablesList::iterator ExecutablesList::find(const QString& title, bool ci) { const auto cif = ci ? Qt::CaseInsensitive : Qt::CaseSensitive; return std::find_if(begin(), end(), [&](auto&& e) { return (e.title().compare(title, cif) == 0); }); } ExecutablesList::const_iterator ExecutablesList::find(const QString& title, bool ci) const { const auto cif = ci ? Qt::CaseInsensitive : Qt::CaseSensitive; return std::find_if(begin(), end(), [&](auto&& e) { return (e.title().compare(title, cif) == 0); }); } bool ExecutablesList::titleExists(const QString& title) const { auto test = [&](const Executable& exe) { return exe.title() == title; }; return std::find_if(m_Executables.begin(), m_Executables.end(), test) != m_Executables.end(); } void ExecutablesList::setExecutable(const Executable& exe) { setExecutable(exe, MergeExisting); } void ExecutablesList::setExecutable(const Executable& exe, SetFlags flags) { auto itor = find(exe.title()); if (itor != end()) { if (flags == IgnoreExisting) { return; } if (flags == MoveExisting) { const auto newTitle = makeNonConflictingTitle(exe.title()); if (!newTitle) { log::error("executable '{}' was in the way but could not be renamed", exe.title()); return; } log::warn("executable '{}' was in the way and was renamed to '{}'", itor->title(), *newTitle); itor->title(*newTitle); itor = end(); } } if (itor == m_Executables.end()) { m_Executables.push_back(exe); } else { itor->mergeFrom(exe); } } void ExecutablesList::remove(const QString& title) { auto itor = find(title); if (itor != m_Executables.end()) { m_Executables.erase(itor); } } std::optional ExecutablesList::makeNonConflictingTitle(const QString& prefix) { const int max = 100; QString title = prefix; for (int i = 1; i < max; ++i) { if (!titleExists(title)) { return title; } title = prefix + QString(" (%1)").arg(i); } log::error("ran out of executable titles for prefix '{}'", prefix); return {}; } void ExecutablesList::upgradeFromCustom(MOBase::IPluginGame const* game) { log::debug("upgrading executables list"); Q_ASSERT(game != nullptr); // prior to 2.2.1, plugin executables were special in the sense that they // did not store certain settings, like the binary info and working directory; // those were filled in when MO started, but never saved // // this interferes with the new executables list, which is completely // customizable, because plugin executables are only added to the list when // they don't exist at all and are ignored otherwise, leaving some of the // fields completely blank // // when the "custom" setting is found in the .ini file (see load()), it is // assumed that the older scheme is still present; in that case, the plugin // executables force their binary info and working directory into the existing // executables one last time // // from that point on, plugin executables are ignored unless they're // completely missing from the list, allowing users to customize them as they // want for (const auto& exe : getPluginExecutables(game)) { auto itor = find(exe.title()); if (itor == end()) { continue; } if (!itor->binaryInfo().exists()) { itor->binaryInfo(exe.binaryInfo()); } if (itor->workingDirectory().isEmpty()) { itor->workingDirectory(exe.workingDirectory()); } } } void ExecutablesList::dump() const { for (const auto& e : m_Executables) { QStringList flags; if (e.flags() & Executable::ShowInToolbar) { flags.push_back("toolbar"); } if (e.flags() & Executable::UseApplicationIcon) { flags.push_back("icon"); } if (e.flags() & Executable::Hide) { flags.push_back("hide"); } if (e.flags() & Executable::MinimizeToSystemTray) { flags.push_back("minimizeToSystemTray"); } log::debug(" . executable '{}'\n" " binary: {}\n" " arguments: {}\n" " steam ID: {}\n" " directory: {}\n" " flags: {} ({})", e.title(), e.binaryInfo().filePath(), e.arguments(), e.steamAppID(), e.workingDirectory(), flags.join("|"), e.flags()); } } Executable::Executable(QString title) : m_title(title) {} Executable::Executable(const MOBase::ExecutableInfo& info, Flags flags) : m_title(info.title()), m_binaryInfo(info.binary()), m_arguments(info.arguments().join(" ")), m_steamAppID(info.steamAppID()), m_workingDirectory(info.workingDirectory().path()), m_flags(flags) {} const QString& Executable::title() const { return m_title; } const QFileInfo& Executable::binaryInfo() const { return m_binaryInfo; } const QString& Executable::arguments() const { return m_arguments; } const QString& Executable::steamAppID() const { return m_steamAppID; } const QString& Executable::workingDirectory() const { return m_workingDirectory; } Executable::Flags Executable::flags() const { return m_flags; } Executable& Executable::title(const QString& s) { m_title = s; return *this; } Executable& Executable::binaryInfo(const QFileInfo& fi) { m_binaryInfo = fi; return *this; } Executable& Executable::arguments(const QString& s) { m_arguments = s; return *this; } Executable& Executable::steamAppID(const QString& s) { m_steamAppID = s; return *this; } Executable& Executable::workingDirectory(const QString& s) { m_workingDirectory = s; return *this; } Executable& Executable::flags(Flags f) { m_flags = f; return *this; } bool Executable::isShownOnToolbar() const { return m_flags.testFlag(ShowInToolbar); } void Executable::setShownOnToolbar(bool state) { if (state) { m_flags |= ShowInToolbar; } else { m_flags &= ~ShowInToolbar; } } bool Executable::usesOwnIcon() const { return m_flags.testFlag(UseApplicationIcon); } bool Executable::minimizeToSystemTray() const { return m_flags.testFlag(MinimizeToSystemTray); } bool Executable::hide() const { return m_flags.testFlag(Hide); } void Executable::mergeFrom(const Executable& other) { // this happens after executables are loaded from settings and plugin // executables are being added, or when users are modifying executables m_title = other.title(); m_binaryInfo = other.binaryInfo(); m_arguments = other.arguments(); m_steamAppID = other.steamAppID(); m_workingDirectory = other.workingDirectory(); m_flags = other.flags(); }