970 lines
36 KiB
C++
970 lines
36 KiB
C++
#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 <qmath.h>
|
|
|
|
MainWindow::MainWindow(QWidget *parent)
|
|
: QMainWindow(parent), ui(new Ui::MainWindow) {
|
|
ui->setupUi(this);
|
|
|
|
setAcceptDrops(true);
|
|
|
|
mTypeMap = QMap<QString, int>();
|
|
mTypeOrder = QStringList();
|
|
mRawFileMap = QMap<QString, QString>();
|
|
//mImageMap = QMap<QString, Image>();
|
|
mTreeMap = QMap<QString, QTreeWidgetItem *>();
|
|
mStrTableMap = QMap<QString, QVector<QPair<QString, QString>>>();
|
|
mBSPVersion = 0;
|
|
mDiskLumpCount = 0;
|
|
mDiskLumpOrder = QVector<quint32>();
|
|
mLumps = QMap<quint32, Lump>();
|
|
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](std::shared_ptr<RawFile> 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(Utils::ASSET_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(Utils::ASSET_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(Utils::ASSET_RAWFILE));
|
|
ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1);
|
|
});
|
|
|
|
// connect(mTreeWidget, &XTreeWidget::ImageSelected, this, [this](std::shared_ptr<Image> 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(Utils::ASSET_IMAGE));
|
|
// ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1);
|
|
// });
|
|
|
|
// connect(mTreeWidget, &XTreeWidget::MenuSelected, this, [](std::shared_ptr<Menu> menu, const QString aParentName) {
|
|
// Q_UNUSED(menu);
|
|
// });
|
|
|
|
connect(mTreeWidget, &XTreeWidget::MaterialSelected, this, [this](std::shared_ptr<Material> 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(Utils::ASSET_MATERIAL));
|
|
ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1);
|
|
});
|
|
|
|
connect(mTreeWidget, &XTreeWidget::DDSFileSelected, this, [this](std::shared_ptr<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(Utils::ASSET_IMAGE));
|
|
ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1);
|
|
});
|
|
|
|
connect(mTreeWidget, &XTreeWidget::IWIFileSelected, this, [this](std::shared_ptr<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(Utils::ASSET_IMAGE));
|
|
ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1);
|
|
});
|
|
|
|
connect(mTreeWidget, &XTreeWidget::FastFileSelected, this, [this](std::shared_ptr<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(Utils::ASSET_FAST_FILE));
|
|
ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1);
|
|
});
|
|
|
|
connect(mTreeWidget, &XTreeWidget::ZoneFileSelected, this, [this](std::shared_ptr<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(Utils::ASSET_ZONE_FILE));
|
|
ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1);
|
|
});
|
|
|
|
connect(mTreeWidget, &XTreeWidget::LocalStringSelected, this, [this](std::shared_ptr<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(Utils::ASSET_LOCALIZE_ENTRY));
|
|
ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1);
|
|
});
|
|
|
|
connect(mTreeWidget, &XTreeWidget::TechSetSelected, this, [this](std::shared_ptr<MaterialTechSet> 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->name;
|
|
for (int i = 0; i < ui->tabWidget->count(); i++) {
|
|
if (ui->tabWidget->tabText(i) == fileStem) {
|
|
delete techSetViewer;
|
|
return;
|
|
}
|
|
}
|
|
|
|
ui->tabWidget->addTab(techSetViewer, aTechSet->name);
|
|
ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(Utils::ASSET_TECHNIQUE_SET));
|
|
ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1);
|
|
});
|
|
|
|
connect(mTreeWidget, &XTreeWidget::StrTableSelected, this, [this](std::shared_ptr<StringTable> 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->name;
|
|
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(Utils::ASSET_STRINGTABLE));
|
|
ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1);
|
|
});
|
|
|
|
// connect(mTreeWidget, &XTreeWidget::SoundSelected, this, [this](std::shared_ptr<Sound> 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(Utils::ASSET_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;
|
|
}
|
|
|
|
std::shared_ptr<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;
|
|
//if (!zoneFile.Load(aZoneFilePath)) {
|
|
// qDebug() << "Error: Failed to load zone file!";
|
|
// return false;
|
|
//}
|
|
//mTreeWidget->AddZoneFile(std::make_shared<ZoneFile>(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(std::make_shared<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(std::make_shared<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(std::make_shared<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<IPAKDataChunkMetaData> metas = QVector<IPAKDataChunkMetaData>();
|
|
|
|
QVector<IPAKIndexEntry> entries = QVector<IPAKIndexEntry>();
|
|
QVector<IPAKSection> sections = QVector<IPAKSection>(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!");
|
|
}
|
|
}
|