diff --git a/libs/fastfile/PC/fastfile_cod10_pc.cpp b/libs/fastfile/PC/fastfile_cod10_pc.cpp index c2098cc..865707c 100644 --- a/libs/fastfile/PC/fastfile_cod10_pc.cpp +++ b/libs/fastfile/PC/fastfile_cod10_pc.cpp @@ -90,12 +90,7 @@ bool FastFile_COD10_PC::Load(const QByteArray aData) { } // Select key based on game. - QByteArray key; - if (GetPlatform() == "360") { - key = QByteArray::fromHex("0E50F49F412317096038665622DD091332A209BA0A05A00E1377CEDB0A3CB1D3"); - } else if (GetPlatform() == "PC") { - key = QByteArray::fromHex("641D8A2FE31D3AA63622BBC9CE8587229D42B0F8ED9B924130BF88B65EDC50BE"); - } + QByteArray key = QByteArray::fromHex("641D8A2FE31D3AA63622BBC9CE8587229D42B0F8ED9B924130BF88B65EDC50BE"); // Read the 8-byte magic. QByteArray fileMagic(8, Qt::Uninitialized); @@ -114,11 +109,7 @@ bool FastFile_COD10_PC::Load(const QByteArray aData) { 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"); @@ -126,6 +117,7 @@ bool FastFile_COD10_PC::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_PC* zoneFile = new ZoneFile_COD10_PC(); diff --git a/libs/fastfile/PC/fastfile_cod11_pc.cpp b/libs/fastfile/PC/fastfile_cod11_pc.cpp index 5cd7ca2..1950b2a 100644 --- a/libs/fastfile/PC/fastfile_cod11_pc.cpp +++ b/libs/fastfile/PC/fastfile_cod11_pc.cpp @@ -80,22 +80,11 @@ bool FastFile_COD11_PC::Load(const QByteArray aData) { 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); - } + 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"); - } + QByteArray key = QByteArray::fromHex("641D8A2FE31D3AA63622BBC9CE8587229D42B0F8ED9B924130BF88B65EDC50BE"); // Read the 8-byte magic. QByteArray fileMagic(8, Qt::Uninitialized); @@ -114,11 +103,7 @@ bool FastFile_COD11_PC::Load(const QByteArray aData) { 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"); @@ -126,6 +111,7 @@ bool FastFile_COD11_PC::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_COD11_PC* zoneFile = new ZoneFile_COD11_PC(); diff --git a/libs/fastfile/PC/fastfile_cod12_pc.cpp b/libs/fastfile/PC/fastfile_cod12_pc.cpp index 547d0c9..89cd449 100644 --- a/libs/fastfile/PC/fastfile_cod12_pc.cpp +++ b/libs/fastfile/PC/fastfile_cod12_pc.cpp @@ -136,7 +136,6 @@ bool FastFile_COD12_PC::Load(const QByteArray aData) { // Sinze Fast Files are aligns, we must skip the full block fastFileStream.device()->seek(blockPosition + 16 + blockSize); } - Utils::ExportData(GetBaseStem() + ".zone", decompressedData); // Load the zone file with the decompressed data (using an Xbox platform flag). diff --git a/libs/fastfile/PC/fastfile_cod6_pc.cpp b/libs/fastfile/PC/fastfile_cod6_pc.cpp index f25c64c..1aa09ef 100644 --- a/libs/fastfile/PC/fastfile_cod6_pc.cpp +++ b/libs/fastfile/PC/fastfile_cod6_pc.cpp @@ -72,12 +72,30 @@ bool FastFile_COD6_PC::Load(const QString aFilePath) { } bool FastFile_COD6_PC::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); - // Assume the first 12 bytes are a header; the rest is zlib-compressed zone data. - const QByteArray compressedData = aData.mid(21); - decompressedData = Compression::DecompressZLIB(compressedData); + QByteArray decompressedData = Compression::DecompressZLIB(compressed); + + if (decompressedData.isEmpty() || decompressedData.size() < 1024) + { + QByteArray stripped = Compression::StripHashBlocks(compressed); + QByteArray retry = Compression::DecompressZLIB(stripped); + if (!retry.isEmpty()) + decompressedData.swap(retry); + } + + if (decompressedData.isEmpty()) + { + qWarning() << "Unable to decompress fast-file"; + return false; + } Utils::ExportData(GetBaseStem() + ".zone", decompressedData); diff --git a/libs/fastfile/PC/fastfile_cod8_pc.cpp b/libs/fastfile/PC/fastfile_cod8_pc.cpp index 0c1d196..52f8d5f 100644 --- a/libs/fastfile/PC/fastfile_cod8_pc.cpp +++ b/libs/fastfile/PC/fastfile_cod8_pc.cpp @@ -81,82 +81,10 @@ bool FastFile_COD8_PC::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)); - // For COD7/COD9, use BigEndian. - 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"); - } - - // 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); - - // 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++; - } + decompressedData = Compression::DecompressZLIB(aData.mid(21)); + Utils::ExportData(GetBaseStem() + ".zone", decompressedData); ZoneFile_COD8_PC* zoneFile = new ZoneFile_COD8_PC(); zoneFile->SetStem(GetBaseStem() + ".zone"); diff --git a/libs/fastfile/PC/fastfile_cod9_pc.cpp b/libs/fastfile/PC/fastfile_cod9_pc.cpp index 8f513a5..de4d678 100644 --- a/libs/fastfile/PC/fastfile_cod9_pc.cpp +++ b/libs/fastfile/PC/fastfile_cod9_pc.cpp @@ -67,8 +67,6 @@ bool FastFile_COD9_PC::Load(const QString aFilePath) { } bool FastFile_COD9_PC::Load(const QByteArray aData) { - QByteArray decompressedData; - // Create a XDataStream on the input data. XDataStream fastFileStream(aData); fastFileStream.setByteOrder(XDataStream::LittleEndian); @@ -78,26 +76,9 @@ bool FastFile_COD9_PC::Load(const QByteArray aData) { SetType(pParseFFFileType(&fastFileStream)); SetSignage(pParseFFSignage(&fastFileStream)); SetMagic(pParseFFMagic(&fastFileStream)); - quint32 version = pParseFFVersion(&fastFileStream); - SetVersion(version); - SetPlatform(pCalculateFFPlatform(version)); - SetGame("COD9"); + SetVersion(pParseFFVersion(&fastFileStream)); - // 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. + // Validate the fastfile magic. QByteArray fileMagic(8, Qt::Uninitialized); fastFileStream.readRawData(fileMagic.data(), 8); if (fileMagic != "PHEEBs71") { @@ -106,35 +87,100 @@ bool FastFile_COD9_PC::Load(const QByteArray aData) { } fastFileStream.skipRawData(4); - // Read IV table name (32 bytes). - QByteArray fileName(32, Qt::Uninitialized); - fastFileStream.readRawData(fileName.data(), 32); + // Read IV seed name (32 bytes). + QByteArray nameKey(32, Qt::Uninitialized); + fastFileStream.readRawData(nameKey.data(), 32); - // Skip the RSA signature (256 bytes). - QByteArray rsaSignature(256, Qt::Uninitialized); - fastFileStream.readRawData(rsaSignature.data(), 256); + // --- Salsa20 + IV setup --- + static QVector ivCounter(4, 1); + QByteArray ivTable = Encryption::InitIVTable(nameKey); + ivCounter.fill(1); // reset global counters - if (GetPlatform() == "360") { - //decompressedData = Compressor::cod9_decryptFastFile(aData); - } else if (GetPlatform() == "PC") { - decompressedData = Encryption::decryptFastFile_BO2(aData); + // Skip RSA signature (0x100) + fastFileStream.skipRawData(0x100); + + // Decrypt + decompress loop + QByteArray finalZone; + int chunkIndex = 0; + + while (!fastFileStream.atEnd()) { + quint32 dataLength = 0; + fastFileStream >> dataLength; + if (dataLength == 0) + break; + + QByteArray encryptedBlock(dataLength, Qt::Uninitialized); + if (fastFileStream.readRawData(encryptedBlock.data(), dataLength) != dataLength) { + qWarning() << "Unexpected EOF while reading block"; + break; + } + + // Derive IV for this chunk + QByteArray iv = Encryption::GetIV(ivTable, chunkIndex % 4); + + // Salsa20 decryption + QByteArray decryptedBlock = Encryption::salsa20DecryptSection( + encryptedBlock, + QByteArray::fromHex("641D8A2FE31D3AA63622BBC9CE8587229D42B0F8ED9B924130BF88B65EDC50BE"), + iv, + 64 + ); + + // SHA1 hash of decrypted block + QCryptographicHash sha1(QCryptographicHash::Sha1); + sha1.addData(decryptedBlock); + QByteArray sha1Hash = sha1.result(); + + // Inflate into buffer + z_stream strm{}; + strm.avail_in = static_cast(decryptedBlock.size()); + strm.next_in = reinterpret_cast(decryptedBlock.data()); + + QByteArray decompressedData; + QByteArray buffer(0x10000, Qt::Uninitialized); + inflateInit2(&strm, -15); + + int ret; + do { + strm.avail_out = buffer.size(); + strm.next_out = reinterpret_cast(buffer.data()); + + ret = inflate(&strm, Z_NO_FLUSH); + + if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR) { + qWarning() << "inflate failed with code" << ret; + break; + } + + int have = buffer.size() - strm.avail_out; + if (have > 0) + decompressedData.append(buffer.constData(), have); + + } while (ret != Z_STREAM_END); + + inflateEnd(&strm); + + finalZone.append(decompressedData); + + // Update IV table for next block + Encryption::UpdateIVTable(ivTable, chunkIndex % 4, sha1Hash); + + chunkIndex++; } - // 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(); - } + // Export decompressed zone + Utils::ExportData(GetBaseStem() + ".zone", finalZone); - // Load the zone file with the decompressed data (using an Xbox platform flag). + // Load zone file ZoneFile_COD9_PC* zoneFile = new ZoneFile_COD9_PC(); zoneFile->SetStem(GetBaseStem() + ".zone"); - if (!zoneFile->Load(decompressedData)) { + if (!zoneFile->Load(finalZone)) { qWarning() << "Failed to load ZoneFile!"; return false; } SetZoneFile(zoneFile); return true; + + return true; }