From 4b19b7aeba0bf28bd80d3ffc249249d43d72c07e Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Thu, 12 Oct 2023 19:04:19 -0400 Subject: [PATCH] Improve Sagasu design & indexing Now the hashes are collected in a central database (location to be improved) similar to FFXIV Explorer. This database needs to be generated once and doesn't have to be regen every time Sagasu is opened like before. This indexer currently is a separate program. Also adds a feature to extract files from the file tree window. --- CMakeLists.txt | 2 +- extern/libphysis | 2 +- sagasu/CMakeLists.txt | 18 +++- sagasu/include/filetreemodel.h | 51 +++++++++ sagasu/include/filetreewindow.h | 24 ++--- sagasu/include/hashdatabase.h | 26 +++++ sagasu/src/filetreemodel.cpp | 180 ++++++++++++++++++++++++++++++++ sagasu/src/filetreewindow.cpp | 126 +++------------------- sagasu/src/hashdatabase.cpp | 94 +++++++++++++++++ sagasu/src/indexer.cpp | 173 ++++++++++++++++++++++++++++++ sagasu/src/mainwindow.cpp | 24 +++-- 11 files changed, 579 insertions(+), 141 deletions(-) create mode 100644 sagasu/include/filetreemodel.h create mode 100644 sagasu/include/hashdatabase.h create mode 100644 sagasu/src/filetreemodel.cpp create mode 100644 sagasu/src/hashdatabase.cpp create mode 100644 sagasu/src/indexer.cpp 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); }