Skip to content

Dev to main v3.1.0#52

Merged
primetime43 merged 13 commits intomainfrom
dev
Dec 29, 2025
Merged

Dev to main v3.1.0#52
primetime43 merged 13 commits intomainfrom
dev

Conversation

@primetime43
Copy link
Owner

@primetime43 primetime43 commented Dec 29, 2025

Dev to main v3.1.0

CoD4FastFileHandler and CoD5FastFileHandler now accept an isXbox360 parameter to select the correct (signed) header for Xbox 360 files. FastFileHandlerFactory and FastFileProcessor.Compress are updated to detect the Xbox 360 platform and use the appropriate header bytes, ensuring correct FastFile creation for Xbox 360.
…gned/unsigned

Simplify header logic by removing isXbox360 from CoD4/5 handlers and factory. Handlers now determine signed/unsigned header from the FastFile's IsSigned property during recompression, preserving the original file's status. Updated FastFileProcessor.Compress to use a signed parameter instead of platform-based detection. This improves accuracy and consistency when reading and writing FastFiles.
Update CoD4/CoD5 FastFile handlers to write a 256-byte signature block and compress zone data as a single zlib stream for Xbox 360 signed files. Preserve block-based format for unsigned files.
Rewrite signed FastFile output to use IWffs100 streaming header,
preserve hash table/auth data, and compress zone with full zlib
(best compression, 78 DA header). Write version in big-endian.
Fallback to zeros if hash table is unavailable. Unsigned/PS3
block format is unchanged.
- Enable creation of Xbox 360 signed FastFiles (.ff) for CoD4 and WaW, preserving hash table from original signed files
- Add UI options for Xbox 360 signed formats and prompt user for original FF when required
- Refactor CoD4/CoD5 handlers to use new FastFileProcessor.CompressXbox360Signed method
- Implement streaming header, hash table, and zlib stream logic for signed format in FastFileProcessor and FastFileConstants
- Extend Compiler to support direct compilation to Xbox 360 signed format
- Ensure correct headers and version bytes for all formats
- Maintain compatibility with unsigned and other platform FastFiles
Replaced separate COD5, COD4, and MW2 FastFile menu items with a single "Open FastFile..." option that auto-detects the game type. Updated the menu item's tooltip and shortcut, and removed all related submenu code and event handlers. The file dialog now allows selection of any FastFile type, streamlining the user experience.
Adds a "Platform" dropdown to the GUI, allowing users to select the target platform (PS3, Xbox 360 unsigned, Xbox 360 signed, PC, Wii) for FastFile compilation. The compile logic and Compiler class are updated to use the selected platform, ensuring correct versioning and format for each output type. Xbox 360 signed output now requires loading an original signed FastFile to extract the hash table, with appropriate user warnings. The About dialog and UI layout are updated to reflect multi-platform support. MW2FastFileHandler and related logic now preserve and handle signed/unsigned status correctly during recompression.
Refactored the Recompress method to always output unsigned block format for MW2 FastFiles, regardless of original signature status. Removed conditional logic for signed Xbox 360 files and legacy streaming format. Now always writes IWffu100 header, preserves version, adds minimal extended header, compresses in 64KB blocks, and appends explicit end marker. Updated comments to clarify MW2 XBlock vs. older formats and rationale for unsigned output.
Move MW2 FastFile compression logic from MW2FastFileHandler to a new FastFileProcessor.CompressMW2 method. This centralizes and unifies platform-specific compression for PS3 (block-based) and Xbox 360/PC (single zlib stream). Removes old inlined helpers and adds reusable methods for block compression and extended header writing, improving maintainability and correctness.
@primetime43 primetime43 self-assigned this Dec 29, 2025
Copilot AI review requested due to automatic review settings December 29, 2025 07:16
@primetime43 primetime43 changed the title Dev to maon v3.1.0 Dev to main v3.1.0 Dec 29, 2025
@primetime43 primetime43 merged commit 9f9b694 into main Dec 29, 2025
5 checks passed
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces version 3.1.0 of the FastFile Tool suite, adding comprehensive support for Xbox 360 signed format FastFiles across all GUI applications. The update expands platform support beyond PS3 to include Xbox 360 (both signed and unsigned variants), PC, and Wii platforms.

