udk-manip/main.cpp
2026-01-20 14:45:53 -05:00

314 lines
8.6 KiB
C++

#include <QCoreApplication>
#include <QTextStream>
#include <QFile>
#include <QDir>
#include <QFileInfo>
#include "coalesced.h"
static QTextStream cout(stdout);
static QTextStream cerr(stderr);
void printError(const QString& message)
{
cerr << "Error: " << message << "\n";
cerr.flush();
}
void showUsage()
{
cout << "UDK Config Extractor v1.0\n";
cout << "Extract and pack UDK/UE3 coalesced INI files\n\n";
cout << "Usage:\n";
cout << " udk-config-extractor <command> [arguments]\n\n";
cout << "Commands:\n";
cout << " list <file> List files in archive\n";
cout << " info <file> Show archive metadata\n";
cout << " unpack <file> [output_dir] Extract all INI files\n";
cout << " pack <input_dir> <output_file> Pack directory into coalesced file\n";
cout << " extract <file> <index|name> [output] Extract single file\n\n";
cout << "Options:\n";
cout << " -h, --help Show this help message\n";
cout << " -v, --version Show version\n\n";
cout << "Drag & Drop:\n";
cout << " Drag a coalesced file onto exe Extracts to ./unpacked\n";
cout << " Drag a directory onto exe Packs to ./coalesced.ini\n";
cout.flush();
}
int cmdList(const QStringList& args)
{
if (args.isEmpty()) {
printError("Usage: udk-config-extractor list <file>");
return 1;
}
CoalescedFile coalesced;
if (!coalesced.load(args[0])) {
printError(coalesced.lastError());
return 1;
}
cout << QString("%1 %2 %3\n")
.arg("Index", 5)
.arg("Size", 8)
.arg("Filename");
cout << QString("%1 %2 %3\n")
.arg(QString("-").repeated(5), 5)
.arg(QString("-").repeated(8), 8)
.arg(QString("-").repeated(50));
const QList<IniEntry>& entries = coalesced.entries();
for (int i = 0; i < entries.count(); ++i) {
const IniEntry& entry = entries[i];
cout << QString("%1 %2 %3\n")
.arg(i, 5)
.arg(entry.content.size(), 8)
.arg(entry.filename);
}
cout << QString("\nTotal: %1 files, %2 bytes\n")
.arg(coalesced.count())
.arg(coalesced.totalSize());
cout.flush();
return 0;
}
int cmdInfo(const QStringList& args)
{
if (args.isEmpty()) {
printError("Usage: udk-config-extractor info <file>");
return 1;
}
CoalescedFile coalesced;
if (!coalesced.load(args[0])) {
printError(coalesced.lastError());
return 1;
}
cout << "Archive Information:\n";
cout << QString(" File: %1\n").arg(coalesced.loadedPath());
cout << QString(" Entries: %1\n").arg(coalesced.count());
cout << QString(" Total content size: %1 bytes\n").arg(coalesced.totalSize());
qint64 minSize = LLONG_MAX;
qint64 maxSize = 0;
QString minFile, maxFile;
for (const IniEntry& entry : coalesced.entries()) {
if (entry.content.size() < minSize) {
minSize = entry.content.size();
minFile = entry.filename;
}
if (entry.content.size() > maxSize) {
maxSize = entry.content.size();
maxFile = entry.filename;
}
}
if (coalesced.count() > 0) {
cout << QString(" Smallest: %1 bytes (%2)\n").arg(minSize).arg(minFile);
cout << QString(" Largest: %1 bytes (%2)\n").arg(maxSize).arg(maxFile);
cout << QString(" Average: %1 bytes\n").arg(coalesced.totalSize() / coalesced.count());
}
cout.flush();
return 0;
}
int cmdUnpack(const QStringList& args)
{
if (args.isEmpty()) {
printError("Usage: udk-config-extractor unpack <file> [output_dir]");
return 1;
}
CoalescedFile coalesced;
if (!coalesced.load(args[0])) {
printError(coalesced.lastError());
return 1;
}
QString outputDir = args.size() > 1 ? args[1] : ".";
QDir dir(outputDir);
if (!dir.exists() && !dir.mkpath(".")) {
printError(QString("Cannot create output directory: %1").arg(outputDir));
return 1;
}
int extracted = 0;
qint64 totalBytes = 0;
for (const IniEntry& entry : coalesced.entries()) {
QString localPath = entry.filename;
// Remove leading ".." components for safety
while (localPath.startsWith("..\\") || localPath.startsWith("../")) {
localPath = localPath.mid(3);
}
localPath.replace('\\', '/');
QString fullPath = dir.filePath(localPath);
QFileInfo fi(fullPath);
if (!fi.dir().exists() && !QDir().mkpath(fi.path())) {
printError(QString("Cannot create directory: %1").arg(fi.path()));
continue;
}
QFile file(fullPath);
if (!file.open(QIODevice::WriteOnly)) {
printError(QString("Cannot write file: %1").arg(fullPath));
continue;
}
file.write(entry.content);
file.close();
extracted++;
totalBytes += entry.content.size();
}
cout << QString("Extracted %1 files (%2 bytes) to %3\n")
.arg(extracted)
.arg(totalBytes)
.arg(QDir(outputDir).absolutePath());
cout.flush();
return 0;
}
int cmdPack(const QStringList& args)
{
if (args.size() < 2) {
printError("Usage: udk-config-extractor pack <input_dir> <output_file>");
return 1;
}
CoalescedFile packer;
if (!packer.packDirectory(args[0])) {
printError(packer.lastError());
return 1;
}
if (!packer.save(args[1])) {
printError(QString("Cannot write output file: %1").arg(args[1]));
return 1;
}
cout << QString("Packed %1 files (%2 bytes) to %3\n")
.arg(packer.count())
.arg(packer.totalSize())
.arg(args[1]);
cout.flush();
return 0;
}
int cmdExtract(const QStringList& args)
{
if (args.size() < 2) {
printError("Usage: udk-config-extractor extract <file> <index|name> [output]");
return 1;
}
CoalescedFile coalesced;
if (!coalesced.load(args[0])) {
printError(coalesced.lastError());
return 1;
}
QString target = args[1];
const IniEntry* entry = nullptr;
bool ok;
int index = target.toInt(&ok);
if (ok) {
entry = coalesced.entry(index);
if (!entry) {
printError(QString("Index %1 out of range (0-%2)").arg(index).arg(coalesced.count() - 1));
return 1;
}
} else {
entry = coalesced.entry(target);
if (!entry) {
printError(QString("File not found: %1").arg(target));
return 1;
}
}
QString outputPath;
if (args.size() > 2) {
outputPath = args[2];
} else {
QFileInfo fi(entry->filename);
outputPath = fi.fileName();
}
QFile file(outputPath);
if (!file.open(QIODevice::WriteOnly)) {
printError(QString("Cannot write file: %1").arg(outputPath));
return 1;
}
file.write(entry->content);
file.close();
cout << QString("Extracted '%1' to '%2' (%3 bytes)\n")
.arg(entry->filename)
.arg(outputPath)
.arg(entry->content.size());
cout.flush();
return 0;
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QCoreApplication::setApplicationName("udk-config-extractor");
QCoreApplication::setApplicationVersion("1.0");
QStringList args = app.arguments();
args.removeFirst(); // Remove program name
if (args.isEmpty()) {
showUsage();
return 1;
}
QString cmd = args.takeFirst().toLower();
if (cmd == "-h" || cmd == "--help" || cmd == "help") {
showUsage();
return 0;
}
if (cmd == "-v" || cmd == "--version" || cmd == "version") {
cout << QString("udk-config-extractor %1\n").arg(QCoreApplication::applicationVersion());
cout.flush();
return 0;
}
if (cmd == "list") {
return cmdList(args);
} else if (cmd == "info") {
return cmdInfo(args);
} else if (cmd == "unpack") {
return cmdUnpack(args);
} else if (cmd == "pack") {
return cmdPack(args);
} else if (cmd == "extract") {
return cmdExtract(args);
} else if (QFileInfo(cmd).isDir()) {
// Directory dragged onto exe - pack to coalesced.ini
return cmdPack(QStringList() << cmd << "coalesced.ini");
} else if (QFile::exists(cmd)) {
// File dragged onto exe - unpack to ./unpacked
return cmdUnpack(QStringList() << cmd << "unpacked");
} else {
printError(QString("Unknown command: %1").arg(cmd));
showUsage();
return 1;
}
}