Refactor: Improve fastfile decoding.

This commit is contained in:
njohnson 2025-09-15 18:51:04 -04:00
parent dd00cee809
commit 13af32ed2d
18 changed files with 470 additions and 284 deletions

View File

@ -73,12 +73,6 @@ bool FastFile_COD10_360::Load(const QByteArray aData) {
XDataStream fastFileStream(aData); XDataStream fastFileStream(aData);
fastFileStream.setByteOrder(XDataStream::LittleEndian); 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. // Read the 8-byte magic.
QByteArray fileMagic(8, Qt::Uninitialized); QByteArray fileMagic(8, Qt::Uninitialized);
fastFileStream.readRawData(fileMagic.data(), 8); fastFileStream.readRawData(fileMagic.data(), 8);
@ -96,7 +90,7 @@ bool FastFile_COD10_360::Load(const QByteArray aData) {
QByteArray rsaSignature(256, Qt::Uninitialized); QByteArray rsaSignature(256, Qt::Uninitialized);
fastFileStream.readRawData(rsaSignature.data(), 256); 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. // For COD9, write out the complete decompressed zone for testing.
QFile testFile("exports/" + GetBaseStem() + ".zone"); QFile testFile("exports/" + GetBaseStem() + ".zone");
@ -104,6 +98,7 @@ bool FastFile_COD10_360::Load(const QByteArray aData) {
testFile.write(decompressedData); testFile.write(decompressedData);
testFile.close(); testFile.close();
} }
Utils::ExportData(GetBaseStem() + ".zone", decompressedData);
// Load the zone file with the decompressed data (using an Xbox platform flag). // Load the zone file with the decompressed data (using an Xbox platform flag).
ZoneFile_COD10_360* zoneFile = new ZoneFile_COD10_360(); ZoneFile_COD10_360* zoneFile = new ZoneFile_COD10_360();

View File

@ -68,29 +68,74 @@ bool FastFile_COD11_360::Load(const QString aFilePath) {
return true; 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) { bool FastFile_COD11_360::Load(const QByteArray aData) {
QByteArray decompressedData; QByteArray decompressedData;
// Prepare data stream for parsing // Prepare data stream for parsing
XDataStream fastFileStream(aData); XDataStream fastFileStream(aData);
fastFileStream.setByteOrder(XDataStream::LittleEndian); fastFileStream.setByteOrder(XDataStream::BigEndian);
// Verify magic header // Verify magic header
QByteArray fileMagic(8, Qt::Uninitialized); QByteArray fileMagic(8, Qt::Uninitialized);
fastFileStream.readRawData(fileMagic.data(), 8); fastFileStream.readRawData(fileMagic.data(), 8);
if (fileMagic != "TAff0000") { quint32 version = fastFileStream.ParseUInt32();
qWarning() << "Invalid fast file magic for COD12!";
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; return false;
} }
// Skip: File size (4 bytes), flags/version (4 bytes), unknown (8 bytes), build tag (32 bytes), RSA signature (256 bytes) if (blockCount > 17280)
fastFileStream.skipRawData(4 + 4 + 8 + 32 + 256); // total 304 bytes skipped so far + 8 bytes magic = 312 bytes at correct position. {
qWarning() << "Fast file has too many blocks:" << blockCount << "> 17280!";
return false;
}
fastFileStream.skipRawData(12 * blockCount);
// Correctly positioned at 0x138 qint32 startPos = fastFileStream.ParseInt32();
QByteArray encryptedData = aData.mid(0x138); Q_UNUSED(startPos);
decompressedData = Encryption::decryptFastFile_BO3(encryptedData);
// 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); Utils::ExportData(GetBaseStem() + ".zone", decompressedData);
// Load the zone file with decompressed data // Load the zone file with decompressed data

View File

@ -75,21 +75,66 @@ bool FastFile_COD12_360::Load(const QByteArray aData) {
XDataStream fastFileStream(aData); XDataStream fastFileStream(aData);
fastFileStream.setByteOrder(XDataStream::LittleEndian); fastFileStream.setByteOrder(XDataStream::LittleEndian);
// Verify magic header // Skip header magic
QByteArray fileMagic(8, Qt::Uninitialized); fastFileStream.skipRawData(8);
fastFileStream.readRawData(fileMagic.data(), 8);
if (fileMagic != "TAff0000") { quint32 version;
qWarning() << "Invalid fast file magic for COD12!"; 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; return false;
} }
// Skip: File size (4 bytes), flags/version (4 bytes), unknown (8 bytes), build tag (32 bytes), RSA signature (256 bytes) fastFileStream.skipRawData(128);
fastFileStream.skipRawData(4 + 4 + 8 + 32 + 256); // total 304 bytes skipped so far + 8 bytes magic = 312 bytes at correct position.
// Correctly positioned at 0x138 quint64 size;
QByteArray encryptedData = aData.mid(0x138); fastFileStream >> size;
decompressedData = Encryption::decryptFastFile_BO3(encryptedData);
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 // Output for verification/testing
Utils::ExportData(GetBaseStem() + ".zone", decompressedData); Utils::ExportData(GetBaseStem() + ".zone", decompressedData);

View File

@ -70,30 +70,47 @@ bool FastFile_COD6_360::Load(const QString aFilePath) {
} }
bool FastFile_COD6_360::Load(const QByteArray aData) { bool FastFile_COD6_360::Load(const QByteArray aData) {
const qint64 zlibOffset = Compression::FindZlibOffset(aData); XDataStream fastFileStream(aData);
if (zlibOffset == -1) fastFileStream.setByteOrder(XDataStream::BigEndian);
{
qWarning() << "Z-Lib stream not found";
return false;
}
QByteArray compressed = aData.mid(zlibOffset);
QByteArray decompressedData = Compression::DecompressZLIB(compressed); QByteArray magic(8, Qt::Uninitialized);
fastFileStream.readRawData(magic.data(), 8);
if (decompressedData.isEmpty() || decompressedData.size() < 1024) quint32 version = fastFileStream.ParseUInt32();
{
QByteArray stripped = Compression::StripHashBlocks(compressed);
QByteArray retry = Compression::DecompressZLIB(stripped);
if (!retry.isEmpty())
decompressedData.swap(retry);
}
if (decompressedData.isEmpty()) if (version != 269)
{ {
qWarning() << "Unable to decompress fast-file"; qDebug() << QString("Invalid version: %1!").arg(version);
return false; 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); Utils::ExportData(GetBaseStem() + ".zone", decompressedData);
ZoneFile_COD6_360* zoneFile = new ZoneFile_COD6_360(); ZoneFile_COD6_360* zoneFile = new ZoneFile_COD6_360();

View File

@ -73,14 +73,9 @@ bool FastFile_COD7_360::Load(const QByteArray aData) {
// Create a XDataStream on the input data. // Create a XDataStream on the input data.
XDataStream fastFileStream(aData); XDataStream fastFileStream(aData);
fastFileStream.skipRawData(12);
// For COD7/COD9, use BigEndian.
fastFileStream.setByteOrder(XDataStream::BigEndian); fastFileStream.setByteOrder(XDataStream::BigEndian);
// Select key based on game. fastFileStream.skipRawData(16);
QByteArray key = QByteArray::fromHex("1ac1d12d527c59b40eca619120ff8217ccff09cd16896f81b829c7f52793405d");
fastFileStream.skipRawData(4);
// Read the 8-byte magic. // Read the 8-byte magic.
QByteArray fileMagic(8, Qt::Uninitialized); QByteArray fileMagic(8, Qt::Uninitialized);
@ -102,6 +97,8 @@ bool FastFile_COD7_360::Load(const QByteArray aData) {
QByteArray rsaSignature(256, Qt::Uninitialized); QByteArray rsaSignature(256, Qt::Uninitialized);
fastFileStream.readRawData(rsaSignature.data(), 256); fastFileStream.readRawData(rsaSignature.data(), 256);
QByteArray key = QByteArray::fromHex("1ac1d12d527c59b40eca619120ff8217ccff09cd16896f81b829c7f52793405d");
// Now the stream should be positioned at 0x13C, where sections begin. // Now the stream should be positioned at 0x13C, where sections begin.
int sectionIndex = 0; int sectionIndex = 0;
while (true) { while (true) {

View File

@ -0,0 +1,148 @@
#include "fastfile_cod7_5_360.h"
#include "zonefile_cod7_360.h"
#include "compression.h"
#include "encryption.h"
#include <QFile>
#include <QDebug>
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;
}

View File

@ -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

View File

@ -76,9 +76,6 @@ bool FastFile_COD8_360::Load(const QByteArray aData) {
// For COD7/COD9, use BigEndian. // For COD7/COD9, use BigEndian.
fastFileStream.setByteOrder(XDataStream::BigEndian); fastFileStream.setByteOrder(XDataStream::BigEndian);
// Select key based on game.
QByteArray key = QByteArray::fromHex("0E50F49F412317096038665622DD091332A209BA0A05A00E1377CEDB0A3CB1D3");
// Read the 8-byte magic. // Read the 8-byte magic.
QByteArray fileMagic(8, Qt::Uninitialized); QByteArray fileMagic(8, Qt::Uninitialized);
fastFileStream.readRawData(fileMagic.data(), 8); fastFileStream.readRawData(fileMagic.data(), 8);
@ -92,11 +89,7 @@ bool FastFile_COD8_360::Load(const QByteArray aData) {
QByteArray fileName(32, Qt::Uninitialized); QByteArray fileName(32, Qt::Uninitialized);
fastFileStream.readRawData(fileName.data(), 32); fastFileStream.readRawData(fileName.data(), 32);
// Skip the RSA signature (256 bytes). decompressedData = Encryption::DecryptFile(aData, fileName, "0E50F49F412317096038665622DD091332A209BA0A05A00E1377CEDB0A3CB1D3");
QByteArray rsaSignature(256, Qt::Uninitialized);
fastFileStream.readRawData(rsaSignature.data(), 256);
decompressedData = Encryption::decryptFastFile_BO2(aData);
// For COD9, write out the complete decompressed zone for testing. // For COD9, write out the complete decompressed zone for testing.
QFile testFile("exports/" + GetBaseStem() + ".zone"); QFile testFile("exports/" + GetBaseStem() + ".zone");

View File

@ -76,9 +76,6 @@ bool FastFile_COD9_360::Load(const QByteArray aData) {
// For COD7/COD9, use BigEndian. // For COD7/COD9, use BigEndian.
fastFileStream.setByteOrder(XDataStream::BigEndian); fastFileStream.setByteOrder(XDataStream::BigEndian);
// Select key based on game.
QByteArray key = QByteArray::fromHex("0E50F49F412317096038665622DD091332A209BA0A05A00E1377CEDB0A3CB1D3");
// Read the 8-byte magic. // Read the 8-byte magic.
QByteArray fileMagic(8, Qt::Uninitialized); QByteArray fileMagic(8, Qt::Uninitialized);
fastFileStream.readRawData(fileMagic.data(), 8); fastFileStream.readRawData(fileMagic.data(), 8);
@ -96,7 +93,7 @@ bool FastFile_COD9_360::Load(const QByteArray aData) {
QByteArray rsaSignature(256, Qt::Uninitialized); QByteArray rsaSignature(256, Qt::Uninitialized);
fastFileStream.readRawData(rsaSignature.data(), 256); 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. // For COD9, write out the complete decompressed zone for testing.
QFile testFile("exports/" + GetBaseStem() + ".zone"); QFile testFile("exports/" + GetBaseStem() + ".zone");

View File

@ -110,15 +110,7 @@ bool FastFile_COD10_PS3::Load(const QByteArray aData) {
QByteArray fileName(32, Qt::Uninitialized); QByteArray fileName(32, Qt::Uninitialized);
fastFileStream.readRawData(fileName.data(), 32); fastFileStream.readRawData(fileName.data(), 32);
// Skip the RSA signature (256 bytes). //decompressedData = Encryption::decryptFastFile_BO2(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);
}
// For COD9, write out the complete decompressed zone for testing. // For COD9, write out the complete decompressed zone for testing.
QFile testFile("exports/" + GetBaseStem() + ".zone"); QFile testFile("exports/" + GetBaseStem() + ".zone");

View File

@ -1,6 +1,7 @@
#include "fastfile_cod11_ps3.h" #include "fastfile_cod11_ps3.h"
#include "zonefile_cod11_ps3.h" #include "zonefile_cod11_ps3.h"
#include "encryption.h" #include "encryption.h"
#include "compression.h"
#include <QFile> #include <QFile>
#include <QDebug> #include <QDebug>
@ -66,68 +67,77 @@ bool FastFile_COD11_PS3::Load(const QString aFilePath) {
return true; 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) { bool FastFile_COD11_PS3::Load(const QByteArray aData) {
QByteArray decompressedData; QByteArray decompressedData;
// Create a XDataStream on the input data. // Prepare data stream for parsing
XDataStream fastFileStream(aData); 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); fastFileStream.setByteOrder(XDataStream::BigEndian);
if (GetPlatform() == "PC") {
fastFileStream.setByteOrder(XDataStream::LittleEndian);
}
// Select key based on game. // Verify magic header
QByteArray key;
if (GetPlatform() == "360") {
key = QByteArray::fromHex("0E50F49F412317096038665622DD091332A209BA0A05A00E1377CEDB0A3CB1D3");
} else if (GetPlatform() == "PC") {
key = QByteArray::fromHex("641D8A2FE31D3AA63622BBC9CE8587229D42B0F8ED9B924130BF88B65EDC50BE");
}
// Read the 8-byte magic.
QByteArray fileMagic(8, Qt::Uninitialized); QByteArray fileMagic(8, Qt::Uninitialized);
fastFileStream.readRawData(fileMagic.data(), 8); fastFileStream.readRawData(fileMagic.data(), 8);
if (fileMagic != "PHEEBs71") { quint32 version = fastFileStream.ParseUInt32();
qWarning() << "Invalid fast file magic!";
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; return false;
} }
fastFileStream.skipRawData(4);
// Read IV table name (32 bytes). if (blockCount > 17280)
QByteArray fileName(32, Qt::Uninitialized); {
fastFileStream.readRawData(fileName.data(), 32); qWarning() << "Fast file has too many blocks:" << blockCount << "> 17280!";
return false;
// 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);
} }
fastFileStream.skipRawData(12 * blockCount);
// For COD9, write out the complete decompressed zone for testing. qint32 startPos = fastFileStream.ParseInt32();
QFile testFile("exports/" + GetBaseStem() + ".zone"); Q_UNUSED(startPos);
if(testFile.open(QIODevice::WriteOnly)) {
testFile.write(decompressedData); qint32 endPos = fastFileStream.ParseInt32();
testFile.close(); 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_COD11_PS3* zoneFile = new ZoneFile_COD11_PS3();
zoneFile->SetStem(GetBaseStem() + ".zone"); zoneFile->SetStem(GetBaseStem() + ".zone");
if (!zoneFile->Load(decompressedData)) { if (!zoneFile->Load(decompressedData)) {

View File

@ -110,15 +110,7 @@ bool FastFile_COD12_PS3::Load(const QByteArray aData) {
QByteArray fileName(32, Qt::Uninitialized); QByteArray fileName(32, Qt::Uninitialized);
fastFileStream.readRawData(fileName.data(), 32); fastFileStream.readRawData(fileName.data(), 32);
// Skip the RSA signature (256 bytes). decompressedData = Encryption::decryptFastFile_BO3(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);
}
// For COD9, write out the complete decompressed zone for testing. // For COD9, write out the complete decompressed zone for testing.
QFile testFile("exports/" + GetBaseStem() + ".zone"); QFile testFile("exports/" + GetBaseStem() + ".zone");

View File

@ -86,29 +86,24 @@ bool FastFile_COD4_PS3::Load(const QByteArray aData) {
SetMagic(pParseFFMagic(&fastFileStream)); SetMagic(pParseFFMagic(&fastFileStream));
SetVersion(pParseFFVersion(&fastFileStream)); SetVersion(pParseFFVersion(&fastFileStream));
int pos = 12;
// Loop until EOF or invalid chunk // Loop until EOF or invalid chunk
while (pos <= aData.size()) { fastFileStream.setByteOrder(XDataStream::BigEndian);
while (!fastFileStream.atEnd()) {
// Read 2-byte BIG-ENDIAN chunk size // Read 2-byte BIG-ENDIAN chunk size
quint32 chunkSize; quint16 chunkSize;
XDataStream chunkStream(aData.mid(pos, 2)); fastFileStream >> chunkSize;
chunkStream.setByteOrder(XDataStream::BigEndian);
chunkStream >> chunkSize;
pos += 2; if (chunkSize == 0 || fastFileStream.device()->pos() + chunkSize > aData.size()) {
if (chunkSize == 0 || pos + chunkSize > aData.size()) {
qWarning() << "Invalid or incomplete chunk detected, stopping."; qWarning() << "Invalid or incomplete chunk detected, stopping.";
break; break;
} }
const QByteArray compressedChunk = aData.mid(pos, chunkSize); QByteArray compressedChunk(chunkSize, Qt::Uninitialized);
fastFileStream.readRawData(compressedChunk.data(), chunkSize);
decompressedData.append(Compression::DecompressDeflate(compressedChunk)); QByteArray decompressedChunk = Compression::DecompressDeflate(compressedChunk);
decompressedData.append(decompressedChunk);
pos += chunkSize;
} }
Utils::ExportData(GetBaseStem() + ".zone", decompressedData); Utils::ExportData(GetBaseStem() + ".zone", decompressedData);
ZoneFile_COD4_PS3* zoneFile = new ZoneFile_COD4_PS3(); ZoneFile_COD4_PS3* zoneFile = new ZoneFile_COD4_PS3();

View File

@ -86,29 +86,24 @@ bool FastFile_COD5_PS3::Load(const QByteArray aData) {
SetMagic(pParseFFMagic(&fastFileStream)); SetMagic(pParseFFMagic(&fastFileStream));
SetVersion(pParseFFVersion(&fastFileStream)); SetVersion(pParseFFVersion(&fastFileStream));
int pos = 12;
// Loop until EOF or invalid chunk // Loop until EOF or invalid chunk
while (pos <= aData.size()) { fastFileStream.setByteOrder(XDataStream::BigEndian);
while (!fastFileStream.atEnd()) {
// Read 2-byte BIG-ENDIAN chunk size // Read 2-byte BIG-ENDIAN chunk size
quint32 chunkSize; quint16 chunkSize;
XDataStream chunkStream(aData.mid(pos, 2)); fastFileStream >> chunkSize;
chunkStream.setByteOrder(XDataStream::BigEndian);
chunkStream >> chunkSize;
pos += 2; if (chunkSize == 0 || fastFileStream.device()->pos() + chunkSize > aData.size()) {
if (chunkSize == 0 || pos + chunkSize > aData.size()) {
qWarning() << "Invalid or incomplete chunk detected, stopping."; qWarning() << "Invalid or incomplete chunk detected, stopping.";
break; break;
} }
const QByteArray compressedChunk = aData.mid(pos, chunkSize); QByteArray compressedChunk(chunkSize, Qt::Uninitialized);
fastFileStream.readRawData(compressedChunk.data(), chunkSize);
decompressedData.append(Compression::DecompressDeflate(compressedChunk)); QByteArray decompressedChunk = Compression::DecompressDeflate(compressedChunk);
decompressedData.append(decompressedChunk);
pos += chunkSize;
} }
Utils::ExportData(GetBaseStem() + ".zone", decompressedData); Utils::ExportData(GetBaseStem() + ".zone", decompressedData);
ZoneFile_COD5_PS3* zoneFile = new ZoneFile_COD5_PS3(); ZoneFile_COD5_PS3* zoneFile = new ZoneFile_COD5_PS3();

View File

@ -72,26 +72,30 @@ bool FastFile_COD6_PS3::Load(const QString aFilePath) {
} }
bool FastFile_COD6_PS3::Load(const QByteArray aData) { bool FastFile_COD6_PS3::Load(const QByteArray aData) {
StatusBarManager::instance().updateStatus("Loading COD5 Fast File w/data", 1000); const qint64 zlibOffset = Compression::FindZlibOffset(aData);
QByteArray decompressedData; 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. QByteArray decompressedData = Compression::DecompressZLIB(compressed);
XDataStream fastFileStream(aData);
fastFileStream.setByteOrder(XDataStream::LittleEndian);
// Parse header values. if (decompressedData.isEmpty() || decompressedData.size() < 1024)
SetCompany(pParseFFCompany(&fastFileStream)); {
SetType(pParseFFFileType(&fastFileStream)); QByteArray stripped = Compression::StripHashBlocks(compressed);
SetSignage(pParseFFSignage(&fastFileStream)); QByteArray retry = Compression::DecompressZLIB(stripped);
SetMagic(pParseFFMagic(&fastFileStream)); if (!retry.isEmpty())
quint32 version = pParseFFVersion(&fastFileStream); decompressedData.swap(retry);
SetVersion(version); }
const QString platformStr = pCalculateFFPlatform(version);
SetPlatform(platformStr);
SetGame("COD5");
// For COD5, simply decompress from offset 12. if (decompressedData.isEmpty())
decompressedData = Compression::DecompressZLIB(aData.mid(12)); {
qWarning() << "Unable to decompress fast-file";
return false;
}
Utils::ExportData(GetBaseStem() + ".zone", decompressedData); Utils::ExportData(GetBaseStem() + ".zone", decompressedData);

View File

@ -62,10 +62,8 @@ bool FastFile_COD7_PS3::Load(const QString aFilePath) {
qDebug() << "Error: Failed to load fastfile: " << fastFileStem; qDebug() << "Error: Failed to load fastfile: " << fastFileStem;
return false; return false;
} }
file->close(); file->close();
// Open zone file after decompressing ff and writing
return true; return true;
} }
@ -81,10 +79,7 @@ bool FastFile_COD7_PS3::Load(const QByteArray aData) {
SetType(pParseFFFileType(&fastFileStream)); SetType(pParseFFFileType(&fastFileStream));
SetSignage(pParseFFSignage(&fastFileStream)); SetSignage(pParseFFSignage(&fastFileStream));
SetMagic(pParseFFMagic(&fastFileStream)); SetMagic(pParseFFMagic(&fastFileStream));
quint32 version = pParseFFVersion(&fastFileStream); SetVersion(pParseFFVersion(&fastFileStream));
SetVersion(version);
SetPlatform(pCalculateFFPlatform(version));
SetGame("COD7");
// Load the zone file with the decompressed data (using an Xbox platform flag). // Load the zone file with the decompressed data (using an Xbox platform flag).
ZoneFile_COD7_PS3* zoneFile = new ZoneFile_COD7_PS3(); ZoneFile_COD7_PS3* zoneFile = new ZoneFile_COD7_PS3();
@ -92,19 +87,10 @@ bool FastFile_COD7_PS3::Load(const QByteArray aData) {
// For COD7/COD9, use BigEndian. // For COD7/COD9, use BigEndian.
fastFileStream.setByteOrder(XDataStream::BigEndian); fastFileStream.setByteOrder(XDataStream::BigEndian);
if (GetPlatform() == "PC") {
fastFileStream.setByteOrder(XDataStream::LittleEndian);
// Select key based on game. // Select key based on game.
QByteArray key; QByteArray key = "46D3F997F29C9ACE175B0DAE3AB8C0C1B8E423E2E3BF7E3C311EA35245BF193A";
fastFileStream.skipRawData(4); 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. // Read the 8-byte magic.
QByteArray fileMagic(8, Qt::Uninitialized); QByteArray fileMagic(8, Qt::Uninitialized);
@ -163,12 +149,12 @@ bool FastFile_COD7_PS3::Load(const QByteArray aData) {
sectionIndex++; sectionIndex++;
} }
Utils::ExportData(GetBaseStem() + ".zone", decompressedData);
if (!zoneFile->Load(decompressedData)) { if (!zoneFile->Load(decompressedData)) {
qWarning() << "Failed to load ZoneFile!"; qWarning() << "Failed to load ZoneFile!";
return false; return false;
} }
}
SetZoneFile(zoneFile); SetZoneFile(zoneFile);
return true; return true;

View File

@ -118,51 +118,6 @@ bool FastFile_COD8_PS3::Load(const QByteArray aData) {
QByteArray fileName(32, Qt::Uninitialized); QByteArray fileName(32, Qt::Uninitialized);
fastFileStream.readRawData(fileName.data(), 32); 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)) { if (!zoneFile->Load(decompressedData)) {
qWarning() << "Failed to load ZoneFile!"; qWarning() << "Failed to load ZoneFile!";
return false; return false;

View File

@ -117,7 +117,7 @@ bool FastFile_COD9_PS3::Load(const QByteArray aData) {
if (GetPlatform() == "360") { if (GetPlatform() == "360") {
//decompressedData = Compressor::cod9_decryptFastFile(aData); //decompressedData = Compressor::cod9_decryptFastFile(aData);
} else if (GetPlatform() == "PC") { } else if (GetPlatform() == "PC") {
decompressedData = Encryption::decryptFastFile_BO2(aData); //decompressedData = Encryption::decryptFastFile_BO2(aData);
} }
// For COD9, write out the complete decompressed zone for testing. // For COD9, write out the complete decompressed zone for testing.