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

Download Manifest Format

The Download manifest manages content streaming and prioritization during game installation and updates. It defines which files are essential for gameplay and their download order.

Overview

The Download manifest assigns a priority to each file entry so the client can download essential content first (enabling play before full download) and stream remaining content in the background. Tag bitmaps enable per-platform and per-locale filtering. File sizes in entries support progress estimation.

File Structure

The Download manifest is BLTE-encoded and contains:

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

Binary Format

struct DownloadHeader {
    char     magic[2];           // "DL" (0x44, 0x4C)
    uint8_t  version;            // Version (1, 2, or 3)
    uint8_t  ekey_size;          // Encoding key size in bytes (16)
    uint8_t  has_checksum;       // Checksum presence flag
    uint32_t entry_count;        // Number of entries (big-endian)
    uint16_t tag_count;          // Number of tags (big-endian)

    // Version 2+ fields (header grows to 12 bytes)
    uint8_t  flag_size;          // Number of flag bytes per entry (max 4)

    // Version 3+ fields (header grows to 16 bytes)
    int8_t   base_priority;      // Base priority offset
    uint8_t  _reserved[3];       // Reserved (agent does not validate these)
};

Entry Order

The download manifest stores data in this order:

  1. Header
  2. All file entries
  3. All tags (appear after entries)

File Entry

struct DownloadEntry {
    uint8_t  ekey[16];           // Encoding key (variable size from header)
    uint8_t  file_size[5];       // 40-bit file size (big-endian)
    int8_t   priority;           // Download priority (adjusted by base_priority)

    // Optional fields
    uint32_t checksum;           // If has_checksum is true (big-endian)
    uint8_t  flags[N];           // If version >= 2, N = flag_size
};

Tag Entry

Tags appear after all file entries in the manifest:

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

Each bit in the bitmap corresponds to a file entry index. If bit N is set, entry N has this tag.

Priority System

Priority Calculation

In version 3+, priorities are adjusted:

final_priority = entry.priority - header.base_priority

Priority Levels

Lower values indicate higher priority:

PriorityCategoryTypical Content
< 0CriticalMust download before game starts
0EssentialRequired for basic gameplay
1-2HighImportant for full experience
3-5NormalStandard content
> 5LowOptional/deferred content

Priority-Based Download

#![allow(unused)]
fn main() {
fn get_download_order(entries: &[DownloadFileEntry]) -> Vec<&DownloadFileEntry>
{
    let mut sorted = entries.iter().collect::<Vec<_>>();
    sorted.sort_by_key(|e| (e.priority, e.file_size));
    sorted
}
}

Streaming Strategy

Minimum Playable Set

Calculate minimum download for gameplay:

#![allow(unused)]
fn main() {
fn get_minimum_download(
    download_file: &DownloadFile
) -> (Vec<DownloadFileEntry>, u64) {
    let essential: Vec<_> = download_file.entries
        .iter()
        .filter(|e| e.priority <= 1)  // Essential + Critical
        .cloned()
        .collect();

    let total_size = essential.iter()
        .map(|e| e.file_size as u64)
        .sum();

    (essential, total_size)
}
}

Progressive Download

Download in priority order while game runs:

#![allow(unused)]
fn main() {
struct DownloadManager {
    queue: VecDeque<DownloadItem>,
    active: Vec<DownloadTask>,
    completed: HashSet<[u8; 16]>,
}

impl DownloadManager {
    pub fn start_progressive_download(&mut self) {
        // Sort by priority
        self.queue.sort_by_key(|item| item.priority);

        // Start downloading highest priority
        while self.active.len() < MAX_CONCURRENT {
            if let Some(item) = self.queue.pop_front() {
                self.start_download(item);
            }
        }
    }
}
}

Tag-Based Filtering

Platform-Specific Downloads

Tags are stored separately from entries. Each tag contains a bitmap indicating which entries it applies to. To filter by tag, find the tag by name and check its bitmap:

#![allow(unused)]
fn main() {
fn filter_by_tag<'a>(
    manifest: &'a DownloadManifest,
    tag_name: &str,
) -> Vec<(usize, &'a DownloadFileEntry)> {
    let tag = match manifest.tags.iter().find(|t| t.name == tag_name) {
        Some(t) => t,
        None => return Vec::new(),
    };

    manifest.entries.iter().enumerate()
        .filter(|(index, _)| tag.has_file(*index))
        .collect()
}
}

Language Packs

#![allow(unused)]
fn main() {
fn get_language_pack<'a>(
    manifest: &'a DownloadManifest,
    locale: &str,
) -> Vec<&'a DownloadFileEntry> {
    let tag = match manifest.tags.iter().find(|t| t.name == locale) {
        Some(t) => t,
        None => return Vec::new(),
    };

    manifest.entries.iter().enumerate()
        .filter(|(index, _)| tag.has_file(*index))
        .map(|(_, entry)| entry)
        .collect()
}
}

Download Optimization

Bandwidth Management

#![allow(unused)]
fn main() {
struct BandwidthManager {
    max_bandwidth: u64,      // Bytes per second
    current_usage: u64,
    priority_limits: Vec<u64>, // Per-priority limits
}

impl BandwidthManager {
    pub fn allocate_bandwidth(&mut self, priority: u8) -> u64 {
        let priority_limit = self.priority_limits[priority as usize];
        let available = self.max_bandwidth - self.current_usage;

        std::cmp::min(priority_limit, available)
    }
}
}

Chunk-Based Downloads

For large files, download in chunks:

