#include "common.h" #include "quam/pak.h" #include #include struct PakHeader { quint32 dirOffset; quint32 dirNum; }; struct PakEntry { std::string name; PakFile file; }; static constexpr quint32 sizeOfPakEntry = 64; static PakHeader readPakHeader(std::istream &st) { auto magic = readBytes<4>(st); if(magic != std::array{'P', 'A', 'C', 'K'}) { throw std::runtime_error("not a pak file (invalid magic number)"); } auto dirOffset = readLE(st); auto dirSize = readLE(st); if(dirSize % sizeOfPakEntry != 0) { throw std::runtime_error("invalid directory size"); } PakHeader hdr; hdr.dirOffset = dirOffset; hdr.dirNum = dirSize / sizeOfPakEntry; return hdr; } static PakEntry readPakEntry(std::istream &st) { auto entName = readBytes<56>(st); auto entOffset = readLE(st); auto entSize = readLE(st); auto pos = st.tellg(); st.seekg(entOffset); PakFile file; file.resize(entSize); st.read(file.data(), entSize); st.seekg(pos); auto zero = std::find(entName.cbegin(), entName.cend(), '\0'); std::string name; std::copy(entName.cbegin(), zero, std::back_inserter(name)); if(name.front() == '/') { throw std::runtime_error("empty root directory name"); } PakEntry ent; ent.name = std::move(name); ent.file = std::move(file); return ent; } void insertFile(PakDir &dir, std::string name, PakFile file) { std::optional next; if(auto slash = name.find('/'); slash != std::string::npos) { next = name.substr(slash + 1); name = name.substr(0, slash); } auto existingNode = std::find_if(dir.begin(), dir.end(), [&name](PakNode const &node) { return node.name == name; }); if(next) { auto ref = existingNode != dir.end() ? *existingNode : dir.emplace_back(PakDir{}, std::move(name)); insertFile(std::get(ref), *std::move(next), std::move(file)); } else { if(existingNode != dir.end()) { throw std::runtime_error("duplicate file"); } dir.emplace_back(std::move(file), std::move(name)); } } PakDir readPak(std::istream &st) { auto hdr = readPakHeader(st); st.seekg(hdr.dirOffset); PakDir root; for(quint32 i = 0; i < hdr.dirNum; i++) { auto ent = readPakEntry(st); insertFile(root, std::move(ent.name), std::move(ent.file)); } return root; } PakDirRoot::PakDirRoot(PakDir &&root, QObject *parent) : QObject{parent}, PakDir{std::move(root)} { } PakDirRoot::~PakDirRoot() { } PakDirModel::PakDirModel(PakDir const *root, QObject *parent) : QAbstractItemModel{parent}, m_root{root} { } PakDirModel::~PakDirModel() { } QVariant PakDirModel::data(QModelIndex const &index, int role) const { if(!index.isValid()) { return QVariant{}; } auto node = static_cast(index.internalPointer()); auto col = Pak::Column(index.column()); switch(role) { case Qt::DecorationRole: if(col == Pak::Column::Name) { auto icon = std::holds_alternative(*node) ? "folder" : "text-x-generic"; return QVariant{QIcon::fromTheme(icon)}; } break; case Qt::DisplayRole: switch(col) { case Pak::Column::Size: if(auto file = std::get_if(node)) { return QVariant{QString::number(file->size())}; } break; case Pak::Column::Name: return QVariant{tr(node->name.data())}; } default: break; } return QVariant{}; } Qt::ItemFlags PakDirModel::flags(QModelIndex const &index) const { if(!index.isValid()) { return Qt::NoItemFlags; } else { return Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; } } QVariant PakDirModel::headerData(int section, Qt::Orientation orientation, int role) const { if(orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch(Pak::Column(section)) { case Pak::Column::Size: return QVariant{tr("Size")}; case Pak::Column::Name: return QVariant{tr("Name")}; } } return QVariant{}; } QModelIndex PakDirModel::index(int row, int col, QModelIndex const &parent) const { if(!hasIndex(row, col, parent) || row > m_root->size()) { return QModelIndex{}; } else { // despite index data being const, this function does not take a const // pointer, which is very annoying! return createIndex(row, col, const_cast(&m_root->at(row))); } } QModelIndex PakDirModel::parent(QModelIndex const &) const { return QModelIndex{}; } int PakDirModel::rowCount(QModelIndex const &) const { return m_root->size(); } int PakDirModel::columnCount(QModelIndex const &) const { return QMetaEnum::fromType().keyCount(); } PakDirModelSorter::~PakDirModelSorter() { } // EOF