diff --git a/CMakeLists.txt b/CMakeLists.txt index c36ab52..3ccdc7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,7 +51,9 @@ add_executable( source/quam/pak.h source/quam/project.cc source/quam/project.h - source/quam/project.ui) + source/quam/project.ui + source/quam/wad.cc + source/quam/wad.h) make_qt_project(quam) diff --git a/source/common.h b/source/common.h index 3c8a4e9..9c5e670 100644 --- a/source/common.h +++ b/source/common.h @@ -25,6 +25,12 @@ static inline QString trMain(char const *sourceText, return QCoreApplication::translate("main", sourceText, disambiguation, n); } +static inline quint8 readByte(std::istream &st) { + char b; + st.read(&b, 1); + return quint8(b); +} + template static inline T readLE(std::istream &st) { std::array b; @@ -58,8 +64,8 @@ static inline QDebug operator<<(QDebug debug, std::unique_ptr const &t) { } template -static inline QDebug operator <<(QDebug debug, - std::variant const &t) { +static inline QDebug operator<<(QDebug debug, + std::variant const &t) { std::visit([&](auto &&arg) {debug << arg;}, t); return debug; } @@ -68,4 +74,12 @@ static inline std::ifstream openReadBin(std::filesystem::path path) { return std::ifstream{path, std::ios_base::in | std::ios_base::binary}; } +template +static inline std::string ntbsToString(std::array const &ntbs) { + std::string str; + auto zero = std::find(ntbs.cbegin(), ntbs.cend(), '\0'); + std::copy(ntbs.cbegin(), zero, std::back_inserter(str)); + return str; +} + // EOF diff --git a/source/quam/archive.cc b/source/quam/archive.cc index 2ad26d3..f4a0c7e 100644 --- a/source/quam/archive.cc +++ b/source/quam/archive.cc @@ -10,18 +10,114 @@ Arc::FileType Arc::getFileType(int n) { } } -ArcNode::ArcNode(ArcDir &&t, std::string &&n, Arc::FileType ty) : - super_type(std::move(t)), +ArcDir::iterator ArcDir::findNode(std::string const &name) { + return std::find_if(begin(), end(), [&name](ArcNode const &node) { + return node.name == name; + }); +} + +ArcNode::ArcNode(ArcDir &&f, std::string &&n, Arc::FileType t) : + super_type(std::move(f)), name(std::move(n)), - type(ty) + type(t) { } -ArcNode::ArcNode(ArcFile &&t, std::string &&n, Arc::FileType ty) : - super_type(std::move(t)), +ArcNode::ArcNode(ArcFile &&f, std::string &&n, Arc::FileType t) : + super_type(std::move(f)), name(std::move(n)), - type(ty) + type(t) { } +/* +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()); + + switch(role) { + case Qt::DecorationRole: + if(index.column() == Arc::ColumnName) { + auto icon = + std::holds_alternative(*node) ? "folder" + : "text-x-generic"; + return QVariant{QIcon::fromTheme(icon)}; + } + break; + case Qt::DisplayRole: + switch(index.column()) { + case Arc::ColumnSize: + if(auto file = std::get_if(node)) { + return QVariant{QString::number(file->size())}; + } + break; + case Arc::ColumnName: + 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(section) { + case Arc::ColumnSize: return QVariant{tr("Size")}; + case Arc::ColumnName: 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 Arc::ColumnMax; +} +*/ + // EOF diff --git a/source/quam/archive.h b/source/quam/archive.h index 8a2ba4f..02e2d47 100644 --- a/source/quam/archive.h +++ b/source/quam/archive.h @@ -34,6 +34,8 @@ struct ArcNode; struct ArcDir : public std::vector { using std::vector::vector; + + ArcDir::iterator findNode(std::string const &name); }; Q_DECLARE_METATYPE(ArcDir) @@ -47,8 +49,8 @@ struct ArcNode : public std::variant { ArcNode() = default; - ArcNode(ArcDir &&t, std::string &&n, Arc::FileType ty = Arc::FileNormal); - ArcNode(ArcFile &&t, std::string &&n, Arc::FileType ty = Arc::FileNormal); + ArcNode(ArcDir &&f, std::string &&n, Arc::FileType t = Arc::FileNormal); + ArcNode(ArcFile &&f, std::string &&n, Arc::FileType t = Arc::FileNormal); ArcNode(ArcNode const &) = default; ArcNode(ArcNode &&) = default; diff --git a/source/quam/pak.cc b/source/quam/pak.cc index ec4d219..f8aaddc 100644 --- a/source/quam/pak.cc +++ b/source/quam/pak.cc @@ -1,8 +1,5 @@ #include "quam/pak.h" -#include -#include - struct PakHeader { quint32 dirOffset; quint32 dirNum; @@ -50,9 +47,7 @@ static PakEntry readPakEntry(std::istream &st) { st.seekg(pos); - std::string name; - auto zero = std::find(entName.cbegin(), entName.cend(), '\0'); - std::copy(entName.cbegin(), zero, std::back_inserter(name)); + std::string name = ntbsToString(entName); if(name.front() == '/') { throw std::runtime_error("empty root directory name"); @@ -64,7 +59,7 @@ static PakEntry readPakEntry(std::istream &st) { return ent; } -void insertFile(ArcDir &dir, std::string name, ArcFile file) { +static void insertFile(ArcDir &dir, std::string name, ArcFile file) { std::optional next; if(auto slash = name.find('/'); slash != std::string::npos) { @@ -72,19 +67,16 @@ void insertFile(ArcDir &dir, std::string name, ArcFile file) { name = name.substr(0, slash); } - auto existingNode = std::find_if(dir.begin(), dir.end(), - [&name](ArcNode const &node) { - return node.name == name; - }); + auto existing = dir.findNode(name); if(next) { auto ref = - existingNode != dir.end() - ? *existingNode + existing != dir.end() + ? *existing : dir.emplace_back(ArcDir{}, std::move(name)); insertFile(std::get(ref), *std::move(next), std::move(file)); } else { - if(existingNode != dir.end()) { + if(existing != dir.end()) { throw std::runtime_error("duplicate file"); } dir.emplace_back(std::move(file), std::move(name)); diff --git a/source/quam/wad.cc b/source/quam/wad.cc new file mode 100644 index 0000000..6061a65 --- /dev/null +++ b/source/quam/wad.cc @@ -0,0 +1,81 @@ +#include "quam/wad.h" + +namespace Wad { + enum Compression { + CompressNone, + CompressLZSS, + }; +} + +struct WadHeader { + quint32 dirOffset; + quint32 dirNum; +}; + +struct WadEntry { + std::string name; + Arc::FileType type; + ArcFile file; +}; + +static WadHeader readWadHeader(std::istream &st) { + auto magic = readBytes<4>(st); + + if(magic != std::array{'W', 'A', 'D', '2'}) { + throw std::runtime_error("not a wad2 file (invalid magic number)"); + } + + WadHeader hdr; + hdr.dirOffset = readLE(st); + hdr.dirNum = readLE(st); + return hdr; +} + +static WadEntry readWadEntry(std::istream &st) { + auto entOffset = readLE(st); + auto entSize = readLE(st); + auto entCSize = readLE(st); + auto entType = readByte(st); + auto entCompr = readByte(st); + /* padding */ readBytes<2>(st); + auto entName = readBytes<16>(st); + + if(entSize != entCSize || entCompr != Wad::CompressNone) { + throw std::runtime_error("compressed files not implemented"); + } + + auto pos = st.tellg(); + + st.seekg(entOffset); + + ArcFile file; + file.resize(entSize); + st.read(file.data(), entSize); + + st.seekg(pos); + + WadEntry ent; + ent.name = ntbsToString(entName); + ent.file = std::move(file); + ent.type = Arc::getFileType(entType); + return ent; +} + +ArcDir readWad(std::istream &st) { + auto hdr = readWadHeader(st); + st.seekg(hdr.dirOffset); + + ArcDir root; + + for(quint32 i = 0; i < hdr.dirNum; i++) { + auto ent = readWadEntry(st); + if(root.findNode(ent.name) != root.end()) { + throw std::runtime_error("duplicate file"); + } + root.emplace_back(std::move(ent.file), std::move(ent.name), ent.type); + } + + return root; +} + +// EOF diff --git a/source/quam/wad.h b/source/quam/wad.h new file mode 100644 index 0000000..c99236b --- /dev/null +++ b/source/quam/wad.h @@ -0,0 +1,8 @@ +#pragma once + +#include "common.h" +#include "quam/archive.h" + +ArcDir readWad(std::istream &st); + +// EOF