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:
parent
2017abb175
commit
904abf2e36
@ -1,6 +1,6 @@
|
||||
QT += core widgets gui multimedia network
|
||||
|
||||
#RC_ICONS = app.ico
|
||||
RC_ICONS = app.ico
|
||||
|
||||
SUBDIRS += app
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 {};
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user