Skip to content

Commit

Permalink
updated logic to work with new warzone fastfiles, and cleaned up code.
Browse files Browse the repository at this point in the history
  • Loading branch information
LJW-Dev committed Dec 30, 2023
1 parent 2d1e0f8 commit 331aaed
Show file tree
Hide file tree
Showing 9 changed files with 395 additions and 97 deletions.
2 changes: 1 addition & 1 deletion MW2 FF Extractor.csproj → COD FF Extractor.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>MW2_FF_Extractor</RootNamespace>
<RootNamespace>MW_FF_Extractor</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion MW2 FF Extractor.sln → COD FF Extractor.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.4.33103.184
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MW2 FF Extractor", "MW2 FF Extractor.csproj", "{41E9FEEC-3880-4359-B9C9-19B1BAF69543}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "COD FF Extractor", "COD FF Extractor.csproj", "{41E9FEEC-3880-4359-B9C9-19B1BAF69543}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
146 changes: 77 additions & 69 deletions Decompression/Decompressor.cs → Decompression/MW2.cs
Original file line number Diff line number Diff line change
@@ -1,57 +1,17 @@
using System.Text;
using static MW2_FF_Extractor.Structs;

namespace MW2_FF_Extractor
using COD_FF_Extractor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static COD_FF_Extractor.Structs;

namespace COD_FF_Extractor.Decompression
{
public static class Decompressor
class MW2
{
// Decompresses FastFiles. returns -1 if it is an original FastFile, if it's a patch FastFile return the size of the patched FastFile
public static long Decompress(string path, string outPath)
private static void decompressBlocks(BinaryReader reader, BinaryWriter writer)
{
BinaryReader reader = Utils.OpenBinaryReader(path);
if (reader == null)
throw new Exception(String.Format("Unable to open file {0}.", path));

BinaryWriter writer = Utils.OpenBinaryWriter(outPath);
if (writer == null)
throw new Exception(String.Format("Unable to create file {0}.", outPath));

// Get FF type
byte[] FFMagic = reader.ReadBytes(0x08);
FastFileType FFType;

if (Utils.CompareArray(FFMagic, FFHeaderMagic))
FFType = FastFileType.REGULAR;
else if (Utils.CompareArray(FFMagic, FFPatchHeaderMagic))
FFType = FastFileType.PATCH;
else
throw new Exception("Invalid FF magic.");

long DiffSize = -1;
if (FFType == FastFileType.PATCH)
{
// Check if patch FF is empty
reader.BaseStream.Position = 0x28;
if (reader.ReadInt64() == 0) //Read patch FF compressed size
return DiffSize;

// Get size of FF after patching
reader.BaseStream.Position = 0x140;
DiffSize = reader.ReadInt64();
}

// Read FF name
if(FFType == FastFileType.REGULAR)
reader.BaseStream.Position = 0x208;
else
reader.BaseStream.Position = 0x318;
string FastFileName = Encoding.UTF8.GetString(reader.ReadBytes(0x40)).Replace("\x00", String.Empty);

// Read the block header
if (FFType == FastFileType.REGULAR)
reader.BaseStream.Position = 0x80DC;
else
reader.BaseStream.Position = 0x81EC;
BlockHeader headerBlock = new BlockHeader();
headerBlock.decompressedDataLen = reader.ReadInt32();
reader.BaseStream.Position += 0x3;
Expand Down Expand Up @@ -106,7 +66,7 @@ public static long Decompress(string path, string outPath)
default:
throw new Exception("Unknown type of compression!");
}

if (decompressedData == null)
throw new Exception("Decompressor returned null!");

Expand All @@ -115,29 +75,77 @@ public static long Decompress(string path, string outPath)
decompressedDataCount += block.decompressedLen;
loopCount++;
}
}

private static void decompressRegularFF(BinaryReader reader, BinaryWriter writer)
{
// move to start of the signed fast file
long fastFileStart = 0xDC;
reader.BaseStream.Position = fastFileStart;

// skip SHA256 hashes
reader.BaseStream.Position += 0x8000;

decompressBlocks(reader, writer);
}

