Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Install Manifest Format

The Install manifest tracks which game files should be installed on disk and manages file tags for selective installation based on system requirements and user preferences.

Overview

The Install manifest maps content keys to installation paths and uses a tag bitmap system for selective installation based on platform, architecture, and locale. File sizes in entries support installation size estimation.

File Structure

The Install manifest is BLTE-encoded and contains:

[BLTE Container]
  [Header]
  [Tag Section]
  [File Entries]

Binary Format

struct InstallHeader {
    uint16_t magic;              // 'IN' (0x494E)
    uint8_t  version;            // Version (1 or 2)
    uint8_t  ckey_length;        // Content key length in bytes (16)
    uint16_t tag_count;          // Number of tags (big-endian)
    uint32_t entry_count;        // Number of file entries (big-endian)

    // Version 2+ fields (6 additional bytes, total 16 bytes)
    uint8_t  content_key_size;   // Content key size (Agent.exe) / loose file type (CascLib)
    uint32_t entry_count_v2;     // Additional entry count (big-endian)
    uint8_t  unknown;            // Unknown byte
};

For version 1, the content key size is derived as ckey_length + 4 (content key + 4-byte file size). Version 2 specifies content_key_size explicitly.

Tag Section

Tags categorize files for selective installation. Each tag consists of:

struct InstallTag {
    char     name[];             // Null-terminated tag name
    uint16_t type;               // Tag type (big-endian)
    uint8_t  bit_mask[];         // Bit mask ((entry_count + 7) / 8 bytes)
};

Important: The bit mask uses big-endian (MSB-first) bit ordering within each byte:

  • Bit 7 (MSB) corresponds to file index byte_index * 8 + 0

  • Bit 0 (LSB) corresponds to file index byte_index * 8 + 7

  • The mask for a given file index is 0x80 >> (file_index % 8)

File Entry

File entries follow the tag section:

struct InstallFileEntry {
    char     path[];             // Null-terminated file path
    uint8_t  content_key[16];    // MD5 content key
    uint32_t file_size;          // File size (big-endian)
};

Tag associations are determined by bit positions in each tag’s bit mask.

Tag System

Tag Types

TypeValueDescriptionExamples
Platform0x0001Operating system tagsWindows, OSX, Android, IOS
Architecture0x0002CPU architecture tagsx86_32, x86_64, arm64
Locale0x0003Language/region tagsenUS, deDE, frFR
Category0x0004Content category tagsspeech, text
Unknown0x0005Unknown tag type(seen in manifests)
Component0x0010Component tagsgame, launcher
Version0x0020Version tagslive, ptr, beta
Optimization0x0040Optimization tagsretail, debug
Region0x0080Region tagsUS, EU, KR
Device0x0100Device tagsdesktop, mobile
Mode0x0200Mode tagsonline, offline
Branch0x0400Branch tagsmain, experimental
Content0x0800Content tagscinematics, audio
Feature0x1000Feature tagsgraphics, physics
Expansion0x2000Expansion tagsbase, expansion1
Alternate0x4000Alternate contentAlternate, HighRes
Option0x8000Option tags(optional features)

Common Tags

Platform Tags:

- Windows, OSX, Android, IOS, Web

Architecture Tags:

- x86_32, x86_64, arm64

Locale Tags:

- enUS, enGB, deDE, frFR, esES, esMX, itIT,
  ruRU, koKR, zhTW, zhCN, ptBR, ptPT

Category Tags:

- speech, text

Alternate Tags:

- Alternate, HighRes

Tag Mask Usage

Tags use bit masks to indicate which files they apply to:

#![allow(unused)]
fn main() {
fn should_install(
    file_index: usize,
    tag: &InstallTag,
    selected: bool
) -> bool {
    let byte_index = file_index / 8;
    let bit_offset = file_index % 8;

    if byte_index >= tag.bit_mask.len() {
        return false;
    }

    // Big-endian (MSB-first) bit ordering within bytes: bit 0 = MSB
    let has_tag = (tag.bit_mask[byte_index] & (0x80 >> bit_offset)) != 0;
    has_tag && selected
}
}

Installation Planning

Size Calculation

Calculate installation size for selected tags:

#![allow(unused)]
fn main() {
fn calculate_install_size(
    entries: &[InstallFileEntry],
    selected_tags: u16
) -> u64 {
    entries.iter()
        .filter(|e| should_install(e, selected_tags))
        .map(|e| e.file_size as u64)
        .sum()
}
}

Path Resolution

Convert relative paths to absolute:

#![allow(unused)]
fn main() {
fn resolve_install_path(
    base_dir: &Path,
    entry: &InstallFileEntry
) -> PathBuf {
    let relative_path = std::str::from_utf8(&entry.path).unwrap();
    base_dir.join(relative_path)
}
}

File Categories

Essential Files

Files with tag mask 0x0000 or 0xFFFF:

  • Core executables

  • Essential libraries

  • Base configuration

  • Critical game data

Optional Content

Files with specific tag requirements:

  • High-resolution textures (HighResTextures tag)

  • Cinematics (Cinematics tag)

  • Additional languages (locale tags)

  • Developer tools (DevTools tag)

Implementation Example

