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.

⚠ Disclaimer

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.
Many different sub-versions exist. Even within the same CoD title, PC, Xbox, and PS3 fastfiles can store data in slightly different ways (block sizes, alignment rules, etc.).

(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.
Many different sub-versions exist. Even within the same CoD title, PC, Xbox, and PS3 fastfiles can store data in slightly different ways (block sizes, alignment rules, etc.).

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.

GSC Code

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

StringTable

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.