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>
This commit is contained in:
njohnson 2026-01-07 16:36:53 -05:00
parent 0fa26e5256
commit d5fe20d746
3 changed files with 1104 additions and 724 deletions

40
app/app_resource.rc Normal file
View File

@ -0,0 +1,40 @@
#include <windows.h>
IDI_ICON1 ICON "E:\\Projects\\Qt\\XPlor\\app\\app.ico"
VS_VERSION_INFO VERSIONINFO
FILEVERSION 0,0,0,0
PRODUCTVERSION 0,0,0,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "\0"
VALUE "FileDescription", "\0"
VALUE "FileVersion", "0.0.0.0\0"
VALUE "LegalCopyright", "\0"
VALUE "OriginalFilename", "app.exe\0"
VALUE "ProductName", "app\0"
VALUE "ProductVersion", "0.0.0.0\0"
VALUE "InternalName", "\0"
VALUE "Comments", "\0"
VALUE "LegalTrademarks", "\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0409, 1200
END
END
/* End of Version info */

View File

@ -1,11 +1,290 @@
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
#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();
}

File diff suppressed because it is too large Load Diff