#![allow(unused)]
fn main() {
struct InstallFile {
    header: InstallHeader,
    tags: Vec<InstallTag>,
    entries: Vec<InstallFileEntry>,
}

impl InstallFile {
    pub fn get_install_list(&self, tags: &[String]) -> Vec<InstallItem> {
        let tag_mask = self.build_tag_mask(tags);

        self.entries.iter()
            .filter(|e| should_install(e, tag_mask))
            .map(|e| InstallItem {
                content_key: e.content_key,
                install_path: String::from_utf8_lossy(&e.path).to_string(),
                file_size: e.file_size,
            })
            .collect()
    }

    fn build_tag_mask(&self, tag_names: &[String]) -> u16 {
        let mut mask = 0u16;

        for name in tag_names {
            if let Some(tag) = self.tags.iter().find(|t| t.name == name) {
                mask |= 1 << tag.id;
            }
        }

        mask
    }
}
}

Selective Installation

Platform-Specific

Install only files for current platform:

#![allow(unused)]
fn main() {
fn get_platform_tags() -> Vec<String> {
    let mut tags = vec!["Base".to_string()];

    #[cfg(target_os = "windows")]
    tags.push("Windows".to_string());

    #[cfg(target_arch = "x86_64")]
    tags.push("x64".to_string());

    tags
}
}

Language Selection

Install specific language assets:

#![allow(unused)]
fn main() {
fn get_locale_tags(selected_locale: &str) -> Vec<String> {
    vec![
        "Base".to_string(),
        selected_locale.to_string(),
    ]
}
}

Optimization Strategies

Parallel Installation

Install multiple files concurrently:

#![allow(unused)]
fn main() {
use rayon::prelude::*;

fn install_files(items: Vec<InstallItem>) {
    items.par_iter()
        .for_each(|item| {
            download_and_install(item);
        });
}
}

Incremental Updates

Track installed files for patching:

#![allow(unused)]
fn main() {
struct InstalledFiles {
    entries: HashMap<PathBuf, InstalledFileInfo>,
}

struct InstalledFileInfo {
    content_key: [u8; 16],
    file_size: u32,
    modified_time: SystemTime,
}
}

Validation

Post-Installation Verification

#![allow(unused)]
fn main() {
fn verify_installation(
    install_dir: &Path,
    install_file: &InstallFile,
    selected_tags: u16
) -> Result<()> {
    for entry in &install_file.entries {
        if !should_install(entry, selected_tags) {
            continue;
        }

        let path = install_dir.join(&entry.path);

        // Verify file exists
        if !path.exists() {
            return Err("Missing file");
        }

        // Verify file size
        let metadata = fs::metadata(&path)?;
        if metadata.len() != entry.file_size as u64 {
            return Err("Size mismatch");
        }
    }

    Ok(())
}
}

Repair Process

Detect and repair corrupted installations:

#![allow(unused)]
fn main() {
fn repair_installation(
    install_file: &InstallFile,
    install_dir: &Path
) -> Vec<RepairAction> {
    let mut actions = Vec::new();

    for entry in &install_file.entries {
        let path = install_dir.join(&entry.path);

        if !path.exists() {
            actions.push(RepairAction::Download(entry.content_key));
        } else if !verify_file(&path, entry) {
            actions.push(RepairAction::Redownload(entry.content_key));
        }
    }

    actions
}
}

Common Issues

  1. Tag conflicts: Multiple tags may include same file
  2. Path separators: Handle platform-specific separators
  3. Case sensitivity: File systems vary in case handling
  4. Symlink support: Some platforms don’t support symlinks
  5. Permission issues: Installation may require elevation

Special Considerations

Shared Files

Files used by multiple products:

#![allow(unused)]
fn main() {
struct SharedFile {
    content_key: [u8; 16],
    products: Vec<String>,
    ref_count: u32,
}
}

Uninstall Tracking

Track files for clean uninstall:

#![allow(unused)]
fn main() {
struct UninstallManifest {
    files: Vec<PathBuf>,
    directories: Vec<PathBuf>,
    registry_keys: Vec<String>,  // Windows only
}
}

Parser Implementation Status

Python Parser (cascette-py)

Status: Complete

Capabilities:

  • Version 1 header parsing with IN magic detection

  • Tag extraction with big-endian (MSB-first) bit ordering

  • Platform/architecture/locale tag type classification

  • File entry parsing with path, content key, and size

  • Tag-to-file association via bitmask resolution

  • BLTE decompression for compressed manifests

Verified Against:

  • WoW 11.0.5.57689 (242 entries, 28 tags)

  • Multiple WoW Classic builds

  • Cross-platform tag validation (Windows, OSX, mobile)

Known Issues: None

See https://github.com/wowemulation-dev/cascette-py for the Python implementation.

Version History

The Install manifest format has two versions:

Version 1

  • Header Size: 10 bytes
  • Magic: “IN” (0x494E)
  • Entry Size: Derived as ckey_length + 4
  • Features:
    • File path to content key mapping
    • Tag-based selective installation
    • Platform/architecture/locale filtering
    • Bit mask system for tag associations
    • Big-endian (MSB-first) bit ordering in tag masks
    • Tag type classification (17 types from Platform through Option)

Version 2

  • Header Size: 16 bytes (10 base + 6 additional)
  • Added Fields: content_key_size (1 byte), entry_count_v2 (4 bytes BE), unknown (1 byte)
  • Features: All version 1 features plus explicit content key size

Version Detection

The version field is at offset 2 in the header. The agent accepts versions 1 and 2 (validates non-zero and <= 2).

Implementation Status

  • cascette-formats: Full support for versions 1 and 2 with validation
  • cascette-py: Complete parsing for version 1 with tag extraction

References