diff --git a/CMakeLists.txt b/CMakeLists.txt index 6816d02..e91890f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,4 +37,5 @@ endif() add_subdirectory(renderer) add_subdirectory(exdviewer) -add_subdirectory(mdlviewer) \ No newline at end of file +add_subdirectory(mdlviewer) +add_subdirectory(explorer) \ No newline at end of file diff --git a/README.md b/README.md index 870b3ce..1acfd1f 100644 --- a/README.md +++ b/README.md @@ -45,4 +45,14 @@ You must pass the path to your `sqpack` directory as the first argument. The viewport uses Vulkan, so it must be supported on your system in order to work. If you're running mdlviewer on macOS (where Qt builds usually don't ship with MoltenVK unfortunatey) -mdlviewer will automatically reconfigure itself to use a standalone SDL2 window. \ No newline at end of file +mdlviewer will automatically reconfigure itself to use a standalone SDL2 window. + +## explorer + +This tool can list known files by libxiv, such as excel sheets. + +### Usage + +You must pass the path to your `sqpack` directory as the first argument. + +`explorer.exe C:\Program Files (x86)\SquareEnix\Final Fantasy XIV\game\sqpack` diff --git a/explorer/CMakeLists.txt b/explorer/CMakeLists.txt new file mode 100644 index 0000000..22c291a --- /dev/null +++ b/explorer/CMakeLists.txt @@ -0,0 +1,24 @@ +add_executable(explorer + src/main.cpp + src/mainwindow.cpp) +target_include_directories(explorer + PUBLIC + include) +target_link_libraries(explorer PUBLIC libxiv ${LIBRARIES} Qt5::Core Qt5::Widgets) + +install(TARGETS explorer + DESTINATION "${INSTALL_BIN_PATH}") + +if(WIN32) + get_target_property(QMAKE_EXE Qt5::qmake IMPORTED_LOCATION) + get_filename_component(QT_BIN_DIR "${QMAKE_EXE}" DIRECTORY) + + find_program(WINDEPLOYQT_ENV_SETUP qtenv2.bat HINTS "${QT_BIN_DIR}") + find_program(WINDEPLOYQT_EXECUTABLE windeployqt HINTS "${QT_BIN_DIR}") + + # Run windeployqt immediately after build + add_custom_command(TARGET explorer + POST_BUILD + COMMAND "${WINDEPLOYQT_ENV_SETUP}" && "${WINDEPLOYQT_EXECUTABLE}" \"$\" + ) +endif() diff --git a/explorer/include/mainwindow.h b/explorer/include/mainwindow.h new file mode 100644 index 0000000..0bd5d50 --- /dev/null +++ b/explorer/include/mainwindow.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +struct PathPart { + uint32_t crcHash; + QMap children; +}; + +class GameData; + +class MainWindow : public QMainWindow { +public: + MainWindow(GameData& data); + +private: + 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; + + GameData& data; + + void addPaths(QTreeWidget *pWidget); + + QTreeWidgetItem* addPartAndChildren(const QString& qString, const PathPart& part); +}; \ No newline at end of file diff --git a/explorer/src/main.cpp b/explorer/src/main.cpp new file mode 100644 index 0000000..63e494d --- /dev/null +++ b/explorer/src/main.cpp @@ -0,0 +1,15 @@ +#include + +#include "mainwindow.h" +#include "gamedata.h" + +int main(int argc, char* argv[]) { + QApplication app(argc, argv); + + GameData data(argv[1]); + + MainWindow w(data); + w.show(); + + return app.exec(); +} \ No newline at end of file diff --git a/explorer/src/mainwindow.cpp b/explorer/src/mainwindow.cpp new file mode 100644 index 0000000..1b72398 --- /dev/null +++ b/explorer/src/mainwindow.cpp @@ -0,0 +1,123 @@ +#include "mainwindow.h" + +#include +#include +#include +#include + +#include "gamedata.h" +#include "exhparser.h" +#include "exdparser.h" + +MainWindow::MainWindow(GameData& data) : data(data) { + setWindowTitle("explorer"); + + addPath("exd/root.exl"); + + for(auto sheetName : data.getAllSheetNames()) { + auto nameLowercase = QString(sheetName.c_str()).toLower().toStdString(); + + addPath("exd/" + QString(nameLowercase.c_str()) + ".exh"); + + auto exh = *data.readExcelSheet(sheetName); + for(auto page : exh.pages) { + for(auto language : exh.language) { + std::string path; + if (language == Language::None) { + path = getEXDFilename(exh, nameLowercase, "", page); + } else { + path = getEXDFilename(exh, nameLowercase, getLanguageCode(language), page); + } + + addPath(("exd/" + path).c_str()); + } + } + } + + addPath("common/font/AXIS_12.fdt"); + + auto commonIndex = data.getIndexListing("common"); + for(auto entry : commonIndex.entries) { + addUnknownPath("common", entry.hash); + } + + auto dummyWidget = new QWidget(); + setCentralWidget(dummyWidget); + + auto layout = new QHBoxLayout(); + dummyWidget->setLayout(layout); + + auto treeWidget = new QTreeWidget(); + treeWidget->setHeaderLabel("Name"); + + addPaths(treeWidget); + + layout->addWidget(treeWidget); +} + +void MainWindow::addPath(QString path) { + auto tokens = path.split('/'); + auto nextToken = tokens[0]; + tokens.pop_front(); + + traversePart(tokens, rootParts[nextToken], nextToken); +} + +void MainWindow::traversePart(QList tokens, PathPart& part, QString pathSoFar) { + if(tokens.empty()) + return; + + auto nextToken = tokens[0]; + tokens.pop_front(); + + pathSoFar = pathSoFar + "/" + nextToken; + part.children[nextToken].crcHash = data.calculateHash(pathSoFar.toStdString()); + + traversePart(tokens, part.children[nextToken], pathSoFar); +} + +void MainWindow::addPaths(QTreeWidget *pWidget) { + for(const auto& name : rootParts.keys()) { + auto item = addPartAndChildren(name, rootParts.value(name)); + pWidget->addTopLevelItem(item); + } +} + +QTreeWidgetItem* MainWindow::addPartAndChildren(const QString& qString, const PathPart& part) { + auto item = new QTreeWidgetItem(); + item->setText(0, qString); + + for(const auto& name : part.children.keys()) { + auto childItem = addPartAndChildren(name, part.children.value(name)); + item->addChild(childItem); + } + + return item; +} + +void MainWindow::addUnknownPath(QString knownDirectory, uint32_t crcHash) { + auto [found, path] = traverseUnknownPath(crcHash, rootParts[knownDirectory], knownDirectory); + if(found) + addPath(path); + else + addPath(knownDirectory + "/Unknown File Hash " + QString::number(crcHash)); +} + +std::tuple MainWindow::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("Unknown")) + continue; + + auto [childFound, newPath] = traverseUnknownPath(crcHash, part.children[path], pathSoFar + "/" + path); + found |= childFound; + if(childFound) + childPath = newPath; + } + + return {found, childPath}; +} diff --git a/libxiv b/libxiv index a7751a5..ce7c99c 160000 --- a/libxiv +++ b/libxiv @@ -1 +1 @@ -Subproject commit a7751a50bb71fdce2c3db9ba2d18f49f3d754300 +Subproject commit ce7c99c3de4853758bd37d46b3e582b27681797a