XPlor/app/main.cpp
njohnson d5fe20d746 Update app with image preview and code style changes
- Integrate ImagePreviewWidget for texture asset preview
- Standardize member variable naming (m_var -> mVar)
- Standardize getter methods (GetXxx -> Xxx)
- Add app resource file for Windows

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 16:36:53 -05:00

291 lines
9.2 KiB
C++

#include "mainwindow.h"
#include "typeregistry.h"
#include <QApplication>
#include <QCoreApplication>
#include <QCommandLineParser>
#include <QCommandLineOption>
#include <QFile>
#include <QFileInfo>
#include <QDirIterator>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QTextStream>
#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) {
if (it.key().startsWith("_") && it.key() != "_name" && it.key() != "_type")
continue;
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();
}
// 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);
app.setApplicationName("XPlor");
app.setApplicationVersion("1.0");
// Initialize log file for CLI output
initCliLog();
QCommandLineParser parser;
parser.setApplicationDescription("XPlor - Call of Duty FastFile Explorer");
parser.addHelpOption();
parser.addVersionOption();
QCommandLineOption cliOption(QStringList() << "cli" << "parse" << "p", "Run in CLI mode (no GUI)");
parser.addOption(cliOption);
QCommandLineOption jsonOption(QStringList() << "json" << "j", "Output results as JSON");
parser.addOption(jsonOption);
QCommandLineOption gameOption(QStringList() << "game" << "g", "Game identifier", "game", "COD4");
parser.addOption(gameOption);
QCommandLineOption platformOption(QStringList() << "platform" << "t", "Platform (PC, Xbox360, PS3)", "platform", "PC");
parser.addOption(platformOption);
parser.addPositionalArgument("file", "FastFile to parse");
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);
MainWindow w;
w.show();
return a.exec();
}