#include "mainwindow.h" #include "ui_mainwindow.h" #include "aboutdialog.h" #include "fastfile.h" #include "highlighter_gsc.h" #include "highlighter_cfg.h" #include "highlighter_shock.h" #include "highlighter_rumble.h" #include "materialviewer.h" #include "preferenceeditor.h" #include "reportissuedialog.h" #include "rumblefileviewer.h" #include "rumblegraphviewer.h" #include "soundviewer.h" #include "stringtableviewer.h" #include "techsetviewer.h" #include "fastfile_factory.h" #include "iwifile.h" #include "ddsfile.h" #include "statusbarmanager.h" #include "ddsviewer.h" #include "fastfileviewer.h" #include "ipak_structs.h" #include "iwiviewer.h" #include "localstringviewer.h" #include "imagewidget.h" #include "zonefileviewer.h" #include "techsetviewer.h" #include "logmanager.h" #include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); setAcceptDrops(true); mTypeMap = QMap(); mTypeOrder = QStringList(); mRawFileMap = QMap(); //mImageMap = QMap(); mTreeMap = QMap(); mStrTableMap = QMap>>(); mBSPVersion = 0; mDiskLumpCount = 0; mDiskLumpOrder = QVector(); mLumps = QMap(); mTreeWidget = new XTreeWidget(this); mLogWidget = new QPlainTextEdit(this); mProgressBar = new QProgressBar(this); mProgressBar->setMaximum(100); // Default max value mProgressBar->setVisible(false); // Initially hidden connect(ui->actionRun_Tests, &QAction::triggered, this, [](bool checked) { Q_UNUSED(checked); }); connect(ui->actionReport_Issue, &QAction::triggered, this, [this](bool checked) { Q_UNUSED(checked); ReportIssueDialog issueDialog("https://git.redline.llc", "njohnson", "XPlor", "4738c4d2efd123efac1506c68c59b285c646df9f", this); if (issueDialog.exec() == QDialog::Accepted) { } }); connect(&StatusBarManager::instance(), &StatusBarManager::statusUpdated, this, &MainWindow::HandleStatusUpdate); connect(&StatusBarManager::instance(), &StatusBarManager::progressUpdated, this, &MainWindow::HandleProgressUpdate); connect(&LogManager::instance(), &LogManager::entryAdded, this, &MainWindow::HandleLogEntry); statusBar()->addPermanentWidget(mProgressBar); connect(ui->actionPreferences, &QAction::triggered, this, [this](bool checked) { Q_UNUSED(checked); PreferenceEditor *prefEditor = new PreferenceEditor(this); prefEditor->exec(); }); ui->tabWidget->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->tabWidget, &QTabWidget::customContextMenuRequested, this, [this](const QPoint &pos) { if (pos.isNull()) return; int tabIndex = ui->tabWidget->tabBar()->tabAt(pos); QMenu *contextMenu = new QMenu(this); QAction *closeAction = new QAction("Close"); contextMenu->addAction(closeAction); connect(closeAction, &QAction::triggered, this, [this, &tabIndex](bool checked) { Q_UNUSED(checked); ui->tabWidget->removeTab(tabIndex); }); QMenu *closeMultipleAction = new QMenu("Close Multiple Tabs"); QAction *closeAllAction = new QAction("Close All"); closeMultipleAction->addAction(closeAllAction); connect(closeAllAction, &QAction::triggered, this, [this](bool checked) { Q_UNUSED(checked); ui->tabWidget->clear(); }); QAction *closeAllButAction = new QAction("Close All BUT This"); closeMultipleAction->addAction(closeAllButAction); connect(closeAllButAction, &QAction::triggered, this, [this, &tabIndex](bool checked) { Q_UNUSED(checked); for (int i = 0; i < ui->tabWidget->count(); i++) { if (i != tabIndex) { ui->tabWidget->removeTab(i); } } }); QAction *closeLeftAction = new QAction("Close All to the Left"); closeMultipleAction->addAction(closeLeftAction); connect(closeLeftAction, &QAction::triggered, this, [this, &tabIndex](bool checked) { Q_UNUSED(checked); for (int i = 0; i < tabIndex; i++) { ui->tabWidget->removeTab(i); } }); QAction *closeRightAction = new QAction("Close All to the Right"); closeMultipleAction->addAction(closeRightAction); connect(closeRightAction, &QAction::triggered, this, [this, &tabIndex](bool checked) { Q_UNUSED(checked); for (int i = tabIndex + 1; i < ui->tabWidget->count(); i++) { ui->tabWidget->removeTab(i); } }); contextMenu->addMenu(closeMultipleAction); QPoint pt(pos); contextMenu->exec(ui->tabWidget->mapToGlobal(pt)); delete contextMenu; }); connect(ui->tabWidget, &QTabWidget::tabCloseRequested, this, [this](int index) { ui->tabWidget->removeTab(index); }); connect(mTreeWidget, &XTreeWidget::Cleared, this, [this]() { ui->tabWidget->clear(); }); connect(mTreeWidget, &XTreeWidget::RawFileSelected, this, [this](const XRawFile* rawFile, const QString aParentName) { QTabWidget *rawTabWidget = new QTabWidget(this); rawTabWidget->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); QPlainTextEdit *scriptEditor = new QPlainTextEdit(rawTabWidget); scriptEditor->setAcceptDrops(false); scriptEditor->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); scriptEditor->setFont(QFont("Consolas")); // if (rawFile->contents.isEmpty()) { // scriptEditor->setPlainText("EMPTY"); // } else { // scriptEditor->setPlainText(rawFile->contents); // } QString fileStem;// = rawFile->path.split('/').last(); for (int i = 0; i < ui->tabWidget->count(); i++) { if (ui->tabWidget->tabText(i) == fileStem) { delete scriptEditor; return; } } const int tabStopSpaces = 4; QFontMetrics metrics(scriptEditor->font()); scriptEditor->setTabStopDistance(tabStopSpaces * metrics.horizontalAdvance(' ')); QSyntaxHighlighter *highlighter = nullptr; if (fileStem.contains(".gsc")) { highlighter = new Highlighter_GSC(scriptEditor->document()); } else if (fileStem.contains(".cfg")) { highlighter = new Highlighter_CFG(scriptEditor->document()); } else if (fileStem.contains(".rmb")) { highlighter = new Highlighter_Rumble(scriptEditor->document()); RumbleGraphViewer *rmbGraphViewer = new RumbleGraphViewer(this); rmbGraphViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); rmbGraphViewer->SetRumbleGraphFile(rawFile); rawTabWidget->addTab(rmbGraphViewer, "UI Editor"); rawTabWidget->addTab(scriptEditor, "Text Editor"); ui->tabWidget->addTab(rawTabWidget, fileStem); ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_RUMBLE)); ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); return; } else if (fileStem.contains(".shock")) { highlighter = new Highlighter_Shock(scriptEditor->document()); } /*else if (rawFile->contents.left(6) == "RUMBLE") { RumbleFileViewer *rmbFileViewer = new RumbleFileViewer(this); rmbFileViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); rmbFileViewer->SetRumbleFile(rawFile); rawTabWidget->addTab(rmbFileViewer, "UI Editor"); rawTabWidget->addTab(scriptEditor, "Text Editor"); ui->tabWidget->addTab(rawTabWidget, fileStem); ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_RUMBLE)); ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); return; }*/ else { delete highlighter; } ui->tabWidget->addTab(scriptEditor, fileStem); ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_RAWFILE)); ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); }); // connect(mTreeWidget, &XTreeWidget::ImageSelected, this, [this](std::shared_ptr image, const QString aParentName) { // ImageWidget *mImageWidget = new ImageWidget(this); // mImageWidget->setAcceptDrops(false); // mImageWidget->SetImage(image); // mImageWidget->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); // QString fileStem = image->materialName; // for (int i = 0; i < ui->tabWidget->count(); i++) { // if (ui->tabWidget->tabText(i) == fileStem) { // delete mImageWidget; // return; // } // } // ui->tabWidget->addTab(mImageWidget, fileStem); // ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_IMAGE)); // ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); // }); // connect(mTreeWidget, &XTreeWidget::MenuSelected, this, [](std::shared_ptr menu, const QString aParentName) { // Q_UNUSED(menu); // }); connect(mTreeWidget, &XTreeWidget::MaterialSelected, this, [this](const XMaterial* material, const QString aParentName) { MaterialViewer *matViewer = new MaterialViewer(this); matViewer->setAcceptDrops(false); matViewer->SetMaterial(material); matViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); // QString fileStem = material->name; // for (int i = 0; i < ui->tabWidget->count(); i++) { // if (ui->tabWidget->tabText(i) == fileStem) { // delete matViewer; // return; // } // } //ui->tabWidget->addTab(matViewer, fileStem); ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_MATERIAL)); ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); }); connect(mTreeWidget, &XTreeWidget::DDSFileSelected, this, [this](const DDSFile* ddsFile, const QString aParentName) { DDSViewer *ddsViewer = new DDSViewer(this); ddsViewer->setAcceptDrops(false); ddsViewer->SetDDSFile(ddsFile); ddsViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); QString fileStem = ddsFile->fileStem + ".dds"; for (int i = 0; i < ui->tabWidget->count(); i++) { if (ui->tabWidget->tabText(i) == fileStem) { delete ddsViewer; return; } } ui->tabWidget->addTab(ddsViewer, fileStem); ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_IMAGE)); ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); }); connect(mTreeWidget, &XTreeWidget::IWIFileSelected, this, [this](const IWIFile* iwiFile, const QString aParentName) { IWIViewer *iwiViewer = new IWIViewer(this); iwiViewer->setAcceptDrops(false); iwiViewer->SetIWIFile(iwiFile); iwiViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); QString fileStem = iwiFile->fileStem + ".iwi"; for (int i = 0; i < ui->tabWidget->count(); i++) { if (ui->tabWidget->tabText(i) == fileStem) { delete iwiViewer; return; } } ui->tabWidget->addTab(iwiViewer, fileStem); ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_IMAGE)); ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); }); connect(mTreeWidget, &XTreeWidget::FastFileSelected, this, [this](const FastFile* aFastFile, const QString aParentName) { FastFileViewer *fastFileViewer = new FastFileViewer(this); fastFileViewer->setAcceptDrops(false); fastFileViewer->SetFastFile(aFastFile); fastFileViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); QString fileStem = aFastFile->GetStem(); for (int i = 0; i < ui->tabWidget->count(); i++) { if (ui->tabWidget->tabText(i) == fileStem) { delete fastFileViewer; return; } } ui->tabWidget->addTab(fastFileViewer, fileStem); ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon("FF")); ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); }); connect(mTreeWidget, &XTreeWidget::ZoneFileSelected, this, [this](const ZoneFile* aZoneFile, const QString aParentName) { ZoneFileViewer *zoneFileViewer = new ZoneFileViewer(this); zoneFileViewer->setAcceptDrops(false); zoneFileViewer->SetZoneFile(aZoneFile); zoneFileViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); QString fileStem = aZoneFile->GetBaseStem() + ".zone"; for (int i = 0; i < ui->tabWidget->count(); i++) { if (ui->tabWidget->tabText(i) == fileStem) { delete zoneFileViewer; return; } } QWidget *containerWidget = new QWidget(); QVBoxLayout *layout = new QVBoxLayout(containerWidget); layout->addWidget(zoneFileViewer); containerWidget->setLayout(layout); // Create a scroll area and set its properties QScrollArea *scrollArea = new QScrollArea(ui->tabWidget); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); scrollArea->setWidgetResizable(true); // Important to allow resizing scrollArea->setWidget(containerWidget); ui->tabWidget->addTab(scrollArea, fileStem); ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon("ZF")); ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); }); connect(mTreeWidget, &XTreeWidget::LocalStringSelected, this, [this](const ZoneFile* aZoneFile, const QString aParentName) { LocalStringViewer *localStrViewer = new LocalStringViewer(this); localStrViewer->setAcceptDrops(false); localStrViewer->SetZoneFile(aZoneFile); localStrViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); QString fileStem = aZoneFile->GetStem() + ".str"; for (int i = 0; i < ui->tabWidget->count(); i++) { if (ui->tabWidget->tabText(i) == fileStem) { delete localStrViewer; return; } } ui->tabWidget->addTab(localStrViewer, fileStem); ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_LOCALIZE_ENTRY)); ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); }); connect(mTreeWidget, &XTreeWidget::TechSetSelected, this, [this](const XMaterialTechniqueSet* aTechSet, const QString aParentName) { TechSetViewer *techSetViewer = new TechSetViewer(this); techSetViewer->setAcceptDrops(false); techSetViewer->SetTechSet(aTechSet); techSetViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); QString fileStem = aTechSet->GetName(); for (int i = 0; i < ui->tabWidget->count(); i++) { if (ui->tabWidget->tabText(i) == fileStem) { delete techSetViewer; return; } } ui->tabWidget->addTab(techSetViewer, aTechSet->GetName()); ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_TECHNIQUE_SET)); ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); }); connect(mTreeWidget, &XTreeWidget::StrTableSelected, this, [this](const XStringTable* aStrTable, const QString aParentName) { StringTableViewer *strTableViewer = new StringTableViewer(this); strTableViewer->setAcceptDrops(false); strTableViewer->SetStringTable(aStrTable); strTableViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); QString fileStem = aStrTable->GetName()->GetString(); for (int i = 0; i < ui->tabWidget->count(); i++) { if (ui->tabWidget->tabText(i) == fileStem) { delete strTableViewer; return; } } ui->tabWidget->addTab(strTableViewer, fileStem); ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_STRINGTABLE)); ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); }); // connect(mTreeWidget, &XTreeWidget::SoundSelected, this, [this](std::shared_ptr aSound, const QString aParentName) { // SoundViewer *soundViewer = new SoundViewer(this); // soundViewer->setAcceptDrops(false); // soundViewer->SetSound(aSound); // soundViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); // QString fileStem = aSound->path.split('/').last(); // for (int i = 0; i < ui->tabWidget->count(); i++) { // if (ui->tabWidget->tabText(i) == fileStem) { // delete soundViewer; // return; // } // } // ui->tabWidget->addTab(soundViewer, fileStem); // ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_SOUND)); // ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); // }); connect(mTreeWidget, &XTreeWidget::ItemSelected, this, [this](const QString itemText) { for (int i = 0; i < ui->tabWidget->count(); i++) { if (ui->tabWidget->tabText(i) == itemText) { ui->tabWidget->setCurrentIndex(i); break; } } }); connect(mTreeWidget, &XTreeWidget::ItemClosed, this, [this](const QString itemText) { for (int i = 0; i < ui->tabWidget->count(); i++) { const QString parentName = ui->tabWidget->widget(i)->property("PARENT_NAME").toString(); if (parentName == itemText) { ui->tabWidget->removeTab(i); break; } } }); // Connect Help > About dialog connect(ui->actionAbout, &QAction::triggered, this, [this](bool checked) { Q_UNUSED(checked); AboutDialog *aboutDialog = new AboutDialog(this); aboutDialog->exec(); delete aboutDialog; }); connect(ui->actionOpen_Fast_File, &QAction::triggered, this, [this](bool checked) { Q_UNUSED(checked); OpenFastFile(); }); connect(ui->actionOpen_Zone_File, &QAction::triggered, this, [this](bool checked) { Q_UNUSED(checked); OpenZoneFile(); }); QDockWidget *treeDockWidget = new QDockWidget(this); treeDockWidget->setWidget(mTreeWidget); treeDockWidget->setWindowTitle("Tree Browser"); addDockWidget(Qt::LeftDockWidgetArea, treeDockWidget); QDockWidget *logDockWidget = new QDockWidget(this); logDockWidget->setWidget(mLogWidget); logDockWidget->setWindowTitle("Logs"); addDockWidget(Qt::RightDockWidgetArea, logDockWidget); ui->toolBar->addAction(ui->actionNew_Fast_File); ui->toolBar->addAction(ui->actionNew_Zone_File); ui->toolBar->addAction(ui->actionOpen_Fast_File); ui->toolBar->addAction(ui->actionOpen_Zone_File); ui->toolBar->addAction(ui->actionOpen_Folder); ui->toolBar->addAction(ui->actionSave); ui->toolBar->addSeparator(); ui->toolBar->addAction(ui->actionCut); ui->toolBar->addAction(ui->actionCopy); ui->toolBar->addAction(ui->actionPaste); ui->toolBar->addSeparator(); ui->toolBar->addSeparator(); ui->toolBar->addSeparator(); ui->toolBar->addAction(ui->actionFind_2); Reset(); } MainWindow::~MainWindow() { delete ui; } void MainWindow::Reset() { // Clear data tree // mTreeWidget->clear(); // Reset class vars mTypeMap.clear(); mTypeOrder.clear(); mTagCount = 0; mRecordCount = 0; mRawFileMap.clear(); mTreeMap.clear(); mStrTableMap.clear(); } /* OpenFastFile() Opens a file dialog in the steam folder, and opens the selected file. */ bool MainWindow::OpenFastFile(const QString aFastFilePath) { const QString fastFileStem = aFastFilePath.section("/", -1, -1); if (mTreeWidget->HasFastFile(fastFileStem)) { LogManager::instance().addError("Can't add duplicate file!"); return false; } FastFile* fastFile = FastFileFactory::Create(aFastFilePath); fastFile->SetStem(fastFileStem); mTreeWidget->AddFastFile(fastFile); // Open zone file after decompressing ff and writing return true; } /* OpenFastFile() Opens a file dialog in the steam folder, and opens the selected file. */ bool MainWindow::OpenFastFile() { // 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 Fast File", steamPath, "Fast File (*.ff);;All Files (*.*)"); if (fastFilePath.isNull()) { // User pressed cancel return false; } else if (!QFile::exists(fastFilePath)) { QMessageBox::warning(this, "Warning!", QString("%1 does not exist!.").arg(fastFilePath)); return false; } if (!OpenFastFile(fastFilePath)) { qDebug() << "Failed to open Fast file!"; return false; } return true; } /* OpenZoneFile() Opens a file dialog in the steam folder, and opens the selected file. */ bool MainWindow::OpenZoneFile(const QString aZoneFilePath, bool fromFF) { Q_UNUSED(aZoneFilePath); Q_UNUSED(fromFF); // ZoneFile* zoneFile = ZoneFile::Create(); // if (!zoneFile.Load(aZoneFilePath)) { // qDebug() << "Error: Failed to load zone file!"; // return false; // } // mTreeWidget->AddZoneFile(std::make_shared(zoneFile)); return true; } bool MainWindow::OpenZoneFile() { // 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 Zone File", steamPath, "Zone File (*.zone);;All Files (*.*)"); if (zoneFilePath.isNull()) { // User pressed cancel return false; } else if (!QFile::exists(zoneFilePath)) { QMessageBox::warning(this, "Warning!", QString("%1 does not exist!.").arg(zoneFilePath)); qDebug() << "Failed to open Zone file!"; return false; } return true; } 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); // 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 } struct Command { quint32 size = 0; bool compressed = false; }; quint32 DXT1 = 0x31545844; // 'DXT1' quint32 DXT3 = 0x33545844; // 'DXT3' quint32 DXT5 = 0x35545844; // 'DXT5' int MainWindow::LoadFile_IWI(const QString aFilePath) { mTreeWidget->AddIWIFile(new IWIFile(aFilePath)); return 0; } int MainWindow::LoadFile_DDSFiles(const QStringList aFilePaths) { for (const QString &filePath : aFilePaths) { if (!filePath.endsWith(".dds", Qt::CaseInsensitive)) { qDebug() << "Error: Invalid filename " << filePath; return -1; } mTreeWidget->AddDDSFile(new DDSFile(filePath)); } return 0; } void MainWindow::HandleLogEntry(const QString &entry) { QString logContents = mLogWidget->toPlainText() + "\n" + entry; if (mLogWidget->toPlainText().isEmpty()) { logContents = entry; } mLogWidget->setPlainText(logContents); } void MainWindow::HandleStatusUpdate(const QString &message, int timeout) { statusBar()->showMessage(message, timeout); mProgressBar->setVisible(false); // Hide progress bar if just a message } void MainWindow::HandleProgressUpdate(const QString &message, int progress, int max) { mProgressBar->setMaximum(max); mProgressBar->setValue(progress); mProgressBar->setVisible(true); QString progressText = QString("%1 (%2/%3)").arg(message).arg(progress).arg(max); statusBar()->showMessage(progressText); } int MainWindow::LoadFile_DDS(const QString aFilePath) { if (!aFilePath.endsWith(".dds", Qt::CaseInsensitive)) { qDebug() << "Error: Invalid filename " << aFilePath; return -1; } mTreeWidget->AddDDSFile(new DDSFile(aFilePath)); return 0; } int MainWindow::LoadFile_XSUB(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); QByteArray magic(4, Qt::Uninitialized); stream.readRawData(magic.data(), 4); if (magic != "KAPI") { qDebug() << "Wrong magic, invalid XSUB file!"; return -1; } qDebug() << "Magic: " << magic; stream.skipRawData(5 * 4); quint32 fileSize; stream >> fileSize; qDebug() << "File Size: " << fileSize; return 0; // Success } int MainWindow::LoadFile_IPAK(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::BigEndian); IPAKHeader header; stream >> header; if (header.version == "50000") { if (header.magic == "KAPI") { stream.setByteOrder(QDataStream::LittleEndian); } else if (header.magic == "IPAK") { stream.setByteOrder(QDataStream::BigEndian); } else { qDebug() << "Invalid IPAK file!"; return -1; } } else { qDebug() << "Invalid IPAK file version!"; return -1; } qDebug() << "IPAK File " << "\n" << "- Platform: " << header.platform << "\n" << "- Magic: " << header.magic << "\n" << "- Size: " << header.size << "\n" << "- Version: " << header.version << "\n" << "- Sections: " << header.sectionCount; QDir outputFolder = QDir(QDir::currentPath() + "/output"); outputFolder.remove(QDir::currentPath() + "/output"); outputFolder.mkdir(QDir::currentPath() + "/output"); QVector metas = QVector(); QVector entries = QVector(); QVector sections = QVector(header.sectionCount); for (uint i = 0; i < header.sectionCount; i++) { IPAKSection currentSection; stream >> currentSection; sections << currentSection; qDebug() << " - IPAK Section " << i + 1 << "\n" << " - Type: " << currentSection.type << " -> " << currentSection.typeInt << "\n" << " - Offset: " << currentSection.offset << "\n" << " - Item (IWI) Count: " << currentSection.itemCount << "\n" << " - Size: " << currentSection.size; qint64 sectionPos = stream.device()->pos(); stream.device()->seek(currentSection.offset); QString sectionType = currentSection.type; if (sectionType == "Data") { IPAKDataChunkHeader chunkHeader; stream >> chunkHeader; qDebug() << " - Chunk Header\n" << " - Count: " << chunkHeader.count << "\n" << " - Offset: " << chunkHeader.offset; for (uint j = 0; j < 31; j++) { IPAKDataChunkCommand command; stream >> command; if (!command.size) { continue; } chunkHeader.commands << command; qDebug() << " - Command\n" << " - Size: " << command.size << "\n" << " - Compressed: " << command.compressed; } for (uint j = 0; j < chunkHeader.count; j++) { auto command = chunkHeader.commands[j]; qDebug() << "Reading from " << stream.device()->pos(); QByteArray data = stream.device()->read(command.size); qDebug() << " to " << stream.device()->pos(); QString outputFilePath = outputFolder.filePath(QString("%1.iwi").arg(j)); if (command.compressed) { //data = Compression::DecompressLZO(data); } QFile outputFile(outputFilePath); if (!outputFile.open(QIODevice::WriteOnly)) { qDebug() << "Failed to extract IPAK file."; } qDebug() << " - File Name: " << outputFile.fileName(); outputFile.write(data); outputFile.close(); } qDebug() << stream.device()->pos(); stream.skipRawData(sizeof(quint32) * (31 - chunkHeader.count)); qDebug() << stream.device()->pos(); } else if (sectionType == "Index") { for (uint j = 0; j < currentSection.itemCount; j++) { IPAKIndexEntry entry; stream >> entry; if (entry.size == 0) { continue; } entries << entry; quint64 entryPos = stream.device()->pos(); qDebug() << " - Index Entry " << j + 1 << "\n" << " - Name Hash: " << entry.nameHash << "\n" << " - Data Hash: " << entry.dataHash << "\n" << " - Offset: " << entry.offset << "\n" << " - Size: " << entry.size; stream.device()->seek(entry.offset); QByteArray sectionData(entry.size, Qt::Uninitialized); stream.readRawData(sectionData.data(), entry.size); const QString entryKey = QString::number(entry.nameHash); QFile outputFile(outputFolder.filePath(QString("%1.dds").arg(entryKey))); if (!outputFile.open(QIODevice::WriteOnly)) { qDebug() << "Failed to extract IPAK file."; } qDebug() << " - File Name: " << outputFile.fileName(); outputFile.write(sectionData); outputFile.close(); stream.device()->seek(entryPos); } } stream.device()->seek(sectionPos); qDebug() << stream.device()->pos(); } return 0; // Success } void MainWindow::dragEnterEvent(QDragEnterEvent *event) { const QMimeData *mimeData = event->mimeData(); bool goodDrag = true; if (mimeData->hasUrls()) { foreach (const QUrl url, mimeData->urls()) { if (!url.toString().contains(".ff") && !url.toString().contains(".zone") && !url.toString().contains(".ipak") && !url.toString().contains(".d3dbsp") && !url.toString().contains(".lzoin") && !url.toString().contains(".xsub") && !url.toString().contains(".iwi") && !url.toString().contains(".dds")) { goodDrag = false; } } } else { goodDrag = false; } if (goodDrag) { event->acceptProposedAction(); } } void MainWindow::dragMoveEvent(QDragMoveEvent *event) { Q_UNUSED(event); } void MainWindow::dragLeaveEvent(QDragLeaveEvent *event) { Q_UNUSED(event); } void MainWindow::dropEvent(QDropEvent *event) { const QMimeData *mimeData = event->mimeData(); if (mimeData->hasUrls()) { QStringList ddsPaths = QStringList(); foreach (const QUrl url, mimeData->urls()) { const QString urlStr = url.toLocalFile(); if (urlStr.contains(".zone")) { qDebug() << "OpenZoneFile Returned: " << OpenZoneFile(urlStr); } else if (urlStr.contains(".ff")) { qDebug() << "OpenFastFile Returned: " << OpenFastFile(urlStr); } else if (urlStr.contains(".ipak")) { qDebug() << "LoadFile_IPAK Returned: " << LoadFile_IPAK(urlStr); } else if (urlStr.contains(".xsub")) { qDebug() << "LoadFile_XSUB Returned: " << LoadFile_XSUB(urlStr); } else if (urlStr.contains(".iwi")) { qDebug() << "LoadFile_IWI Returned: " << LoadFile_IWI(urlStr); } else if (urlStr.contains(".dds")) { if (mimeData->urls().size() == 1) { qDebug() << "LoadFile_DDS Returned: " << LoadFile_DDS(urlStr); } else { ddsPaths << urlStr; } } else if (urlStr.contains(".d3dbsp")) { LoadFile_D3DBSP(urlStr); } else if (urlStr.contains(".lzoin")) { QFile lzoFile(urlStr); if (!lzoFile.open(QIODevice::ReadOnly)) { qDebug() << "LZO: Failed to read file!"; continue; } QByteArray data;// = Compression::DecompressLZO(lzoFile.readAll()); lzoFile.close(); if (data.isEmpty()) { qDebug() << "LZO: Decompression gave empty result!"; continue; } QFile outputFile(url.toLocalFile().replace("lzoin", "lzoout")); if (!outputFile.open(QIODevice::WriteOnly)) { qDebug() << "LZO: Failed to write file!"; continue; } outputFile.write(data); outputFile.close(); } else { const QString ext = urlStr.split('.').last(); ui->statusBar->showMessage( QString("Can't display dropped file! .%1").arg(ext)); } } if (ddsPaths.size() > 1) { qDebug() << "LoadFile_DDSFiles Returned: " << LoadFile_DDSFiles(ddsPaths); } } else { ui->statusBar->showMessage("Can't display dropped data!"); } }