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);
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();

View File

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

View File

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

View File

@ -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();

View File

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

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.
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");

View File

@ -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");

View File

@ -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");

View File

@ -1,6 +1,7 @@
#include "fastfile_cod11_ps3.h"
#include "zonefile_cod11_ps3.h"
#include "encryption.h"
#include "compression.h"
#include <QFile>
#include <QDebug>
@ -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)) {

View File

@ -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");

View File

@ -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();

View File

@ -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();

View File

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

View File

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

View File

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

View File

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