Understanding and Modding Call of Duty FastFiles
Introduction:
For many of the older Call of Duty titles (ranging from Call of Duty 4 to Black Ops, and beyond), game data is packed inside FastFiles (often referred to as "ZoneFiles" or "FFs"). Over the course of my research, I learned how these files are structured, how they're loaded, and the methods used to compress, encrypt, and verify them. Additionally, I looked into how certain assets like the incredibly popular rawfile (GSC script containers) are stored. Below, I'll walk through the technical details I've gathered and share some code snippets in Rust to demonstrate how one might parse or handle these files.
The information provided in this post is for educational and research purposes only, specifically focusing on reverse engineering game files and software structures. No malicious intent is implied or encouraged. All examples and technical details are shared under the principles of fair use, to foster learning, inform the community, and advance the understanding of how these files function internally.
Please be aware that reverse engineering and modifying game files may violate the terms of service, end-user license agreements, or other legal agreements. if you choose to explore or replicate any of these topics, you do so at your own risk, you assume full responsibility for any consequences, including the risk of voiding warranties, legal repercussions, or other penalties.
FastFile Basics
A FastFile (FF) is essentially a container for various game assets. Nearly every CoD engine game from CoD4 onward uses FFs to store data like textures, sounds, scripts, models, and more. The major traits of FastFiles include:
- Magic string at the beginning (e.g., "IWff0100", "IWffu100", "TAff0100", or "S1ff0100"), indicating the game/engine flavor.
- A version integer which helps identify the exact CoD engine build and platform (PC, Xbox 360, PS3, etc.).
- (Often) RSA-2048 signatures for security, plus zlib compression for reducing file size.
- In some later games, additional encryption layers are applied, such as Salsa20 on Black Ops 1 for PS3/Xbox 360.
(Special note, FastFiles area also ready in littleEndian so when trying to read the data for example the version integer, you will have to do so in littleEndian).
Versions and System Capabilities
After reading the version field, you can determine which system (Xbox 360, PS3, PC) and which CoD title the FF belongs to. Pre-alpha versions (like early CoD4) sometimes lack the version integer, requiring special handling. For example:
| System | MW2 | BO1 | MW3 | BO2 |
|---|---|---|---|---|
| Xbox 360 | 0x10D | 0x1D9 | 0x70 | 0x92 |
| PC | 0x114 | 0x1D9 | 0x01 | 0x93 |
| PS3 | 0x10D | 0x1D9 | 0x70 | 0x92 |
(The specific version numbers vary across different references. The table above is just a snapshot.)
Pointers in Zone Files
Internally, CoD’s engine uses relocation tricks. If a pointer in the file is -1 or -2, the engine expects certain data to follow. If the pointer is a “real” offset, its upper bits indicate which block in g_streamBlocks[] is used, and the lower bits are the offset. The engine relocates or aligns data after loading, which can shift these offsets around. Key alignment notes:
- Strings are typically 4-byte aligned.
- Asset headers (like rawfile headers, xmodels, etc.) can be in pools.
- The zone header itself is stripped out once the engine is done reading it, so offsets to zone data can shift.
FastFiles and Zone Files (MW2)
Main FastFile Structure
The main structure of a FastFile in Modern Warfare 2 (MW2) is as follows:
pub struct FastFile {
pub magic: Vec<char>, // "IWff0100" or "IWffu100"
pub version: i32, // e.g., 0x10D (X360) or 0x114 (PC)
pub updateable: bool,
pub date_created: u64,
pub language: i32, // e.g., LANGUAGE_ENGLISH=1, etc.
pub entry_count: i32,
pub size: i32,
pub max_size: i32,
pub compressed: Vec<u8>,
pub zone: Vec<u8>,
} Unsigned vs. Signed FastFiles
- Unsigned ("IWffu100") are simpler. Commonly used for single-player assets on Xbox or all assets on PS3. The remainder of the file is just zlib-compressed zone data.
- Signed ("IWff0100") includes additional RSA checks. A subheader with 2048-bit signatures ensures the data blocks (XBlocks) are authentic.
Unsigned FF example
- Read the Header
- Imediately after the header, read compressed data.
- Decompress it with zLib → yields the zone file.
Signed FF example
- Read the Header
- Next is a subheader containing signature hashes (AuthHash,AuthSignatureHash, etc).
- The actual data is split into XBlocks (0x200000 bytes each on X360, 0x10000 bytes on PS3).
- Each XBlocks has a signature. The engine verifies them on load.
- Decompress each block (if needed) and combine them → yields the zone file.
Compression Details
- Xbox 360: The entire zone is compressed as one chunk for unsigned FFs. For signed FFs, each XBlock is 2 MB, with a 0x2000-byte hash block for verification.
- PS3: Each 0x10000 byte block is compressed individually with zlib. The last block is padded with zeros if not aligned. These blocks get appended after the subheader or as part of the XBlocks.
Zone File Structure
Once we decompress the data from the FF, we get the Zone File. Internally, the engine calls this the XFile. It has its own header (XFile) plus the list of script strings and assets. The engine then relocates pointers, aligns memory, and sets up assets in RAM.
ZoneFile Header
Here's a typical Zone File Structure (MW2):
struct ZoneHeader {
size: i32,
unk1: i32,
unk2: i32,
unk3: i32,
unk4: i32,
images_size: i32,
x_block_size: i32,
unk5: i32,
unk6: i32,
script_string_list_count: i32,
script_string_list_header: i32,
x_asset_count: i32,
x_asset_header: i32,
script_string_list: Vec<ScriptString>, // See ScriptStrings Struct
} - size: total size of this zone file (post-decompression).
- images_size: size of the image data (textures, etc).
- x_block_size: array to indicate how much memory the engine should allocate for each block type.
- script_string_list_count: number of script strings in the list.
- script_string_list_header: offset to the start of the script string list.
- x_asset_count: number of assets in the list.
- x_asset_header: offset to the start of the asset list.
- script_string_list: list of script strings
Asset List
Immediately following the ZoneHeader is an asset list, containing:
- An integer count of script strings.
- A pointer table to those strings.
- An integer count of assets.
- A pointer table to the actual assets.
pub struct XAssetList {
pub script_string_count: i32,
pub script_strings: *const *const std::os::raw::c_char,
pub asset_count: i32,
pub assets: *mut XAsset,
}
pub struct XAsset {
pub asset_type: i32,
pub header: *mut XAssetHeader,
} Each XAsset has a type (xmodel, rawfile, material, etc.) and a header pointer that references the actual loaded data structure.
Asset Types
One of the most interesting enumerations in CoD modding is the XAssetType. It covers everything from images (image), materials (material), to the rawfile scripts that modders love tinkering with:
| Asset Type | Xbox ID | PS3 ID | PC ID | Is Used | Max Count | Asset Size (PC) |
|---|---|---|---|---|---|---|
| physpreset | 0x00 | 0x00 | 0x00 | True | 0x40 | 0x2C |
| phys_collmap | 0x01 | 0x01 | 0x01 | True | 0x400 | 0x48 |
| xanim | 0x02 | 0x02 | 0x02 | True | 0x1000 | 0x58 |
| xmodelsurfs | 0x03 | 0x03 | 0x03 | True | 0x1000 | 0x24 |
| xmodel | 0x04 | 0x04 | 0x04 | True | 0x600 | 0x130 |
| material | 0x05 | 0x05 | 0x05 | True | 0x1000 | 0x60 |
| pixelshader | 0x06 | 0x06 | 0x06 | True | 0x1FA0 | 0x10 |
| vertexshader | N/A | 0x07 | 0x07 | True | 0x400 | 0x10 |
| vertexdecl | N/A | N/A | 0x08 | True | 0x30 | 0x64 |
| techset | 0x07 | 0x08 | 0x09 | True | 0x300 | 0xCC |
| image | 0x08 | 0x09 | 0x0A | True | 0xE00 | 0x20 |
| sound | 0x09 | 0x0A | 0x0B | True | 0x3E80 | 0x0C |
| sndcurve | 0x0A | 0x0B | 0x0C | True | 0x40 | 0x88 |
| loaded_sound | 0x0B | 0x0C | 0x0D | True | 0x546 | 0x2C |
| col_map_sp | 0x0C | 0x0D | 0x0E | True | 1 | 0x100 |
| col_map_mp | 0x0D | 0x0E | 0x0F | True | 1 | 0x100 |
| com_map | 0x0E | 0x0F | 0x10 | True | 1 | 0x10 |
| game_map_sp | 0x0F | 0x10 | 0x11 | True | 1 | 0x38 |
| game_map_mp | 0x10 | 0x11 | 0x12 | True | 1 | 0x08 |
| map_ents | 0x11 | 0x12 | 0x13 | True | 2 | 0x2C |
| fx_map | 0x12 | 0x13 | 0x14 | True | 1 | 0x74 |
| gfx_map | 0x13 | 0x14 | 0x15 | True | 1 | 0x274 |
| lightdef | 0x14 | 0x15 | 0x16 | True | 0x20 | 0x10 |
| ui_map | 0x15 | 0x16 | 0x17 | False | 0 | N/A |
| font | 0x16 | 0x17 | 0x18 | True | 0x10 | 0x18 |
| menufile | 0x17 | 0x18 | 0x19 | True | 0x80 | 0x0C |
| menu | 0x18 | 0x19 | 0x1A | True | 0x264 | 0x190 |
| localize | 0x19 | 0x1A | 0x1B | True | 0x1B58 | 0x08 |
| weapon | 0x1A | 0x1B | 0x1C | True | 0x578 | 0x684 |
| snddriverglobals | 0x1B | 0x1C | 0x1D | True | 1 | N/A |
| fx | 0x1C | 0x1D | 0x1E | True | 0x258 | 0x20 |
| impactfx | 0x1D | 0x1E | 0x1F | True | 4 | 0x08 |
| aitype | 0x1E | 0x1F | 0x20 | False | 0 | N/A |
| mptype | 0x1F | 0x20 | 0x21 | False | 0 | N/A |
| character | 0x20 | 0x21 | 0x22 | False | 0 | N/A |
| xmodelalias | 0x21 | 0x22 | 0x23 | False | 0 | N/A |
| rawfile | 0x22 | 0x23 | 0x24 | True | 0x400 | 0x10 |
| stringtable | 0x23 | 0x24 | 0x25 | True | 0x190 | 0x10 |
| leaderboarddef | 0x24 | 0x25 | 0x26 | True | 0x64 | 0x18 |
| structureddatadef | 0x25 | 0x26 | 0x27 | True | 0x18 | 0x0C |
| tracer | 0x26 | 0x27 | 0x28 | True | 0x20 | 0x70 |
| vehicle | 0x27 | 0x28 | 0x29 | True | 0x80 | 0x2D0 |
| addon_map_ents | 0x28 | 0x29 | 0x2A | True | 1 | 0x24 |
This enumeration helps the engine figure out how to parse each asset’s header, how to place it in memory, and which code to call during load or usage in-game.
FastFiles and Zone Files (BO1)
Structure and Encryption
Treyarch’s Black Ops 1 introduced additional encryption for FFs, especially on console. The subheader changed slightly, containing a different magic ("PHEEBs71"). After the subheader, the zone is split into 0x7FC0-byte blocks for PS3/X360. Each block is:
- Compressed with a more intensive zlib setting.
- Encrypted with Salsa20, using a specific key.
pub struct AuthSignature {
pub bytes: [u8; 256],
}
pub struct AuthHeader {
pub unknown: i32,
pub magic: [u8; 8], // "PHEEBs71"
pub reserved: i32,
pub fastfile_name: [u8; 32],
pub signed_subheader_hash: AuthSignature,
} If you fully decrypt and decompress these blocks, you eventually get to the zone file (which, again, has the standard XFile header and assets).
Salsa20 Keys
Each key is 32-Bytes in size
pub struct Salsa20Key {
pub key: [u8; 32],
}
pub static PS3_KEY: Salsa20Key = Salsa20Key {
key: [
0x0C, 0x99, 0xB3, 0xDD,
0xB8, 0xD6, 0xD0, 0x84,
0x5D, 0x11, 0x47, 0xE4,
0x70, 0xF2, 0x8A, 0x8B,
0xF2, 0xAE, 0x69, 0xA8,
0xA9, 0xF5, 0x34, 0x76,
0x7B, 0x54, 0xE9, 0x18,
0x0F, 0xF5, 0x53, 0x70,
],
};
pub static XBOX_360_KEY: Salsa20Key = Salsa20Key {
key: [
0x1A, 0xC1, 0xD1, 0x2D,
0x52, 0x7C, 0x59, 0xB4,
0x0E, 0xCA, 0x61, 0x91,
0x20, 0xFF, 0x82, 0x17,
0xCC, 0xFF, 0x09, 0xCD,
0x16, 0x89, 0x6F, 0x81,
0xB8, 0x29, 0xC7, 0xF5,
0x27, 0x93, 0x40, 0x5D,
],
}; The game code calls Salsa20 to decrypt each compressed block. If you want to recreate modded FFs that run on console, you need these keys (and must also satisfy signature checks if the console demands them).
(A Note, the PS3 keys were dumped by simply dumping the SPU at runtime).
Rawfiles (GSC Scripts)
Within the zone’s asset list, one of the most sought-after asset types is the rawfile. A rawfile often corresponds to a .gsc script that the engine runs for logic (e.g., handling spawn logic, scoreboard, weapons, game modes, etc.). This code is reminiscent of C/Java in syntax, but it’s a proprietary language the engine interprets.
(Depicted above is a FastFile Editor I wrote in Rust, using WinAPI Calls for GUI and Windowing).
A Snippet of GSC code looks like this:
init()
{
precacheString(&"MP_CHALLENGE_COMPLETED");
if ( !mayProcessChallenges() )
return;
level.missionCallbacks = [];
registerMissionCallback( "playerKilled", ::ch_kills );
// ...
}
onPlayerConnect()
{
for (;;)
{
level waittill("connected", player);
// ...
player thread onPlayerSpawned();
player thread initMissionData();
// etc.
}
} When you locate a rawfile asset inside the zone, its header typically contains:
- Script Length (in bytes).
- Script Name (e.g., "maps/mp/gametypes/_globallogic.gsc").
- Script Data (the actual script code).
Modding these is often the primary reason many delve into reverse-engineering CoD’s FastFile system.
StringTable Assets
In Call of Duty’s engine, StringTable assets function much like embedded CSV (Excel-like) tables. Developers can define data in .csv or .txt files (e.g., raw/sometable.csv), and the engine compiles them into an in-memory table structure. The basic concept: each table has a name, a column count, a row count, and a pointer to the actual cell data.
General Access
To index a cell at (desiredRow, desiredColumn), you compute:
desiredEntry = (columnCount * desiredRow) + desiredColumn Given that a StringTable has rowCount rows and columnCount columns, the total number of values is rowCount * columnCount.
Historical Variations
Call of Duty 4 & World at War
pub struct StringTable_Cod4WaW {
pub name: *const c_char,
pub column_count: i32,
pub row_count: i32,
pub values: *const *const c_char,
} These earliest iterations are quite simple. Each entry in the table is just a const char * pointer. No hashing or indexing array just a direct pointer to the cell’s string.
Modern Warfare 2, Modern Warfare 3, Ghosts, Advanced Warfare
pub struct StringTableCell_IW {
pub string: *const c_char,
pub hash: i32,
}
pub struct StringTable_IW {
pub name: *const c_char,
pub column_count: i32,
pub row_count: i32,
pub strings: *mut StringTableCell_IW,
} Note the addition of a hash field in each cell. This hash can be generated by a function like:
pub fn hash_infinity_ward(input: &str) -> u32 {
let mut hash = 0u32;
for &byte in input.as_bytes() {
// Convert to lowercase, then accumulate
let lower = byte.to_ascii_lowercase() as u32;
hash = lower + (31 * hash);
}
hash
} This is a classic CoD engine approach for quick lookups, often referred to as a variation of the Bernstein or 31x hash.
Black Ops 1, Black Ops 2, Black Ops 3
pub struct StringTableCell_Treyarch {
pub string: *const c_char,
pub hash: i32,
}
pub struct StringTable_Treyarch {
pub name: *const c_char,
pub column_count: i32,
pub row_count: i32,
pub values: *mut StringTableCell_Treyarch,
pub cell_index: *mut i16,
} Treyarch’s versions are similar but use a different hash function:
pub fn hash_treyarch(input: &str) -> u32 {
let mut hash = 5381u32;
for &byte in input.as_bytes() {
hash = ((hash << 5).wrapping_add(hash)).wrapping_add(byte as u32);
}
hash
} The presence of __int16 *cellIndex indicates an additional indexing layer, possibly for reordering or quickly jumping to specific rows.
Source Format (CSV-Like)
Developers (and modders) often see these csv or .txt files in the raw/ directory. A typical layout might be:
entry1,entry2,entry3
entry4,entry5,entry6 In actual CoD data, you can find more elaborate tables, such as clantagfeatures.csv (Seen Below).
#INDX,#name,#unlocklvl,#unlockplvl,#cost,#type,#data,#nframes,#phase,#frame1,#frame2,#frame3,#frame4,#frame5,#frame6,#frame7,#frame8
0,none,,,,tagcolor,,,,,,,,,,,
1,red,,13,250,tagcolor,1,,,,,,,,,,
2,green,,13,250,tagcolor,2,,,,,,,,,,
... Localize Assets
Localize assets are the simplest of all CoD assets. They provide a way for developers (and translators) to maintain large sets of localized strings without tangling with code. The structure hasn’t changed much from CoD4 Alpha all the way to BO3:
pub struct LocalizeEntry {
pub name: String,
pub value: String,
} - name: The string identifier or reference name, like SERVERISFULL.
- value: The localized text, such as Server is full.
Source Format: .str Files
Localize assets originate from .str files typically located at raw/english/localizedstrings/. Each file begins with a header:
VERSION "1"
CONFIG "C: reescod3cod3inStringEd.cfg"
FILENOTES "" Then each localized string is declared as:
REFERENCE SOME_STRING_ID
LANG_ENGLISH "Some text" Additional languages get appended similarly:
LANG_GERMAN "Wert"
LANG_SPANISH "#same" - The engine may prefix references with an internal identifier when compiling (e.g., EXE_SERVERISFULL).
- #same indicates re-use of the English string if no direct translation is provided.
Developers also add optional fields like NOTES or FLAGS which are ignored by the compiler but handy for commentary:
REFERENCE SERVERISFULL
NOTES "Message shown when a client tries to join a full server"
FLAGS "0"
LANG_ENGLISH "Server is full."
LANG_GERMAN "Server ist voll." In the compiled game data, these references become LocalizeEntry value, name. For modding or patching, you can replace the text in .str or manipulate the LocalizeEntry directly in the zone.
Conclusion
Modding Call of Duty was initially what drew me into reverse engineering in the first place. Much of this research was carried out years ago (were talking early 2010's), back when community-driven modding for these games was at its peak. Even though the data presented here might be considered “old news,” it still serves a valuable purpose: keeping the older CoD modding scene alive and encouraging new enthusiasts to experiment. This post only scratches the surface of the incredible variety of assets contained in FastFiles everything from menu files, custom maps, sound files, weapon definitions, animations, and beyond. Each asset type offers countless possibilities for creativity and customization. By understanding how FastFiles work, we can continue to breathe new life into classic titles and celebrate the vibrant community spirit that got so many of us started in reverse engineering.