#![allow(unused)]
fn main() {
struct ChunkedDownload {
    encoding_key: [u8; 16],
    total_size: u64,
    chunk_size: u64,
    chunks_completed: Vec<bool>,
}

impl ChunkedDownload {
    pub fn get_next_chunk(&self) -> Option<(u64, u64)> {
        for (idx, &completed) in self.chunks_completed.iter().enumerate() {
            if !completed {
                let offset = idx as u64 * self.chunk_size;
                let size = std::cmp::min(
                    self.chunk_size,
                    self.total_size - offset
                );
                return Some((offset, size));
            }
        }
        None
    }
}
}

Progress Tracking

Download Statistics

#![allow(unused)]
fn main() {
struct DownloadProgress {
    total_files: u32,
    completed_files: u32,
    total_bytes: u64,
    downloaded_bytes: u64,
    current_speed: f64,
    eta_seconds: u64,
}

impl DownloadProgress {
    pub fn update(&mut self, bytes_downloaded: u64) {
        self.downloaded_bytes += bytes_downloaded;
        self.current_speed = self.calculate_speed();
        self.eta_seconds = self.calculate_eta();
    }

    pub fn completion_percentage(&self) -> f32 {
        (self.downloaded_bytes as f32 / self.total_bytes as f32) * 100.0
    }
}
}

Implementation Example

#![allow(unused)]
fn main() {
struct DownloadFile {
    header: DownloadHeader,
    priorities: Vec<DownloadPriority>,
    tags: Vec<DownloadTag>,
    entries: Vec<DownloadFileEntry>,
}

impl DownloadFile {
    pub fn get_download_plan(
        &self,
        tags: &[String],
        max_priority: u8
    ) -> DownloadPlan {
        let tag_mask = self.build_tag_mask(tags);

        let files: Vec<_> = self.entries
            .iter()
            .filter(|e| e.priority <= max_priority)
            .filter(|e| (e.tag_mask & tag_mask) != 0)
            .cloned()
            .collect();

        let total_size = files.iter()
            .map(|f| f.file_size as u64)
            .sum();

        DownloadPlan {
            files,
            total_size,
            estimated_time: self.estimate_time(total_size),
        }
    }
}
}

On-Demand Streaming

Asset Request Handling

#![allow(unused)]
fn main() {
struct OnDemandManager {
    download_file: DownloadFile,
    cache: LruCache<[u8; 16], Vec<u8>>,
}

impl OnDemandManager {
    pub async fn get_asset(&mut self, encoding_key: &[u8; 16]) -> Result<Vec<u8>> {
        // Check cache first
        if let Some(data) = self.cache.get(encoding_key) {
            return Ok(data.clone());
        }

        // Find in download manifest
        if let Some(entry) = self.find_entry(encoding_key) {
            // Download with high priority
            let data = self.download_immediate(entry).await?;
            self.cache.put(*encoding_key, data.clone());
            return Ok(data);
        }

        Err("Asset not found")
    }
}
}

Verification

Checksum Validation

#![allow(unused)]
fn main() {
fn verify_download(
    data: &[u8],
    entry: &DownloadFileEntry
) -> bool {
    if entry.checksum != [0; 16] {
        let computed = md5::compute(data);
        computed.0 == entry.checksum
    } else {
        true // No checksum to verify
    }
}
}

Common Issues

  1. Priority conflicts: Multiple systems requesting same file
  2. Bandwidth throttling: ISP or network limitations
  3. Incomplete downloads: Handle partial file recovery
  4. Cache corruption: Verify cached files periodically
  5. Tag mismatches: Platform detection errors

Special Features

Differential Downloads

Download only changed portions:

#![allow(unused)]
fn main() {
struct DifferentialDownload {
    old_version: [u8; 16],
    new_version: [u8; 16],
    patches: Vec<PatchInfo>,
}
}

Peer-to-Peer Support

Share downloaded content locally:

#![allow(unused)]
fn main() {
struct P2PManager {
    local_peers: Vec<PeerInfo>,
    shared_files: HashSet<[u8; 16]>,
}
}

Parser Implementation Status

Python Parser (cascette-py)

Status: Complete

Capabilities:

  • Version 1-3 header parsing with DL magic detection

  • 40-bit big-endian compressed size parsing

  • Priority system with base priority adjustment (v3)

  • Tag parsing with bitmap support (tags stored after all entries)

  • Platform/architecture tag identification with type classification

  • Sample entry display (first 100 entries)

  • Format evolution tracking across versions

  • BLTE decompression for compressed manifests

  • Correct entry/tag ordering (entries first, then tags)

Verified Against:

  • WoW 11.0.5.57689 (2.4M entries, 28 tags)

  • WoW 9.0.2.37176 (Shadowlands)

  • WoW 7.3.5.25848 (Legion)

  • WoW Classic builds

Known Issues: None

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

Version History

The Download manifest format has evolved through 3 versions:

Version 1 (Initial)

  • Header Size: 11 bytes
  • Features: Basic download prioritization with encoding keys, file sizes, optional checksums
  • Fields: magic, version, ekey_size, has_checksum, entry_count, tag_count

Version 2 (Flag Support)

  • Header Size: 12 bytes
  • Added Features: Entry-level flags for additional metadata
  • New Fields: flag_size (number of flag bytes per entry, max 4)
  • Use Cases: Platform-specific flags, content type markers

Version 3 (Priority System)

  • Header Size: 16 bytes
  • Added Features: Base priority adjustment for dynamic prioritization
  • New Fields: base_priority (signed adjustment), reserved (3 bytes)
  • Priority Calculation: final_priority = entry.priority - header.base_priority

Version Detection

Parsers detect version by reading the version field at offset 2 in the header. All versions use the same “DL” magic bytes and big-endian encoding.

Implementation Status

  • cascette-formats: Full support for versions 1-3 with version-aware parsing
  • cascette-py: Complete parsing for versions 1-3 with validation

References