Key Changes:

  • Added Xbox 360 signed format support with hash table preservation from original files
  • Expanded platform-specific compression methods including MW2 format support
  • Enhanced all three GUI applications with platform selection and version display

Reviewed changes

Copilot reviewed 20 out of 23 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
FastFileToolGUI/MainForm.cs Added Xbox 360 signed format handling with original FF validation and platform selection expansion
FastFileToolGUI/MainForm.Designer.cs Updated window title to include version number
FastFileToolGUI/FastFileToolGUI.csproj Bumped version to 3.1.0
FastFileLib/FastFileProcessor.cs Implemented Xbox 360 signed compression, MW2 compression methods, and full zlib compression
FastFileLib/FastFileLib.csproj Bumped version to 3.1.0
FastFileLib/FastFileConverter.cs Updated zone header patching to support platform-specific offsets and CoD4/WaW differences
FastFileLib/FastFileConstants.cs Added Xbox 360 signed format constants and platform-specific zone header offset helpers
FastFileLib/Compiler.cs Refactored constructor to support platform selection and added Xbox 360 signed compilation
FastFileConverterGUI/Form1.cs Updated window title to include version number
FastFileConverterGUI/FastFileConverterGUI.csproj Bumped version to 3.1.0
FastFileCompilerGUI/MainForm.cs Added platform dropdown, Xbox 360 signed validation, and updated compilation workflow
FastFileCompilerGUI/MainForm.Designer.cs Added platform selection UI controls and tooltips
FastFileCompilerGUI/FastFileCompilerGUI.csproj Bumped version to 3.1.0
Files not reviewed (3)
  • Call of Duty FastFile Editor/UI/MainWindowForm.Designer.cs: Language not supported
  • FastFileCompilerGUI/MainForm.Designer.cs: Language not supported
  • FastFileToolGUI/MainForm.Designer.cs: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

hashTableAndAuth = new byte[FastFileConstants.Xbox360SignedHashTableSize];
using var origReader = new BinaryReader(File.OpenRead(originalFfPath));
origReader.BaseStream.Seek(FastFileConstants.Xbox360SignedHashTableStart, SeekOrigin.Begin);
origReader.Read(hashTableAndAuth, 0, hashTableAndAuth.Length);
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BinaryReader.Read method is used here, but its return value (number of bytes actually read) is not checked. If the file is shorter than expected or if an I/O error occurs, fewer bytes than requested might be read, leading to incomplete or corrupted hash table data. Consider using ReadBytes or checking the return value to ensure all expected bytes were read.

Suggested change
origReader.Read(hashTableAndAuth, 0, hashTableAndAuth.Length);
int totalRead = 0;
while (totalRead < hashTableAndAuth.Length)
{
int bytesRead = origReader.Read(hashTableAndAuth, totalRead, hashTableAndAuth.Length - totalRead);
if (bytesRead == 0)
{
break;
}
totalRead += bytesRead;
}
if (totalRead != hashTableAndAuth.Length)
{
// Incomplete hash table read; fall back to zeroed hash table behavior.
hashTableAndAuth = null;
}

Copilot uses AI. Check for mistakes.
{
using var origReader = new BinaryReader(File.OpenRead(_originalFfPath));
origReader.BaseStream.Seek(FastFileConstants.Xbox360SignedHashTableStart, SeekOrigin.Begin);
origReader.Read(hashTableAndAuth, 0, hashTableAndAuth.Length);
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BinaryReader.Read method is used here, but its return value (number of bytes actually read) is not checked. If the file is shorter than expected or if an I/O error occurs, fewer bytes than requested might be read, leading to incomplete or corrupted hash table data. Consider using ReadBytes or checking the return value to ensure all expected bytes were read.

Suggested change
origReader.Read(hashTableAndAuth, 0, hashTableAndAuth.Length);
byte[] readBytes = origReader.ReadBytes(hashTableAndAuth.Length);
Array.Copy(readBytes, 0, hashTableAndAuth, 0, readBytes.Length);

Copilot uses AI. Check for mistakes.

/// <summary>
/// Compresses a zone file to a FastFile with Xbox 360 signed format.
/// Xbox 360 signed files use IWffs100 streaming format with a single zlib stream.
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment describes the streaming header as "IWffs100", but according to the earlier documentation comment (line 902), Xbox 360 signed files use "IWff0100" (signed header), not "IWffs100" (streaming header). The streaming header is a separate 8-byte header written after the main signed header. The comment should clarify that Xbox 360 signed files use both IWff0100 and IWffs100 headers in sequence.

Suggested change
/// Xbox 360 signed files use IWffs100 streaming format with a single zlib stream.
/// Xbox 360 signed files use an IWff0100 signed header followed by an IWffs100 streaming header,
/// with a single zlib stream.

Copilot uses AI. Check for mistakes.
}

