#include "modinforegular.h" #include "categories.h" #include "messagedialog.h" #include "report.h" #include "scriptextender.h" #include "settings.h" #include #include #include #include using namespace MOBase; using namespace MOShared; namespace { //Arguably this should be a class static or we should be using FileString rather //than QString for the names. Or both. static bool ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS) { return QString::compare(LHS->name(), RHS->name(), Qt::CaseInsensitive) < 0; } } ModInfoRegular::ModInfoRegular(PluginContainer *pluginContainer, const IPluginGame *game, const QDir &path, DirectoryEntry **directoryStructure) : ModInfoWithConflictInfo(pluginContainer, directoryStructure) , m_Name(path.dirName()) , m_Path(path.absolutePath()) , m_Repository() , m_GameName(game->gameShortName()) , m_IsAlternate(false) , m_Converted(false) , m_Validated(false) , m_MetaInfoChanged(false) , m_EndorsedState(ENDORSED_UNKNOWN) , m_TrackedState(TRACKED_UNKNOWN) , m_NexusBridge(pluginContainer) { testValid(); m_CreationTime = QFileInfo(path.absolutePath()).created(); // read out the meta-file for information readMeta(); if (m_GameName.compare(game->gameShortName(), Qt::CaseInsensitive) != 0) if (!game->primarySources().contains(m_GameName, Qt::CaseInsensitive)) m_IsAlternate = true; //populate m_Archives m_Archives = QStringList(); if (Settings::instance().archiveParsing()) { archives(true); } connect(&m_NexusBridge, SIGNAL(descriptionAvailable(QString,int,QVariant,QVariant)) , this, SLOT(nxmDescriptionAvailable(QString,int,QVariant,QVariant))); connect(&m_NexusBridge, SIGNAL(endorsementToggled(QString,int,QVariant,QVariant)) , this, SLOT(nxmEndorsementToggled(QString,int,QVariant,QVariant))); connect(&m_NexusBridge, SIGNAL(trackingToggled(QString,int,QVariant,bool)) , this, SLOT(nxmTrackingToggled(QString,int,QVariant,bool))); connect(&m_NexusBridge, SIGNAL(requestFailed(QString,int,int,QVariant,QNetworkReply::NetworkError,QString)) , this, SLOT(nxmRequestFailed(QString,int,int,QVariant,QNetworkReply::NetworkError,QString))); } ModInfoRegular::~ModInfoRegular() { try { saveMeta(); } catch (const std::exception &e) { qCritical("failed to save meta information for \"%s\": %s", qUtf8Printable(m_Name), e.what()); } } bool ModInfoRegular::isEmpty() const { QDirIterator iter(m_Path, QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); if (!iter.hasNext()) return true; iter.next(); if ((iter.fileName() == "meta.ini") && !iter.hasNext()) return true; return false; } void ModInfoRegular::readMeta() { QSettings metaFile(m_Path + "/meta.ini", QSettings::IniFormat); m_Comments = metaFile.value("comments", "").toString(); m_Notes = metaFile.value("notes", "").toString(); QString tempGameName = metaFile.value("gameName", m_GameName).toString(); if (tempGameName.size()) m_GameName = tempGameName; m_NexusID = metaFile.value("modid", -1).toInt(); m_Version.parse(metaFile.value("version", "").toString()); m_NewestVersion = metaFile.value("newestVersion", "").toString(); m_IgnoredVersion = metaFile.value("ignoredVersion", "").toString(); m_InstallationFile = metaFile.value("installationFile", "").toString(); m_NexusDescription = metaFile.value("nexusDescription", "").toString(); m_NexusFileStatus = metaFile.value("nexusFileStatus", "1").toInt(); m_Repository = metaFile.value("repository", "Nexus").toString(); m_Converted = metaFile.value("converted", false).toBool(); m_Validated = metaFile.value("validated", false).toBool(); // this handles changes to how the URL works after 2.2.0 // // in 2.2.0, "hasCustomUrl" does not exist and "url" is only used when the mod // id is invalid, although it can be set at any time in the mod info dialog // // post 2.2.0, a custom url can be set on any mod, whether the mod id is // valid or not, so an additional flag "hasCustomURL" is required, with a // corresponding checkbox in the mod info dialog // // there are several cases to handle to make sure no data is lost and to // determine whether the user has set a custom url before: // // 1) some mods have an incorrect url set along with a valid mod id; // there is apparently a bug with the fomod installer that can set the // url of a mod to a value used by a _previous_ installation // // 2) it is possible to set the url even if the mod id is valid, in which // case it is saved, but never used in 2.2.0 // // 3) opening the mod info dialog on the nexus tab for a mod that has a // valid id will force the url to be the same as what the plugin gives // back // // the algorithm is as follows: // always read the url from the meta file and store it so this piece of data // is never lost; the problem then only becomes about whether to enable // hasCustomURL // // if hasCustomURL is present in the meta file, just read that and be // done with it // // if not, then the flag depends on the mod id and the url // if the mod id is valid, the custom url is disabled; although the url // could be _set_ by the user when a mod id was valid, it was never // _used_, so the behaviour won't change // // if the mod id is invalid, the url should normally be empty, unless the // user specified one, in which case hasCustomURL should be true // (the only case where this fails is if a mod id was valid before and // the user visited the nexus tab, in which case the url was set // automatically, but then the id was manually changed to 0 // // in that case, the mod id is invalid and the url is not empty, but it // was never set by the user; this case is impossible to distinguish // from a user manually entering a url, and so is handled as such) // always read the url m_CustomURL = metaFile.value("url").toString(); if (metaFile.contains("hasCustomURL")) { m_HasCustomURL = metaFile.value("hasCustomURL").toBool(); } else { if (m_NexusID > 0) { // the mod id is valid, disable the custom url m_HasCustomURL = false; } else { if (!m_CustomURL.isEmpty()) { // the mod id is invalid and the url is not empty, enable it m_HasCustomURL = true; } } } m_LastNexusQuery = QDateTime::fromString(metaFile.value("lastNexusQuery", "").toString(), Qt::ISODate); m_LastNexusUpdate = QDateTime::fromString(metaFile.value("lastNexusUpdate", "").toString(), Qt::ISODate); m_NexusLastModified = QDateTime::fromString(metaFile.value("nexusLastModified", QDateTime::currentDateTimeUtc()).toString(), Qt::ISODate); m_Color = metaFile.value("color",QColor()).value(); m_TrackedState = metaFile.value("tracked", false).toBool() ? TRACKED_TRUE : TRACKED_FALSE; if (metaFile.contains("endorsed")) { if (metaFile.value("endorsed").canConvert()) { switch (metaFile.value("endorsed").toInt()) { case ENDORSED_FALSE: m_EndorsedState = ENDORSED_FALSE; break; case ENDORSED_TRUE: m_EndorsedState = ENDORSED_TRUE; break; case ENDORSED_NEVER: m_EndorsedState = ENDORSED_NEVER; break; default: m_EndorsedState = ENDORSED_UNKNOWN; break; } } else { m_EndorsedState = metaFile.value("endorsed", false).toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; } } QString categoriesString = metaFile.value("category", "").toString(); QStringList categories = categoriesString.split(',', QString::SkipEmptyParts); for (QStringList::iterator iter = categories.begin(); iter != categories.end(); ++iter) { bool ok = false; int categoryID = iter->toInt(&ok); if (categoryID < 0) { // ignore invalid id continue; } if (ok && (categoryID != 0) && (CategoryFactory::instance().categoryExists(categoryID))) { m_Categories.insert(categoryID); if (iter == categories.begin()) { m_PrimaryCategory = categoryID; } } } int numFiles = metaFile.beginReadArray("installedFiles"); for (int i = 0; i < numFiles; ++i) { metaFile.setArrayIndex(i); m_InstalledFileIDs.insert(std::make_pair(metaFile.value("modid").toInt(), metaFile.value("fileid").toInt())); } metaFile.endArray(); m_MetaInfoChanged = false; } void ModInfoRegular::saveMeta() { // only write meta data if the mod directory exists if (m_MetaInfoChanged && QFile::exists(absolutePath())) { QSettings metaFile(absolutePath().append("/meta.ini"), QSettings::IniFormat); if (metaFile.status() == QSettings::NoError) { std::set temp = m_Categories; temp.erase(m_PrimaryCategory); metaFile.setValue("category", QString("%1").arg(m_PrimaryCategory) + "," + SetJoin(temp, ",")); metaFile.setValue("newestVersion", m_NewestVersion.canonicalString()); metaFile.setValue("ignoredVersion", m_IgnoredVersion.canonicalString()); metaFile.setValue("version", m_Version.canonicalString()); metaFile.setValue("installationFile", m_InstallationFile); metaFile.setValue("repository", m_Repository); metaFile.setValue("gameName", m_GameName); metaFile.setValue("modid", m_NexusID); metaFile.setValue("comments", m_Comments); metaFile.setValue("notes", m_Notes); metaFile.setValue("nexusDescription", m_NexusDescription); metaFile.setValue("url", m_CustomURL); metaFile.setValue("hasCustomURL", m_HasCustomURL); metaFile.setValue("nexusFileStatus", m_NexusFileStatus); metaFile.setValue("lastNexusQuery", m_LastNexusQuery.toString(Qt::ISODate)); metaFile.setValue("lastNexusUpdate", m_LastNexusUpdate.toString(Qt::ISODate)); metaFile.setValue("nexusLastModified", m_NexusLastModified.toString(Qt::ISODate)); metaFile.setValue("converted", m_Converted); metaFile.setValue("validated", m_Validated); metaFile.setValue("color", m_Color); if (m_EndorsedState != ENDORSED_UNKNOWN) { metaFile.setValue("endorsed", m_EndorsedState); } metaFile.setValue("tracked", m_TrackedState); metaFile.remove("installedFiles"); metaFile.beginWriteArray("installedFiles"); int idx = 0; for (auto iter = m_InstalledFileIDs.begin(); iter != m_InstalledFileIDs.end(); ++iter) { metaFile.setArrayIndex(idx++); metaFile.setValue("modid", iter->first); metaFile.setValue("fileid", iter->second); } metaFile.endArray(); metaFile.sync(); // sync needs to be called to ensure the file is created if (metaFile.status() == QSettings::NoError) { m_MetaInfoChanged = false; } else { qCritical() << QString("failed to write %1/meta.ini: error %2") .arg(absolutePath()).arg(metaFile.status()); } } else { qCritical() << QString("failed to write %1/meta.ini: error %2") .arg(absolutePath()).arg(metaFile.status()); } } } bool ModInfoRegular::updateAvailable() const { if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { return false; } if (m_NexusFileStatus == 4 || m_NexusFileStatus == 6) { return true; } return m_NewestVersion.isValid() && (m_Version < m_NewestVersion); } bool ModInfoRegular::downgradeAvailable() const { if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { return false; } return m_NewestVersion.isValid() && (m_NewestVersion < m_Version); } void ModInfoRegular::nxmDescriptionAvailable(QString, int, QVariant, QVariant resultData) { QVariantMap result = resultData.toMap(); setNexusDescription(result["description"].toString()); if ((m_EndorsedState != ENDORSED_NEVER) && (result.contains("endorsement"))) { QVariantMap endorsement = result["endorsement"].toMap(); QString endorsementStatus = endorsement["endorse_status"].toString(); if (endorsementStatus.compare("Endorsed", Qt::CaseInsensitive) == 00) setEndorsedState(ENDORSED_TRUE); else if (endorsementStatus.compare("Abstained", Qt::CaseInsensitive) == 00) setEndorsedState(ENDORSED_NEVER); else setEndorsedState(ENDORSED_FALSE); } m_LastNexusQuery = QDateTime::currentDateTimeUtc(); m_NexusLastModified = QDateTime::fromSecsSinceEpoch(result["updated_timestamp"].toInt(), Qt::UTC); m_MetaInfoChanged = true; saveMeta(); disconnect(sender(), SIGNAL(descriptionAvailable(QString, int, QVariant, QVariant))); emit modDetailsUpdated(true); } void ModInfoRegular::nxmEndorsementToggled(QString, int, QVariant, QVariant resultData) { QMap results = resultData.toMap(); if (results["status"].toString().compare("Endorsed") == 0) { m_EndorsedState = ENDORSED_TRUE; } else if (results["status"].toString().compare("Abstained") == 0) { m_EndorsedState = ENDORSED_NEVER; } else { m_EndorsedState = ENDORSED_FALSE; } m_MetaInfoChanged = true; saveMeta(); emit modDetailsUpdated(true); } void ModInfoRegular::nxmTrackingToggled(QString, int, QVariant, bool tracked) { if (tracked) m_TrackedState = TRACKED_TRUE; else m_TrackedState = TRACKED_FALSE; m_MetaInfoChanged = true; saveMeta(); emit modDetailsUpdated(true); } void ModInfoRegular::nxmRequestFailed(QString, int, int, QVariant userData, QNetworkReply::NetworkError error, const QString &errorMessage) { QString fullMessage = errorMessage; if (userData.canConvert() && (userData.toInt() == 1)) { fullMessage += "\nNexus will reject endorsements within 15 Minutes of a failed attempt, the error message may be misleading."; } if (QApplication::activeWindow() != nullptr) { MessageDialog::showMessage(fullMessage, QApplication::activeWindow()); } emit modDetailsUpdated(false); } bool ModInfoRegular::updateNXMInfo() { if (needsDescriptionUpdate()) { m_NexusBridge.requestDescription(m_GameName, m_NexusID, QVariant()); return true; } return false; } bool ModInfoRegular::needsDescriptionUpdate() const { if (m_NexusID > 0) { QDateTime time = QDateTime::currentDateTimeUtc(); QDateTime target = m_LastNexusQuery.addDays(1); if (time >= target) { return true; } } return false; } void ModInfoRegular::setCategory(int categoryID, bool active) { m_MetaInfoChanged = true; if (active) { m_Categories.insert(categoryID); if (m_PrimaryCategory == -1) { m_PrimaryCategory = categoryID; } } else { std::set::iterator iter = m_Categories.find(categoryID); if (iter != m_Categories.end()) { m_Categories.erase(iter); } if (categoryID == m_PrimaryCategory) { if (m_Categories.size() == 0) { m_PrimaryCategory = -1; } else { m_PrimaryCategory = *(m_Categories.begin()); } } } } bool ModInfoRegular::setName(const QString &name) { if (name.contains('/') || name.contains('\\')) { return false; } QString newPath = m_Path.mid(0).replace(m_Path.length() - m_Name.length(), m_Name.length(), name); QDir modDir(m_Path.mid(0, m_Path.length() - m_Name.length())); if (m_Name.compare(name, Qt::CaseInsensitive) == 0) { QString tempName = name; tempName.append("_temp"); while (modDir.exists(tempName)) { tempName.append("_"); } if (!modDir.rename(m_Name, tempName)) { return false; } if (!modDir.rename(tempName, name)) { qCritical("rename to final name failed after successful rename to intermediate name"); modDir.rename(tempName, m_Name); return false; } } else { if (!shellRename(modDir.absoluteFilePath(m_Name), modDir.absoluteFilePath(name))) { qCritical("failed to rename mod %s (errorcode %d)", qUtf8Printable(name), ::GetLastError()); return false; } } std::map::iterator nameIter = s_ModsByName.find(m_Name); if (nameIter != s_ModsByName.end()) { unsigned int index = nameIter->second; s_ModsByName.erase(nameIter); m_Name = name; m_Path = newPath; s_ModsByName[m_Name] = index; std::sort(s_Collection.begin(), s_Collection.end(), ModInfo::ByName); updateIndices(); } else { // otherwise mod isn't registered yet? m_Name = name; m_Path = newPath; } return true; } void ModInfoRegular::setComments(const QString &comments) { m_Comments = comments; m_MetaInfoChanged = true; } void ModInfoRegular::setNotes(const QString ¬es) { m_Notes = notes; m_MetaInfoChanged = true; } void ModInfoRegular::setGameName(const QString &gameName) { m_GameName = gameName; m_MetaInfoChanged = true; } void ModInfoRegular::setNexusID(int modID) { m_NexusID = modID; m_MetaInfoChanged = true; } void ModInfoRegular::setVersion(const VersionInfo &version) { m_Version = version; m_MetaInfoChanged = true; } void ModInfoRegular::setNewestVersion(const VersionInfo &version) { if (version != m_NewestVersion) { m_NewestVersion = version; m_MetaInfoChanged = true; } } void ModInfoRegular::setNexusDescription(const QString &description) { if (qHash(description) != qHash(m_NexusDescription)) { m_NexusDescription = description; m_MetaInfoChanged = true; } } void ModInfoRegular::setEndorsedState(EEndorsedState endorsedState) { if (endorsedState != m_EndorsedState) { m_EndorsedState = endorsedState; m_MetaInfoChanged = true; } } void ModInfoRegular::setTrackedState(ETrackedState trackedState) { if (trackedState != m_TrackedState) { m_TrackedState = trackedState; m_MetaInfoChanged = true; } } void ModInfoRegular::setInstallationFile(const QString &fileName) { m_InstallationFile = fileName; m_MetaInfoChanged = true; } void ModInfoRegular::addNexusCategory(int categoryID) { m_Categories.insert(CategoryFactory::instance().resolveNexusID(categoryID)); } void ModInfoRegular::setIsEndorsed(bool endorsed) { if (m_EndorsedState != ENDORSED_NEVER) { m_EndorsedState = endorsed ? ENDORSED_TRUE : ENDORSED_FALSE; m_MetaInfoChanged = true; } } void ModInfoRegular::setNeverEndorse() { m_EndorsedState = ENDORSED_NEVER; m_MetaInfoChanged = true; } void ModInfoRegular::setIsTracked(bool tracked) { if (tracked != (m_TrackedState == TRACKED_TRUE)) { m_TrackedState = tracked ? TRACKED_TRUE : TRACKED_FALSE; m_MetaInfoChanged = true; } } void ModInfoRegular::setColor(QColor color) { m_Color = color; m_MetaInfoChanged = true; } QColor ModInfoRegular::getColor() { return m_Color; } bool ModInfoRegular::remove() { m_MetaInfoChanged = false; return shellDelete(QStringList(absolutePath()), true); } void ModInfoRegular::endorse(bool doEndorse) { if (doEndorse != (m_EndorsedState == ENDORSED_TRUE)) { m_NexusBridge.requestToggleEndorsement(m_GameName, getNexusID(), m_Version.canonicalString(), doEndorse, QVariant(1)); } } void ModInfoRegular::track(bool doTrack) { if (doTrack != (m_TrackedState == TRACKED_TRUE)) { m_NexusBridge.requestToggleTracking(m_GameName, getNexusID(), doTrack, QVariant(1)); } } void ModInfoRegular::markConverted(bool converted) { m_Converted = converted; m_MetaInfoChanged = true; saveMeta(); emit modDetailsUpdated(true); } void ModInfoRegular::markValidated(bool validated) { m_Validated = validated; m_MetaInfoChanged = true; saveMeta(); emit modDetailsUpdated(true); } QString ModInfoRegular::absolutePath() const { return m_Path; } void ModInfoRegular::ignoreUpdate(bool ignore) { if (ignore) { m_IgnoredVersion = m_NewestVersion; } else { m_IgnoredVersion.clear(); } m_MetaInfoChanged = true; } bool ModInfoRegular::canBeUpdated() const { QDateTime now = QDateTime::currentDateTimeUtc(); QDateTime target = getExpires(); if (now >= target) return m_NexusID > 0; return false; } QDateTime ModInfoRegular::getExpires() const { return m_LastNexusUpdate.addSecs(300); } std::vector ModInfoRegular::getFlags() const { std::vector result = ModInfoWithConflictInfo::getFlags(); if ((m_NexusID > 0) && (endorsedState() == ENDORSED_FALSE) && Settings::instance().endorsementIntegration()) { result.push_back(ModInfo::FLAG_NOTENDORSED); } if ((m_NexusID > 0) && (trackedState() == TRACKED_TRUE)) { result.push_back(ModInfo::FLAG_TRACKED); } if (!isValid() && !m_Validated) { result.push_back(ModInfo::FLAG_INVALID); } if (m_Notes.length() != 0) { result.push_back(ModInfo::FLAG_NOTES); } if (m_PluginSelected) { result.push_back(ModInfo::FLAG_PLUGIN_SELECTED); } if (m_IsAlternate && !m_Converted) { result.push_back(ModInfo::FLAG_ALTERNATE_GAME); } return result; } std::vector ModInfoRegular::getContents() const { QTime now = QTime::currentTime(); if (m_LastContentCheck.isNull() || (m_LastContentCheck.secsTo(now) > 60)) { m_CachedContent.clear(); QDir dir(absolutePath()); if (dir.entryList(QStringList() << "*.esp" << "*.esm" << "*.esl").size() > 0) { m_CachedContent.push_back(CONTENT_PLUGIN); } if (dir.entryList(QStringList() << "*.bsa" << "*.ba2").size() > 0) { m_CachedContent.push_back(CONTENT_BSA); } //use >1 for ini files since there is meta.ini in all mods already. if (dir.entryList(QStringList() << "*.ini").size() > 1) { m_CachedContent.push_back(CONTENT_INI); } if (dir.entryList(QStringList() << "*.modgroups").size() > 0) { m_CachedContent.push_back(CONTENT_MODGROUP); } ScriptExtender *extender = qApp->property("managed_game") .value() ->feature(); if (extender != nullptr) { QString sePluginPath = extender->PluginPath(); if (dir.exists(sePluginPath)) { m_CachedContent.push_back(CONTENT_SKSEFILES); QDir sePluginDir(absolutePath() + "/" + sePluginPath); if (sePluginDir.entryList(QStringList() << "*.dll").size() > 0) { m_CachedContent.push_back(CONTENT_SKSE); } } } if (dir.exists("textures") || dir.exists("icons") || dir.exists("bookart")) m_CachedContent.push_back(CONTENT_TEXTURE); if (dir.exists("meshes")) m_CachedContent.push_back(CONTENT_MESH); if (dir.exists("interface") || dir.exists("menus")) m_CachedContent.push_back(CONTENT_INTERFACE); if (dir.exists("music") || dir.exists("sound")) m_CachedContent.push_back(CONTENT_SOUND); if (dir.exists("scripts")) m_CachedContent.push_back(CONTENT_SCRIPT); if (dir.exists("SkyProc Patchers")) m_CachedContent.push_back(CONTENT_SKYPROC); if (dir.exists("MCM")) m_CachedContent.push_back(CONTENT_MCM); m_LastContentCheck = QTime::currentTime(); } return m_CachedContent; } int ModInfoRegular::getHighlight() const { if (!isValid() && !m_Validated) return HIGHLIGHT_INVALID; auto flags = getFlags(); if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_PLUGIN_SELECTED) != flags.end()) return HIGHLIGHT_PLUGIN; return HIGHLIGHT_NONE; } QString ModInfoRegular::getDescription() const { if (!isValid() && !m_Validated) { return tr("%1 contains no esp/esm/esl and no asset (textures, meshes, interface, ...) directory").arg(name()); } else { const std::set &categories = getCategories(); std::wostringstream categoryString; categoryString << ToWString(tr("Categories:
")); CategoryFactory &categoryFactory = CategoryFactory::instance(); for (std::set::const_iterator catIter = categories.begin(); catIter != categories.end(); ++catIter) { if (catIter != categories.begin()) { categoryString << " , "; } categoryString << "" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << ""; } return ToQString(categoryString.str()); } } int ModInfoRegular::getNexusFileStatus() const { return m_NexusFileStatus; } void ModInfoRegular::setNexusFileStatus(int status) { m_NexusFileStatus = status; m_MetaInfoChanged = true; saveMeta(); emit modDetailsUpdated(true); } QString ModInfoRegular::comments() const { return m_Comments; } QString ModInfoRegular::notes() const { return m_Notes; } QDateTime ModInfoRegular::creationTime() const { return m_CreationTime; } QString ModInfoRegular::getNexusDescription() const { return m_NexusDescription; } QString ModInfoRegular::repository() const { return m_Repository; } ModInfoRegular::EEndorsedState ModInfoRegular::endorsedState() const { return m_EndorsedState; } ModInfoRegular::ETrackedState ModInfoRegular::trackedState() const { return m_TrackedState; } QDateTime ModInfoRegular::getLastNexusUpdate() const { return m_LastNexusUpdate; } void ModInfoRegular::setLastNexusUpdate(QDateTime time) { m_LastNexusUpdate = time; m_MetaInfoChanged = true; saveMeta(); emit modDetailsUpdated(true); } QDateTime ModInfoRegular::getLastNexusQuery() const { return m_LastNexusQuery; } void ModInfoRegular::setLastNexusQuery(QDateTime time) { m_LastNexusQuery = time; m_MetaInfoChanged = true; saveMeta(); emit modDetailsUpdated(true); } QDateTime ModInfoRegular::getNexusLastModified() const { return m_NexusLastModified; } void ModInfoRegular::setNexusLastModified(QDateTime time) { m_NexusLastModified = time; m_MetaInfoChanged = true; saveMeta(); emit modDetailsUpdated(true); } void ModInfoRegular::setCustomURL(QString const &url) { m_CustomURL = url; m_MetaInfoChanged = true; } QString ModInfoRegular::getCustomURL() const { return m_CustomURL; } void ModInfoRegular::setHasCustomURL(bool b) { m_HasCustomURL = b; m_MetaInfoChanged = true; } bool ModInfoRegular::hasCustomURL() const { return m_HasCustomURL; } QStringList ModInfoRegular::archives(bool checkOnDisk) { if (checkOnDisk) { QStringList result; QDir dir(this->absolutePath()); QStringList bsaList = dir.entryList(QStringList({ "*.bsa", "*.ba2" })); for (const QString &archive : bsaList) { result.append(this->absolutePath() + "/" + archive); } m_Archives = result; } return m_Archives; } void ModInfoRegular::addInstalledFile(int modId, int fileId) { m_InstalledFileIDs.insert(std::make_pair(modId, fileId)); m_MetaInfoChanged = true; } std::vector ModInfoRegular::getIniTweaks() const { QString metaFileName = absolutePath().append("/meta.ini"); QSettings metaFile(metaFileName, QSettings::IniFormat); std::vector result; int numTweaks = metaFile.beginReadArray("INI Tweaks"); if (numTweaks != 0) { qDebug("%d active ini tweaks in %s", numTweaks, QDir::toNativeSeparators(metaFileName).toUtf8().constData()); } for (int i = 0; i < numTweaks; ++i) { metaFile.setArrayIndex(i); QString filename = absolutePath().append("/INI Tweaks/").append(metaFile.value("name").toString()); result.push_back(filename); } metaFile.endArray(); return result; }