private static void decompressPatchFF(BinaryReader reader, BinaryWriter writer)
{
reader.BaseStream.Position = 0x28;
if (reader.ReadInt32() == 0) // check if it's an empty patch file, exit if it is
return;

// move to start of the signed fast file
long fastFileStart = 0x1EC;
reader.BaseStream.Position = fastFileStart;

// skip SHA256 hashes
reader.BaseStream.Position += 0x8000;

decompressBlocks(reader, writer);
}

public static void decompress(string path, string outPath)
{
BinaryReader reader = Utils.OpenBinaryReader(path);
if (reader == null)
throw new Exception(String.Format("Unable to open file {0}.", path));

BinaryWriter writer = Utils.OpenBinaryWriter(outPath);
if (writer == null)
throw new Exception(String.Format("Unable to create file {0}.", outPath));

byte[] FFMagic = reader.ReadBytes(0x08);

if (Utils.CompareArray(FFMagic, FFHeaderMagic))
{
decompressRegularFF(reader, writer);
}
else if (Utils.CompareArray(FFMagic, FFPatchHeaderMagic))
{
decompressPatchFF(reader, writer);
}
else
throw new Exception("Invalid FF magic.");

reader.Close();
writer.Close();

// diff the files if there is a patch file
string FFPatchPath = path.Replace(".ff", ".fp");
if (DiffSize == -1 && File.Exists(FFPatchPath))
{
long outSize = Decompress(FFPatchPath, FFPatchPath + ".tmp");
}

if(outSize > 0)
{
byte[] patchedFF = MWBDiff.BDiff.patchData(File.ReadAllBytes(outPath), File.ReadAllBytes(FFPatchPath + ".tmp"), outSize);
// gets the resulting size of a fastfile when patched with a patch fastfile
// patchFilePath must be the path to a patch file
public static int getDiffResultSizeFromFile(string patchFilePath)
{
BinaryReader reader = Utils.OpenBinaryReader(patchFilePath);
if (reader == null)
throw new Exception(String.Format("Unable to open file {0}.", patchFilePath));

if(patchedFF == null)
throw new Exception("patchData returned null! outSize: " + outSize);
reader.BaseStream.Position = 0x140;
int diffResultSize = reader.ReadInt32();

File.WriteAllBytes(outPath, patchedFF); // overwrite already written non patched FF
File.Delete(FFPatchPath + ".tmp");
}
}

return DiffSize;
reader.Close();

return diffResultSize;
}
}
}
17 changes: 12 additions & 5 deletions Decompression/Structs.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.PortableExecutable;
using System.Text;
using System.Threading.Tasks;
using static COD_FF_Extractor.Structs;