// Read entire zone file and compress as single stream
byte[] zoneData = br.ReadBytes((int)br.BaseStream.Length);
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading the entire file into memory using ReadBytes with BaseStream.Length cast to int could cause issues with large files. If the zone file is larger than 2GB (Int32.MaxValue), the cast will overflow and cause incorrect behavior. Additionally, for very large files, this could cause OutOfMemoryException. Consider either checking the file size first, using a streaming approach, or documenting the maximum supported file size.

Suggested change
byte[] zoneData = br.ReadBytes((int)br.BaseStream.Length);
long zoneLength = br.BaseStream.Length;
if (zoneLength > int.MaxValue)
{
throw new IOException("Zone file is too large to process. Maximum supported size is 2GB.");
}
byte[] zoneData = br.ReadBytes((int)zoneLength);

Copilot uses AI. Check for mistakes.
}

// Read entire zone file and compress as single stream
byte[] zoneData = br.ReadBytes((int)br.BaseStream.Length);
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading the entire file into memory using ReadBytes with BaseStream.Length cast to int could cause issues with large files. If the zone file is larger than 2GB (Int32.MaxValue), the cast will overflow and cause incorrect behavior. Additionally, for very large files, this could cause OutOfMemoryException. Consider either checking the file size first, using a streaming approach, or documenting the maximum supported file size.

Suggested change
byte[] zoneData = br.ReadBytes((int)br.BaseStream.Length);
long zoneLength = br.BaseStream.Length;
if (zoneLength > int.MaxValue)
{
throw new IOException("Zone file is too large to be processed (maximum supported size is 2GB).");
}
byte[] zoneData = br.ReadBytes((int)zoneLength);

Copilot uses AI. Check for mistakes.
hashTableAndAuth = new byte[FastFileConstants.Xbox360SignedHashTableSize];
using var origReader = new BinaryReader(File.OpenRead(originalFfPath));
origReader.BaseStream.Seek(FastFileConstants.Xbox360SignedHashTableStart, SeekOrigin.Begin);
origReader.Read(hashTableAndAuth, 0, hashTableAndAuth.Length);
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BinaryReader.Read method is used here, but its return value (number of bytes actually read) is not checked. If the file is shorter than expected or if an I/O error occurs, fewer bytes than requested might be read, leading to incomplete or corrupted hash table data. Consider using ReadBytes or checking the return value to ensure all expected bytes were read.

Suggested change
origReader.Read(hashTableAndAuth, 0, hashTableAndAuth.Length);
int bytesRead = origReader.Read(hashTableAndAuth, 0, hashTableAndAuth.Length);
if (bytesRead != hashTableAndAuth.Length)
{
throw new EndOfStreamException("Unexpected end of original FF file while reading hash table and auth data.");
}

Copilot uses AI. Check for mistakes.
Comment on lines +199 to +206
if (xbox360Signed && originalFfPath != null)
{
FastFileProcessor.CompressXbox360Signed(packInputTextBox.Text, packOutputTextBox.Text, gameVersion, originalFfPath);
}
else
{
Compress(packInputTextBox.Text, packOutputTextBox.Text, gameVersion, platform);
}
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition checks both xbox360Signed and originalFfPath != null, but at this point in the code flow, if xbox360Signed is true, originalFfPath is guaranteed to be non-null due to the validation at lines 173-178 (which returns early if the user cancels). The null check is redundant and could be simplified to just if (xbox360Signed), or the originalFfPath parameter in the method call could use the null-forgiving operator since we know it's non-null here.

