diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c3eb4d..203fc92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,7 +35,7 @@ ecm_setup_version(${PROJECT_VERSION} VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/novus-version.h ) -find_package(Qt6 ${QT_MIN_VERSION} COMPONENTS Core Widgets Concurrent Core5Compat CONFIG REQUIRED) +find_package(Qt6 ${QT_MIN_VERSION} COMPONENTS Core Widgets Concurrent Core5Compat Sql CONFIG REQUIRED) find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS CoreAddons Config XmlGui) find_package(Vulkan REQUIRED) find_package(glm REQUIRED) diff --git a/extern/libphysis b/extern/libphysis index 5a5234d..aff1996 160000 --- a/extern/libphysis +++ b/extern/libphysis @@ -1 +1 @@ -Subproject commit 5a5234d4c0b288caf80dfdfa07612f1a38d16532 +Subproject commit aff19962aeb5d5ab7365c3e610a47ce5ab74d7cd diff --git a/sagasu/CMakeLists.txt b/sagasu/CMakeLists.txt index ed9f011..ce0243f 100644 --- a/sagasu/CMakeLists.txt +++ b/sagasu/CMakeLists.txt @@ -1,13 +1,27 @@ # SPDX-FileCopyrightText: 2023 Joshua Goins # SPDX-License-Identifier: CC0-1.0 +add_library(novus-sagasu-static STATIC) +target_sources(novus-sagasu-static PRIVATE + include/hashdatabase.h + src/hashdatabase.cpp) +target_link_libraries(novus-sagasu-static PUBLIC physis z Qt6::Core Qt6::Sql novus-common) +target_include_directories(novus-sagasu-static PRIVATE include) + +add_executable(novus-sagasu-indexer) +target_sources(novus-sagasu-indexer PRIVATE + src/indexer.cpp) +target_link_libraries(novus-sagasu-indexer PRIVATE novus-sagasu-static) +target_include_directories(novus-sagasu-indexer PRIVATE include) + add_executable(novus-sagasu) target_sources(novus-sagasu PRIVATE src/main.cpp src/mainwindow.cpp src/filetreewindow.cpp - src/filepropertieswindow.cpp) + src/filepropertieswindow.cpp + src/filetreemodel.cpp) target_include_directories(novus-sagasu PRIVATE include) -target_link_libraries(novus-sagasu PRIVATE physis z Qt6::Core Qt6::Widgets novus-common) +target_link_libraries(novus-sagasu PRIVATE Qt6::Concurrent novus-sagasu-static) install(TARGETS novus-sagasu ${KF${QT_MAJOR_VERSION}_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/sagasu/include/filetreemodel.h b/sagasu/include/filetreemodel.h new file mode 100644 index 0000000..a155865 --- /dev/null +++ b/sagasu/include/filetreemodel.h @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2023 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "hashdatabase.h" +#include +#include + +struct GameData; + +enum class TreeType { Root, Folder, File }; + +struct TreeInformation { + TreeType type; + TreeInformation *parent = nullptr; + QString name; + int row = 0; + uint32_t hash = 0; + + std::vector children; +}; + +class FileTreeModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit FileTreeModel(GameData *data); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &child) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + +private: + GameData *gameData = nullptr; + TreeInformation *rootItem = nullptr; + + void addKnownFolder(QString string); + void addFile(TreeInformation *parentItem, uint32_t filenameHash, QString name); + void addFolder(TreeInformation *parentItem, uint32_t filenameHash); + + QHash knownDirHashes; + + HashDatabase m_database; +}; \ No newline at end of file diff --git a/sagasu/include/filetreewindow.h b/sagasu/include/filetreewindow.h index 4aeb30d..082a3e0 100644 --- a/sagasu/include/filetreewindow.h +++ b/sagasu/include/filetreewindow.h @@ -6,10 +6,7 @@ #include #include -struct PathPart { - uint32_t crcHash; - QMap children; -}; +#include "filetreemodel.h" class FileTreeWindow : public QWidget { @@ -17,20 +14,11 @@ class FileTreeWindow : public QWidget public: explicit FileTreeWindow(GameData *data, QWidget *parent = nullptr); -private: - GameData *data = nullptr; - - void addPath(QString path); - void addUnknownPath(QString knownDirectory, uint32_t crcHash); - void traversePart(QList tokens, PathPart &part, QString pathSoFar); - std::tuple traverseUnknownPath(uint32_t crcHash, PathPart &part, QString pathSoFar); - - QMap rootParts; - - void addPaths(QTreeWidget *pWidget); - - QTreeWidgetItem *addPartAndChildren(const QString &qString, const PathPart &part, const QString &pathSoFar); - Q_SIGNALS: void openFileProperties(QString path); + void extractFile(QString path); + +private: + GameData *data = nullptr; + FileTreeModel *m_fileModel; }; \ No newline at end of file diff --git a/sagasu/include/hashdatabase.h b/sagasu/include/hashdatabase.h new file mode 100644 index 0000000..5653ab2 --- /dev/null +++ b/sagasu/include/hashdatabase.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include + +class HashDatabase : public QObject +{ +public: + HashDatabase(QObject *parent = nullptr); + + void addFolder(QString folder); + void addFile(QString file); + + QVector getKnownFolders(); + + bool knowsFile(const uint32_t i); + + QString getFilename(const uint32_t i); + +private: + QSqlDatabase m_db; +}; \ No newline at end of file diff --git a/sagasu/src/filetreemodel.cpp b/sagasu/src/filetreemodel.cpp new file mode 100644 index 0000000..3a9f33c --- /dev/null +++ b/sagasu/src/filetreemodel.cpp @@ -0,0 +1,180 @@ +// SPDX-FileCopyrightText: 2023 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "filetreemodel.h" +#include "physis.hpp" + +#include + +FileTreeModel::FileTreeModel(GameData *data) + : gameData(data) + , QAbstractItemModel() +{ + rootItem = new TreeInformation(); + rootItem->type = TreeType::Root; + + for (auto knownFolder : m_database.getKnownFolders()) { + addKnownFolder(knownFolder); + } + + auto indexEntries = physis_index_parse("/home/josh/.local/share/astra/game/{1973dab7-aa23-4af1-8a48-bfec78dd6c8e}/game/sqpack/ffxiv/000000.win32.index"); + for (int i = 0; i < indexEntries.num_entries; i++) { + if (knownDirHashes.contains(indexEntries.dir_entries[i])) { + QString name; + if (m_database.knowsFile(indexEntries.filename_entries[i])) { + name = m_database.getFilename(indexEntries.filename_entries[i]); + } + addFile(knownDirHashes[indexEntries.dir_entries[i]], indexEntries.filename_entries[i], name); + } else { + addFolder(rootItem, indexEntries.dir_entries[i]); + } + } +} + +int FileTreeModel::rowCount(const QModelIndex &parent) const +{ + TreeInformation *parentItem; + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) + parentItem = rootItem; + else + parentItem = static_cast(parent.internalPointer()); + + return parentItem->children.size(); +} + +int FileTreeModel::columnCount(const QModelIndex &parent) const +{ + return 1; +} + +QModelIndex FileTreeModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) + return {}; + + TreeInformation *parentItem; + + if (!parent.isValid()) + parentItem = rootItem; + else + parentItem = static_cast(parent.internalPointer()); + + TreeInformation *childItem = parentItem->children[row]; + if (childItem) + return createIndex(row, column, childItem); + + return {}; +} + +QModelIndex FileTreeModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return {}; + + auto childItem = static_cast(index.internalPointer()); + TreeInformation *parentItem = childItem->parent; + + if (parentItem == rootItem) + return {}; + + return createIndex(parentItem->row, 0, parentItem); +} + +QVariant FileTreeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return {}; + + auto item = static_cast(index.internalPointer()); + if (role == Qt::UserRole) { + // build the full path + QString path; + TreeInformation *parent = item; + while (parent != rootItem) { + if (path.isEmpty()) { + path = parent->name; + } else { + path = parent->name + QStringLiteral("/") + path; + } + parent = parent->parent; + } + + return path; + } else if (role == Qt::DisplayRole) { + if (item->type == TreeType::Folder) { + if (item->name.isEmpty()) { + return QStringLiteral("Unknown Folder (%1)").arg(item->hash); + } else { + return item->name; + } + } else if (item->type == TreeType::File) { + if (item->name.isEmpty()) { + return QStringLiteral("Unknown File (%1)").arg(item->hash); + } else { + return item->name; + } + } + } + + return {}; +} + +QVariant FileTreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { + if (section == 0) { + return QStringLiteral("Name"); + } + } + + return QAbstractItemModel::headerData(section, orientation, role); +} + +void FileTreeModel::addKnownFolder(QString string) +{ + auto children = string.split(QStringLiteral("/")); + + QString conct = children[0]; + TreeInformation *parentItem = rootItem; + for (int i = 0; i < children.size(); i++) { + if (i > 0) { + conct += QStringLiteral("/") + children[i]; + } + std::string conctStd = conct.toStdString(); + auto folderItem = new TreeInformation(); + folderItem->name = children[i]; + folderItem->type = TreeType::Folder; + folderItem->parent = parentItem; + folderItem->row = i + 1; + folderItem->hash = physis_generate_partial_hash(conctStd.c_str()); + parentItem->children.push_back(folderItem); + parentItem = folderItem; + knownDirHashes[folderItem->hash] = folderItem; + } +} + +void FileTreeModel::addFile(TreeInformation *parentItem, uint32_t name, QString realName) +{ + auto fileItem = new TreeInformation(); + fileItem->hash = name; + fileItem->name = realName; + fileItem->type = TreeType::File; + fileItem->parent = parentItem; + + parentItem->children.push_back(fileItem); +} + +void FileTreeModel::addFolder(TreeInformation *parentItem, uint32_t name) +{ + auto fileItem = new TreeInformation(); + fileItem->hash = name; + fileItem->type = TreeType::Folder; + fileItem->parent = parentItem; + + parentItem->children.push_back(fileItem); +} + +#include "moc_filetreemodel.cpp" \ No newline at end of file diff --git a/sagasu/src/filetreewindow.cpp b/sagasu/src/filetreewindow.cpp index 544584d..335e0f3 100644 --- a/sagasu/src/filetreewindow.cpp +++ b/sagasu/src/filetreewindow.cpp @@ -1,13 +1,12 @@ // SPDX-FileCopyrightText: 2023 Joshua Goins // SPDX-License-Identifier: GPL-3.0-or-later -#include +#include "filetreewindow.h" + #include #include #include -#include "filetreewindow.h" - FileTreeWindow::FileTreeWindow(GameData *data, QWidget *parent) : QWidget(parent) , data(data) @@ -17,129 +16,34 @@ FileTreeWindow::FileTreeWindow(GameData *data, QWidget *parent) auto layout = new QHBoxLayout(); setLayout(layout); - auto treeWidget = new QTreeWidget(); - treeWidget->setHeaderLabel(QStringLiteral("Name")); + m_fileModel = new FileTreeModel(data); + + auto treeWidget = new QTreeView(); + treeWidget->setModel(m_fileModel); layout->addWidget(treeWidget); - addPath(QStringLiteral("common/font/AXIS_12.fdt")); - - addPath(QStringLiteral("exd/root.exl")); - - /*auto sheetNames = physis_gamedata_get_all_sheet_names(data); - - for(int i = 0; i < sheetNames.name_count; i++) { - auto sheetName = sheetNames.names[i]; - auto nameLowercase = QString::fromStdString(sheetName).toLower().toStdString(); - - addPath(QStringLiteral("exd/") + QLatin1String(nameLowercase.c_str()) + QStringLiteral(".exh")); - - auto exh = physis_gamedata_read_excel_sheet_header(data, sheetName); - for (int j = 0; j < exh->page_count; j++) { - for (int z = 0; z < exh->language_count; z++) { - std::string path = physis_gamedata_get_exd_filename(nameLowercase.c_str(), exh, exh->languages[z], j); - - addPath(QStringLiteral("exd/") + QString::fromStdString(path)); - } - } - }*/ - treeWidget->setContextMenuPolicy(Qt::CustomContextMenu); connect(treeWidget, &QTreeWidget::customContextMenuRequested, this, [this, treeWidget](const QPoint &pos) { - auto *item = treeWidget->itemAt(pos); + auto index = treeWidget->indexAt(pos); - if (item != nullptr) { - auto path = item->data(0, Qt::UserRole).toString(); - qInfo() << path; + if (index.isValid()) { + auto path = m_fileModel->data(index, Qt::UserRole).toString(); auto menu = new QMenu(); - QAction *propertiesAction = menu->addAction(QStringLiteral("Properties")); + auto propertiesAction = menu->addAction(QStringLiteral("Properties")); connect(propertiesAction, &QAction::triggered, this, [=] { Q_EMIT openFileProperties(path); }); - QPoint pt(pos); + auto extractAction = menu->addAction(QStringLiteral("Extract..")); + connect(extractAction, &QAction::triggered, this, [=] { + Q_EMIT extractFile(path); + }); + menu->exec(treeWidget->mapToGlobal(pos)); } }); - - addPaths(treeWidget); -} - -void FileTreeWindow::addPath(QString path) -{ - auto tokens = path.split(QStringLiteral("/")); - auto nextToken = tokens[0]; - tokens.pop_front(); - - traversePart(tokens, rootParts[nextToken], nextToken); -} - -void FileTreeWindow::traversePart(QList tokens, PathPart &part, QString pathSoFar) -{ - if (tokens.empty()) - return; - - auto nextToken = tokens[0]; - tokens.pop_front(); - - pathSoFar = pathSoFar + QStringLiteral("/") + nextToken; - part.children[nextToken].crcHash = physis_calculate_hash(pathSoFar.toStdString().c_str()); - - traversePart(tokens, part.children[nextToken], pathSoFar); -} - -void FileTreeWindow::addPaths(QTreeWidget *pWidget) -{ - for (const auto &name : rootParts.keys()) { - auto item = addPartAndChildren(name, rootParts.value(name), QStringLiteral("")); - pWidget->addTopLevelItem(item); - } -} - -QTreeWidgetItem *FileTreeWindow::addPartAndChildren(const QString &qString, const PathPart &part, const QString &pathSoFar) -{ - QString newPath = pathSoFar.isEmpty() ? qString : pathSoFar + QStringLiteral("/") + qString; - - auto item = new QTreeWidgetItem(); - item->setData(0, Qt::UserRole, newPath); - item->setText(0, qString); - - for (const auto &name : part.children.keys()) { - auto childItem = addPartAndChildren(name, part.children.value(name), newPath); - item->addChild(childItem); - } - - return item; -} - -void FileTreeWindow::addUnknownPath(QString knownDirectory, uint32_t crcHash) -{ - auto [found, path] = traverseUnknownPath(crcHash, rootParts[knownDirectory], knownDirectory); - if (found) - addPath(path); - else - addPath(knownDirectory + QStringLiteral("/Unknown File Hash ") + QString::number(crcHash)); -} - -std::tuple FileTreeWindow::traverseUnknownPath(uint32_t crcHash, PathPart &part, QString pathSoFar) -{ - if (part.crcHash == crcHash) - return {true, pathSoFar}; - - bool found = false; - QString childPath = pathSoFar; - for (auto path : part.children.keys()) { - if (path.contains(QStringLiteral("Unknown"))) - continue; - - auto [childFound, newPath] = traverseUnknownPath(crcHash, part.children[path], pathSoFar + QStringLiteral("/") + path); - found |= childFound; - if (childFound) - childPath = newPath; - } - - return {found, childPath}; } #include "moc_filetreewindow.cpp" \ No newline at end of file diff --git a/sagasu/src/hashdatabase.cpp b/sagasu/src/hashdatabase.cpp new file mode 100644 index 0000000..88c59a0 --- /dev/null +++ b/sagasu/src/hashdatabase.cpp @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: 2023 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "hashdatabase.h" + +#include +#include + +HashDatabase::HashDatabase(QObject *parent) + : QObject(parent) +{ + m_db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE")); + m_db.setDatabaseName(QStringLiteral("customdb.db")); + + if (!m_db.open()) { + qFatal() << "Failed to open custom db!"; + } + + QSqlQuery query; + query.exec(QStringLiteral("CREATE TABLE IF NOT EXISTS folder_hashes (hash INTEGER PRIMARY KEY, name TEXT NOT NULL)")); + query.exec(QStringLiteral("CREATE TABLE IF NOT EXISTS file_hashes (hash INTEGER PRIMARY KEY, name TEXT NOT NULL)")); +} + +void HashDatabase::addFolder(QString folder) +{ + std::string folderStd = folder.toStdString(); + + QSqlQuery query; + query.prepare( + QStringLiteral("REPLACE INTO folder_hashes (hash, name) " + "VALUES (?, ?)")); + query.addBindValue(physis_generate_partial_hash(folderStd.c_str())); + query.addBindValue(folder); + query.exec(); +} + +void HashDatabase::addFile(QString file) +{ + QString filename = file; + if (file.contains(QStringLiteral("/"))) { + int lastSlash = file.lastIndexOf(QStringLiteral("/")); + filename = file.sliced(lastSlash + 1, file.length() - lastSlash - 1); + } + + qInfo() << filename; + + std::string folderStd = filename.toStdString(); + + QSqlQuery query; + query.prepare( + QStringLiteral("REPLACE INTO file_hashes (hash, name) " + "VALUES (?, ?)")); + query.addBindValue(physis_generate_partial_hash(folderStd.c_str())); + query.addBindValue(filename); + query.exec(); +} + +QVector HashDatabase::getKnownFolders() +{ + QSqlQuery query; + query.exec(QStringLiteral("SELECT name FROM folder_hashes")); + + QVector folders; + while (query.next()) { + QString country = query.value(0).toString(); + folders.push_back(country); + } + + return folders; +} + +bool HashDatabase::knowsFile(const uint32_t i) +{ + QSqlQuery query; + query.prepare(QStringLiteral("SELECT COUNT(1) FROM file_hashes WHERE hash = ?;")); + query.addBindValue(i); + query.exec(); + + query.next(); + + return query.value(0) == 1; +} + +QString HashDatabase::getFilename(const uint32_t i) +{ + QSqlQuery query; + query.prepare(QStringLiteral("SELECT name FROM file_hashes WHERE hash = ?;")); + query.addBindValue(i); + query.exec(); + + query.next(); + + return query.value(0).toString(); +} diff --git a/sagasu/src/indexer.cpp b/sagasu/src/indexer.cpp new file mode 100644 index 0000000..ae15ee3 --- /dev/null +++ b/sagasu/src/indexer.cpp @@ -0,0 +1,173 @@ +// SPDX-FileCopyrightText: 2023 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "hashdatabase.h" +#include "settings.h" + +const std::array known_folders{"common", "common/font", "exd"}; + +const std::array common_font{"common/font/fontIcon_Xinput.tex", + "common/font/gfdata.gfd", + "common/font/fontIcon_Ps3.tex", + "common/font/gfdata.gfd", + "common/font/fontIcon_Ps4.tex", + "common/font/gfdata.gfd", + "common/font/fontIcon_Ps5.tex", + "common/font/gfdata.gfd", + "common/font/MiedingerMid_36.fdt", + "common/font/MiedingerMid_18.fdt", + "common/font/MiedingerMid_14.fdt", + "common/font/MiedingerMid_12.fdt", + "common/font/MiedingerMid_10.fdt", + "common/font/Meidinger_40.fdt", + "common/font/Meidinger_20.fdt", + "common/font/Meidinger_16.fdt", + "common/font/=TrumpGothic_68.fdt", + "common/font/TrumpGothic_34.fdt", + "common/font/TrumpGothic_23.fdt", + "common/font/TrumpGothic_184.fdt", + "common/font/Jupiter_46.fdt", + "common/font/Jupiter_23.fdt", + "common/font/Jupiter_20.fdt", + "common/font/Jupiter_16.fdt", + "common/font/Jupiter_90.fdt", + "common/font/Jupiter_45.fdt", + "common/font/MiedingerMid_36.fdt", + "common/font/MiedingerMid_18.fdt", + "common/font/MiedingerMid_14.fdt", + "common/font/MiedingerMid_12.fdt", + "common/font/MiedingerMid_10.fdt", + "common/font/Meidinger_40.fdt", + "common/font/Meidinger_20.fdt", + "common/font/Meidinger_16.fdt", + "common/font/TrumpGothic_68.fdt", + "common/font/TrumpGothic_34.fdt", + "common/font/TrumpGothic_23.fdt", + "common/font/TrumpGothic_184.fdt", + "common/font/Jupiter_46.fdt", + "common/font/Jupiter_23.fdt", + "common/font/Jupiter_20.fdt", + "common/font/Jupiter_16.fdt", + "common/font/Jupiter_90.fdt", + "common/font/Jupiter_45.fdt", + "common/font/AXIS_18_lobby.fdt", + "common/font/AXIS_14_lobby.fdt", + "common/font/AXIS_12_lobby.fdt", + "common/font/AXIS_12_lobby.fdt", + "common/font/>MiedingerMid_18_lobby.fdt", + "common/font/L=MiedingerMid_18_lobby.fdt", + "common/font/>MiedingerMid_14_lobby.fdt", + "common/font/MiedingerMid_12_lobby.fdt", + "common/font/MiedingerMid_10_lobby.fdt", + "common/font/Meidinger_20_lobby.fdt", + "common/font/Meidinger_20_lobby.fdt", + "common/font/Meidinger_16_lobby.fdt", + "common/font/TrumpGothic_34_lobby.fdt", + "common/font/TrumpGothic_34_lobby.fdt", + "common/font/TrumpGothic_23_lobby.fdt", + "common/font/TrumpGothic_184_lobby.fdt", + "common/font/Jupiter_23_lobby.fdt", + "common/font/Jupiter_23_lobby.fdt", + "common/font/Jupiter_20_lobby.fdt", + "common/font/Jupiter_16_lobby.fdt", + "common/font/Jupiter_45_lobby.fdt", + "common/font/Jupiter_45_lobby.fdt", + "common/font/AXIS_18_lobby.fdt", + "common/font/MiedingerMid_18_lobby.fdt", + "common/font/MiedingerMid_18_lobby.fdt", + "common/font/MiedingerMid_14_lobby.fdt", + "common/font/MiedingerMid_12_lobby.fdt", + "common/font/MiedingerMid_10_lobby.fdt", + "common/font/Meidinger_20_lobby.fdt", + "common/font/Meidinger_20_lobby.fdt", + "common/font/Meidinger_16_lobby.fdt", + "common/font/TrumpGothic_34_lobby.fdt", + "common/font/TrumpGothic_34_lobby.fdt", + "common/font/TrumpGothic_23_lobby.fdt", + "common/font/TrumpGothic_184_lobby.fdt", + "common/font/Jupiter_23_lobby.fdt", + "common/font/Jupiter_23_lobby.fdt", + "common/font/Jupiter_20_lobby.fdt", + "common/font/Jupiter_16_lobby.fdt", + "common/font/Jupiter_45_lobby.fdt", + "common/font/Jupiter_45_lobby.fdt", + "common/font/AXIS_18_lobby.fdt", + "common/font/AXIS_14_lobby.fdt", + "common/font/AXIS_12_lobby.fdt", + "common/font/AXIS_12_lobby.fdt", + "common/font/MiedingerMid_36_lobby.fdt", + "common/font/MiedingerMid_18_lobby.fdt", + "common/font/MiedingerMid_14_lobby.fdt", + "common/font/MiedingerMid_12_lobby.fdt", + "common/font/MiedingerMid_10_lobby.fdt", + "common/font/Meidinger_40_lobby.fdt", + "common/font/Meidinger_20_lobby.fdt", + "common/font/Meidinger_16_lobby.fdt", + "common/font/TrumpGothic_68_lobby.fdt", + "common/font/TrumpGothic_34_lobby.fdt", + "common/font/TrumpGothic_23_lobby.fdt", + "common/font/TrumpGothic_184_lobby.fdt", + "common/font/Jupiter_46_lobby.fdt", + "common/font/Jupiter_23_lobby.fdt", + "common/font/Jupiter_20_lobby.fdt", + "common/font/Jupiter_16_lobby.fdt", + "common/font/Jupiter_90_lobby.fdt", + "common/font/Jupiter_45_lobby.fdt", + "common/font/AXIS_36_lobby.fdt", + "common/font/MiedingerMid_36_lobby.fdt", + "common/font/MiedingerMid_18_lobby.fdt", + "common/font/MiedingerMid_14_lobby.fdt", + "common/font/MiedingerMid_12_lobby.fdt", + "common/font/MiedingerMid_10_lobby.fdt", + "common/font/Meidinger_40_lobby.fdt", + "common/font/Meidinger_20_lobby.fdt", + "common/font/Meidinger_16_lobby.fdt", + "common/font/TrumpGothic_68_lobby.fdt", + "common/font/TrumpGothic_34_lobby.fdt", + "common/font/TrumpGothic_23_lobby.fdt", + "common/font/TrumpGothic_184_lobby.fdt", + "common/font/Jupiter_46_lobby.fdt", + "common/font/Jupiter_23_lobby.fdt", + "common/font/Jupiter_20_lobby.fdt", + "common/font/Jupiter_16_lobby.fdt", + "common/font/Jupiter_90_lobby.fdt", + "common/font/Jupiter_45_lobby.fdt"}; + +int main(int argc, char *argv[]) +{ + HashDatabase database; + + for (auto &folder : known_folders) { + database.addFolder(QLatin1String(folder)); + } + + for (auto &file : common_font) { + database.addFile(QLatin1String(file)); + } + + /*const QString gameDir{getGameDirectory()}; + const std::string gameDirStd{gameDir.toStdString()}; + auto data = physis_gamedata_initialize(gameDirStd.c_str()); + + addPath(QStringLiteral("common/font/AXIS_12.fdt")); + + addPath(QStringLiteral("exd/root.exl")); + + auto sheetNames = physis_gamedata_get_all_sheet_names(data); + + for(int i = 0; i < sheetNames.name_count; i++) { + auto sheetName = sheetNames.names[i]; + auto nameLowercase = QString::fromStdString(sheetName).toLower().toStdString(); + + addPath(QStringLiteral("exd/") + QLatin1String(nameLowercase.c_str()) + QStringLiteral(".exh")); + + auto exh = physis_gamedata_read_excel_sheet_header(data, sheetName); + for (int j = 0; j < exh->page_count; j++) { + for (int z = 0; z < exh->language_count; z++) { + std::string path = physis_gamedata_get_exd_filename(nameLowercase.c_str(), exh, exh->languages[z], j); + + addPath(QStringLiteral("exd/") + QString::fromStdString(path)); + } + } + }*/ +} \ No newline at end of file diff --git a/sagasu/src/mainwindow.cpp b/sagasu/src/mainwindow.cpp index e319bfb..e6d2ab6 100644 --- a/sagasu/src/mainwindow.cpp +++ b/sagasu/src/mainwindow.cpp @@ -3,17 +3,11 @@ #include "mainwindow.h" -#include -#include -#include #include -#include #include +#include #include #include -#include -#include -#include #include "filepropertieswindow.h" #include "filetreewindow.h" @@ -29,10 +23,24 @@ MainWindow::MainWindow(GameData *data) auto tree = new FileTreeWindow(data); connect(tree, &FileTreeWindow::openFileProperties, this, [=](QString path) { - qInfo() << "opening properties window for " << path; auto window = mdiArea->addSubWindow(new FilePropertiesWindow(data, path)); window->show(); }); + connect(tree, &FileTreeWindow::extractFile, this, [this, data](QString path) { + const QFileInfo info(path); + + const QString savePath = QFileDialog::getSaveFileName(this, tr("Save File"), info.fileName(), QStringLiteral("*.%1").arg(info.completeSuffix())); + if (!savePath.isEmpty()) { + qInfo() << "Saving to" << savePath; + + std::string savePathStd = path.toStdString(); + + auto fileData = physis_gamedata_extract_file(data, savePathStd.c_str()); + QFile file(savePath); + file.open(QIODevice::WriteOnly); + file.write(reinterpret_cast(fileData.data), fileData.size); + } + }); mdiArea->addSubWindow(tree); }