Add configurable QuickBMS path with auto-detection

- Add QuickBMS path setting to Settings class with auto-detection
- Add Tools page in Preferences with QuickBMS configuration UI
- Update Compression class to use configurable path instead of hardcoded
- Add startup prompt if QuickBMS not found, with option to locate

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
njohnson 2026-01-08 00:54:57 -05:00
parent 2017abb175
commit 904abf2e36
9 changed files with 222 additions and 5 deletions

View File

@ -1,6 +1,6 @@
QT += core widgets gui multimedia network
#RC_ICONS = app.ico
RC_ICONS = app.ico
SUBDIRS += app

View File

@ -2,10 +2,11 @@
#include "splashscreen.h"
#include "typeregistry.h"
#include "settings.h"
#include "compression.h"
// Application metadata
#define APP_NAME "XPlor"
#define APP_VERSION "1.0.8"
#define APP_VERSION "1.8"
#define APP_ORG_NAME "RedLine Solutions LLC."
#define APP_ORG_DOMAIN "redline.llc"
@ -367,6 +368,12 @@ int main(int argc, char *argv[])
a.setWindowIcon(themedIcon);
}
// Initialize QuickBMS path from settings
QString quickBmsPath = Settings::instance().quickBmsPath();
if (!quickBmsPath.isEmpty()) {
Compression::setQuickBmsPath(quickBmsPath);
}
// Show splash screen
SplashScreen splash;
splash.setWaitForInteraction(false); // Normal behavior - close when finished

View File

@ -43,6 +43,7 @@
#include <QRegularExpression>
#include <QCollator>
#include <QImage>
#include <QTimer>
#include <algorithm>
// Generate themed app icon by replacing red with accent color
@ -971,6 +972,38 @@ MainWindow::MainWindow(QWidget *parent)
// LoadDefinitions is now called from main.cpp during splash screen
//LoadTreeCategories();
// Check for QuickBMS after window is shown
QTimer::singleShot(500, this, [this]() {
QString quickBmsPath = Settings::instance().quickBmsPath();
if (quickBmsPath.isEmpty()) {
// QuickBMS not found - prompt user
QMessageBox::StandardButton reply = QMessageBox::question(
this,
"QuickBMS Not Found",
"QuickBMS is required for decompressing certain Xbox 360 formats.\n\n"
"Would you like to locate quickbms.exe now?\n\n"
"(You can also configure this later in Edit > Preferences > Tools)",
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes
);
if (reply == QMessageBox::Yes) {
QString file = QFileDialog::getOpenFileName(
this,
"Locate QuickBMS Executable",
QDir::homePath(),
"QuickBMS (quickbms.exe);;All Files (*.*)"
);
if (!file.isEmpty()) {
Settings::instance().setQuickBmsPath(file);
Compression::setQuickBmsPath(file);
statusBar()->showMessage("QuickBMS configured: " + file, 5000);
}
}
}
});
}
MainWindow::~MainWindow()

View File

