XPlor/tests/360/autotest_cod12_360.cpp
2025-09-10 21:58:26 -04:00

268 lines
8.7 KiB
C++

#include <QtTest/QtTest>
#include <QDirIterator>
#include <QFileInfo>
#include "autotest_cod.h"
#include "compression.h"
class AutoTest_COD12_360 : public AutoTest_COD {
Q_OBJECT
const QString EXPORT_DIR = "./exports/cod12/360";
private slots:
void initTestCase();
void testDecompression_data();
void testDecompression();
void testCompression_data();
void testCompression();
void testFactory_data();
void testFactory();
void cleanupTestCase();
};
void AutoTest_COD12_360::initTestCase() {
// Ensure the exports directory exists.
createDirectory(EXPORT_DIR);
}
void AutoTest_COD12_360::testDecompression_data() {
AutoTest_COD::testDecompression_data();
}
void AutoTest_COD12_360::testDecompression() {
QFETCH(QString, fastFilePath);
const QString testName = "Decompress: " + fastFilePath;
// Open the original .ff file.
QFile testFastFile(fastFilePath);
bool fastFileOpened = testFastFile.open(QIODevice::ReadOnly);
if (!fastFileOpened) {
recordResult(testName, false);
}
QVERIFY2(fastFileOpened,
qPrintable("Failed to open test fastfile: " + fastFilePath));
const QByteArray testFFData = testFastFile.readAll();
testFastFile.close();
// Assume the first 12 bytes are a header;
XDataStream fastFileStream(testFFData);
QByteArray magic(8, Qt::Uninitialized);
fastFileStream.readRawData(magic.data(), 8);
bool properMagic = magic == "TAff0000";
if (!properMagic) {
recordResult(testName, false);
}
QVERIFY2(properMagic,
qPrintable("Invalid fastfile magic: " + magic));
quint32 version;
fastFileStream >> version;
bool properVersion = version == 606;
if (!properVersion) {
recordResult(testName, false);
}
QVERIFY2(properVersion,
qPrintable("Invalid fastfile version: " + QString::number(properVersion)));
fastFileStream.skipRawData(1);
quint8 compressionFlag, platformFlag, encryptionFlag;
fastFileStream >> compressionFlag >> platformFlag >> encryptionFlag;
bool properCompression = compressionFlag == 1;
if (!properCompression)
{
recordResult(testName, false);
}
QVERIFY2(properCompression,
qPrintable("Invalid Fast File Compression: " + QString::number(properVersion) + " Only LZX Fast Files are supported."));
bool properPlatform = platformFlag == 4;
if (!properPlatform)
{
recordResult(testName, false);
}
QVERIFY2(properPlatform,
qPrintable("Invalid Fast File Platform: " + QString::number(properVersion) + " Only 360 Fast Files are supported."));
bool properEncryption = encryptionFlag == 0;
if (!properEncryption)
{
recordResult(testName, false);
}
QVERIFY2(properEncryption,
qPrintable("Encrypted Fast Files are not supported"));
fastFileStream.device()->seek(144);
quint64 size;
fastFileStream >> size;
fastFileStream.device()->seek(584);
QByteArray testZoneData;
quint64 consumed = 0;
while (consumed < size) {
/* block header ------------------------------------------------ */
quint32 compressedSize, decompressedSize, blockSize, blockPosition;
fastFileStream >> compressedSize // DWORD 0
>> decompressedSize // DWORD 1
>> blockSize // copy of compressedSize
>> blockPosition; // DWORD 3
if (blockPosition != fastFileStream.device()->pos() - 16) {
qWarning("Block position mismatch"); break;
}
/* padding block ? --------------------------------------------- */
if (decompressedSize == 0) {
fastFileStream.skipRawData(
((fastFileStream.device()->pos() + 0x7FFFFF) & ~0x7FFFFF) -
fastFileStream.device()->pos());
continue;
}
/* read LZO slice ---------------------------------------------- */
fastFileStream.device()->seek(blockPosition + 16);
qDebug() << "Reading block at pos" << blockPosition + 16 << ", compressed:" << compressedSize;
QByteArray compressedData(compressedSize, Qt::Uninitialized);
fastFileStream.readRawData(compressedData.data(), compressedSize);
qDebug() << "Compressed data:" << compressedData.toHex();
if (compressedData.at(0) == 'c') {
return;
}
QByteArray decompressed = Compression::DecompressXMem(compressedData);
if (decompressed.isEmpty()) {
qWarning() << "Empty decompression output, skipping";
continue;
}
if (!decompressed.left(4).contains('\0')) {
qDebug() << "Block starts with" << decompressed.left(16).toHex();
}
testZoneData.append(decompressed);
consumed += decompressed.size();
/* advance to next header (blocks are file-aligned) ------------- */
fastFileStream.device()->seek(blockPosition + 16 + blockSize);
}
// Verify the decompressed data via its embedded zone size.
XDataStream zoneStream(testZoneData);
zoneStream.setByteOrder(XDataStream::LittleEndian);
quint32 zoneSize;
zoneStream >> zoneSize;
bool sizeMatches = zoneSize + 44 == testZoneData.size();
if (!sizeMatches) {
recordResult(testName, false);
}
//QVERIFY2(sizeMatches,
// qPrintable("Decompression validation failed for: " + fastFilePath));
// Write the decompressed zone data to the exports folder with a .zone extension.
QFileInfo fi(fastFilePath);
QString outputFileName = fi.completeBaseName() + ".zone";
QString outputFilePath = QDir(EXPORT_DIR).filePath(outputFileName);
QFile outputFile(outputFilePath);
bool zoneFileOpened = outputFile.open(QIODevice::WriteOnly);
if (!zoneFileOpened) {
recordResult(testName, false);
}
QVERIFY2(zoneFileOpened,
qPrintable("Failed to open output zone file for writing: " + outputFilePath));
outputFile.write(testZoneData);
outputFile.close();
}
void AutoTest_COD12_360::testCompression_data() {
AutoTest_COD::testCompression_data();
}
void AutoTest_COD12_360::testCompression() {
QFETCH(QString, zoneFilePath);
QFile zoneFile(zoneFilePath);
QVERIFY2(zoneFile.open(QIODevice::ReadOnly), qPrintable("Failed to open zone file: " + zoneFilePath));
QByteArray decompressedData = zoneFile.readAll();
zoneFile.close();
QFileInfo fi(zoneFilePath);
QString originalFFPath = QDir(getFastFileDirectory()).filePath(fi.completeBaseName() + ".ff");
QFile originalFile(originalFFPath);
QVERIFY2(originalFile.open(QIODevice::ReadOnly), qPrintable("Failed to open original .ff file: " + originalFFPath));
QByteArray originalFFData = originalFile.readAll();
originalFile.close();
QByteArray header = originalFFData.left(12);
QByteArray newCompressedData;// = Compressor::CompressZLIB(decompressedData, Z_BEST_COMPRESSION);
newCompressedData = Compression::CompressZLIBWithSettings(decompressedData, Z_BEST_COMPRESSION, MAX_WBITS, 8, Z_DEFAULT_STRATEGY, {});
int remainder = (newCompressedData.size() + 12) % 32;
if (remainder != 0) {
int paddingNeeded = 32 - remainder;
newCompressedData.append(QByteArray(paddingNeeded, '\0'));
}
QByteArray recompressedData = header + newCompressedData;
QString recompressedFilePath = QDir(EXPORT_DIR).filePath(fi.completeBaseName() + ".ff");
QFile recompressedFile(recompressedFilePath);
QVERIFY2(recompressedFile.open(QIODevice::WriteOnly), qPrintable("Failed to write recompressed file."));
recompressedFile.write(recompressedData);
recompressedFile.close();
QCOMPARE(recompressedData, originalFFData);
}
void AutoTest_COD12_360::testFactory_data() {
AutoTest_COD::testFactory_data();
}
void AutoTest_COD12_360::testFactory() {
QFETCH(QString, fastFilePath);
return;
const QString testName = "Factory ingest: " + fastFilePath;
FastFile* fastFile = FastFileFactory::Create(fastFilePath);
const QString game = fastFile->GetGame();
bool correctGame = game == "COD12";
if (!correctGame) {
recordResult(testName, false);
}
QVERIFY2(correctGame
, qPrintable("Factory parsed wrong game [" + game + "] for fastfile: " + fastFilePath));
const QString platform = fastFile->GetPlatform();
bool correctPlatform = platform == "360";
if (!correctPlatform) {
recordResult(testName, false);
}
QVERIFY2(correctPlatform
, qPrintable("Factory parsed wrong platform [" + platform + "] for fastfile: " + fastFilePath));
recordResult(testName, true);
}
void AutoTest_COD12_360::cleanupTestCase() {
// Any cleanup if necessary.
}
// Don't generate a main() function
#include "autotest_cod12_360.moc"