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
Header
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
| Type | Value | Description | Examples |
|---|---|---|---|
| Platform | 0x0001 | Operating system tags | Windows, OSX, Android, IOS |
| Architecture | 0x0002 | CPU architecture tags | x86_32, x86_64, arm64 |
| Locale | 0x0003 | Language/region tags | enUS, deDE, frFR |
| Category | 0x0004 | Content category tags | speech, text |
| Unknown | 0x0005 | Unknown tag type | (seen in manifests) |
| Component | 0x0010 | Component tags | game, launcher |
| Version | 0x0020 | Version tags | live, ptr, beta |
| Optimization | 0x0040 | Optimization tags | retail, debug |
| Region | 0x0080 | Region tags | US, EU, KR |
| Device | 0x0100 | Device tags | desktop, mobile |
| Mode | 0x0200 | Mode tags | online, offline |
| Branch | 0x0400 | Branch tags | main, experimental |
| Content | 0x0800 | Content tags | cinematics, audio |
| Feature | 0x1000 | Feature tags | graphics, physics |
| Expansion | 0x2000 | Expansion tags | base, expansion1 |
| Alternate | 0x4000 | Alternate content | Alternate, HighRes |
| Option | 0x8000 | Option 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
- Tag conflicts: Multiple tags may include same file
- Path separators: Handle platform-specific separators
- Case sensitivity: File systems vary in case handling
- Symlink support: Some platforms don’t support symlinks
- 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
-
See Root File for file catalog
-
See Download Manifest for download prioritization
-
See Encoding Documentation for content resolution
-
See Format Transitions for format evolution tracking