2026-01-07 16:36:53 -05:00
|
|
|
#include "mainwindow.h"
|
2026-01-08 00:38:04 -05:00
|
|
|
#include "splashscreen.h"
|
2026-01-07 16:36:53 -05:00
|
|
|
#include "typeregistry.h"
|
2026-01-08 00:38:04 -05:00
|
|
|
#include "settings.h"
|
2026-01-08 00:54:57 -05:00
|
|
|
#include "compression.h"
|
2026-01-11 12:09:31 -05:00
|
|
|
#include "logmanager.h"
|
2026-01-08 00:38:04 -05:00
|
|
|
|
|
|
|
|
// Application metadata
|
|
|
|
|
#define APP_NAME "XPlor"
|
2026-01-08 00:54:57 -05:00
|
|
|
#define APP_VERSION "1.8"
|
2026-01-08 00:38:04 -05:00
|
|
|
#define APP_ORG_NAME "RedLine Solutions LLC."
|
|
|
|
|
#define APP_ORG_DOMAIN "redline.llc"
|
2026-01-07 16:36:53 -05:00
|
|
|
|
|
|
|
|
#include <QApplication>
|
|
|
|
|
#include <QCoreApplication>
|
|
|
|
|
#include <QCommandLineParser>
|
|
|
|
|
#include <QCommandLineOption>
|
|
|
|
|
#include <QFile>
|
|
|
|
|
#include <QFileInfo>
|
|
|
|
|
#include <QDirIterator>
|
|
|
|
|
#include <QJsonDocument>
|
|
|
|
|
#include <QJsonObject>
|
|
|
|
|
#include <QJsonArray>
|
|
|
|
|
#include <QTextStream>
|
2026-01-08 00:38:04 -05:00
|
|
|
#include <QThread>
|
|
|
|
|
#include <QImage>
|
|
|
|
|
#include <QIcon>
|
2026-01-07 16:36:53 -05:00
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
#include <windows.h>
|
|
|
|
|
#include <io.h>
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
|
|
static bool g_consoleAllocated = false; // True if we created our own console (need to wait at end)
|
|
|
|
|
|
|
|
|
|
// Attach to parent console on Windows for CLI mode
|
|
|
|
|
static void attachWindowsConsole() {
|
|
|
|
|
// Try to attach to parent console (works when launched from cmd.exe)
|
|
|
|
|
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
|
|
|
|
|
// Redirect stdout
|
|
|
|
|
FILE* fp;
|
|
|
|
|
freopen_s(&fp, "CONOUT$", "w", stdout);
|
|
|
|
|
setvbuf(stdout, NULL, _IONBF, 0);
|
|
|
|
|
|
|
|
|
|
// Redirect stderr
|
|
|
|
|
freopen_s(&fp, "CONOUT$", "w", stderr);
|
|
|
|
|
setvbuf(stderr, NULL, _IONBF, 0);
|
|
|
|
|
|
|
|
|
|
// Redirect stdin
|
|
|
|
|
freopen_s(&fp, "CONIN$", "r", stdin);
|
|
|
|
|
|
|
|
|
|
// Fix C++ streams
|
|
|
|
|
std::cout.clear();
|
|
|
|
|
std::cerr.clear();
|
|
|
|
|
std::cin.clear();
|
|
|
|
|
} else {
|
|
|
|
|
// No parent console - allocate our own console window
|
|
|
|
|
if (AllocConsole()) {
|
|
|
|
|
g_consoleAllocated = true;
|
|
|
|
|
|
|
|
|
|
FILE* fp;
|
|
|
|
|
freopen_s(&fp, "CONOUT$", "w", stdout);
|
|
|
|
|
setvbuf(stdout, NULL, _IONBF, 0);
|
|
|
|
|
|
|
|
|
|
freopen_s(&fp, "CONOUT$", "w", stderr);
|
|
|
|
|
setvbuf(stderr, NULL, _IONBF, 0);
|
|
|
|
|
|
|
|
|
|
freopen_s(&fp, "CONIN$", "r", stdin);
|
|
|
|
|
|
|
|
|
|
std::cout.clear();
|
|
|
|
|
std::cerr.clear();
|
|
|
|
|
std::cin.clear();
|
|
|
|
|
|
|
|
|
|
// Set console title
|
|
|
|
|
SetConsoleTitleA("XPlor CLI");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// Convert QVariantMap to JSON recursively
|
|
|
|
|
static QJsonValue variantToJson(const QVariant& v) {
|
|
|
|
|
if (v.typeId() == QMetaType::QVariantMap) {
|
|
|
|
|
QJsonObject obj;
|
|
|
|
|
const QVariantMap map = v.toMap();
|
|
|
|
|
for (auto it = map.begin(); it != map.end(); ++it) {
|
|
|
|
|
obj[it.key()] = variantToJson(it.value());
|
|
|
|
|
}
|
|
|
|
|
return obj;
|
|
|
|
|
} else if (v.typeId() == QMetaType::QVariantList) {
|
|
|
|
|
QJsonArray arr;
|
|
|
|
|
for (const QVariant& item : v.toList()) {
|
|
|
|
|
arr.append(variantToJson(item));
|
|
|
|
|
}
|
|
|
|
|
return arr;
|
|
|
|
|
} else if (v.typeId() == QMetaType::QString) {
|
|
|
|
|
return v.toString();
|
|
|
|
|
} else if (v.typeId() == QMetaType::Int || v.typeId() == QMetaType::LongLong) {
|
|
|
|
|
return v.toLongLong();
|
|
|
|
|
} else if (v.typeId() == QMetaType::UInt || v.typeId() == QMetaType::ULongLong) {
|
|
|
|
|
return v.toLongLong();
|
|
|
|
|
} else if (v.typeId() == QMetaType::Double || v.typeId() == QMetaType::Float) {
|
|
|
|
|
return v.toDouble();
|
|
|
|
|
} else if (v.typeId() == QMetaType::Bool) {
|
|
|
|
|
return v.toBool();
|
|
|
|
|
} else if (v.typeId() == QMetaType::QByteArray) {
|
|
|
|
|
return QString("0x") + v.toByteArray().toHex();
|
|
|
|
|
} else if (v.isNull()) {
|
|
|
|
|
return QJsonValue::Null;
|
|
|
|
|
}
|
|
|
|
|
return v.toString();
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-08 00:38:04 -05:00
|
|
|
// Generate themed app icon by replacing red with accent color
|
|
|
|
|
static QIcon generateThemedIcon(const QColor &accentColor) {
|
|
|
|
|
// Try loading from Qt resource first (XPlor.png)
|
|
|
|
|
QImage image(":/images/images/XPlor.png");
|
|
|
|
|
|
|
|
|
|
if (image.isNull()) {
|
|
|
|
|
// Fallback: try app.ico in app directory
|
|
|
|
|
QString iconPath = QCoreApplication::applicationDirPath() + "/app.ico";
|
|
|
|
|
image = QImage(iconPath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (image.isNull()) {
|
|
|
|
|
return QIcon();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert to ARGB32 for pixel manipulation
|
|
|
|
|
image = image.convertToFormat(QImage::Format_ARGB32);
|
|
|
|
|
|
|
|
|
|
// Replace red-ish pixels with the accent color
|
|
|
|
|
for (int y = 0; y < image.height(); y++) {
|
|
|
|
|
QRgb *line = reinterpret_cast<QRgb*>(image.scanLine(y));
|
|
|
|
|
for (int x = 0; x < image.width(); x++) {
|
|
|
|
|
QColor pixel(line[x]);
|
|
|
|
|
int r = pixel.red();
|
|
|
|
|
int g = pixel.green();
|
|
|
|
|
int b = pixel.blue();
|
|
|
|
|
int a = pixel.alpha();
|
|
|
|
|
|
|
|
|
|
// Detect red-ish pixels (high red, low green/blue)
|
|
|
|
|
// The icon uses #ad0c0c (173, 12, 12) as the red color
|
|
|
|
|
if (r > 100 && g < 80 && b < 80 && a > 0) {
|
|
|
|
|
// Calculate how "red" this pixel is (0-1 scale)
|
|
|
|
|
float intensity = static_cast<float>(r) / 255.0f;
|
|
|
|
|
|
|
|
|
|
// Apply the accent color with the same intensity
|
|
|
|
|
QColor newColor = accentColor;
|
|
|
|
|
newColor.setRed(static_cast<int>(accentColor.red() * intensity));
|
|
|
|
|
newColor.setGreen(static_cast<int>(accentColor.green() * intensity));
|
|
|
|
|
newColor.setBlue(static_cast<int>(accentColor.blue() * intensity));
|
|
|
|
|
newColor.setAlpha(a);
|
|
|
|
|
|
|
|
|
|
line[x] = newColor.rgba();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return QIcon(QPixmap::fromImage(image));
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-07 16:36:53 -05:00
|
|
|
// CLI output - writes to both console and log file for reliability
|
|
|
|
|
static QFile* g_logFile = nullptr;
|
|
|
|
|
|
|
|
|
|
static void initCliLog() {
|
|
|
|
|
QString logPath = QCoreApplication::applicationDirPath() + "/xplor_cli.log";
|
|
|
|
|
g_logFile = new QFile(logPath);
|
|
|
|
|
g_logFile->open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void cliOut(const QString& msg) {
|
|
|
|
|
std::cout << msg.toStdString() << std::flush;
|
|
|
|
|
if (g_logFile && g_logFile->isOpen()) {
|
|
|
|
|
g_logFile->write(msg.toUtf8());
|
|
|
|
|
g_logFile->flush();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void cliErr(const QString& msg) {
|
|
|
|
|
std::cerr << msg.toStdString() << std::flush;
|
|
|
|
|
if (g_logFile && g_logFile->isOpen()) {
|
|
|
|
|
g_logFile->write(msg.toUtf8());
|
|
|
|
|
g_logFile->flush();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int runCli(const QString& filePath, const QString& game, const QString& platform, bool jsonOutput) {
|
|
|
|
|
TypeRegistry registry;
|
|
|
|
|
const QString definitionsDir = QCoreApplication::applicationDirPath() + "/definitions/";
|
|
|
|
|
QDirIterator it(definitionsDir, {"*.xscript"}, QDir::Files, QDirIterator::Subdirectories);
|
|
|
|
|
|
|
|
|
|
int defCount = 0;
|
|
|
|
|
while (it.hasNext()) {
|
|
|
|
|
const QString path = it.next();
|
|
|
|
|
const QString fileName = QFileInfo(path).fileName();
|
|
|
|
|
QFile f(path);
|
|
|
|
|
if (!f.open(QIODevice::ReadOnly)) {
|
|
|
|
|
cliErr("[ERROR] Cannot open definition: " + path + "\n");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
registry.ingestScript(QString::fromUtf8(f.readAll()), path);
|
|
|
|
|
defCount++;
|
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
|
cliErr("[ERROR] Loading " + fileName + ": " + e.what() + "\n");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (defCount == 0) {
|
|
|
|
|
cliErr("[ERROR] No definitions found in: " + definitionsDir + "\n");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!jsonOutput) {
|
|
|
|
|
cliErr("[INFO] Loaded " + QString::number(defCount) + " definitions\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QFile inputFile(filePath);
|
|
|
|
|
if (!inputFile.open(QIODevice::ReadOnly)) {
|
|
|
|
|
cliErr("[ERROR] Cannot open file: " + filePath + "\n");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QString rootType = registry.chooseType(&inputFile, filePath);
|
|
|
|
|
if (rootType.isEmpty()) {
|
|
|
|
|
cliErr("[ERROR] No matching definition for file: " + filePath + "\n");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!jsonOutput) {
|
|
|
|
|
cliErr("[INFO] Matched type: " + rootType + "\n");
|
|
|
|
|
cliErr("[INFO] Parsing...\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QVariantMap result;
|
|
|
|
|
try {
|
|
|
|
|
result = registry.parse(rootType, &inputFile, filePath, nullptr);
|
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
|
cliErr("[ERROR] Parse failed: " + QString(e.what()) + "\n");
|
|
|
|
|
if (jsonOutput) {
|
|
|
|
|
QJsonObject errorObj;
|
|
|
|
|
errorObj["success"] = false;
|
|
|
|
|
errorObj["error"] = QString(e.what());
|
|
|
|
|
errorObj["file"] = filePath;
|
|
|
|
|
errorObj["type"] = rootType;
|
|
|
|
|
cliOut(QJsonDocument(errorObj).toJson(QJsonDocument::Compact) + "\n");
|
|
|
|
|
}
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (jsonOutput) {
|
|
|
|
|
QJsonObject output;
|
|
|
|
|
output["success"] = true;
|
|
|
|
|
output["file"] = filePath;
|
|
|
|
|
output["type"] = rootType;
|
|
|
|
|
output["data"] = variantToJson(result).toObject();
|
|
|
|
|
cliOut(QJsonDocument(output).toJson(QJsonDocument::Indented) + "\n");
|
|
|
|
|
} else {
|
|
|
|
|
cliErr("[SUCCESS] Parsed " + filePath + " as " + rootType + "\n");
|
|
|
|
|
if (result.contains("asset_count")) {
|
|
|
|
|
cliErr("[INFO] Asset count: " + result["asset_count"].toString() + "\n");
|
|
|
|
|
}
|
|
|
|
|
if (result.contains("parsed_assets")) {
|
|
|
|
|
QVariantList assets = result["parsed_assets"].toList();
|
|
|
|
|
QHash<QString, int> typeCounts;
|
|
|
|
|
for (const QVariant& a : assets) {
|
|
|
|
|
QVariantMap am = a.toMap();
|
|
|
|
|
QString t = am.value("_type", "unknown").toString();
|
|
|
|
|
typeCounts[t]++;
|
|
|
|
|
}
|
|
|
|
|
cliErr("[INFO] Parsed assets:\n");
|
|
|
|
|
for (auto it = typeCounts.begin(); it != typeCounts.end(); ++it) {
|
|
|
|
|
cliErr(" " + it.key() + ": " + QString::number(it.value()) + "\n");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
cliOut("OK\n");
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int main(int argc, char *argv[])
|
|
|
|
|
{
|
|
|
|
|
bool cliMode = false;
|
|
|
|
|
for (int i = 1; i < argc; i++) {
|
|
|
|
|
QString arg(argv[i]);
|
|
|
|
|
if (arg == "--cli" || arg == "--parse" || arg == "-p" || arg == "--json") {
|
|
|
|
|
cliMode = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cliMode) {
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
attachWindowsConsole();
|
|
|
|
|
#endif
|
|
|
|
|
QCoreApplication app(argc, argv);
|
2026-01-08 00:38:04 -05:00
|
|
|
app.setOrganizationDomain(APP_ORG_DOMAIN);
|
|
|
|
|
app.setOrganizationName(APP_ORG_NAME);
|
|
|
|
|
app.setApplicationName(APP_NAME);
|
|
|
|
|
app.setApplicationVersion(APP_VERSION);
|
2026-01-07 16:36:53 -05:00
|
|
|
|
|
|
|
|
// Initialize log file for CLI output
|
|
|
|
|
initCliLog();
|
|
|
|
|
|
|
|
|
|
QCommandLineParser parser;
|
2026-01-08 00:38:04 -05:00
|
|
|
parser.setApplicationDescription(
|
|
|
|
|
"XPlor - Binary File Format Explorer\n\n"
|
|
|
|
|
"Parse and explore binary file formats using XScript definitions.\n"
|
|
|
|
|
"Supports Call of Duty FastFiles, Asura archives, and custom formats.\n\n"
|
|
|
|
|
"Features:\n"
|
|
|
|
|
" - XScript DSL for defining binary structures\n"
|
|
|
|
|
" - Hex viewer with highlighting\n"
|
|
|
|
|
" - Audio/image preview for embedded assets\n"
|
|
|
|
|
" - Theme support with customizable colors"
|
|
|
|
|
);
|
2026-01-07 16:36:53 -05:00
|
|
|
parser.addHelpOption();
|
|
|
|
|
parser.addVersionOption();
|
|
|
|
|
|
|
|
|
|
QCommandLineOption cliOption(QStringList() << "cli" << "parse" << "p", "Run in CLI mode (no GUI)");
|
|
|
|
|
parser.addOption(cliOption);
|
|
|
|
|
|
2026-01-08 00:38:04 -05:00
|
|
|
QCommandLineOption jsonOption(QStringList() << "json" << "j", "Output parsed data as JSON");
|
2026-01-07 16:36:53 -05:00
|
|
|
parser.addOption(jsonOption);
|
|
|
|
|
|
2026-01-08 00:38:04 -05:00
|
|
|
QCommandLineOption gameOption(QStringList() << "game" << "g", "Game identifier (e.g., COD4, COD5, MW2)", "game", "COD4");
|
2026-01-07 16:36:53 -05:00
|
|
|
parser.addOption(gameOption);
|
|
|
|
|
|
2026-01-08 00:38:04 -05:00
|
|
|
QCommandLineOption platformOption(QStringList() << "platform" << "t", "Target platform (PC, Xbox360, PS3)", "platform", "PC");
|
2026-01-07 16:36:53 -05:00
|
|
|
parser.addOption(platformOption);
|
|
|
|
|
|
2026-01-08 00:38:04 -05:00
|
|
|
parser.addPositionalArgument("file", "Binary file to parse (e.g., FastFile, archive)");
|
2026-01-07 16:36:53 -05:00
|
|
|
parser.process(app);
|
|
|
|
|
|
|
|
|
|
const QStringList args = parser.positionalArguments();
|
|
|
|
|
if (args.isEmpty()) {
|
|
|
|
|
cliErr("Error: No input file specified\n\n");
|
|
|
|
|
cliErr(parser.helpText() + "\n");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int result = runCli(args.first(), parser.value(gameOption), parser.value(platformOption), parser.isSet(jsonOption));
|
|
|
|
|
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
// If we allocated our own console window, wait for user input before closing
|
|
|
|
|
if (g_consoleAllocated) {
|
|
|
|
|
cliErr("\nPress Enter to exit...");
|
|
|
|
|
std::cin.get();
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QApplication a(argc, argv);
|
2026-01-08 00:38:04 -05:00
|
|
|
a.setOrganizationDomain(APP_ORG_DOMAIN);
|
|
|
|
|
a.setOrganizationName(APP_ORG_NAME);
|
|
|
|
|
a.setApplicationName(APP_NAME);
|
|
|
|
|
a.setApplicationVersion(APP_VERSION);
|
|
|
|
|
|
|
|
|
|
// Set themed window icon
|
|
|
|
|
Theme currentTheme = Settings::instance().theme();
|
|
|
|
|
QIcon themedIcon = generateThemedIcon(QColor(currentTheme.accentColor));
|
|
|
|
|
if (!themedIcon.isNull()) {
|
|
|
|
|
a.setWindowIcon(themedIcon);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-08 00:54:57 -05:00
|
|
|
// Initialize QuickBMS path from settings
|
|
|
|
|
QString quickBmsPath = Settings::instance().quickBmsPath();
|
|
|
|
|
if (!quickBmsPath.isEmpty()) {
|
|
|
|
|
Compression::setQuickBmsPath(quickBmsPath);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-08 00:38:04 -05:00
|
|
|
// Show splash screen
|
|
|
|
|
SplashScreen splash;
|
|
|
|
|
splash.setWaitForInteraction(false); // Normal behavior - close when finished
|
|
|
|
|
splash.show();
|
|
|
|
|
a.processEvents();
|
|
|
|
|
|
|
|
|
|
splash.setStatus("Initializing...");
|
|
|
|
|
splash.setProgress(0, 100);
|
|
|
|
|
a.processEvents();
|
|
|
|
|
|
|
|
|
|
// Load definitions with progress updates
|
|
|
|
|
splash.setStatus("Loading definitions...");
|
|
|
|
|
splash.setProgress(10, 100);
|
|
|
|
|
a.processEvents();
|
|
|
|
|
|
|
|
|
|
const QString definitionsDir = QCoreApplication::applicationDirPath() + "/definitions/";
|
|
|
|
|
|
|
|
|
|
// First pass: count files
|
|
|
|
|
QStringList defFiles;
|
|
|
|
|
QDirIterator countIt(definitionsDir, {"*.xscript"}, QDir::Files, QDirIterator::Subdirectories);
|
|
|
|
|
while (countIt.hasNext()) {
|
|
|
|
|
defFiles.append(countIt.next());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Second pass: load definitions
|
|
|
|
|
TypeRegistry registry;
|
|
|
|
|
QVector<DefinitionLoadResult> defResults;
|
|
|
|
|
int loaded = 0;
|
|
|
|
|
int total = defFiles.size();
|
|
|
|
|
|
2026-01-11 12:09:31 -05:00
|
|
|
LogManager::instance().addEntry(QString("[INIT] Loading %1 definition files...").arg(total));
|
|
|
|
|
|
2026-01-08 00:38:04 -05:00
|
|
|
for (const QString& path : defFiles) {
|
|
|
|
|
QString fileName = QFileInfo(path).fileName();
|
|
|
|
|
splash.setStatus(QString("Loading: %1").arg(fileName));
|
|
|
|
|
splash.setProgress(10 + (loaded * 70 / qMax(1, total)), 100);
|
|
|
|
|
a.processEvents();
|
|
|
|
|
|
|
|
|
|
QFile f(path);
|
|
|
|
|
if (!f.open(QIODevice::ReadOnly)) {
|
2026-01-11 12:09:31 -05:00
|
|
|
LogManager::instance().addError(QString("[DEF] Failed to open: %1").arg(fileName));
|
2026-01-08 00:38:04 -05:00
|
|
|
defResults.append({path, fileName, false, "Failed to open file"});
|
|
|
|
|
} else {
|
|
|
|
|
try {
|
|
|
|
|
registry.ingestScript(QString::fromUtf8(f.readAll()), path);
|
2026-01-11 12:09:31 -05:00
|
|
|
LogManager::instance().addEntry(QString("[DEF] Loaded: %1").arg(fileName));
|
2026-01-08 00:38:04 -05:00
|
|
|
defResults.append({path, fileName, true, QString()});
|
|
|
|
|
} catch (const std::exception& e) {
|
2026-01-11 12:09:31 -05:00
|
|
|
LogManager::instance().addError(QString("[DEF] Error in %1: %2").arg(fileName).arg(e.what()));
|
2026-01-08 00:38:04 -05:00
|
|
|
defResults.append({path, fileName, false, QString::fromUtf8(e.what())});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
loaded++;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-11 12:09:31 -05:00
|
|
|
int successCount = std::count_if(defResults.begin(), defResults.end(),
|
|
|
|
|
[](const DefinitionLoadResult& r) { return r.success; });
|
|
|
|
|
LogManager::instance().addEntry(QString("[INIT] Loaded %1/%2 definitions successfully").arg(successCount).arg(total));
|
|
|
|
|
LogManager::instance().addLine();
|
|
|
|
|
|
2026-01-08 00:38:04 -05:00
|
|
|
splash.setStatus("Creating main window...");
|
|
|
|
|
splash.setProgress(85, 100);
|
|
|
|
|
a.processEvents();
|
|
|
|
|
|
2026-01-07 16:36:53 -05:00
|
|
|
MainWindow w;
|
2026-01-08 00:38:04 -05:00
|
|
|
|
|
|
|
|
// Pass loaded definitions to MainWindow
|
|
|
|
|
w.setTypeRegistry(std::move(registry), defResults);
|
|
|
|
|
|
|
|
|
|
splash.setStatus("Ready");
|
|
|
|
|
splash.setProgress(100, 100);
|
|
|
|
|
a.processEvents();
|
|
|
|
|
|
|
|
|
|
QThread::msleep(200);
|
|
|
|
|
|
|
|
|
|
// finish() will show the main window and keep splash on top if waitForInteraction is enabled
|
|
|
|
|
splash.finish(&w);
|
|
|
|
|
|
2026-01-07 16:36:53 -05:00
|
|
|
return a.exec();
|
|
|
|
|
}
|