Copilot uses AI. Check for mistakes.
finally
{
// Clean up temp file
try { File.Delete(tempZonePath); } catch { }
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The empty catch block silently swallows any exceptions that occur during temp file deletion. While this might be intentional to prevent cleanup failures from affecting the overall operation, it could hide legitimate issues (e.g., permissions problems, disk full). Consider at minimum logging the exception or using a more specific exception filter to only catch expected exceptions like IOException or UnauthorizedAccessException.

Suggested change
try { File.Delete(tempZonePath); } catch { }
try
{
File.Delete(tempZonePath);
}
catch (IOException ex)
{
System.Diagnostics.Debug.WriteLine($"Failed to delete temp file '{tempZonePath}': {ex}");
}
catch (UnauthorizedAccessException ex)
{
System.Diagnostics.Debug.WriteLine($"Failed to delete temp file '{tempZonePath}': {ex}");
}

Copilot uses AI. Check for mistakes.
Comment on lines +900 to +1001
/// <summary>
/// Compresses a zone file to a FastFile with Xbox 360 signed format.
/// Xbox 360 signed files use IWffs100 streaming format with a single zlib stream.
/// </summary>
/// <param name="inputPath">Path to the .zone file</param>
/// <param name="outputPath">Path to output the .ff file</param>
/// <param name="gameVersion">Target game version</param>
/// <param name="originalFfPath">Path to original FF file (to preserve hash table)</param>
/// <returns>1 (single stream compressed)</returns>
public static int CompressXbox360Signed(string inputPath, string outputPath, GameVersion gameVersion, string originalFfPath)
{
// Read hash table from original file before opening output
byte[] hashTableAndAuth = null;
if (!string.IsNullOrEmpty(originalFfPath) && File.Exists(originalFfPath))
{
hashTableAndAuth = new byte[FastFileConstants.Xbox360SignedHashTableSize];
using var origReader = new BinaryReader(File.OpenRead(originalFfPath));
origReader.BaseStream.Seek(FastFileConstants.Xbox360SignedHashTableStart, SeekOrigin.Begin);
origReader.Read(hashTableAndAuth, 0, hashTableAndAuth.Length);
}

using var br = new BinaryReader(new FileStream(inputPath, FileMode.Open, FileAccess.Read), Encoding.Default);
using var bw = new BinaryWriter(new FileStream(outputPath, FileMode.Create, FileAccess.Write), Encoding.Default);

// Write signed header (IWff0100)
bw.Write(FastFileConstants.SignedHeaderBytes);

// Write version (big-endian)
bw.Write(FastFileInfo.GetVersionBytes(gameVersion, "Xbox360"));

// Write streaming header (IWffs100)
bw.Write(FastFileConstants.StreamingHeaderBytes);

// Write hash table and auth data (preserved from original or zeros)
if (hashTableAndAuth != null)
{
bw.Write(hashTableAndAuth);
}
else
{
bw.Write(new byte[FastFileConstants.Xbox360SignedHashTableSize]);
}

// Read entire zone file and compress as single stream
byte[] zoneData = br.ReadBytes((int)br.BaseStream.Length);
byte[] compressedData = CompressFullZlib(zoneData);
bw.Write(compressedData);

// No end marker for signed format
return 1;
}

/// <summary>
/// Compresses a zone file to a FastFile with Xbox 360 signed format using provided version bytes.
/// </summary>
/// <param name="inputPath">Path to the .zone file</param>
/// <param name="outputPath">Path to output the .ff file</param>
/// <param name="versionBytes">Version bytes (4 bytes, big-endian)</param>
/// <param name="originalFfPath">Path to original FF file (to preserve hash table)</param>
/// <returns>1 (single stream compressed)</returns>
public static int CompressXbox360Signed(string inputPath, string outputPath, byte[] versionBytes, string originalFfPath)
{
// Read hash table from original file before opening output
byte[] hashTableAndAuth = null;
if (!string.IsNullOrEmpty(originalFfPath) && File.Exists(originalFfPath))
{
hashTableAndAuth = new byte[FastFileConstants.Xbox360SignedHashTableSize];
using var origReader = new BinaryReader(File.OpenRead(originalFfPath));
origReader.BaseStream.Seek(FastFileConstants.Xbox360SignedHashTableStart, SeekOrigin.Begin);
origReader.Read(hashTableAndAuth, 0, hashTableAndAuth.Length);
}

using var br = new BinaryReader(new FileStream(inputPath, FileMode.Open, FileAccess.Read), Encoding.Default);
using var bw = new BinaryWriter(new FileStream(outputPath, FileMode.Create, FileAccess.Write), Encoding.Default);

// Write signed header (IWff0100)
bw.Write(FastFileConstants.SignedHeaderBytes);

// Write version bytes
bw.Write(versionBytes);

// Write streaming header (IWffs100)
bw.Write(FastFileConstants.StreamingHeaderBytes);

// Write hash table and auth data (preserved from original or zeros)
if (hashTableAndAuth != null)
{
bw.Write(hashTableAndAuth);
}
else
{
bw.Write(new byte[FastFileConstants.Xbox360SignedHashTableSize]);
}

// Read entire zone file and compress as single stream
byte[] zoneData = br.ReadBytes((int)br.BaseStream.Length);
byte[] compressedData = CompressFullZlib(zoneData);
bw.Write(compressedData);

// No end marker for signed format
return 1;
}
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two overloads of CompressXbox360Signed contain nearly identical code with only a 2-line difference (lines 928 vs 979). This duplication makes maintenance harder and increases the risk of bugs when updating one version but forgetting the other. Consider refactoring by having the GameVersion overload call the byte[] version after converting the GameVersion to version bytes, which would eliminate the duplication.

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +110
var fastFile = new List<byte>();

// Write signed header (IWff0100)
fastFile.AddRange(FastFileConstants.SignedHeaderBytes);

// Write version (big-endian) - use Xbox360 platform version
fastFile.AddRange(FastFileInfo.GetVersionBytes(_gameVersion, "Xbox360"));

// Write streaming header (IWffs100)
fastFile.AddRange(FastFileConstants.StreamingHeaderBytes);

// Read and write hash table from original file (or zeros if not available)
byte[] hashTableAndAuth = new byte[FastFileConstants.Xbox360SignedHashTableSize];
if (!string.IsNullOrEmpty(_originalFfPath) && File.Exists(_originalFfPath))
{
using var origReader = new BinaryReader(File.OpenRead(_originalFfPath));
origReader.BaseStream.Seek(FastFileConstants.Xbox360SignedHashTableStart, SeekOrigin.Begin);
origReader.Read(hashTableAndAuth, 0, hashTableAndAuth.Length);
}
fastFile.AddRange(hashTableAndAuth);

// Compress entire zone as single zlib stream
byte[] compressedData = FastFileProcessor.CompressFullZlib(zoneData);
fastFile.AddRange(compressedData);

// No end marker for signed format
return fastFile.ToArray();
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CompileXbox360Signed method in Compiler.cs (lines 82-111) duplicates nearly identical logic to CompressXbox360Signed in FastFileProcessor.cs (lines 900-950). Both methods read the hash table from the original file, write the same headers, and compress the data. Consider having Compiler.CompileXbox360Signed call FastFileProcessor.CompressXbox360Signed with a temporary file, or extract the shared logic into a helper method to avoid this duplication.

Suggested change
var fastFile = new List<byte>();
// Write signed header (IWff0100)
fastFile.AddRange(FastFileConstants.SignedHeaderBytes);
// Write version (big-endian) - use Xbox360 platform version
fastFile.AddRange(FastFileInfo.GetVersionBytes(_gameVersion, "Xbox360"));
// Write streaming header (IWffs100)
fastFile.AddRange(FastFileConstants.StreamingHeaderBytes);
// Read and write hash table from original file (or zeros if not available)
byte[] hashTableAndAuth = new byte[FastFileConstants.Xbox360SignedHashTableSize];
if (!string.IsNullOrEmpty(_originalFfPath) && File.Exists(_originalFfPath))
{
using var origReader = new BinaryReader(File.OpenRead(_originalFfPath));
origReader.BaseStream.Seek(FastFileConstants.Xbox360SignedHashTableStart, SeekOrigin.Begin);
origReader.Read(hashTableAndAuth, 0, hashTableAndAuth.Length);
}
fastFile.AddRange(hashTableAndAuth);
// Compress entire zone as single zlib stream
byte[] compressedData = FastFileProcessor.CompressFullZlib(zoneData);
fastFile.AddRange(compressedData);
// No end marker for signed format
return fastFile.ToArray();
// Delegate to shared implementation in FastFileProcessor to avoid duplication.
return FastFileProcessor.CompressXbox360Signed(zoneData, _gameVersion, _originalFfPath);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants