diff --git a/apps/launcher/utils/cellnameloader.cpp b/apps/launcher/utils/cellnameloader.cpp index d47e923eb2..8cc1191d76 100644 --- a/apps/launcher/utils/cellnameloader.cpp +++ b/apps/launcher/utils/cellnameloader.cpp @@ -1,7 +1,11 @@ #include "cellnameloader.hpp" +#include + #include +#include #include +#include #include QSet CellNameLoader::getCellNames(const QStringList& contentPaths) @@ -16,7 +20,17 @@ QSet CellNameLoader::getCellNames(const QStringList& contentPaths) continue; try { - esmReader.open(Files::pathFromQString(contentPath)); + std::filesystem::path filepath = Files::pathFromQString(contentPath); + auto stream = Files::openBinaryInputFileStream(filepath); + if (!stream->is_open()) + continue; + + const ESM::Format format = ESM::readFormat(*stream); + if (format != ESM::Format::Tes3) + continue; + + stream->seekg(0); + esmReader.open(std::move(stream), filepath); // Loop through all records while (esmReader.hasMoreRecs()) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index fa76f77f02..7b4f5db158 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -1,13 +1,17 @@ #include "contentmodel.hpp" #include "esmfile.hpp" +#include #include #include #include #include +#include #include +#include +#include #include ContentSelectorModel::ContentModel::ContentModel(QObject* parent, QIcon& warningIcon, bool showOMWScripts) @@ -102,7 +106,7 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex& index return Qt::NoItemFlags; // game files can always be checked - if (file->isGameFile()) + if (file == mGameFile) return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; Qt::ItemFlags returnFlags; @@ -212,7 +216,7 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int case Qt::CheckStateRole: { - if (file->isGameFile()) + if (file == mGameFile) return QVariant(); return mCheckStates[file->filePath()]; @@ -220,7 +224,7 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int case Qt::UserRole: { - if (file->isGameFile()) + if (file == mGameFile) return ContentType_GameFile; else if (flags(index)) return ContentType_Addon; @@ -467,29 +471,59 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf try { - ESM::ESMReader fileReader; - ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding.toStdString())); - fileReader.setEncoder(&encoder); - fileReader.open(Files::pathFromQString(dir.absoluteFilePath(path2))); - EsmFile* file = new EsmFile(path2); - - for (std::vector::const_iterator itemIter = fileReader.getGameFiles().begin(); - itemIter != fileReader.getGameFiles().end(); ++itemIter) - file->addGameFile(QString::fromUtf8(itemIter->name.c_str())); - - file->setAuthor(QString::fromUtf8(fileReader.getAuthor().c_str())); file->setDate(info.lastModified()); - file->setFormat(fileReader.getFormatVersion()); file->setFilePath(info.absoluteFilePath()); - file->setDescription(QString::fromUtf8(fileReader.getDesc().c_str())); + std::filesystem::path filepath = Files::pathFromQString(info.absoluteFilePath()); - // HACK - // Load order constraint of Bloodmoon.esm needing Tribunal.esm is missing - // from the file supplied by Bethesda, so we have to add it ourselves - if (file->fileName().compare("Bloodmoon.esm", Qt::CaseInsensitive) == 0) + auto stream = Files::openBinaryInputFileStream(filepath); + if (!stream->is_open()) { - file->addGameFile(QString::fromUtf8("Tribunal.esm")); + qWarning() << "Failed to open addon file " << info.fileName() << ": " + << std::generic_category().message(errno).c_str(); + continue; + } + const ESM::Format format = ESM::readFormat(*stream); + stream->seekg(0); + switch (format) + { + case ESM::Format::Tes3: + { + ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding.toStdString())); + ESM::ESMReader fileReader; + fileReader.setEncoder(&encoder); + fileReader.open(std::move(stream), filepath); + file->setAuthor(QString::fromUtf8(fileReader.getAuthor().c_str())); + file->setFormat(fileReader.getFormatVersion()); + file->setDescription(QString::fromUtf8(fileReader.getDesc().c_str())); + for (const auto& master : fileReader.getGameFiles()) + file->addGameFile(QString::fromUtf8(master.name.c_str())); + + // HACK + // Load order constraint of Bloodmoon.esm needing Tribunal.esm is missing + // from the file supplied by Bethesda, so we have to add it ourselves + if (file->fileName().compare("Bloodmoon.esm", Qt::CaseInsensitive) == 0) + file->addGameFile(QString::fromUtf8("Tribunal.esm")); + + break; + } + case ESM::Format::Tes4: + { + ToUTF8::StatelessUtf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding.toStdString())); + ESM4::Reader reader(std::move(stream), filepath, nullptr, &encoder, true); + file->setAuthor(QString::fromUtf8(reader.getAuthor().c_str())); + file->setFormat(reader.esmVersion()); + file->setDescription(QString::fromUtf8(reader.getDesc().c_str())); + for (const auto& master : reader.getGameFiles()) + file->addGameFile(QString::fromUtf8(master.name.c_str())); + break; + } + default: + { + qWarning() << "Error reading addon file " << info.fileName() << ": unsupported ESM format " + << ESM::NAME(format).toString().c_str(); + continue; + } } // Put the file in the table @@ -543,6 +577,15 @@ QStringList ContentSelectorModel::ContentModel::gameFiles() const return gameFiles; } +void ContentSelectorModel::ContentModel::setCurrentGameFile(const EsmFile* file) +{ + QModelIndex oldIndex = indexFromItem(mGameFile); + QModelIndex index = indexFromItem(file); + mGameFile = file; + emit dataChanged(oldIndex, oldIndex); + emit dataChanged(index, index); +} + void ContentSelectorModel::ContentModel::sortFiles() { emit layoutAboutToBeChanged(); @@ -557,10 +600,9 @@ void ContentSelectorModel::ContentModel::sortFiles() for (int j = 0; j < i; ++j) { const QStringList& gameFiles = mFiles.at(j)->gameFiles(); - if (gameFiles.contains(file->fileName(), Qt::CaseInsensitive) - || (!mFiles.at(j)->isGameFile() && gameFiles.isEmpty() - && file->fileName().compare("Morrowind.esm", Qt::CaseInsensitive) - == 0)) // Hack: implicit dependency on Morrowind.esm for dependency-less files + // All addon files are implicitly dependent on the game file + // so that they don't accidentally become the game file + if (gameFiles.contains(file->fileName(), Qt::CaseInsensitive) || file == mGameFile) { index = j; break; diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index f5924ecccf..d56f8f9a3b 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -54,6 +54,7 @@ namespace ContentSelectorModel const EsmFile* item(int row) const; EsmFile* item(int row); QStringList gameFiles() const; + void setCurrentGameFile(const EsmFile* file); bool isEnabled(const QModelIndex& index) const; bool isChecked(const QString& filepath) const; @@ -81,6 +82,7 @@ namespace ContentSelectorModel QString toolTip(const EsmFile* file) const; + const EsmFile* mGameFile; ContentFileList mFiles; QStringList mArchives; QHash mCheckStates; diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 937309a864..2f01200927 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -232,6 +232,7 @@ void ContentSelectorView::ContentSelector::setGameFileSelected(int index, bool s QModelIndex index2(mContentModel->indexFromItem(file)); mContentModel->setData(index2, selected, Qt::UserRole + 1); } + mContentModel->setCurrentGameFile(selected ? file : nullptr); } void ContentSelectorView::ContentSelector::slotAddonTableItemActivated(const QModelIndex& index)