mirror of
https://github.com/redstrate/Novus.git
synced 2025-04-24 04:57:45 +00:00
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.
This commit is contained in:
parent
7b59ed7f28
commit
4b19b7aeba
11 changed files with 579 additions and 141 deletions
|
@ -35,7 +35,7 @@ ecm_setup_version(${PROJECT_VERSION}
|
||||||
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/novus-version.h
|
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(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS CoreAddons Config XmlGui)
|
||||||
find_package(Vulkan REQUIRED)
|
find_package(Vulkan REQUIRED)
|
||||||
find_package(glm REQUIRED)
|
find_package(glm REQUIRED)
|
||||||
|
|
2
extern/libphysis
vendored
2
extern/libphysis
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit 5a5234d4c0b288caf80dfdfa07612f1a38d16532
|
Subproject commit aff19962aeb5d5ab7365c3e610a47ce5ab74d7cd
|
|
@ -1,13 +1,27 @@
|
||||||
# SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
# SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
||||||
# SPDX-License-Identifier: CC0-1.0
|
# 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)
|
add_executable(novus-sagasu)
|
||||||
target_sources(novus-sagasu PRIVATE
|
target_sources(novus-sagasu PRIVATE
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/mainwindow.cpp
|
src/mainwindow.cpp
|
||||||
src/filetreewindow.cpp
|
src/filetreewindow.cpp
|
||||||
src/filepropertieswindow.cpp)
|
src/filepropertieswindow.cpp
|
||||||
|
src/filetreemodel.cpp)
|
||||||
target_include_directories(novus-sagasu PRIVATE include)
|
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})
|
install(TARGETS novus-sagasu ${KF${QT_MAJOR_VERSION}_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||||
|
|
51
sagasu/include/filetreemodel.h
Normal file
51
sagasu/include/filetreemodel.h
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "hashdatabase.h"
|
||||||
|
#include <QAbstractItemModel>
|
||||||
|
#include <QFutureWatcher>
|
||||||
|
|
||||||
|
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<TreeInformation *> 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<uint32_t, TreeInformation *> knownDirHashes;
|
||||||
|
|
||||||
|
HashDatabase m_database;
|
||||||
|
};
|
|
@ -6,10 +6,7 @@
|
||||||
#include <QMdiSubWindow>
|
#include <QMdiSubWindow>
|
||||||
#include <physis.hpp>
|
#include <physis.hpp>
|
||||||
|
|
||||||
struct PathPart {
|
#include "filetreemodel.h"
|
||||||
uint32_t crcHash;
|
|
||||||
QMap<QString, PathPart> children;
|
|
||||||
};
|
|
||||||
|
|
||||||
class FileTreeWindow : public QWidget
|
class FileTreeWindow : public QWidget
|
||||||
{
|
{
|
||||||
|
@ -17,20 +14,11 @@ class FileTreeWindow : public QWidget
|
||||||
public:
|
public:
|
||||||
explicit FileTreeWindow(GameData *data, QWidget *parent = nullptr);
|
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<QString> tokens, PathPart &part, QString pathSoFar);
|
|
||||||
std::tuple<bool, QString> traverseUnknownPath(uint32_t crcHash, PathPart &part, QString pathSoFar);
|
|
||||||
|
|
||||||
QMap<QString, PathPart> rootParts;
|
|
||||||
|
|
||||||
void addPaths(QTreeWidget *pWidget);
|
|
||||||
|
|
||||||
QTreeWidgetItem *addPartAndChildren(const QString &qString, const PathPart &part, const QString &pathSoFar);
|
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void openFileProperties(QString path);
|
void openFileProperties(QString path);
|
||||||
|
void extractFile(QString path);
|
||||||
|
|
||||||
|
private:
|
||||||
|
GameData *data = nullptr;
|
||||||
|
FileTreeModel *m_fileModel;
|
||||||
};
|
};
|
26
sagasu/include/hashdatabase.h
Normal file
26
sagasu/include/hashdatabase.h
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QSqlDatabase>
|
||||||
|
#include <QSqlQuery>
|
||||||
|
|
||||||
|
class HashDatabase : public QObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
HashDatabase(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
void addFolder(QString folder);
|
||||||
|
void addFile(QString file);
|
||||||
|
|
||||||
|
QVector<QString> getKnownFolders();
|
||||||
|
|
||||||
|
bool knowsFile(const uint32_t i);
|
||||||
|
|
||||||
|
QString getFilename(const uint32_t i);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSqlDatabase m_db;
|
||||||
|
};
|
180
sagasu/src/filetreemodel.cpp
Normal file
180
sagasu/src/filetreemodel.cpp
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "filetreemodel.h"
|
||||||
|
#include "physis.hpp"
|
||||||
|
|
||||||
|
#include <QtConcurrent>
|
||||||
|
|
||||||
|
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<TreeInformation *>(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<TreeInformation *>(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<TreeInformation *>(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<TreeInformation *>(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"
|
|
@ -1,13 +1,12 @@
|
||||||
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <QDebug>
|
#include "filetreewindow.h"
|
||||||
|
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QTreeWidget>
|
#include <QTreeWidget>
|
||||||
|
|
||||||
#include "filetreewindow.h"
|
|
||||||
|
|
||||||
FileTreeWindow::FileTreeWindow(GameData *data, QWidget *parent)
|
FileTreeWindow::FileTreeWindow(GameData *data, QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, data(data)
|
, data(data)
|
||||||
|
@ -17,129 +16,34 @@ FileTreeWindow::FileTreeWindow(GameData *data, QWidget *parent)
|
||||||
auto layout = new QHBoxLayout();
|
auto layout = new QHBoxLayout();
|
||||||
setLayout(layout);
|
setLayout(layout);
|
||||||
|
|
||||||
auto treeWidget = new QTreeWidget();
|
m_fileModel = new FileTreeModel(data);
|
||||||
treeWidget->setHeaderLabel(QStringLiteral("Name"));
|
|
||||||
|
auto treeWidget = new QTreeView();
|
||||||
|
treeWidget->setModel(m_fileModel);
|
||||||
layout->addWidget(treeWidget);
|
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);
|
treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
connect(treeWidget, &QTreeWidget::customContextMenuRequested, this, [this, treeWidget](const QPoint &pos) {
|
connect(treeWidget, &QTreeWidget::customContextMenuRequested, this, [this, treeWidget](const QPoint &pos) {
|
||||||
auto *item = treeWidget->itemAt(pos);
|
auto index = treeWidget->indexAt(pos);
|
||||||
|
|
||||||
if (item != nullptr) {
|
if (index.isValid()) {
|
||||||
auto path = item->data(0, Qt::UserRole).toString();
|
auto path = m_fileModel->data(index, Qt::UserRole).toString();
|
||||||
qInfo() << path;
|
|
||||||
|
|
||||||
auto menu = new QMenu();
|
auto menu = new QMenu();
|
||||||
|
|
||||||
QAction *propertiesAction = menu->addAction(QStringLiteral("Properties"));
|
auto propertiesAction = menu->addAction(QStringLiteral("Properties"));
|
||||||
connect(propertiesAction, &QAction::triggered, this, [=] {
|
connect(propertiesAction, &QAction::triggered, this, [=] {
|
||||||
Q_EMIT openFileProperties(path);
|
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));
|
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<QString> 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<bool, QString> 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"
|
#include "moc_filetreewindow.cpp"
|
94
sagasu/src/hashdatabase.cpp
Normal file
94
sagasu/src/hashdatabase.cpp
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "hashdatabase.h"
|
||||||
|
|
||||||
|
#include <QSqlError>
|
||||||
|
#include <physis.hpp>
|
||||||
|
|
||||||
|
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<QString> HashDatabase::getKnownFolders()
|
||||||
|
{
|
||||||
|
QSqlQuery query;
|
||||||
|
query.exec(QStringLiteral("SELECT name FROM folder_hashes"));
|
||||||
|
|
||||||
|
QVector<QString> 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();
|
||||||
|
}
|
173
sagasu/src/indexer.cpp
Normal file
173
sagasu/src/indexer.cpp
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
||||||
|
// 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
|
@ -3,17 +3,11 @@
|
||||||
|
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
|
|
||||||
#include <KAboutApplicationDialog>
|
|
||||||
#include <KAboutData>
|
|
||||||
#include <QAction>
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QDebug>
|
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
|
#include <QFileDialog>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QMenuBar>
|
#include <QMenuBar>
|
||||||
#include <QTableWidget>
|
|
||||||
#include <QTreeWidget>
|
|
||||||
#include <QUrl>
|
|
||||||
|
|
||||||
#include "filepropertieswindow.h"
|
#include "filepropertieswindow.h"
|
||||||
#include "filetreewindow.h"
|
#include "filetreewindow.h"
|
||||||
|
@ -29,10 +23,24 @@ MainWindow::MainWindow(GameData *data)
|
||||||
|
|
||||||
auto tree = new FileTreeWindow(data);
|
auto tree = new FileTreeWindow(data);
|
||||||
connect(tree, &FileTreeWindow::openFileProperties, this, [=](QString path) {
|
connect(tree, &FileTreeWindow::openFileProperties, this, [=](QString path) {
|
||||||
qInfo() << "opening properties window for " << path;
|
|
||||||
auto window = mdiArea->addSubWindow(new FilePropertiesWindow(data, path));
|
auto window = mdiArea->addSubWindow(new FilePropertiesWindow(data, path));
|
||||||
window->show();
|
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<const char *>(fileData.data), fileData.size);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
mdiArea->addSubWindow(tree);
|
mdiArea->addSubWindow(tree);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue