From dab4a698b2bbf3c380c154a39d73fb7c3b7d7224 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Wed, 30 Apr 2025 19:44:20 -0400 Subject: [PATCH] Decompile Lua bytecode in Data Explorer The game has *tons* of Lua scripts for event scripting, and are very interesting to explore. I had to do manually call luadec before, but now it should be much easier by automatically decompiling Lua scripts in the Data Explorer. --- .gitmodules | 3 +++ apps/sagasu/CMakeLists.txt | 1 + apps/sagasu/src/mainwindow.cpp | 6 +++++ common/include/filetypes.h | 15 ++++++++++- common/src/filetypes.cpp | 9 ++++--- extern/CMakeLists.txt | 5 ++++ extern/luadec51 | 1 + parts/CMakeLists.txt | 3 ++- parts/luab/CMakeLists.txt | 16 ++++++++++++ parts/luab/luabpart.cpp | 48 ++++++++++++++++++++++++++++++++++ parts/luab/luabpart.h | 24 +++++++++++++++++ 11 files changed, 126 insertions(+), 5 deletions(-) create mode 160000 extern/luadec51 create mode 100644 parts/luab/CMakeLists.txt create mode 100644 parts/luab/luabpart.cpp create mode 100644 parts/luab/luabpart.h diff --git a/.gitmodules b/.gitmodules index 582d9ea..40cdb82 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "extern/dxbc"] path = extern/dxbc url = ../dxbc +[submodule "extern/luadec51"] + path = extern/luadec51 + url = ../luadec51 diff --git a/apps/sagasu/CMakeLists.txt b/apps/sagasu/CMakeLists.txt index 31015d7..a88b25d 100644 --- a/apps/sagasu/CMakeLists.txt +++ b/apps/sagasu/CMakeLists.txt @@ -45,6 +45,7 @@ target_link_libraries(novus-sagasu Novus::TexPart Novus::DicPart Novus::MtrlPart + Novus::LuabPart Physis::Logger Qt6::Concurrent Qt6::Network) diff --git a/apps/sagasu/src/mainwindow.cpp b/apps/sagasu/src/mainwindow.cpp index 64294ac..a90b183 100644 --- a/apps/sagasu/src/mainwindow.cpp +++ b/apps/sagasu/src/mainwindow.cpp @@ -25,6 +25,7 @@ #include "filepropertieswindow.h" #include "filetypes.h" #include "hexpart.h" +#include "luabpart.h" #include "mdlpart.h" #include "mtrlpart.h" #include "shpkpart.h" @@ -173,6 +174,11 @@ void MainWindow::refreshParts(const QString &path) mtrlWidget->load(physis_material_parse(file)); partHolder->addTab(mtrlWidget, i18nc("@title:tab", "Material")); } break; + case FileType::LuaBytecode: { + auto luabWidget = new LuabPart(); + luabWidget->load(file); + partHolder->addTab(luabWidget, i18nc("@title:tab", "Lua")); + } break; default: break; } diff --git a/common/include/filetypes.h b/common/include/filetypes.h index 380322f..5dd5176 100644 --- a/common/include/filetypes.h +++ b/common/include/filetypes.h @@ -7,7 +7,20 @@ #include "novuscommon_export.h" -enum class FileType { Unknown, ExcelList, ExcelHeader, ExcelData, Model, Texture, ShaderPackage, CharaMakeParams, Skeleton, Dictionary, Material }; +enum class FileType { + Unknown, + ExcelList, + ExcelHeader, + ExcelData, + Model, + Texture, + ShaderPackage, + CharaMakeParams, + Skeleton, + Dictionary, + Material, + LuaBytecode +}; class NOVUSCOMMON_EXPORT FileTypes { diff --git a/common/src/filetypes.cpp b/common/src/filetypes.cpp index e06b0af..f5befea 100644 --- a/common/src/filetypes.cpp +++ b/common/src/filetypes.cpp @@ -15,7 +15,8 @@ const static QMap extensionToType{{QStringLiteral("exl"), Fil {QStringLiteral("cmp"), FileType::CharaMakeParams}, {QStringLiteral("sklb"), FileType::Skeleton}, {QStringLiteral("dic"), FileType::Dictionary}, - {QStringLiteral("mtrl"), FileType::Material}}; + {QStringLiteral("mtrl"), FileType::Material}, + {QStringLiteral("luab"), FileType::LuaBytecode}}; const static QMap typeToName{{FileType::Unknown, i18n("Unknown")}, {FileType::ExcelList, i18n("Excel List")}, @@ -27,7 +28,8 @@ const static QMap typeToName{{FileType::Unknown, i18n("Unknow {FileType::CharaMakeParams, i18n("Chara Make Params")}, {FileType::Skeleton, i18n("Skeleton")}, {FileType::Dictionary, i18n("Dictionary")}, - {FileType::Material, i18n("Material")}}; + {FileType::Material, i18n("Material")}, + {FileType::LuaBytecode, i18n("Lua Bytecode")}}; const static QMap typeToIcon{{FileType::Unknown, QStringLiteral("unknown")}, {FileType::ExcelList, QStringLiteral("x-office-spreadsheet")}, @@ -39,7 +41,8 @@ const static QMap typeToIcon{{FileType::Unknown, QStringLiter {FileType::CharaMakeParams, QStringLiteral("step_object_SoftBody-symbolic")}, {FileType::Skeleton, QStringLiteral("user-symbolic")}, {FileType::Dictionary, QStringLiteral("accessories-dictionary-symbolic")}, - {FileType::Material, QStringLiteral("map-globe-symbolic")}}; + {FileType::Material, QStringLiteral("map-globe-symbolic")}, + {FileType::LuaBytecode, QStringLiteral("text-x-lua")}}; FileType FileTypes::getFileType(const QString &extension) { diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 01c0ddd..c16c24b 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -23,3 +23,8 @@ add_subdirectory(magic_enum EXCLUDE_FROM_ALL) add_subdirectory(tinygltf EXCLUDE_FROM_ALL) add_subdirectory(imgui EXCLUDE_FROM_ALL) add_subdirectory(dxbc EXCLUDE_FROM_ALL) + +# For some reason, FFXIV uses a *32-bit* Lua compiler. We have to build it as 32-bit or else loading the bytecode fails. +add_compile_options(-m32) +add_link_options(-m32) +add_subdirectory(luadec51 EXCLUDE_FROM_ALL) \ No newline at end of file diff --git a/extern/luadec51 b/extern/luadec51 new file mode 160000 index 0000000..94a5e2b --- /dev/null +++ b/extern/luadec51 @@ -0,0 +1 @@ +Subproject commit 94a5e2b8e08b2c0fdbefc46239b92f1bdcdac303 diff --git a/parts/CMakeLists.txt b/parts/CMakeLists.txt index d7c7d07..d0746c7 100644 --- a/parts/CMakeLists.txt +++ b/parts/CMakeLists.txt @@ -10,4 +10,5 @@ add_subdirectory(mdl) add_subdirectory(mtrl) add_subdirectory(shpk) add_subdirectory(sklb) -add_subdirectory(tex) \ No newline at end of file +add_subdirectory(tex) +add_subdirectory(luab) \ No newline at end of file diff --git a/parts/luab/CMakeLists.txt b/parts/luab/CMakeLists.txt new file mode 100644 index 0000000..64b293b --- /dev/null +++ b/parts/luab/CMakeLists.txt @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: 2025 Joshua Goins +# SPDX-License-Identifier: CC0-1.0 + +add_library(luabpart STATIC) +target_sources(luabpart PRIVATE luabpart.cpp) +target_link_libraries(luabpart + PUBLIC + KF6::I18n + Physis::Physis + Qt6::Core + Qt6::Widgets) +target_include_directories(luabpart PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_compile_definitions(luabpart PRIVATE TRANSLATION_DOMAIN="novus") +add_dependencies(luabpart luadec) + +add_library(Novus::LuabPart ALIAS luabpart) \ No newline at end of file diff --git a/parts/luab/luabpart.cpp b/parts/luab/luabpart.cpp new file mode 100644 index 0000000..83f9bb5 --- /dev/null +++ b/parts/luab/luabpart.cpp @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2025 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "luabpart.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LuabPart::LuabPart(QWidget *parent) + : QWidget(parent) +{ + auto layout = new QVBoxLayout(); + layout->setContentsMargins(0, 0, 0, 0); + + m_codeEdit = new QTextEdit(); + m_codeEdit->setReadOnly(true); + layout->addWidget(m_codeEdit); + + setLayout(layout); +} + +void LuabPart::load(physis_Buffer buffer) +{ + QTemporaryFile temporaryFile; + if (temporaryFile.open()) { + QFile file(temporaryFile.fileName()); + file.open(QIODevice::WriteOnly); + file.write(reinterpret_cast(buffer.data), buffer.size); + file.close(); + + QProcess luaDecProcess; + luaDecProcess.setProgram(QStringLiteral("./luadec")); + luaDecProcess.setArguments({temporaryFile.fileName()}); + luaDecProcess.start(); + luaDecProcess.waitForFinished(); + + m_codeEdit->setText(QString::fromUtf8(luaDecProcess.readAllStandardOutput())); + } +} + +#include "moc_luabpart.cpp" \ No newline at end of file diff --git a/parts/luab/luabpart.h b/parts/luab/luabpart.h new file mode 100644 index 0000000..2780e79 --- /dev/null +++ b/parts/luab/luabpart.h @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2025 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include + +class QTextEdit; + +class LuabPart : public QWidget +{ + Q_OBJECT + +public: + explicit LuabPart(QWidget *parent = nullptr); + + void load(physis_Buffer file); + +private: + GameData *data = nullptr; + QTextEdit *m_codeEdit = nullptr; +}; \ No newline at end of file