namespace MW2_FF_Extractor
namespace COD_FF_Extractor
{
public static class Structs
{
//IWffa100
public static readonly byte[] FFHeaderMagic = new byte[] { 0x49, 0x57, 0x66, 0x66, 0x61, 0x31, 0x30, 0x30 };

//IWffd100
public static readonly byte[] FFPatchHeaderMagic = new byte[] { 0x49, 0x57, 0x66, 0x66, 0x64, 0x31, 0x30, 0x30 };

public enum FastFileType
{
REGULAR,
PATCH
}
public struct FastFileHeader
{
public byte[] Magic;
public int Version;
public byte isCompressed;
public byte isEncrypted;
}

public struct BlockHeader
{
Expand Down
168 changes: 168 additions & 0 deletions Decompression/Warzone.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.PortableExecutable;
using System.Text;
using System.Threading.Tasks;
using static COD_FF_Extractor.Structs;

namespace COD_FF_Extractor.Decompression
{
class Warzone
{
private static FastFileHeader ReadFastFileHeader(BinaryReader reader)
{
FastFileHeader header = new FastFileHeader();

reader.BaseStream.Position = 0;
header.Magic = reader.ReadBytes(0x08);
header.Version = reader.ReadInt32();
reader.ReadInt32();
header.isCompressed = reader.ReadByte();
header.isEncrypted = reader.ReadByte();

return header;
}

private static void decompressBlocks(BinaryReader reader, BinaryWriter writer)
{
// read block header
BlockHeader headerBlock = new BlockHeader();
headerBlock.decompressedDataLen = reader.ReadInt32();
reader.BaseStream.Position += 0x3;
headerBlock.compressionType = reader.ReadByte();

// start decompressing blocks
long totalDecompressedDataSize = 0;
int loopCount = 1;
Block block = new Block();
while (totalDecompressedDataSize < headerBlock.decompressedDataLen)
{
#if DEBUG
Console.WriteLine("Block Offset: {0:X}, total: {1:X}", reader.BaseStream.Position, totalDecompressedDataSize);
#endif

if (loopCount == 512)
{
// Skip an RSA block
reader.ReadBytes(0x4000);
loopCount = 0;
}

block.compressedLen = (reader.ReadInt32() + 3) & 0xFFFFFFC; // calc allignment
block.decompressedLen = reader.ReadInt32();
reader.BaseStream.Position += 0x4;

byte[] compressedData = reader.ReadBytes(block.compressedLen);
byte[] decompressedData;
switch (headerBlock.compressionType)
{
// Decompress None
case 1:
decompressedData = compressedData;
break;

// unknown
case 4:
case 5:
throw new Exception("unimplemented compression type!");

// Decompress Oodle
case 6:
case 7:
case 12:
case 13:
case 14:
case 15:
case 16:
case 17:
decompressedData = OodleSharp.Oodle.Decompress(compressedData, block.compressedLen, block.decompressedLen);
break;

default:
throw new Exception("Unknown type of compression!");
}

if (decompressedData == null)
throw new Exception("Decompressor returned null!");

writer.Write(decompressedData);

totalDecompressedDataSize += block.decompressedLen;
loopCount++;
}
}

private static void decompressRegularFF(BinaryReader reader, BinaryWriter writer)
{
// move to start of the signed fast file
long fastFileStart = 0x124;
reader.BaseStream.Position = fastFileStart;

// skip SHA256 hashes
reader.BaseStream.Position += 0x8000;

decompressBlocks(reader, writer);
}

private static void decompressPatchFF(BinaryReader reader, BinaryWriter writer)
{
reader.BaseStream.Position = 0x30;
if (reader.ReadInt32() == 0) // check if it's an empty patch file, exit if it is
return;

// move to start of the signed fast file
long fastFileStart = 0x23C;
reader.BaseStream.Position = fastFileStart;

// skip SHA256 hashes
reader.BaseStream.Position += 0x8000;

decompressBlocks(reader, writer);
}

public static void decompress(string path, string outPath)
{
BinaryReader reader = Utils.OpenBinaryReader(path);
if (reader == null)
throw new Exception(String.Format("Unable to open file {0}.", path));

BinaryWriter writer = Utils.OpenBinaryWriter(outPath);
if (writer == null)
throw new Exception(String.Format("Unable to create file {0}.", outPath));

FastFileHeader header = ReadFastFileHeader(reader);

if (Utils.CompareArray(header.Magic, FFHeaderMagic)) // regular FastFile
{
decompressRegularFF(reader, writer);
}
else if (Utils.CompareArray(header.Magic, FFPatchHeaderMagic)) // patch FastFile
{
decompressPatchFF(reader, writer);
}
else
throw new Exception("Invalid FF magic.");

reader.Close();
writer.Close();
}

// gets the resulting size of a fastfile when patched with a patch fastfile
// patchFilePath must be the path to a patch file
public static int getDiffResultSizeFromFile(string patchFilePath)
{
BinaryReader reader = Utils.OpenBinaryReader(patchFilePath);
if (reader == null)
throw new Exception(String.Format("Unable to open file {0}.", patchFilePath));

reader.BaseStream.Position = 0x150;
int diffResultSize = reader.ReadInt32();

reader.Close();

return diffResultSize;
}
}
}
Loading

0 comments on commit 331aaed

Please sign in to comment.