From 13af32ed2d82f6032e031de82c621741e1b93599 Mon Sep 17 00:00:00 2001 From: njohnson Date: Mon, 15 Sep 2025 18:51:04 -0400 Subject: [PATCH] Refactor: Improve fastfile decoding. --- libs/fastfile/360/fastfile_cod10_360.cpp | 9 +- libs/fastfile/360/fastfile_cod11_360.cpp | 63 +++++++-- libs/fastfile/360/fastfile_cod12_360.cpp | 65 ++++++++-- libs/fastfile/360/fastfile_cod6_360.cpp | 51 +++++--- libs/fastfile/360/fastfile_cod7_360.cpp | 9 +- libs/fastfile/360/fastfile_cod7_5_360.cpp | 148 ++++++++++++++++++++++ libs/fastfile/360/fastfile_cod7_5_360.h | 20 +++ libs/fastfile/360/fastfile_cod8_360.cpp | 9 +- libs/fastfile/360/fastfile_cod9_360.cpp | 5 +- libs/fastfile/PS3/fastfile_cod10_ps3.cpp | 10 +- libs/fastfile/PS3/fastfile_cod11_ps3.cpp | 104 ++++++++------- libs/fastfile/PS3/fastfile_cod12_ps3.cpp | 10 +- libs/fastfile/PS3/fastfile_cod4_ps3.cpp | 23 ++-- libs/fastfile/PS3/fastfile_cod5_ps3.cpp | 23 ++-- libs/fastfile/PS3/fastfile_cod6_ps3.cpp | 38 +++--- libs/fastfile/PS3/fastfile_cod7_ps3.cpp | 120 ++++++++---------- libs/fastfile/PS3/fastfile_cod8_ps3.cpp | 45 ------- libs/fastfile/PS3/fastfile_cod9_ps3.cpp | 2 +- 18 files changed, 470 insertions(+), 284 deletions(-) create mode 100644 libs/fastfile/360/fastfile_cod7_5_360.cpp create mode 100644 libs/fastfile/360/fastfile_cod7_5_360.h diff --git a/libs/fastfile/360/fastfile_cod10_360.cpp b/libs/fastfile/360/fastfile_cod10_360.cpp index db68998..ef47f9a 100644 --- a/libs/fastfile/360/fastfile_cod10_360.cpp +++ b/libs/fastfile/360/fastfile_cod10_360.cpp @@ -73,12 +73,6 @@ bool FastFile_COD10_360::Load(const QByteArray aData) { XDataStream fastFileStream(aData); fastFileStream.setByteOrder(XDataStream::LittleEndian); - // For COD7/COD9, use BigEndian. - fastFileStream.setByteOrder(XDataStream::BigEndian); - - // Select key based on game. - QByteArray key = QByteArray::fromHex("0E50F49F412317096038665622DD091332A209BA0A05A00E1377CEDB0A3CB1D3"); - // Read the 8-byte magic. QByteArray fileMagic(8, Qt::Uninitialized); fastFileStream.readRawData(fileMagic.data(), 8); @@ -96,7 +90,7 @@ bool FastFile_COD10_360::Load(const QByteArray aData) { QByteArray rsaSignature(256, Qt::Uninitialized); fastFileStream.readRawData(rsaSignature.data(), 256); - decompressedData = Encryption::decryptFastFile_BO2(aData); + decompressedData = Encryption::DecryptFile(aData, fileName, "0E50F49F412317096038665622DD091332A209BA0A05A00E1377CEDB0A3CB1D3"); // For COD9, write out the complete decompressed zone for testing. QFile testFile("exports/" + GetBaseStem() + ".zone"); @@ -104,6 +98,7 @@ bool FastFile_COD10_360::Load(const QByteArray aData) { testFile.write(decompressedData); testFile.close(); } + Utils::ExportData(GetBaseStem() + ".zone", decompressedData); // Load the zone file with the decompressed data (using an Xbox platform flag). ZoneFile_COD10_360* zoneFile = new ZoneFile_COD10_360(); diff --git a/libs/fastfile/360/fastfile_cod11_360.cpp b/libs/fastfile/360/fastfile_cod11_360.cpp index bf384e8..4ba4dd7 100644 --- a/libs/fastfile/360/fastfile_cod11_360.cpp +++ b/libs/fastfile/360/fastfile_cod11_360.cpp @@ -68,29 +68,74 @@ bool FastFile_COD11_360::Load(const QString aFilePath) { return true; } +enum DB_CompressorType : qint32 +{ + DB_COMPRESSOR_INVALID = 0x0, + DB_COMPRESSOR_ZLIB = 0x1, + DB_COMPRESSOR_LZX = 0x2, + DB_COMPRESSOR_PASSTHROUGH = 0x3, +}; + bool FastFile_COD11_360::Load(const QByteArray aData) { QByteArray decompressedData; // Prepare data stream for parsing XDataStream fastFileStream(aData); - fastFileStream.setByteOrder(XDataStream::LittleEndian); + fastFileStream.setByteOrder(XDataStream::BigEndian); // Verify magic header QByteArray fileMagic(8, Qt::Uninitialized); fastFileStream.readRawData(fileMagic.data(), 8); - if (fileMagic != "TAff0000") { - qWarning() << "Invalid fast file magic for COD12!"; + quint32 version = fastFileStream.ParseUInt32(); + + fastFileStream.skipRawData(1); + + DB_CompressorType compressorType = (DB_CompressorType)fastFileStream.ParseInt8(); + + fastFileStream.skipRawData(10); + + qint32 blockCount = fastFileStream.ParseInt32(); + + if (version != 1838) + { + qWarning() << "Invalid fast file version:" << version << "!"; return false; } - // Skip: File size (4 bytes), flags/version (4 bytes), unknown (8 bytes), build tag (32 bytes), RSA signature (256 bytes) - fastFileStream.skipRawData(4 + 4 + 8 + 32 + 256); // total 304 bytes skipped so far + 8 bytes magic = 312 bytes at correct position. + if (blockCount > 17280) + { + qWarning() << "Fast file has too many blocks:" << blockCount << "> 17280!"; + return false; + } + fastFileStream.skipRawData(12 * blockCount); - // Correctly positioned at 0x138 - QByteArray encryptedData = aData.mid(0x138); - decompressedData = Encryption::decryptFastFile_BO3(encryptedData); + qint32 startPos = fastFileStream.ParseInt32(); + Q_UNUSED(startPos); - // Output for verification/testing + qint32 endPos = fastFileStream.ParseInt32(); + Q_UNUSED(endPos); + + if (fileMagic == "S1ffu100") + { + QByteArray compressedData = aData.mid(fastFileStream.device()->pos()); + if (compressorType == DB_COMPRESSOR_ZLIB) + { + decompressedData = Compression::DecompressZLIB(compressedData); + } + else if (compressorType == DB_COMPRESSOR_LZX) + { + decompressedData = Compression::DecompressXMem(compressedData, 0, 0x80000, 0); + } + } + else if (fileMagic == "S1ff0100") + { + + } + else + { + qWarning() << "Invalid fast file magic:" << fileMagic << "!"; + return false; + } Utils::ExportData(GetBaseStem() + ".zone", decompressedData); // Load the zone file with decompressed data diff --git a/libs/fastfile/360/fastfile_cod12_360.cpp b/libs/fastfile/360/fastfile_cod12_360.cpp index a9215a1..a8804d6 100644 --- a/libs/fastfile/360/fastfile_cod12_360.cpp +++ b/libs/fastfile/360/fastfile_cod12_360.cpp @@ -75,21 +75,66 @@ bool FastFile_COD12_360::Load(const QByteArray aData) { XDataStream fastFileStream(aData); fastFileStream.setByteOrder(XDataStream::LittleEndian); - // Verify magic header - QByteArray fileMagic(8, Qt::Uninitialized); - fastFileStream.readRawData(fileMagic.data(), 8); - if (fileMagic != "TAff0000") { - qWarning() << "Invalid fast file magic for COD12!"; + // Skip header magic + fastFileStream.skipRawData(8); + + quint32 version; + fastFileStream >> version; + + quint8 unknownFlag, compressionFlag, platformFlag, encryptionFlag; + fastFileStream >> unknownFlag >> compressionFlag >> platformFlag >> encryptionFlag; + + if (compressionFlag != 1) { + qDebug() << "Invalid fastfile compression: " << compressionFlag; + return false; + } else if (platformFlag != 4) { + qDebug() << "Invalid platform: " << platformFlag; + return false; + } else if (encryptionFlag != 0) { + qDebug() << "Decryption not supported yet!"; return false; } - // Skip: File size (4 bytes), flags/version (4 bytes), unknown (8 bytes), build tag (32 bytes), RSA signature (256 bytes) - fastFileStream.skipRawData(4 + 4 + 8 + 32 + 256); // total 304 bytes skipped so far + 8 bytes magic = 312 bytes at correct position. + fastFileStream.skipRawData(128); - // Correctly positioned at 0x138 - QByteArray encryptedData = aData.mid(0x138); - decompressedData = Encryption::decryptFastFile_BO3(encryptedData); + quint64 size; + fastFileStream >> size; + fastFileStream.skipRawData(432); + + int consumed = 0; + while(consumed < size) + { + // Read Block Header + quint32 compressedSize, decompressedSize, blockSize, blockPosition; + fastFileStream >> compressedSize >> decompressedSize >> blockSize >> blockPosition; + + // Validate the block position, it should match + if(blockPosition != fastFileStream.device()->pos() - 16) + { + qDebug() << "Block Position does not match Stream Position."; + return false; + } + + // Check for padding blocks + if(decompressedSize == 0) + { + fastFileStream.device()->read((((fastFileStream.device()->pos()) + ((0x800000) - 1)) & ~((0x800000) - 1)) - fastFileStream.device()->pos()); + continue; + } + + fastFileStream.device()->read(2); + + QByteArray compressedData(compressedSize - 2, Qt::Uninitialized); + qDebug() << "Data position: " << fastFileStream.device()->pos() << " - Size: " << compressedSize; + fastFileStream.readRawData(compressedData.data(), compressedSize - 2); + decompressedData.append(Compression::DecompressDeflate(compressedData)); + + consumed += decompressedSize; + + // Sinze Fast Files are aligns, we must skip the full block + fastFileStream.device()->seek(blockPosition + 16 + blockSize); + } // Output for verification/testing Utils::ExportData(GetBaseStem() + ".zone", decompressedData); diff --git a/libs/fastfile/360/fastfile_cod6_360.cpp b/libs/fastfile/360/fastfile_cod6_360.cpp index e478731..b0990f5 100644 --- a/libs/fastfile/360/fastfile_cod6_360.cpp +++ b/libs/fastfile/360/fastfile_cod6_360.cpp @@ -70,30 +70,47 @@ bool FastFile_COD6_360::Load(const QString aFilePath) { } bool FastFile_COD6_360::Load(const QByteArray aData) { - const qint64 zlibOffset = Compression::FindZlibOffset(aData); - if (zlibOffset == -1) - { - qWarning() << "Z-Lib stream not found"; - return false; - } - QByteArray compressed = aData.mid(zlibOffset); + XDataStream fastFileStream(aData); + fastFileStream.setByteOrder(XDataStream::BigEndian); - QByteArray decompressedData = Compression::DecompressZLIB(compressed); + QByteArray magic(8, Qt::Uninitialized); + fastFileStream.readRawData(magic.data(), 8); - if (decompressedData.isEmpty() || decompressedData.size() < 1024) - { - QByteArray stripped = Compression::StripHashBlocks(compressed); - QByteArray retry = Compression::DecompressZLIB(stripped); - if (!retry.isEmpty()) - decompressedData.swap(retry); - } + quint32 version = fastFileStream.ParseUInt32(); - if (decompressedData.isEmpty()) + if (version != 269) { - qWarning() << "Unable to decompress fast-file"; + qDebug() << QString("Invalid version: %1!").arg(version); return false; } + bool localPatch = fastFileStream.ParseBool(); + Q_UNUSED(localPatch); + + quint8 compressor = fastFileStream.ParseUInt8(); + Q_UNUSED(compressor); + + // Skip fastfile date/time + fastFileStream.skipRawData(11); + + quint32 hashCount = fastFileStream.ParseUInt32(); + fastFileStream.skipRawData(12 * hashCount); + + fastFileStream.skipRawData(8); + + QByteArray decompressedData; + if (magic == "IWff0100") + { + + } + else if (magic == "IWffu100") + { + quint32 zlibSize = aData.size() - fastFileStream.device()->pos(); + QByteArray zlibData(zlibSize, Qt::Uninitialized); + fastFileStream.readRawData(zlibData.data(), zlibSize); + + decompressedData = Compression::DecompressZLIB(zlibData); + } Utils::ExportData(GetBaseStem() + ".zone", decompressedData); ZoneFile_COD6_360* zoneFile = new ZoneFile_COD6_360(); diff --git a/libs/fastfile/360/fastfile_cod7_360.cpp b/libs/fastfile/360/fastfile_cod7_360.cpp index 5194514..c5ae73f 100644 --- a/libs/fastfile/360/fastfile_cod7_360.cpp +++ b/libs/fastfile/360/fastfile_cod7_360.cpp @@ -73,14 +73,9 @@ bool FastFile_COD7_360::Load(const QByteArray aData) { // Create a XDataStream on the input data. XDataStream fastFileStream(aData); - fastFileStream.skipRawData(12); - - // For COD7/COD9, use BigEndian. fastFileStream.setByteOrder(XDataStream::BigEndian); - // Select key based on game. - QByteArray key = QByteArray::fromHex("1ac1d12d527c59b40eca619120ff8217ccff09cd16896f81b829c7f52793405d"); - fastFileStream.skipRawData(4); + fastFileStream.skipRawData(16); // Read the 8-byte magic. QByteArray fileMagic(8, Qt::Uninitialized); @@ -102,6 +97,8 @@ bool FastFile_COD7_360::Load(const QByteArray aData) { QByteArray rsaSignature(256, Qt::Uninitialized); fastFileStream.readRawData(rsaSignature.data(), 256); + QByteArray key = QByteArray::fromHex("1ac1d12d527c59b40eca619120ff8217ccff09cd16896f81b829c7f52793405d"); + // Now the stream should be positioned at 0x13C, where sections begin. int sectionIndex = 0; while (true) { diff --git a/libs/fastfile/360/fastfile_cod7_5_360.cpp b/libs/fastfile/360/fastfile_cod7_5_360.cpp new file mode 100644 index 0000000..efc12af --- /dev/null +++ b/libs/fastfile/360/fastfile_cod7_5_360.cpp @@ -0,0 +1,148 @@ +#include "fastfile_cod7_5_360.h" +#include "zonefile_cod7_360.h" + +#include "compression.h" +#include "encryption.h" + +#include +#include + +FastFile_COD7_5_360::FastFile_COD7_5_360() + : FastFile() { + SetCompany(COMPANY_INFINITY_WARD); + SetType(FILETYPE_FAST_FILE); + SetSignage(SIGNAGE_UNSIGNED); + SetMagic(0); + SetVersion(0); + SetPlatform("360"); + SetGame("COD7.5"); +} + +FastFile_COD7_5_360::FastFile_COD7_5_360(const QByteArray& aData) + : FastFile_COD7_5_360() { + + if (!aData.isEmpty()) { + Load(aData); + } +} + +FastFile_COD7_5_360::FastFile_COD7_5_360(const QString aFilePath) + : FastFile_COD7_5_360() { + + if (!aFilePath.isEmpty()) { + Load(aFilePath); + } +} + +FastFile_COD7_5_360::~FastFile_COD7_5_360() { + +} + +QByteArray FastFile_COD7_5_360::GetBinaryData() const { + return QByteArray(); +} + +bool FastFile_COD7_5_360::Load(const QString aFilePath) { + if (aFilePath.isEmpty()) { + return false; + } + + // Check fastfile can be read + QFile *file = new QFile(aFilePath); + if (!file->open(QIODevice::ReadOnly)) { + qDebug() << QString("Error: Failed to open FastFile: %1!").arg(aFilePath); + return false; + } + + // Decompress fastfile and close + const QString fastFileStem = aFilePath.section("/", -1, -1); + SetStem(fastFileStem); + if (!Load(file->readAll())) { + qDebug() << "Error: Failed to load fastfile: " << fastFileStem; + return false; + } + + file->close(); + + // Open zone file after decompressing ff and writing + return true; +} + +enum NX_Language : qint32 +{ + LANGUAGE_ENGLISH = 0x0, + LANGUAGE_FRENCH = 0x1, + LANGUAGE_GERMAN = 0x2, + LANGUAGE_ITALIAN = 0x3, + LANGUAGE_SPANISH = 0x4, + LANGUAGE_BRITISH = 0x5, + LANGUAGE_RUSSIAN = 0x6, + LANGUAGE_POLISH = 0x7, + LANGUAGE_KOREAN = 0x8, + LANGUAGE_TAIWANESE = 0x9, + LANGUAGE_JAPANESE = 0xA, + LANGUAGE_CHINESE = 0xB, + LANGUAGE_THAI = 0xC, + LANGUAGE_LEET = 0xD, + LANGUAGE_CZECH = 0xE, + MAX_LANGUAGES = 0xF, +}; + +bool FastFile_COD7_5_360::Load(const QByteArray aData) { + // Create a XDataStream on the input data. + XDataStream fastFileStream(aData); + fastFileStream.setByteOrder(XDataStream::BigEndian); + + QByteArray magic(8, Qt::Uninitialized); + fastFileStream.readRawData(magic.data(), 8); + + quint32 version = fastFileStream.ParseUInt32(); + + if (version != 357) + { + qDebug() << QString("Invalid version: %1!").arg(version); + return false; + } + + bool localPatch = fastFileStream.ParseBool(); + Q_UNUSED(localPatch); + + quint8 compressor = fastFileStream.ParseUInt8(); + Q_UNUSED(compressor); + + // Skip fastfile date/time + fastFileStream.skipRawData(8); + + NX_Language language = (NX_Language)fastFileStream.ParseInt32(); + Q_UNUSED(language); + + quint32 hashCount = fastFileStream.ParseUInt32(); + fastFileStream.skipRawData(12 * hashCount); + + fastFileStream.skipRawData(8); + + QByteArray decompressedData; + if (magic == "NXff0100") + { + + } + else if (magic == "NXffu100") + { + quint32 zlibSize = aData.size() - fastFileStream.device()->pos(); + QByteArray zlibData(zlibSize, Qt::Uninitialized); + fastFileStream.readRawData(zlibData.data(), zlibSize); + + decompressedData = Compression::DecompressZLIB(zlibData); + } + Utils::ExportData(GetBaseStem() + ".zone", decompressedData); + + ZoneFile_COD7_360* zoneFile = new ZoneFile_COD7_360(); + zoneFile->SetStem(GetBaseStem() + ".zone"); + if (!zoneFile->Load(decompressedData)) { + qWarning() << "Failed to load ZoneFile!"; + return false; + } + SetZoneFile(zoneFile); + + return true; +} diff --git a/libs/fastfile/360/fastfile_cod7_5_360.h b/libs/fastfile/360/fastfile_cod7_5_360.h new file mode 100644 index 0000000..ecb8d32 --- /dev/null +++ b/libs/fastfile/360/fastfile_cod7_5_360.h @@ -0,0 +1,20 @@ +#ifndef FASTFILE_COD7_5_360_H +#define FASTFILE_COD7_5_360_H + +#include "fastfile.h" + +class FastFile_COD7_5_360 : public FastFile +{ +public: + FastFile_COD7_5_360(); + FastFile_COD7_5_360(const QByteArray& aData); + FastFile_COD7_5_360(const QString aFilePath); + ~FastFile_COD7_5_360(); + + QByteArray GetBinaryData() const override; + + bool Load(const QString aFilePath) override; + bool Load(const QByteArray aData) override; +}; + +#endif // FASTFILE_COD7_5_360_H diff --git a/libs/fastfile/360/fastfile_cod8_360.cpp b/libs/fastfile/360/fastfile_cod8_360.cpp index 815c146..7f8be32 100644 --- a/libs/fastfile/360/fastfile_cod8_360.cpp +++ b/libs/fastfile/360/fastfile_cod8_360.cpp @@ -76,9 +76,6 @@ bool FastFile_COD8_360::Load(const QByteArray aData) { // For COD7/COD9, use BigEndian. fastFileStream.setByteOrder(XDataStream::BigEndian); - // Select key based on game. - QByteArray key = QByteArray::fromHex("0E50F49F412317096038665622DD091332A209BA0A05A00E1377CEDB0A3CB1D3"); - // Read the 8-byte magic. QByteArray fileMagic(8, Qt::Uninitialized); fastFileStream.readRawData(fileMagic.data(), 8); @@ -92,11 +89,7 @@ bool FastFile_COD8_360::Load(const QByteArray aData) { QByteArray fileName(32, Qt::Uninitialized); fastFileStream.readRawData(fileName.data(), 32); - // Skip the RSA signature (256 bytes). - QByteArray rsaSignature(256, Qt::Uninitialized); - fastFileStream.readRawData(rsaSignature.data(), 256); - - decompressedData = Encryption::decryptFastFile_BO2(aData); + decompressedData = Encryption::DecryptFile(aData, fileName, "0E50F49F412317096038665622DD091332A209BA0A05A00E1377CEDB0A3CB1D3"); // For COD9, write out the complete decompressed zone for testing. QFile testFile("exports/" + GetBaseStem() + ".zone"); diff --git a/libs/fastfile/360/fastfile_cod9_360.cpp b/libs/fastfile/360/fastfile_cod9_360.cpp index 0bfecdb..bf2b7b8 100644 --- a/libs/fastfile/360/fastfile_cod9_360.cpp +++ b/libs/fastfile/360/fastfile_cod9_360.cpp @@ -76,9 +76,6 @@ bool FastFile_COD9_360::Load(const QByteArray aData) { // For COD7/COD9, use BigEndian. fastFileStream.setByteOrder(XDataStream::BigEndian); - // Select key based on game. - QByteArray key = QByteArray::fromHex("0E50F49F412317096038665622DD091332A209BA0A05A00E1377CEDB0A3CB1D3"); - // Read the 8-byte magic. QByteArray fileMagic(8, Qt::Uninitialized); fastFileStream.readRawData(fileMagic.data(), 8); @@ -96,7 +93,7 @@ bool FastFile_COD9_360::Load(const QByteArray aData) { QByteArray rsaSignature(256, Qt::Uninitialized); fastFileStream.readRawData(rsaSignature.data(), 256); - decompressedData = Encryption::decryptFastFile_BO2(aData); + decompressedData = Encryption::DecryptFile(aData, fileName, "0E50F49F412317096038665622DD091332A209BA0A05A00E1377CEDB0A3CB1D3"); // For COD9, write out the complete decompressed zone for testing. QFile testFile("exports/" + GetBaseStem() + ".zone"); diff --git a/libs/fastfile/PS3/fastfile_cod10_ps3.cpp b/libs/fastfile/PS3/fastfile_cod10_ps3.cpp index bfea938..753f406 100644 --- a/libs/fastfile/PS3/fastfile_cod10_ps3.cpp +++ b/libs/fastfile/PS3/fastfile_cod10_ps3.cpp @@ -110,15 +110,7 @@ bool FastFile_COD10_PS3::Load(const QByteArray aData) { QByteArray fileName(32, Qt::Uninitialized); fastFileStream.readRawData(fileName.data(), 32); - // Skip the RSA signature (256 bytes). - QByteArray rsaSignature(256, Qt::Uninitialized); - fastFileStream.readRawData(rsaSignature.data(), 256); - - if (GetPlatform() == "360") { - //decompressedData = Compressor::cod9_decryptFastFile(aData); - } else if (GetPlatform() == "PC") { - decompressedData = Encryption::decryptFastFile_BO2(aData); - } + //decompressedData = Encryption::decryptFastFile_BO2(aData); // For COD9, write out the complete decompressed zone for testing. QFile testFile("exports/" + GetBaseStem() + ".zone"); diff --git a/libs/fastfile/PS3/fastfile_cod11_ps3.cpp b/libs/fastfile/PS3/fastfile_cod11_ps3.cpp index b152595..45b94ca 100644 --- a/libs/fastfile/PS3/fastfile_cod11_ps3.cpp +++ b/libs/fastfile/PS3/fastfile_cod11_ps3.cpp @@ -1,6 +1,7 @@ #include "fastfile_cod11_ps3.h" #include "zonefile_cod11_ps3.h" #include "encryption.h" +#include "compression.h" #include #include @@ -66,68 +67,77 @@ bool FastFile_COD11_PS3::Load(const QString aFilePath) { return true; } +enum DB_CompressorType : qint32 +{ + DB_COMPRESSOR_INVALID = 0x0, + DB_COMPRESSOR_ZLIB = 0x1, + DB_COMPRESSOR_LZX = 0x2, + DB_COMPRESSOR_PASSTHROUGH = 0x3, +}; + bool FastFile_COD11_PS3::Load(const QByteArray aData) { QByteArray decompressedData; - // Create a XDataStream on the input data. + // Prepare data stream for parsing XDataStream fastFileStream(aData); - fastFileStream.setByteOrder(XDataStream::LittleEndian); - - // Parse header values. - SetCompany(pParseFFCompany(&fastFileStream)); - SetType(pParseFFFileType(&fastFileStream)); - SetSignage(pParseFFSignage(&fastFileStream)); - SetMagic(pParseFFMagic(&fastFileStream)); - quint32 version = pParseFFVersion(&fastFileStream); - SetVersion(version); - SetPlatform(pCalculateFFPlatform(version)); - SetGame("COD9"); - - // For COD7/COD9, use BigEndian. fastFileStream.setByteOrder(XDataStream::BigEndian); - if (GetPlatform() == "PC") { - fastFileStream.setByteOrder(XDataStream::LittleEndian); - } - // Select key based on game. - QByteArray key; - if (GetPlatform() == "360") { - key = QByteArray::fromHex("0E50F49F412317096038665622DD091332A209BA0A05A00E1377CEDB0A3CB1D3"); - } else if (GetPlatform() == "PC") { - key = QByteArray::fromHex("641D8A2FE31D3AA63622BBC9CE8587229D42B0F8ED9B924130BF88B65EDC50BE"); - } - - // Read the 8-byte magic. + // Verify magic header QByteArray fileMagic(8, Qt::Uninitialized); fastFileStream.readRawData(fileMagic.data(), 8); - if (fileMagic != "PHEEBs71") { - qWarning() << "Invalid fast file magic!"; + quint32 version = fastFileStream.ParseUInt32(); + + fastFileStream.skipRawData(1); + + DB_CompressorType compressorType = (DB_CompressorType)fastFileStream.ParseInt8(); + + fastFileStream.skipRawData(10); + + qint32 blockCount = fastFileStream.ParseInt32(); + + if (version != 1838) + { + qWarning() << "Invalid fast file version:" << version << "!"; return false; } - fastFileStream.skipRawData(4); - // Read IV table name (32 bytes). - QByteArray fileName(32, Qt::Uninitialized); - fastFileStream.readRawData(fileName.data(), 32); - - // Skip the RSA signature (256 bytes). - QByteArray rsaSignature(256, Qt::Uninitialized); - fastFileStream.readRawData(rsaSignature.data(), 256); - - if (GetPlatform() == "360") { - //decompressedData = Compressor::cod9_decryptFastFile(aData); - } else if (GetPlatform() == "PC") { - decompressedData = Encryption::decryptFastFile_BO2(aData); + if (blockCount > 17280) + { + qWarning() << "Fast file has too many blocks:" << blockCount << "> 17280!"; + return false; } + fastFileStream.skipRawData(12 * blockCount); - // For COD9, write out the complete decompressed zone for testing. - QFile testFile("exports/" + GetBaseStem() + ".zone"); - if(testFile.open(QIODevice::WriteOnly)) { - testFile.write(decompressedData); - testFile.close(); + qint32 startPos = fastFileStream.ParseInt32(); + Q_UNUSED(startPos); + + qint32 endPos = fastFileStream.ParseInt32(); + Q_UNUSED(endPos); + + if (fileMagic == "S1ffu100") + { + QByteArray compressedData = aData.mid(fastFileStream.device()->pos()); + if (compressorType == DB_COMPRESSOR_ZLIB) + { + decompressedData = Compression::DecompressZLIB(compressedData); + } + else if (compressorType == DB_COMPRESSOR_LZX) + { + decompressedData = Compression::DecompressXMem(compressedData, 0, 0x80000, 0); + } } + else if (fileMagic == "S1ff0100") + { - // Load the zone file with the decompressed data (using an Xbox platform flag). + } + else + { + qWarning() << "Invalid fast file magic:" << fileMagic << "!"; + return false; + } + Utils::ExportData(GetBaseStem() + ".zone", decompressedData); + + // Load the zone file with decompressed data ZoneFile_COD11_PS3* zoneFile = new ZoneFile_COD11_PS3(); zoneFile->SetStem(GetBaseStem() + ".zone"); if (!zoneFile->Load(decompressedData)) { diff --git a/libs/fastfile/PS3/fastfile_cod12_ps3.cpp b/libs/fastfile/PS3/fastfile_cod12_ps3.cpp index f0aa9f3..ae8f481 100644 --- a/libs/fastfile/PS3/fastfile_cod12_ps3.cpp +++ b/libs/fastfile/PS3/fastfile_cod12_ps3.cpp @@ -110,15 +110,7 @@ bool FastFile_COD12_PS3::Load(const QByteArray aData) { QByteArray fileName(32, Qt::Uninitialized); fastFileStream.readRawData(fileName.data(), 32); - // Skip the RSA signature (256 bytes). - QByteArray rsaSignature(256, Qt::Uninitialized); - fastFileStream.readRawData(rsaSignature.data(), 256); - - if (GetPlatform() == "360") { - //decompressedData = Compressor::cod9_decryptFastFile(aData); - } else if (GetPlatform() == "PC") { - decompressedData = Encryption::decryptFastFile_BO2(aData); - } + decompressedData = Encryption::decryptFastFile_BO3(aData); // For COD9, write out the complete decompressed zone for testing. QFile testFile("exports/" + GetBaseStem() + ".zone"); diff --git a/libs/fastfile/PS3/fastfile_cod4_ps3.cpp b/libs/fastfile/PS3/fastfile_cod4_ps3.cpp index 2d085ff..a8743fb 100644 --- a/libs/fastfile/PS3/fastfile_cod4_ps3.cpp +++ b/libs/fastfile/PS3/fastfile_cod4_ps3.cpp @@ -86,29 +86,24 @@ bool FastFile_COD4_PS3::Load(const QByteArray aData) { SetMagic(pParseFFMagic(&fastFileStream)); SetVersion(pParseFFVersion(&fastFileStream)); - int pos = 12; // Loop until EOF or invalid chunk - while (pos <= aData.size()) { + fastFileStream.setByteOrder(XDataStream::BigEndian); + while (!fastFileStream.atEnd()) { // Read 2-byte BIG-ENDIAN chunk size - quint32 chunkSize; - XDataStream chunkStream(aData.mid(pos, 2)); - chunkStream.setByteOrder(XDataStream::BigEndian); - chunkStream >> chunkSize; + quint16 chunkSize; + fastFileStream >> chunkSize; - pos += 2; - - if (chunkSize == 0 || pos + chunkSize > aData.size()) { + if (chunkSize == 0 || fastFileStream.device()->pos() + chunkSize > aData.size()) { qWarning() << "Invalid or incomplete chunk detected, stopping."; break; } - const QByteArray compressedChunk = aData.mid(pos, chunkSize); + QByteArray compressedChunk(chunkSize, Qt::Uninitialized); + fastFileStream.readRawData(compressedChunk.data(), chunkSize); - decompressedData.append(Compression::DecompressDeflate(compressedChunk)); - - pos += chunkSize; + QByteArray decompressedChunk = Compression::DecompressDeflate(compressedChunk); + decompressedData.append(decompressedChunk); } - Utils::ExportData(GetBaseStem() + ".zone", decompressedData); ZoneFile_COD4_PS3* zoneFile = new ZoneFile_COD4_PS3(); diff --git a/libs/fastfile/PS3/fastfile_cod5_ps3.cpp b/libs/fastfile/PS3/fastfile_cod5_ps3.cpp index 5f610b1..5c98ca9 100644 --- a/libs/fastfile/PS3/fastfile_cod5_ps3.cpp +++ b/libs/fastfile/PS3/fastfile_cod5_ps3.cpp @@ -86,29 +86,24 @@ bool FastFile_COD5_PS3::Load(const QByteArray aData) { SetMagic(pParseFFMagic(&fastFileStream)); SetVersion(pParseFFVersion(&fastFileStream)); - int pos = 12; // Loop until EOF or invalid chunk - while (pos <= aData.size()) { + fastFileStream.setByteOrder(XDataStream::BigEndian); + while (!fastFileStream.atEnd()) { // Read 2-byte BIG-ENDIAN chunk size - quint32 chunkSize; - XDataStream chunkStream(aData.mid(pos, 2)); - chunkStream.setByteOrder(XDataStream::BigEndian); - chunkStream >> chunkSize; + quint16 chunkSize; + fastFileStream >> chunkSize; - pos += 2; - - if (chunkSize == 0 || pos + chunkSize > aData.size()) { + if (chunkSize == 0 || fastFileStream.device()->pos() + chunkSize > aData.size()) { qWarning() << "Invalid or incomplete chunk detected, stopping."; break; } - const QByteArray compressedChunk = aData.mid(pos, chunkSize); + QByteArray compressedChunk(chunkSize, Qt::Uninitialized); + fastFileStream.readRawData(compressedChunk.data(), chunkSize); - decompressedData.append(Compression::DecompressDeflate(compressedChunk)); - - pos += chunkSize; + QByteArray decompressedChunk = Compression::DecompressDeflate(compressedChunk); + decompressedData.append(decompressedChunk); } - Utils::ExportData(GetBaseStem() + ".zone", decompressedData); ZoneFile_COD5_PS3* zoneFile = new ZoneFile_COD5_PS3(); diff --git a/libs/fastfile/PS3/fastfile_cod6_ps3.cpp b/libs/fastfile/PS3/fastfile_cod6_ps3.cpp index 4b82fa5..884886a 100644 --- a/libs/fastfile/PS3/fastfile_cod6_ps3.cpp +++ b/libs/fastfile/PS3/fastfile_cod6_ps3.cpp @@ -72,26 +72,30 @@ bool FastFile_COD6_PS3::Load(const QString aFilePath) { } bool FastFile_COD6_PS3::Load(const QByteArray aData) { - StatusBarManager::instance().updateStatus("Loading COD5 Fast File w/data", 1000); - QByteArray decompressedData; + const qint64 zlibOffset = Compression::FindZlibOffset(aData); + qDebug() << "ZLib Offset: " << zlibOffset; + if (zlibOffset == -1) + { + qWarning() << "Z-Lib stream not found"; + return false; + } + QByteArray compressed = aData.mid(zlibOffset); - // Create a XDataStream on the input data. - XDataStream fastFileStream(aData); - fastFileStream.setByteOrder(XDataStream::LittleEndian); + QByteArray decompressedData = Compression::DecompressZLIB(compressed); - // Parse header values. - SetCompany(pParseFFCompany(&fastFileStream)); - SetType(pParseFFFileType(&fastFileStream)); - SetSignage(pParseFFSignage(&fastFileStream)); - SetMagic(pParseFFMagic(&fastFileStream)); - quint32 version = pParseFFVersion(&fastFileStream); - SetVersion(version); - const QString platformStr = pCalculateFFPlatform(version); - SetPlatform(platformStr); - SetGame("COD5"); + if (decompressedData.isEmpty() || decompressedData.size() < 1024) + { + QByteArray stripped = Compression::StripHashBlocks(compressed); + QByteArray retry = Compression::DecompressZLIB(stripped); + if (!retry.isEmpty()) + decompressedData.swap(retry); + } - // For COD5, simply decompress from offset 12. - decompressedData = Compression::DecompressZLIB(aData.mid(12)); + if (decompressedData.isEmpty()) + { + qWarning() << "Unable to decompress fast-file"; + return false; + } Utils::ExportData(GetBaseStem() + ".zone", decompressedData); diff --git a/libs/fastfile/PS3/fastfile_cod7_ps3.cpp b/libs/fastfile/PS3/fastfile_cod7_ps3.cpp index 947d4d2..5ec48d2 100644 --- a/libs/fastfile/PS3/fastfile_cod7_ps3.cpp +++ b/libs/fastfile/PS3/fastfile_cod7_ps3.cpp @@ -62,10 +62,8 @@ bool FastFile_COD7_PS3::Load(const QString aFilePath) { qDebug() << "Error: Failed to load fastfile: " << fastFileStem; return false; } - file->close(); - // Open zone file after decompressing ff and writing return true; } @@ -81,10 +79,7 @@ bool FastFile_COD7_PS3::Load(const QByteArray aData) { SetType(pParseFFFileType(&fastFileStream)); SetSignage(pParseFFSignage(&fastFileStream)); SetMagic(pParseFFMagic(&fastFileStream)); - quint32 version = pParseFFVersion(&fastFileStream); - SetVersion(version); - SetPlatform(pCalculateFFPlatform(version)); - SetGame("COD7"); + SetVersion(pParseFFVersion(&fastFileStream)); // Load the zone file with the decompressed data (using an Xbox platform flag). ZoneFile_COD7_PS3* zoneFile = new ZoneFile_COD7_PS3(); @@ -92,82 +87,73 @@ bool FastFile_COD7_PS3::Load(const QByteArray aData) { // For COD7/COD9, use BigEndian. fastFileStream.setByteOrder(XDataStream::BigEndian); - if (GetPlatform() == "PC") { - fastFileStream.setByteOrder(XDataStream::LittleEndian); - // Select key based on game. - QByteArray key; - fastFileStream.skipRawData(4); - if (GetPlatform() == "360") { - key = QByteArray::fromHex("1ac1d12d527c59b40eca619120ff8217ccff09cd16896f81b829c7f52793405d"); - } else if (GetPlatform() == "PS3") { - key = QByteArray::fromHex("46D3F997F29C9ACE175B0DAE3AB8C0C1B8E423E2E3BF7E3C311EA35245BF193A"); - // or - // key = QByteArray::fromHex("0C99B3DDB8D6D0845D1147E470F28A8BF2AE69A8A9F534767B54E9180FF55370"); - } + // Select key based on game. + QByteArray key = "46D3F997F29C9ACE175B0DAE3AB8C0C1B8E423E2E3BF7E3C311EA35245BF193A"; + fastFileStream.skipRawData(4); - // Read the 8-byte magic. - QByteArray fileMagic(8, Qt::Uninitialized); - fastFileStream.readRawData(fileMagic.data(), 8); - if (fileMagic != "PHEEBs71") { - qWarning() << "Invalid fast file magic!"; - return false; - } - fastFileStream.skipRawData(4); + // Read the 8-byte magic. + QByteArray fileMagic(8, Qt::Uninitialized); + fastFileStream.readRawData(fileMagic.data(), 8); + if (fileMagic != "PHEEBs71") { + qWarning() << "Invalid fast file magic!"; + return false; + } + fastFileStream.skipRawData(4); - // Read IV table name (32 bytes). - QByteArray fileName(32, Qt::Uninitialized); - fastFileStream.readRawData(fileName.data(), 32); + // Read IV table name (32 bytes). + QByteArray fileName(32, Qt::Uninitialized); + fastFileStream.readRawData(fileName.data(), 32); - // Build the IV table from the fileName. - QByteArray ivTable = Encryption::InitIVTable(fileName); + // Build the IV table from the fileName. + QByteArray ivTable = Encryption::InitIVTable(fileName); - // Skip the RSA signature (256 bytes). - QByteArray rsaSignature(256, Qt::Uninitialized); - fastFileStream.readRawData(rsaSignature.data(), 256); + // Skip the RSA signature (256 bytes). + QByteArray rsaSignature(256, Qt::Uninitialized); + fastFileStream.readRawData(rsaSignature.data(), 256); - // Now the stream should be positioned at 0x13C, where sections begin. - int sectionIndex = 0; - while (true) { - qint32 sectionSize = 0; - fastFileStream >> sectionSize; - qDebug() << "Section index:" << sectionIndex << "Size:" << sectionSize - << "Pos:" << fastFileStream.device()->pos(); - if (sectionSize == 0) - break; + // Now the stream should be positioned at 0x13C, where sections begin. + int sectionIndex = 0; + while (true) { + qint32 sectionSize = 0; + fastFileStream >> sectionSize; + qDebug() << "Section index:" << sectionIndex << "Size:" << sectionSize + << "Pos:" << fastFileStream.device()->pos(); + if (sectionSize == 0) + break; - // Read the section data. - QByteArray sectionData; - sectionData.resize(sectionSize); - fastFileStream.readRawData(sectionData.data(), sectionSize); + // Read the section data. + QByteArray sectionData; + sectionData.resize(sectionSize); + fastFileStream.readRawData(sectionData.data(), sectionSize); - // Compute the IV for this section. - QByteArray iv = Encryption::GetIV(ivTable, sectionIndex); + // Compute the IV for this section. + QByteArray iv = Encryption::GetIV(ivTable, sectionIndex); - // Decrypt the section using Salsa20. - QByteArray decData = Encryption::salsa20DecryptSection(sectionData, key, iv); + // Decrypt the section using Salsa20. + QByteArray decData = Encryption::salsa20DecryptSection(sectionData, key, iv); - // Compute SHA1 hash of the decrypted data. - QByteArray sectionHash = QCryptographicHash::hash(decData, QCryptographicHash::Sha1); + // Compute SHA1 hash of the decrypted data. + QByteArray sectionHash = QCryptographicHash::hash(decData, QCryptographicHash::Sha1); - // Update the IV table based on the section hash. - Encryption::UpdateIVTable(ivTable, sectionIndex, sectionHash); + // Update the IV table based on the section hash. + Encryption::UpdateIVTable(ivTable, sectionIndex, sectionHash); - // Build a compressed data buffer by prepending the two-byte zlib header. - QByteArray compressedData; - compressedData.append(char(0x78)); - compressedData.append(char(0x01)); - compressedData.append(decData); + // Build a compressed data buffer by prepending the two-byte zlib header. + QByteArray compressedData; + compressedData.append(char(0x78)); + compressedData.append(char(0x01)); + compressedData.append(decData); - decompressedData.append(Compression::DecompressZLIB(compressedData)); + decompressedData.append(Compression::DecompressZLIB(compressedData)); - sectionIndex++; - } + sectionIndex++; + } + Utils::ExportData(GetBaseStem() + ".zone", decompressedData); - if (!zoneFile->Load(decompressedData)) { - qWarning() << "Failed to load ZoneFile!"; - return false; - } + if (!zoneFile->Load(decompressedData)) { + qWarning() << "Failed to load ZoneFile!"; + return false; } SetZoneFile(zoneFile); diff --git a/libs/fastfile/PS3/fastfile_cod8_ps3.cpp b/libs/fastfile/PS3/fastfile_cod8_ps3.cpp index 2be7db7..65b7efb 100644 --- a/libs/fastfile/PS3/fastfile_cod8_ps3.cpp +++ b/libs/fastfile/PS3/fastfile_cod8_ps3.cpp @@ -118,51 +118,6 @@ bool FastFile_COD8_PS3::Load(const QByteArray aData) { QByteArray fileName(32, Qt::Uninitialized); fastFileStream.readRawData(fileName.data(), 32); - // Build the IV table from the fileName. - QByteArray ivTable = Encryption::InitIVTable(fileName); - - // Skip the RSA signature (256 bytes). - QByteArray rsaSignature(256, Qt::Uninitialized); - fastFileStream.readRawData(rsaSignature.data(), 256); - - // Now the stream should be positioned at 0x13C, where sections begin. - int sectionIndex = 0; - while (true) { - qint32 sectionSize = 0; - fastFileStream >> sectionSize; - qDebug() << "Section index:" << sectionIndex << "Size:" << sectionSize - << "Pos:" << fastFileStream.device()->pos(); - if (sectionSize == 0) - break; - - // Read the section data. - QByteArray sectionData; - sectionData.resize(sectionSize); - fastFileStream.readRawData(sectionData.data(), sectionSize); - - // Compute the IV for this section. - QByteArray iv = Encryption::GetIV(ivTable, sectionIndex); - - // Decrypt the section using Salsa20. - QByteArray decData = Encryption::salsa20DecryptSection(sectionData, key, iv); - - // Compute SHA1 hash of the decrypted data. - QByteArray sectionHash = QCryptographicHash::hash(decData, QCryptographicHash::Sha1); - - // Update the IV table based on the section hash. - Encryption::UpdateIVTable(ivTable, sectionIndex, sectionHash); - - // Build a compressed data buffer by prepending the two-byte zlib header. - QByteArray compressedData; - compressedData.append(char(0x78)); - compressedData.append(char(0x01)); - compressedData.append(decData); - - decompressedData.append(Compression::DecompressZLIB(compressedData)); - - sectionIndex++; - } - if (!zoneFile->Load(decompressedData)) { qWarning() << "Failed to load ZoneFile!"; return false; diff --git a/libs/fastfile/PS3/fastfile_cod9_ps3.cpp b/libs/fastfile/PS3/fastfile_cod9_ps3.cpp index f66bf9c..7c48e8e 100644 --- a/libs/fastfile/PS3/fastfile_cod9_ps3.cpp +++ b/libs/fastfile/PS3/fastfile_cod9_ps3.cpp @@ -117,7 +117,7 @@ bool FastFile_COD9_PS3::Load(const QByteArray aData) { if (GetPlatform() == "360") { //decompressedData = Compressor::cod9_decryptFastFile(aData); } else if (GetPlatform() == "PC") { - decompressedData = Encryption::decryptFastFile_BO2(aData); + //decompressedData = Encryption::decryptFastFile_BO2(aData); } // For COD9, write out the complete decompressed zone for testing.