XPlor/mainwindow.cpp
2025-01-09 17:54:44 -05:00

1229 lines
42 KiB
C++

#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow) {
ui->setupUi(this);
mTypeMap = QMap<QString, int>();
mTypeOrder = QStringList();
mRawFileMap = QMap<QString, QString>();
mTreeMap = QMap<QString, QTreeWidgetItem*>();
mStrTableMap = QMap<QString, QVector<QPair<QString, QString>>>();
mBSPVersion = 0;
mDiskLumpCount = 0;
mDiskLumpOrder = QVector<quint32>();
mLumps = QMap<quint32, Lump>();
connect(ui->treeWidget_Scripts, &QTreeWidget::itemSelectionChanged, this, &MainWindow::ScriptSelected);
connect(ui->comboBox_StringTable, &QComboBox::currentTextChanged, this, &MainWindow::StrTableSelected);
// Initialize Asset Index Table
ui->tableWidget_Index->setColumnCount(3);
ui->tableWidget_Index->setHorizontalHeaderLabels({"Asset Type", "Asset Name", "Asset Count"});
ui->tableWidget_Index->verticalHeader()->setVisible(false);
ui->tableWidget_Index->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui->tableWidget_Index->setSelectionBehavior(QAbstractItemView::SelectRows);
ui->tableWidget_Index->setSelectionMode(QAbstractItemView::SingleSelection);
ui->tableWidget_Index->setShowGrid(false);
ui->tableWidget_Index->setStyleSheet("QTableView {selection-background-color: red;}");
// Initialize Asset Order Table
ui->tableWidget_Order->setColumnCount(3);
ui->tableWidget_Order->setHorizontalHeaderLabels({"Asset Type", "Asset Name", "Asset Count"});
ui->tableWidget_Order->verticalHeader()->setVisible(false);
ui->tableWidget_Order->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui->tableWidget_Order->setSelectionBehavior(QAbstractItemView::SelectRows);
ui->tableWidget_Order->setSelectionMode(QAbstractItemView::SingleSelection);
ui->tableWidget_Order->setShowGrid(false);
ui->tableWidget_Order->setStyleSheet("QTableView {selection-background-color: red;}");
Qt3DExtras::Qt3DWindow *view = new Qt3DExtras::Qt3DWindow();
view->defaultFrameGraph()->setClearColor(QColor(QRgb(0x4d4d4f)));
QWidget *container = QWidget::createWindowContainer(view);
QSize screenSize = view->screen()->size();
container->setMinimumSize(QSize(200, 100));
container->setMaximumSize(screenSize);
QHBoxLayout *hLayout = new QHBoxLayout(ui->frame_Scene);
QVBoxLayout *vLayout = new QVBoxLayout();
vLayout->setAlignment(Qt::AlignTop);
hLayout->addWidget(container, 1);
hLayout->addLayout(vLayout);
// Root entity
Qt3DCore::QEntity *rootEntity = new Qt3DCore::QEntity();
// Camera
Qt3DRender::QCamera *cameraEntity = view->camera();
cameraEntity->lens()->setPerspectiveProjection(45.0f, 16.0f/9.0f, 0.1f, 1000.0f);
cameraEntity->setPosition(QVector3D(0, 0, 50.0f)); // Move farther along Z-axis
cameraEntity->setUpVector(QVector3D(0, 1, 0));
cameraEntity->setViewCenter(QVector3D(0, 0, 0));
Qt3DCore::QEntity *lightEntity = new Qt3DCore::QEntity(rootEntity);
Qt3DRender::QPointLight *light = new Qt3DRender::QPointLight(lightEntity);
light->setColor("white");
light->setIntensity(1);
lightEntity->addComponent(light);
Qt3DCore::QTransform *lightTransform = new Qt3DCore::QTransform(lightEntity);
lightTransform->setTranslation(cameraEntity->position());
lightEntity->addComponent(lightTransform);
// For camera controls
Qt3DExtras::QFirstPersonCameraController *camController = new Qt3DExtras::QFirstPersonCameraController(rootEntity);
camController->setCamera(cameraEntity);
// Set root object of the scene
view->setRootEntity(rootEntity);
// Load custom 3D model
Qt3DRender::QMesh *customMesh = new Qt3DRender::QMesh();
customMesh->setSource(QUrl::fromLocalFile(":/obj/data/obj/defaultactor_LOD0.obj"));
// Adjust the model transformation
Qt3DCore::QTransform *customTransform = new Qt3DCore::QTransform();
customTransform->setRotationX(-90);
customTransform->setRotationY(-90);
// Keep translation if necessary
customTransform->setTranslation(QVector3D(0.0f, -100.0f, -200.0f));
Qt3DExtras::QNormalDiffuseMapMaterial *customMaterial = new Qt3DExtras::QNormalDiffuseMapMaterial();
Qt3DRender::QTextureLoader *normalMap = new Qt3DRender::QTextureLoader();
normalMap->setSource(QUrl::fromLocalFile(":/obj/data/obj/normalmap.png"));
customMaterial->setNormal(normalMap);
Qt3DRender::QTextureLoader *diffuseMap = new Qt3DRender::QTextureLoader();
diffuseMap->setSource(QUrl::fromLocalFile(":/obj/data/obj/diffusemap.png"));
customMaterial->setDiffuse(diffuseMap);
Qt3DCore::QEntity *m_torusEntity = new Qt3DCore::QEntity(rootEntity);
m_torusEntity->addComponent(customMesh);
m_torusEntity->addComponent(customMaterial);
m_torusEntity->addComponent(customTransform);
LoadFile_D3DBSP(":/d3dbsp/data/d3dbsp/barebones.d3dbsp");
Reset();
}
MainWindow::~MainWindow() {
delete ui;
}
void MainWindow::Reset() {
// Reset tabwidget to 'General' tab
ui->tabWidget->setCurrentIndex(0);
// Reset 'General' tab fields
ui->lineEdit_FastFile->clear();
ui->comboBox_Company->setCurrentIndex(0);
ui->comboBox_FileType->setCurrentIndex(0);
ui->checkBox_Signed->setChecked(false);
ui->lineEdit_Magic->clear();
ui->spinBox_Magic->clear();
ui->spinBox_TagCount->clear();
ui->spinBox_FileSize->clear();
ui->spinBox_RecordCount->clear();
// Reset 'Unknowns' tab fields
ui->lineEdit_U1->clear();
ui->spinBox_U1->clear();
ui->lineEdit_U2->clear();
ui->spinBox_U2->clear();
ui->lineEdit_U3->clear();
ui->spinBox_U3->clear();
ui->lineEdit_U4->clear();
ui->spinBox_U4->clear();
ui->lineEdit_U5->clear();
ui->spinBox_U5->clear();
ui->lineEdit_U6->clear();
ui->spinBox_U6->clear();
ui->lineEdit_U7->clear();
ui->spinBox_U7->clear();
ui->lineEdit_U8->clear();
ui->spinBox_U8->clear();
ui->lineEdit_U9->clear();
ui->spinBox_U9->clear();
ui->lineEdit_U10->clear();
ui->spinBox_U10->clear();
ui->lineEdit_U11->clear();
ui->spinBox_U11->clear();
// Reset 'Tags' tab fields
ui->listWidget_Tags->clear();
// Reset 'Localized Strings' tab fields
ui->listWidget_LocalString->clear();
// Reset 'Asset Index/Order' tab fields
ui->tableWidget_Index->clear();
ui->tableWidget_Order->clear();
// Reset 'Raw Files' tab fields
ui->treeWidget_Scripts->clear();
ui->plainTextEdit_Scripts->clear();
// Reset 'Tech Sets' tab fields
ui->listWidget_TechSets->clear();
// Reset 'Zone Dump' tab fields
ui->spinBox_DumpIndex->clear();
ui->comboBox_DumpAsset->setCurrentIndex(0);
ui->plainTextEdit_ZoneDump->clear();
// Reset 'String Tables' tab fields
ui->comboBox_StringTable->setCurrentIndex(0);
ui->tableWidget_StringTable->clear();
// Reset '3D Scene' tab fields
ui->treeWidget_Models->clear();
// Reset class vars
mTypeMap.clear();
mTypeOrder.clear();
mTagCount = 0;
mRecordCount = 0;
mRawFileMap.clear();
mTreeMap.clear();
mStrTableMap.clear();
}
void MainWindow::StrTableSelected(QString aStrTableName) {
ui->tableWidget_StringTable->clear();
ui->tableWidget_StringTable->setColumnCount(2);
int entryIndex = 0;
for (auto strTableEntry : mStrTableMap[aStrTableName]) {
ui->tableWidget_StringTable->insertRow(ui->tableWidget_StringTable->rowCount() + 1);
ui->tableWidget_StringTable->setItem(entryIndex, 0, new QTableWidgetItem(strTableEntry.first));
ui->tableWidget_StringTable->setItem(entryIndex, 1, new QTableWidgetItem(Utils::AssetTypeToString(strTableEntry.second)));
entryIndex++;
}
}
void MainWindow::ScriptSelected() {
QTreeWidgetItem *selectedItem = ui->treeWidget_Scripts->selectedItems()[0];
if (!selectedItem) {
qDebug() << "Attempted to load invalid tree item!";
return;
}
const QString itemName = selectedItem->text(0);
const QStringList scriptExts = {"gsc", "csc", "atr", "shock", "vision", "rmb"};
if (!scriptExts.contains(itemName.split('.').last())) {
qDebug() << QString("Attempted to parse invalid raw file: %1!").arg(itemName);
return;
}
ui->plainTextEdit_Scripts->clear();
for (auto [scriptName, scriptContents] : mRawFileMap.asKeyValueRange()) {
if (scriptName.contains(itemName)) {
ui->plainTextEdit_Scripts->setPlainText(scriptContents);
return;
}
}
}
QByteArray MainWindow::DecompressZLIB(QByteArray compressedData) {
QByteArray decompressedData;
uLongf decompressedSize = compressedData.size() * 4;
decompressedData.resize(static_cast<int>(decompressedSize));
Bytef *destination = reinterpret_cast<Bytef*>(decompressedData.data());
uLongf *destLen = &decompressedSize;
const Bytef *source = reinterpret_cast<const Bytef*>(compressedData.data());
uLong sourceLen = compressedData.size();
int result = uncompress(destination, destLen, source, sourceLen);
if (result == Z_OK) {
decompressedData.resize(static_cast<int>(decompressedSize));
} else {
decompressedData.clear();
qDebug() << QString("In DecompressZLIB: %1").arg(Utils::ZLibErrorToString(result)).toLatin1();
}
return decompressedData;
}
/*
OpenFastFile()
Opens a file dialog in the steam folder,
and opens the selected file.
*/
QFile* MainWindow::OpenFastFile() {
// Reset dialog before opening new file
Reset();
// Open file dialog to steam apps
const QString steamPath = "C:/Program Files (x86)/Steam/steamapps/common/Call of Duty World at War/zone/english/";
const QString fastFilePath = QFileDialog::getOpenFileName(this, "Open FastFile", steamPath, "FastFile (*.ff);;All Files (*.*)");
if (!QFile::exists(fastFilePath)) {
QMessageBox::warning(this, "Warning!", QString("%1 does not exist!.").arg(fastFilePath));
return nullptr;
}
ui->lineEdit_FastFile->setText(fastFilePath);
const QString fastFileStem = fastFilePath.split('/').last();
setWindowTitle(QString("FastFile Wizard - %1").arg(fastFileStem));
// Check fastfile can be read
QFile *fastFile = new QFile(fastFilePath);
if (!fastFile->open(QIODevice::ReadOnly)) {
QMessageBox::warning(this, "Warning!", QString("%1 could not be read!.").arg(fastFilePath));
return nullptr;
}
return fastFile;
}
/*
OpenZoneFile()
Opens a file dialog in the steam folder,
and opens the selected file.
*/
QFile* MainWindow::OpenZoneFile() {
// Reset dialog before opening new file
Reset();
// Open file dialog to steam apps
const QString steamPath = "C:/Program Files (x86)/Steam/steamapps/common/Call of Duty World at War/zone/english/";
const QString zoneFilePath = QFileDialog::getOpenFileName(this, "Open ZoneFile", steamPath, "ZoneFile (*.zone);;All Files (*.*)");
if (!QFile::exists(zoneFilePath)) {
QMessageBox::warning(this, "Warning!", QString("%1 does not exist!.").arg(zoneFilePath));
return nullptr;
}
ui->lineEdit_ZoneFile->setText(zoneFilePath);
const QString zoneFileStem = zoneFilePath.split('/').last();
setWindowTitle(QString("FastFile Wizard - %1").arg(zoneFileStem));
// Check fastfile can be read
QFile *zoneFile = new QFile(zoneFilePath);
if (!zoneFile->open(QIODevice::ReadOnly)) {
QMessageBox::warning(this, "Warning!", QString("%1 could not be read!.").arg(zoneFilePath));
return nullptr;
}
return zoneFile;
}
void MainWindow::ParseFFCompany(QDataStream *afastFileStream) {
// Check for null datastream ptr
if (!afastFileStream) { return; }
// Parse company
QByteArray companyData(2, Qt::Uninitialized);
afastFileStream->readRawData(companyData.data(), 2);
if (companyData == "IW") {
qDebug() << "Company found: 'INFINITY_WARD'";
ui->comboBox_Company->setCurrentIndex(1);
} else if (companyData == "TA") {
qDebug() << "Company found: 'TREYARCH'";
ui->comboBox_Company->setCurrentIndex(2);
} else if (companyData == "Sl") {
qDebug() << "Company found: 'SLEDGEHAMMER'";
ui->comboBox_Company->setCurrentIndex(3);
} else if (companyData == "NX") {
qDebug() << "Company found: 'NEVERSOFT'";
ui->comboBox_Company->setCurrentIndex(4);
} else {
qDebug() << QString("Failed to find company, found '%1'!").arg(companyData);
return;
}
}
void MainWindow::ParseFFFileType(QDataStream *afastFileStream) {
// Parse filetype
QByteArray fileTypeData(2, Qt::Uninitialized);
afastFileStream->readRawData(fileTypeData.data(), 2);
if (fileTypeData == "ff") {
qDebug() << "File type found: 'FAST_FILE'";
ui->comboBox_FileType->setCurrentIndex(1);
} else {
qDebug() << "Failed to find file type!";
return;
}
}
void MainWindow::ParseFFSignage(QDataStream *afastFileStream) {
// Parse filetype
QByteArray signedData(1, Qt::Uninitialized);
afastFileStream->readRawData(signedData.data(), 1);
if (signedData == "u") {
qDebug() << "Found valid signage: Unsigned";
ui->checkBox_Signed->setChecked(false);
} else if (signedData == "0") {
qDebug() << "Found valid signage: Signed";
ui->checkBox_Signed->setChecked(true);
} else {
qDebug() << "Failed to determine signage of fastfile!";
return;
}
}
void MainWindow::ParseFFMagic(QDataStream *afastFileStream) {
// Parse magic
QByteArray magicData(3, Qt::Uninitialized);
afastFileStream->readRawData(magicData.data(), 3);
if (magicData == "100") {
qDebug() << QString("Found valid magic: '%1'").arg(magicData);
ui->lineEdit_Magic->setText(magicData.toHex());
ui->spinBox_Magic->setValue(magicData.toInt());
} else {
qDebug() << "Magic invalid!";
return;
}
}
void MainWindow::ParseFFVersion(QDataStream *afastFileStream) {
// Parse version
quint32 version;
*afastFileStream >> version;
qDebug() << "Version:" << version;
if (version == 387) {
qDebug() << QString("Found valid version: '%1'").arg(version);
ui->spinBox_Version->setValue(version);
} else {
qDebug() << "Version invalid!";
return;
}
}
void MainWindow::ParseFFHeader(QFile *aFastFilePtr) {
// Open stream to fastfile
QDataStream afastFileStream(aFastFilePtr);
afastFileStream.setByteOrder(QDataStream::LittleEndian);
ParseFFCompany(&afastFileStream);
ParseFFFileType(&afastFileStream);
ParseFFSignage(&afastFileStream);
ParseFFMagic(&afastFileStream);
ParseFFVersion(&afastFileStream);
}
void MainWindow::ParseZoneHeader(QDataStream *aZoneFileStream) {
ParseZoneSize(aZoneFileStream);
ParseZoneUnknownsA(aZoneFileStream);
ParseZoneTagCount(aZoneFileStream);
ParseZoneUnknownsB(aZoneFileStream);
ParseZoneRecordCount(aZoneFileStream);
if (mTagCount) {
ParseZoneUnknownsC(aZoneFileStream);
ParseZoneTags(aZoneFileStream);
} else {
aZoneFileStream->skipRawData(4);
}
}
void MainWindow::ParseZoneSize(QDataStream *aZoneFileStream) {
// Byte 0-3: (unsigned int?) correlates to the fastfile's
// size after decompression minus 36 bytes (24h)
quint32 zoneFileSize;
*aZoneFileStream >> zoneFileSize;
if (zoneFileSize <= 0) {
qDebug() << "Tried to open empty zone file!";
exit(-1);
}
zoneFileSize += 36;
ui->spinBox_FileSize->setValue(zoneFileSize);
qDebug() << QString("Zone file size: '%1'").arg(zoneFileSize);
}
/*
ParseZoneUnknownsA()
Parses the 1st section of unknowns as hex vals and uint32s
*/
void MainWindow::ParseZoneUnknownsA(QDataStream *aZoneFileStream) {
// Byte 4-7, 8-11, 12-15: unknown
QByteArray unknown1(4, Qt::Uninitialized);
aZoneFileStream->readRawData(unknown1.data(), 4);
ui->lineEdit_U1->setText(unknown1.toHex());
ui->spinBox_U1->setValue(unknown1.toUInt());
QByteArray unknown2(4, Qt::Uninitialized);
aZoneFileStream->readRawData(unknown2.data(), 4);
ui->lineEdit_U2->setText(unknown2.toHex());
ui->spinBox_U2->setValue(unknown2.toUInt());
QByteArray unknown3(4, Qt::Uninitialized);
aZoneFileStream->readRawData(unknown3.data(), 4);
ui->lineEdit_U3->setText(unknown3.toHex());
ui->spinBox_U3->setValue(unknown3.toUInt());
// Byte 16-19, 20-23: empty/unknown
QByteArray unknown4(4, Qt::Uninitialized);
aZoneFileStream->readRawData(unknown4.data(), 4);
ui->lineEdit_U4->setText(unknown4.toHex());
ui->spinBox_U4->setValue(unknown4.toUInt());
QByteArray unknown5(4, Qt::Uninitialized);
aZoneFileStream->readRawData(unknown5.data(), 4);
ui->lineEdit_U5->setText(unknown5.toHex());
ui->spinBox_U5->setValue(unknown5.toUInt());
// Byte 24-27: somehow related to the filesize, but smaller value
QByteArray unknown6(4, Qt::Uninitialized);
aZoneFileStream->readRawData(unknown6.data(), 4);
ui->lineEdit_U6->setText(unknown6.toHex());
ui->spinBox_U6->setValue(unknown6.toUInt());
// Byte 28-31, 32-35: unknown
QByteArray unknown7(4, Qt::Uninitialized);
aZoneFileStream->readRawData(unknown7.data(), 4);
ui->lineEdit_U7->setText(unknown7.toHex());
ui->spinBox_U7->setValue(unknown7.toUInt());
QByteArray unknown8(4, Qt::Uninitialized);
aZoneFileStream->readRawData(unknown8.data(), 4);
ui->lineEdit_U8->setText(unknown8.toHex());
ui->spinBox_U8->setValue(unknown8.toUInt());
qDebug() << QString("Unknowns A: '%1''%2''%3''%4''%5''%6''%7''%8'")
.arg(unknown1.toHex())
.arg(unknown2.toHex())
.arg(unknown3.toHex())
.arg(unknown4.toHex())
.arg(unknown5.toHex())
.arg(unknown6.toHex())
.arg(unknown7.toHex())
.arg(unknown8.toHex());
}
/*
ParseZoneTagCount()
Parses the number of string tags in the zone index
*/
void MainWindow::ParseZoneTagCount(QDataStream *aZoneFileStream) {
// Byte 36-39: might indicate where the index record starts,
// calculation unknown
*aZoneFileStream >> mTagCount;
ui->spinBox_TagCount->setValue(mTagCount);
qDebug() << QString("Tag count: '%1'").arg(mTagCount);
}
/*
ParseZoneRecordCount()
Parses the number of records in the zone index
*/
void MainWindow::ParseZoneRecordCount(QDataStream *aZoneFileStream) {
// Byte 44-47: (unsigned int) number of records
*aZoneFileStream >> mRecordCount;
ui->spinBox_RecordCount->setValue(mRecordCount);
qDebug() << QString("Record count: '%1'").arg(mRecordCount);
}
/*
ParseZoneUnknownsB()
Parses the 2nd section of unknowns as hex vals and uint32s
*/
void MainWindow::ParseZoneUnknownsB(QDataStream *aZoneFileStream) {
// Byte 44-47: Unknown/empty?
QByteArray unknown9(4, Qt::Uninitialized);
aZoneFileStream->readRawData(unknown9.data(), 4);
ui->lineEdit_U9->setText(unknown9.toHex());
ui->spinBox_U9->setValue(unknown9.toUInt());
qDebug() << QString("Unknowns B: \n\t'%1'")
.arg(unknown9.toHex());
}
/*
ParseZoneUnknownsC()
Parses the 3rd section of unknowns as hex vals and uint32s
*/
void MainWindow::ParseZoneUnknownsC(QDataStream *aZoneFileStream) {
// Byte 40-43: Unknown/empty?
QByteArray unknown10(4, Qt::Uninitialized);
aZoneFileStream->readRawData(unknown10.data(), 4);
ui->lineEdit_U10->setText(unknown10.toHex());
ui->spinBox_U10->setValue(unknown10.toUInt());
// Byte 44-47: Unknown/empty?
QByteArray unknown11(4, Qt::Uninitialized);
aZoneFileStream->readRawData(unknown11.data(), 4);
ui->lineEdit_U11->setText(unknown11.toHex());
ui->spinBox_U11->setValue(unknown11.toUInt());
qDebug() << QString("Unknowns C: \n\t'%1'\n\t'%2'")
.arg(unknown10.toHex())
.arg(unknown11.toHex());
}
/*
ParseZoneTags()
Parses the string tags ate the start of zone file
*/
void MainWindow::ParseZoneTags(QDataStream *aZoneFileStream) {
// Byte 48-51: Repeated separators? ÿÿÿÿ x i
aZoneFileStream->skipRawData(4 * (mTagCount - 1));
// Parse tags/strings before index
QString zoneTag;
char zoneTagChar;
for (quint32 i = 0; i < mTagCount - 1; i++) {
*aZoneFileStream >> zoneTagChar;
while (zoneTagChar != 0) {
zoneTag += zoneTagChar;
*aZoneFileStream >> zoneTagChar;
}
ui->listWidget_Tags->addItem(zoneTag);
// qDebug() << "Tag: " << zoneTag;
zoneTag.clear();
}
}
/*
ParseZoneIndex()
Parse the binary zone index data and populate table
*/
void MainWindow::ParseZoneIndex(QDataStream *aZoneFileStream) {
// Don't parse if no records
if (!mRecordCount) { return; }
// Track past assets and counts
int consecutiveIndex = 0;
int consecutiveCount = 0;
QString lastAssetType = "";
// Parse index & map found asset types
for (quint32 i = 0; i < mRecordCount; i++) {
// Skip record start
QByteArray rawAssetType(4, Qt::Uninitialized);
aZoneFileStream->readRawData(rawAssetType.data(), 4);
if (!mTypeMap.contains(rawAssetType.toHex())) {
mTypeMap[rawAssetType.toHex()] = 0;
}
mTypeMap[rawAssetType.toHex()]++;
mTypeOrder << rawAssetType.toHex();
// Skip separator
aZoneFileStream->skipRawData(4);
// Get asset description from type
const QString assetType = rawAssetType.toHex();
// Set lastAsset as current if first run
if (lastAssetType.isEmpty()) {
lastAssetType = assetType;
}
// Track counts or populate asset order table
if (lastAssetType == assetType) {
// Count consecutive assets
consecutiveCount++;
} else {
// Insert row and populate for the previous asset type
ui->tableWidget_Order->insertRow(consecutiveIndex);
ui->tableWidget_Order->setItem(consecutiveIndex, 0, new QTableWidgetItem(lastAssetType));
ui->tableWidget_Order->setItem(consecutiveIndex, 1, new QTableWidgetItem(Utils::AssetTypeToString(lastAssetType)));
ui->tableWidget_Order->setItem(consecutiveIndex, 2, new QTableWidgetItem(QString::number(consecutiveCount)));
// Update counts and asset type
consecutiveCount = 1;
consecutiveIndex++;
lastAssetType = assetType;
}
}
}
void MainWindow::ParseAsset_LocalString(QDataStream *aZoneFileStream) {
// Skip separator
aZoneFileStream->skipRawData(8);
// Parse local string asset contents
QString localStr;
char localStrChar;
*aZoneFileStream >> localStrChar;
while (localStrChar != 0) {
localStr += localStrChar;
*aZoneFileStream >> localStrChar;
}
// Parse rawfile name
QString aliasName;
char aliasNameChar;
*aZoneFileStream >> aliasNameChar;
while (aliasNameChar != 0) {
aliasName += aliasNameChar;
*aZoneFileStream >> aliasNameChar;
}
// qDebug() << QString("%1 = %2").arg(aliasName).arg(localStr);
ui->listWidget_LocalString->addItem(QString("%1 = %2").arg(aliasName).arg(localStr));
}
void MainWindow::ParseAsset_RawFile(QDataStream *aZoneFileStream) {
// Skip start separator FF FF FF FF (pointer?)
aZoneFileStream->skipRawData(4);
quint32 gscLength;
*aZoneFileStream >> gscLength;
// Skip unknown 4 byte data
aZoneFileStream->skipRawData(4);
// Parse rawfile path
QString rawFilePath;
char scriptPathChar;
*aZoneFileStream >> scriptPathChar;
while (scriptPathChar != 0) {
rawFilePath += scriptPathChar;
*aZoneFileStream >> scriptPathChar;
}
rawFilePath.replace(",", "");
const QStringList pathParts = rawFilePath.split('/');
if (pathParts.size() == 0) {
qDebug() << "Failed to parse ff path! " << rawFilePath;
exit(-1);
} else if (pathParts.size() == 1) {
const QString path = pathParts[0];
QTreeWidgetItem *newRootItem = new QTreeWidgetItem(ui->treeWidget_Scripts);
newRootItem->setText(0, path);
} else {
const QString path = pathParts[0];
QTreeWidgetItem *newRootItem;
if (mTreeMap.contains(path)) {
newRootItem = mTreeMap[path];
} else {
newRootItem = new QTreeWidgetItem(ui->treeWidget_Scripts);
newRootItem->setText(0, path);
mTreeMap[path] = newRootItem;
}
QTreeWidgetItem *parentItem = newRootItem;
for (int i = 1; i < pathParts.size(); i++) {
const QString path = pathParts[i];
QTreeWidgetItem *newChildItem;
if (mTreeMap.contains(path)) {
newChildItem = mTreeMap[path];
} else {
newChildItem = new QTreeWidgetItem();
newChildItem->setText(0, path);
mTreeMap[path] = newChildItem;
}
parentItem->addChild(newChildItem);
parentItem = newChildItem;
}
}
// Parse gsc contents
QString rawFileContents;
char rawFileContentsChar;
*aZoneFileStream >> rawFileContentsChar;
while (rawFileContentsChar != 0 && rawFileContentsChar != -1) {
rawFileContents += rawFileContentsChar;
*aZoneFileStream >> rawFileContentsChar;
}
mRawFileMap[rawFilePath] = (rawFileContents.isEmpty()) ? ("EMPTY") : (rawFileContents);
// qDebug() << QString("%1: %2").arg(rawFilePath).arg(rawFileContents);
}
void MainWindow::ParseAsset_PhysPreset(QDataStream *aZoneFileStream) {
}
void MainWindow::ParseAsset_XModel(QDataStream *aZoneFileStream) {
}
void MainWindow::ParseAsset_Material(QDataStream *aZoneFileStream) {
}
void MainWindow::ParseAsset_PixelShader(QDataStream *aZoneFileStream) {
}
void MainWindow::ParseAsset_TechSet(QDataStream *aZoneFileStream) {
aZoneFileStream->skipRawData(4);
// Parse techset name
QString techSetName;
char techSetNameChar;
*aZoneFileStream >> techSetNameChar;
while (techSetNameChar == 0) {
*aZoneFileStream >> techSetNameChar;
}
while (techSetNameChar != 0) {
techSetName += techSetNameChar;
*aZoneFileStream >> techSetNameChar;
}
techSetName.replace(",", "");
ui->listWidget_TechSets->addItem(techSetName);
//qDebug() << "Tech Set: " << techSetName;
}
void MainWindow::ParseAsset_Image(QDataStream *aZoneFileStream) {
}
void MainWindow::ParseAsset_LoadedSound(QDataStream *aZoneFileStream) {
}
void MainWindow::ParseAsset_ColMapMP(QDataStream *aZoneFileStream) {
}
void MainWindow::ParseAsset_GameMapSP(QDataStream *aZoneFileStream) {
}
void MainWindow::ParseAsset_GameMapMP(QDataStream *aZoneFileStream) {
}
void MainWindow::ParseAsset_LightDef(QDataStream *aZoneFileStream) {
}
void MainWindow::ParseAsset_UIMap(QDataStream *aZoneFileStream) {
}
void MainWindow::ParseAsset_SNDDriverGlobals(QDataStream *aZoneFileStream) {
}
void MainWindow::ParseAsset_AIType(QDataStream *aZoneFileStream) {
}
void MainWindow::ParseAsset_FX(QDataStream *aZoneFileStream) {
}
void MainWindow::ParseAsset_XAnim(QDataStream *aZoneFileStream) {
// Read in pointer to x_anim name
QByteArray namePtr(4, Qt::Uninitialized);
aZoneFileStream->readRawData(namePtr.data(), 4);
// Read in counts
quint16 dataByteCount, dataShortCount,
dataIntCount, randomDataByteCount,
randomDataIntCount, numframes;
*aZoneFileStream >> dataByteCount >> dataShortCount >>
dataIntCount >> randomDataByteCount >>
randomDataIntCount >> numframes;
// Read bool flags
bool isLooped, isDelta;
*aZoneFileStream >> isLooped >> isDelta;
// Read in more counts
quint8 noneRotatedBoneCount,
twoDRotatedBoneCount, normalRotatedBoneCount,
twoDStaticRotatedBoneCount, normalStaticRotatedBoneCount,
normalTranslatedBoneCount, preciseTranslatedBoneCount,
staticTranslatedBoneCount, noneTranslatedBoneCount,
totalBoneCount, otherBoneCount1, otherBoneCount2;
*aZoneFileStream >> noneRotatedBoneCount >>
twoDRotatedBoneCount >> normalRotatedBoneCount >>
twoDStaticRotatedBoneCount >> normalStaticRotatedBoneCount >>
normalTranslatedBoneCount >> preciseTranslatedBoneCount >>
staticTranslatedBoneCount >> noneTranslatedBoneCount >>
totalBoneCount >> otherBoneCount1 >> otherBoneCount2;
// Yet more counts
quint8 notifyCount, assetType;
*aZoneFileStream >> notifyCount >> assetType;
// Read more bool flags
bool pad;
*aZoneFileStream >> pad;
// Yet more more counts
unsigned int randomDataShortCount, indexCount;
*aZoneFileStream >> randomDataShortCount >> indexCount;
// Read in floats
float frameRate, frequency;
*aZoneFileStream >> frameRate >> frequency;
// Read in pointers
quint32 boneIDsPtr, dataBytePtr, dataShortPtr, dataIntPtr,
randomDataShortPtr, randomDataBytePtr, randomDataIntPtr,
longIndiciesPtr, notificationsPtr, deltaPartsPtr;
*aZoneFileStream >> boneIDsPtr >> dataBytePtr >> dataShortPtr
>> dataIntPtr >> randomDataShortPtr >> randomDataBytePtr
>> randomDataIntPtr >> longIndiciesPtr >> notificationsPtr
>> deltaPartsPtr;
// Read in x_anim file name
QString xAnimName;
char xAnimNameChar;
*aZoneFileStream >> xAnimNameChar;
while (xAnimNameChar != 0) {
xAnimName += xAnimNameChar;
*aZoneFileStream >> xAnimNameChar;
}
// Parse x_anim index header
QVector<quint8> sectionLengths;
for (int i = 0; i < numframes; i++) {
quint8 sectionlength;
*aZoneFileStream >> sectionlength;
sectionLengths.push_back(sectionlength);
// Skip padding
aZoneFileStream->skipRawData(1);
}
// Skip unknown section
aZoneFileStream->skipRawData(2 * 8);
}
void MainWindow::ParseAsset_MenuFile(QDataStream *aZoneFileStream) {
//MENU_FILE
}
void MainWindow::ParseAsset_Weapon(QDataStream *aZoneFileStream) {
//WEAPON_FILE
}
void MainWindow::ParseAsset_D3DBSP(QDataStream *aZoneFileStream) {
//D3DBSP_DUMP
}
void MainWindow::ParseAsset_StringTable(QDataStream *aZoneFileStream) {
aZoneFileStream->skipRawData(4);
quint32 columnCount, rowCount;
*aZoneFileStream >> columnCount >> rowCount;
columnCount = 0;
rowCount = 0;
aZoneFileStream->skipRawData(4);
QString stringTableName;
char stringTableNameChar;
*aZoneFileStream >> stringTableNameChar;
while (stringTableNameChar != 0) {
stringTableName += stringTableNameChar;
*aZoneFileStream >> stringTableNameChar;
}
ui->comboBox_StringTable->addItem(stringTableName);
QVector<QString> tablePointers = QVector<QString>();
for (quint32 i = 0; i < rowCount; i++) {
QByteArray pointerData(4, Qt::Uninitialized);
aZoneFileStream->readRawData(pointerData.data(), 4);
tablePointers.push_back(pointerData.toHex());
aZoneFileStream->skipRawData(4);
}
for (const QString &pointerAddr : tablePointers) {
QString leadingContent = "";
if (pointerAddr == "FFFFFFFF") {
char leadingContentChar;
*aZoneFileStream >> leadingContentChar;
while (leadingContentChar != 0) {
leadingContent += leadingContentChar;
*aZoneFileStream >> leadingContentChar;
}
} else {
leadingContent = pointerAddr;
}
QString content;
char contentChar;
*aZoneFileStream >> contentChar;
while (contentChar != 0) {
content += contentChar;
*aZoneFileStream >> contentChar;
}
QPair<QString, QString> tableEntry = QPair<QString, QString>();
tableEntry.first = leadingContent;
tableEntry.second = content;
if (!mStrTableMap.contains(stringTableName)) {
mStrTableMap[stringTableName] = QVector<QPair<QString, QString>>();
}
mStrTableMap[stringTableName].push_back(tableEntry);
}
}
void MainWindow::on_pushButton_FastFile_clicked() {
// Try to prompt user to open fastfile
QFile *fastFile;
if (!(fastFile = OpenFastFile())) {
QMessageBox::warning(this, "Warning!", QString("Failed to open FastFile!."));
return;
}
// Parse data from fast file header
ParseFFHeader(fastFile);
// Decompress fastfile and close
const QByteArray fastFileData = fastFile->readAll();
const QByteArray decompressedData = DecompressZLIB(fastFileData);
// ui->plainTextEdit_ZoneDump->setPlainText(decompressedData.toHex());
const QString zoneFilePath = fastFile->fileName().replace(".ff", ".zone");
fastFile->close();
// Check zone file is writeable
QFile *zoneFile = new QFile(zoneFilePath);
if (!zoneFile->open(QIODevice::ReadWrite)) {
qDebug() << QString("Zone file could not be written to: '%1'").arg(zoneFilePath);
return;
}
// Write zone data
zoneFile->write(decompressedData);
zoneFile->close();
// Open zone file as little endian stream
QDataStream zoneFileStream(decompressedData);
zoneFileStream.setByteOrder(QDataStream::LittleEndian);
// Parse data from zone file header
ParseZoneHeader(&zoneFileStream);
ParseZoneIndex(&zoneFileStream);
// Track current and consecutive assets
int assetIndex = 0;
// Iterate asset types found in index
for (auto [assetType, assetCount] : mTypeMap.asKeyValueRange()) {
// Get asset description from type
QString assetStr = Utils::AssetTypeToString(assetType);
// Insert row and populate
ui->tableWidget_Index->insertRow(assetIndex);
ui->tableWidget_Index->setItem(assetIndex, 0, new QTableWidgetItem(assetType));
ui->tableWidget_Index->setItem(assetIndex, 1, new QTableWidgetItem(assetStr));
ui->tableWidget_Index->setItem(assetIndex, 2, new QTableWidgetItem(QString::number(assetCount)));
// Update count
assetIndex++;
}
for (int i = 0; i < mTypeOrder.size(); i++) {
const QString typeHex = mTypeOrder[i];
const QString typeStr = Utils::AssetTypeToString(typeHex);
// qDebug() << "Parsing Asset of Type: " << typeHex;
if (typeStr == "LOCAL STRING") { // localized string asset
ParseAsset_LocalString(&zoneFileStream);
} else if (typeStr == "RAW FILE") { // gsc
ParseAsset_RawFile(&zoneFileStream);
} else if (typeStr == "PHYS PRESET") { // physpreset
ParseAsset_PhysPreset(&zoneFileStream);
} else if (typeStr == "MODEL") { // xmodel
ParseAsset_XModel(&zoneFileStream);
} else if (typeStr == "MATERIAL") { // material
ParseAsset_Material(&zoneFileStream);
} else if (typeStr == "SHADER") { // pixelshader
ParseAsset_PixelShader(&zoneFileStream);
} else if (typeStr == "TECH SET") { // techset include
ParseAsset_TechSet(&zoneFileStream);
} else if (typeStr == "IMAGE") { // image
ParseAsset_Image(&zoneFileStream);
} else if (typeStr == "SOUND") { // loaded_sound
ParseAsset_LoadedSound(&zoneFileStream);
} else if (typeStr == "COLLISION MAP") { // col_map_mp
ParseAsset_ColMapMP(&zoneFileStream);
} else if (typeStr == "MP MAP") { // game_map_sp
ParseAsset_GameMapSP(&zoneFileStream);
} else if (typeStr == "SP MAP") { // game_map_mp
ParseAsset_GameMapMP(&zoneFileStream);
} else if (typeStr == "LIGHT DEF") { // lightdef
ParseAsset_LightDef(&zoneFileStream);
} else if (typeStr == "UI MAP") { // ui_map
ParseAsset_UIMap(&zoneFileStream);
} else if (typeStr == "SND DRIVER GLOBALS") { // snddriverglobals
ParseAsset_SNDDriverGlobals(&zoneFileStream);
} else if (typeStr == "AI TYPE") { // aitype
ParseAsset_AIType(&zoneFileStream);
} else if (typeStr == "EFFECT") { // aitype
ParseAsset_FX(&zoneFileStream);
} else if (typeStr == "ANIMATION") { // aitype
ParseAsset_XAnim(&zoneFileStream);
} else if (typeStr == "STRING TABLE") { // string_table
ParseAsset_StringTable(&zoneFileStream);
} else if (typeStr == "MENU") { // string_table
ParseAsset_MenuFile(&zoneFileStream);
} else if (typeStr == "WEAPON") { // string_table
ParseAsset_Weapon(&zoneFileStream);
} else if (typeStr == "D3DBSP DUMP") { // string_table
ParseAsset_D3DBSP(&zoneFileStream);
} else if (typeStr != "UNKNOWN") {
qDebug() << "Found bad asset type!" << typeStr;
}
}
// Close zone file
zoneFile->close();
// Clean up
delete zoneFile;
delete fastFile;
}
void MainWindow::on_pushButton_FastFile_2_clicked() {
// Check zone file is writeable
QFile *zoneFile;
if (!(zoneFile = OpenZoneFile())) {
QMessageBox::warning(this, "Warning!", QString("Failed to open FastFile!."));
return;
}
const QByteArray decompressedData = zoneFile->readAll();
// Open zone file as little endian stream
QDataStream zoneFileStream(decompressedData);
zoneFileStream.setByteOrder(QDataStream::LittleEndian);
// Parse data from zone file header
ParseZoneHeader(&zoneFileStream);
ParseZoneIndex(&zoneFileStream);
// Track current and consecutive assets
int assetIndex = 0;
// Iterate asset types found in index
for (auto [assetType, assetCount] : mTypeMap.asKeyValueRange()) {
// Get asset description from type
QString assetStr = Utils::AssetTypeToString(assetType);
// Insert row and populate
ui->tableWidget_Index->insertRow(assetIndex);
ui->tableWidget_Index->setItem(assetIndex, 0, new QTableWidgetItem(assetType));
ui->tableWidget_Index->setItem(assetIndex, 1, new QTableWidgetItem(assetStr));
ui->tableWidget_Index->setItem(assetIndex, 2, new QTableWidgetItem(QString::number(assetCount)));
// Update count
assetIndex++;
}
for (int i = 0; i < mTypeOrder.size(); i++) {
const QString typeHex = mTypeOrder[i];
const QString typeStr = Utils::AssetTypeToString(typeHex);
// qDebug() << "Parsing Asset of Type: " << typeHex;
if (typeStr == "LOCAL STRING") { // localized string asset
ParseAsset_LocalString(&zoneFileStream);
} else if (typeStr == "RAW FILE") { // gsc
ParseAsset_RawFile(&zoneFileStream);
} else if (typeStr == "PHYS PRESET") { // physpreset
ParseAsset_PhysPreset(&zoneFileStream);
} else if (typeStr == "MODEL") { // xmodel
ParseAsset_XModel(&zoneFileStream);
} else if (typeStr == "MATERIAL") { // material
ParseAsset_Material(&zoneFileStream);
} else if (typeStr == "SHADER") { // pixelshader
ParseAsset_PixelShader(&zoneFileStream);
} else if (typeStr == "TECH SET") { // techset include
ParseAsset_TechSet(&zoneFileStream);
} else if (typeStr == "IMAGE") { // image
ParseAsset_Image(&zoneFileStream);
} else if (typeStr == "SOUND") { // loaded_sound
ParseAsset_LoadedSound(&zoneFileStream);
} else if (typeStr == "COLLISION MAP") { // col_map_mp
ParseAsset_ColMapMP(&zoneFileStream);
} else if (typeStr == "MP MAP") { // game_map_sp
ParseAsset_GameMapSP(&zoneFileStream);
} else if (typeStr == "SP MAP") { // game_map_mp
ParseAsset_GameMapMP(&zoneFileStream);
} else if (typeStr == "LIGHT DEF") { // lightdef
ParseAsset_LightDef(&zoneFileStream);
} else if (typeStr == "UI MAP") { // ui_map
ParseAsset_UIMap(&zoneFileStream);
} else if (typeStr == "SND DRIVER GLOBALS") { // snddriverglobals
ParseAsset_SNDDriverGlobals(&zoneFileStream);
} else if (typeStr == "AI TYPE") { // aitype
ParseAsset_AIType(&zoneFileStream);
} else if (typeStr == "EFFECT") { // aitype
ParseAsset_FX(&zoneFileStream);
} else if (typeStr == "ANIMATION") { // aitype
ParseAsset_XAnim(&zoneFileStream);
} else if (typeStr == "STRING TABLE") { // string_table
ParseAsset_StringTable(&zoneFileStream);
} else if (typeStr == "MENU") { // string_table
ParseAsset_MenuFile(&zoneFileStream);
} else if (typeStr == "WEAPON") { // string_table
ParseAsset_Weapon(&zoneFileStream);
} else if (typeStr == "D3DBSP DUMP") { // string_table
ParseAsset_D3DBSP(&zoneFileStream);
} else if (typeStr != "UNKNOWN") {
qDebug() << "Found bad asset type!" << typeStr;
}
}
// Close zone file
zoneFile->close();
// Clean up
delete zoneFile;
}
int MainWindow::LoadFile_D3DBSP(const QString aFilePath) {
QFile file(aFilePath);
if (!file.open(QIODevice::ReadOnly)) {
qDebug() << "Error: Unable to open file" << aFilePath;
return 1; // ERR_FILE_NOT_FOUND
}
QDataStream stream(&file);
stream.setByteOrder(QDataStream::LittleEndian);
// Read Magic Value
quint32 magicValue;
stream >> magicValue;
if (magicValue != 'PSBI') {
qDebug() << "Error: Not a valid D3DBSP file";
return 2; // ERR_NOT_A_D3DBSP_FILE
}
// Read BSP Version and Lump Count
stream >> mBSPVersion;
stream >> mDiskLumpCount;
// Assign diskLumpOrderSize
mDiskLumpOrder.resize(mDiskLumpCount);
qDebug() << "BSP Version:" << mBSPVersion;
qDebug() << "Lump Count:" << mDiskLumpCount;
// Read Lump Index Entries
quint32 lumpOffset = sizeof(quint32) * 3 + sizeof(LumpIndexEntry) * mDiskLumpCount;
for (quint32 i = 0; i < mDiskLumpCount; i++) {
LumpIndexEntry indexEntry;
stream >> indexEntry.type >> indexEntry.length;
mDiskLumpOrder[i] = indexEntry.type;
Lump &lump = mLumps[indexEntry.type];
lump.size = indexEntry.length;
lump.content.resize(indexEntry.length);
lump.isEmpty = false;
qDebug() << "Lump Type:" << Utils::LumpTypeToString((LUMP_TYPE)indexEntry.type) << "Lump Size:" << indexEntry.length;
// Handle offsets and padding
qint64 currentPos = file.pos();
file.seek(lumpOffset);
file.read(lump.content.data(), lump.size);
file.seek(currentPos);
lumpOffset += Utils::PadInt4(indexEntry.length);
}
file.close();
return 0; // Success
}