@ -1,5 +1,6 @@
#include "preferenceeditor.h"
#include "settings.h"
#include "compression.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
@ -7,6 +8,7 @@
#include <QDialogButtonBox>
#include <QFontDatabase>
#include <QFileDialog>
#include <QFileInfo>
#include <QFormLayout>
#include <QFrame>
#include <QMessageBox>
@ -39,6 +41,7 @@ void PreferenceEditor::setupUi()
m_categoryList->setSpacing(2);
m_categoryList->addItem("Appearance");
m_categoryList->addItem("General");
m_categoryList->addItem("Tools");
m_categoryList->addItem("Debug & Logging");
m_categoryList->addItem("Tree Browser");
m_categoryList->addItem("Previews");
@ -50,6 +53,7 @@ void PreferenceEditor::setupUi()
m_pageStack = new QStackedWidget(this);
createAppearancePage();
createGeneralPage();
createToolsPage();
createDebugPage();
createTreePage();
createPreviewPage();
@ -218,6 +222,65 @@ void PreferenceEditor::createGeneralPage()
m_pageStack->addWidget(page);
}
void PreferenceEditor::createToolsPage()
{
auto *page = new QWidget(this);
auto *layout = new QVBoxLayout(page);
layout->setSpacing(15);
// Title
auto *titleLabel = new QLabel("External Tools", page);
titleLabel->setStyleSheet("font-size: 14px; font-weight: bold; color: #ad0c0c;");
layout->addWidget(titleLabel);
// QuickBMS group
auto *quickbmsGroup = new QGroupBox("QuickBMS", page);
auto *quickbmsLayout = new QVBoxLayout(quickbmsGroup);
quickbmsLayout->setSpacing(10);
auto *quickbmsDesc = new QLabel(
"QuickBMS is used for decompressing certain Xbox 360 formats (LZXTDECODE). "
"If not configured, XPlor will attempt to find it automatically.",
quickbmsGroup);
quickbmsDesc->setWordWrap(true);
quickbmsDesc->setStyleSheet("color: #888; font-size: 11px;");
quickbmsLayout->addWidget(quickbmsDesc);
auto *pathLayout = new QHBoxLayout();
m_quickBmsEdit = new QLineEdit(quickbmsGroup);
m_quickBmsEdit->setPlaceholderText("Path to quickbms.exe...");
pathLayout->addWidget(m_quickBmsEdit);
auto *browseBtn = new QPushButton("Browse...", quickbmsGroup);
browseBtn->setFixedWidth(80);
connect(browseBtn, &QPushButton::clicked, this, &PreferenceEditor::onBrowseQuickBms);
pathLayout->addWidget(browseBtn);
quickbmsLayout->addLayout(pathLayout);
m_quickBmsStatus = new QLabel(quickbmsGroup);
m_quickBmsStatus->setStyleSheet("font-size: 11px;");
quickbmsLayout->addWidget(m_quickBmsStatus);
// Update status when path changes
connect(m_quickBmsEdit, &QLineEdit::textChanged, this, [this](const QString &path) {
if (path.isEmpty()) {
m_quickBmsStatus->setText("No path configured - will auto-detect");
m_quickBmsStatus->setStyleSheet("color: #888; font-size: 11px;");
} else if (QFileInfo::exists(path)) {
m_quickBmsStatus->setText("Found: " + path);
m_quickBmsStatus->setStyleSheet("color: #4caf50; font-size: 11px;");
} else {
m_quickBmsStatus->setText("Not found at specified path");
m_quickBmsStatus->setStyleSheet("color: #f44336; font-size: 11px;");
}
});
layout->addWidget(quickbmsGroup);
layout->addStretch();
m_pageStack->addWidget(page);
}
void PreferenceEditor::createDebugPage()
{
auto *page = new QWidget(this);
@ -364,6 +427,12 @@ void PreferenceEditor::loadSettings()
m_exportDirEdit->setText(s.exportDirectory());
m_autoExportCheck->setChecked(s.autoExportOnParse());
// Tools
QString qbmsPath = s.quickBmsPath();
m_quickBmsEdit->setText(qbmsPath);
// Trigger status update
emit m_quickBmsEdit->textChanged(qbmsPath);
int fontIdx = m_fontFamilyCombo->findText(s.fontFamily());
if (fontIdx >= 0) m_fontFamilyCombo->setCurrentIndex(fontIdx);
m_fontSizeSpin->setValue(s.fontSize());
@ -396,6 +465,12 @@ void PreferenceEditor::saveSettings()
// General
s.setExportDirectory(m_exportDirEdit->text());
s.setAutoExportOnParse(m_autoExportCheck->isChecked());
// Tools
QString quickBmsPath = m_quickBmsEdit->text();
s.setQuickBmsPath(quickBmsPath);
Compression::setQuickBmsPath(quickBmsPath);
s.setFontFamily(m_fontFamilyCombo->currentText());
s.setFontSize(m_fontSizeSpin->value());
s.setViewZoom(m_zoomSpin->value());
@ -530,6 +605,27 @@ void PreferenceEditor::onBrowseExportDir()
}
}
void PreferenceEditor::onBrowseQuickBms()
{
QString startDir = m_quickBmsEdit->text();
if (startDir.isEmpty()) {
startDir = QDir::homePath();
} else {
startDir = QFileInfo(startDir).absolutePath();
}
QString file = QFileDialog::getOpenFileName(
this,
"Locate QuickBMS Executable",
startDir,
"QuickBMS (quickbms.exe);;All Files (*.*)"
);
if (!file.isEmpty()) {
m_quickBmsEdit->setText(file);
}
}
void PreferenceEditor::onResetSettings()
{
QMessageBox::StandardButton reply = QMessageBox::question(

View File

@ -24,12 +24,14 @@ private slots:
void onApply();
void onAccept();
void onBrowseExportDir();
void onBrowseQuickBms();
void onResetSettings();
private:
void setupUi();
void createAppearancePage();
void createGeneralPage();
void createToolsPage();
void createDebugPage();
void createTreePage();
void createPreviewPage();
@ -50,6 +52,10 @@ private:
QLineEdit *m_exportDirEdit;
QCheckBox *m_autoExportCheck;
// Tools Page
QLineEdit *m_quickBmsEdit;
QLabel *m_quickBmsStatus;
// Debug Page
QCheckBox *m_debugLoggingCheck;
QCheckBox *m_verboseParsingCheck;

View File

@ -3,6 +3,7 @@
#include <QCoreApplication>
#include <QStandardPaths>
#include <QDir>
#include <QFileInfo>
// Built-in themes
static const QMap<QString, Theme> s_themes = {
@ -154,6 +155,52 @@ void Settings::setAutoExportOnParse(bool enable)
emit settingsChanged();
}
// Tools
QString Settings::quickBmsPath() const
{
QString path = m_settings.value("Tools/QuickBmsPath").toString();
if (path.isEmpty() || !QFileInfo::exists(path)) {
path = findQuickBms();
}
return path;
}
void Settings::setQuickBmsPath(const QString& path)
{
m_settings.setValue("Tools/QuickBmsPath", path);
emit settingsChanged();
}
QString Settings::findQuickBms()
{
// Common locations to search for QuickBMS
QStringList searchPaths = {
QCoreApplication::applicationDirPath() + "/quickbms.exe",
QCoreApplication::applicationDirPath() + "/tools/quickbms.exe",
QCoreApplication::applicationDirPath() + "/quickbms/quickbms.exe",
"C:/Program Files/QuickBMS/quickbms.exe",
"C:/Program Files (x86)/QuickBMS/quickbms.exe",
"C:/QuickBMS/quickbms.exe",
QDir::homePath() + "/QuickBMS/quickbms.exe",
"E:/Software/QuickBMS/quickbms.exe", // Legacy path
};
// Also check PATH environment
QString pathEnv = qEnvironmentVariable("PATH");
QStringList pathDirs = pathEnv.split(';', Qt::SkipEmptyParts);
for (const QString& dir : pathDirs) {
searchPaths.append(dir + "/quickbms.exe");
}
for (const QString& path : searchPaths) {
if (QFileInfo::exists(path)) {
return QDir::cleanPath(path);
}
}
return QString(); // Not found
}
// Debug/Logging
bool Settings::debugLoggingEnabled() const
{

View File

@ -41,6 +41,11 @@ public:
bool autoExportOnParse() const;
void setAutoExportOnParse(bool enable);
// Tools
QString quickBmsPath() const;
void setQuickBmsPath(const QString& path);
static QString findQuickBms(); // Auto-detect QuickBMS
// Debug/Logging
bool debugLoggingEnabled() const;
void setDebugLoggingEnabled(bool enable);

View File

@ -9,6 +9,19 @@
#include <QProcess>
#include <QtEndian>
// Static member initialization
QString Compression::s_quickBmsPath;
void Compression::setQuickBmsPath(const QString &path)
{
s_quickBmsPath = path;
}
QString Compression::quickBmsPath()
{
return s_quickBmsPath;
}
QByteArray Compression::CompressXMem(const QByteArray &data)
{
XMEMCODEC_PARAMETERS_LZX lzxParams = {};
@ -176,12 +189,16 @@ QByteArray Compression::DecompressXMemTDecode(const QByteArray &data, int flags,
// Run QuickBMS
QProcess proc;
proc.setWorkingDirectory(tempDir);
QString quickbmsPath = "E:/Software/QuickBMS/quickbms.exe";
QString quickbmsExe = quickBmsPath();
if (quickbmsExe.isEmpty()) {
qWarning() << "QuickBMS path not configured";
return {};
}
QStringList args = {"-o", "-O", outputFile, bmsScript, inputFile};
proc.start(quickbmsPath, args);
proc.start(quickbmsExe, args);
if (!proc.waitForFinished(30000)) {
qWarning() << "QuickBMS timeout or failed to start";
qWarning() << "QuickBMS timeout or failed to start:" << quickbmsExe;
return {};
}

View File

@ -60,6 +60,10 @@ typedef int (__stdcall *OodleLZ_DecompressFunc)(
class Compression {
public:
// QuickBMS path configuration
static void setQuickBmsPath(const QString &path);
static QString quickBmsPath();
static quint32 CalculateAdler32Checksum(const QByteArray &data);
static QByteArray DecompressZLIB(const QByteArray &aCompressedData);
static qint64 FindZlibOffset(const QByteArray &bytes);
@ -101,6 +105,8 @@ private:
static QByteArray DecompressXMemNative(const QByteArray &data);
static QByteArray DecompressXMemTDecode(const QByteArray &data, int flags, int windowSize, int partSize);
static QByteArray DecompressXMemRaw(const QByteArray &data, int flags, int windowSize, int partSize);
static QString s_quickBmsPath;
};