StormLib vs wow-mpq: Technical Differences Guide
This guide outlines the technical differences between StormLib (the reference C++ implementation) and wow-mpq (our Rust implementation). Understanding these differences helps users migrate from StormLib or implement missing features.
Overview
StormLib has evolved over 20+ years to handle edge cases, game-specific quirks, and optimizations. The wow-mpq implementation provides a Rust alternative focusing on core MPQ functionality with compatibility with StormLib.
Key Achievements
- ✅ Bidirectional Archive Compatibility: StormLib can read wow-mpq archives and vice versa
- ✅ Full V1-V4 Format Support: All MPQ versions work seamlessly between implementations
- ✅ HET/BET Table Compatibility: V3+ archives with extended tables work perfectly
- ✅ Attributes File Support: Both 120-byte (wow-mpq) and 149-byte (StormLib) formats
- ✅ Blizzard Archive Support: Handles all official WoW archives (1.12.1 - 5.4.8)
1. Header Structure and Parsing
StormLib Approach
- Unified structure: Single header struct containing all fields for all versions
- 64-bit fields: Uses 64-bit fields from the start for forward compatibility
- Platform handling: Special logic for different platforms and file types
- Fields include:
file_offset_maskfor version-specific offset handlingraw_chunk_sizefor MD5 calculation- Separate 64-bit size fields for all tables
wow-mpq Approach
- Version-specific: Optional fields added progressively based on version
- Modular design: V4 data wrapped in separate struct
- Simpler parsing: Focus on standard MPQ format without platform quirks
#![allow(unused)]
fn main() {
// StormLib style (conceptual)
struct MpqHeader {
signature: u32,
header_size: u32,
archive_size: u32,
format_version: u16,
sector_size: u16,
// ... all fields for all versions ...
raw_chunk_size: u32, // Always present
file_offset_mask: u64, // Calculated based on version
}
// wow-mpq style
struct MpqHeader {
// Core fields always present
header_size: u32,
archive_size: u32,
format_version: FormatVersion,
// Version-specific fields as Options
v4_data: Option<MpqHeaderV4Data>,
}
}
2. Archive Opening Workflow
StormLib: Complex 9-Step Process
- Parameter validation with checks
- File size validation with platform-specific handling
- Header search with game type detection:
- AVI file detection for Warcraft III cinematics
- PE header analysis for DLL files
- Map type detection (Starcraft, Warcraft III, etc.)
- Header processing with compatibility quirks
- Cryptography initialization including storm buffer
- Table loading with defragmentation
- File table building with correlation
- Internal files loading (listfile, attributes)
- Finalization with malformed archive detection
wow-mpq: Streamlined Process
- Basic parameter validation
- Find MPQ header at aligned offsets
- Parse header based on version
- Load hash/block tables or HET/BET tables
- Build file index
Missing features:
- Game-specific file type detection
- Automatic defragmentation
- Malformed archive recovery
- Protection scheme bypasses
3. HET/BET Table Implementation
StormLib Features
- Hash algorithms: Uses Jenkins hashlittle2 for HET, Jenkins one-at-a-time for BET
- Jenkins hash masking: Uses
and_mask_64andor_mask_64for optimization - Bit-packed storage: Custom
BitArrayimplementation for memory efficiency - Detailed bit tracking: Precise bit positions for all fields
- Name hash separation: Stores name hashes separately from file indices
#![allow(unused)]
fn main() {
// StormLib approach
struct HetTable {
bet_indexes: BitArray, // Bit-packed file indexes
name_hashes: Vec<u8>, // Separate name hash storage
and_mask_64: u64, // Hash optimization mask
or_mask_64: u64, // Hash optimization mask
// Detailed bit field tracking
name_hash_bit_size: u32,
index_size_total: u32,
index_size: u32,
}
}
wow-mpq Implementation
- Simpler structure without optimization masks
- Standard byte arrays instead of bit packing
- Less granular field tracking
4. Encryption and Key Detection
StormLib: Advanced Key Recovery
- Filename-based: Standard key calculation from filename
- Sector size detection: Analyze sector boundaries
- Content pattern matching:
- WAVE files:
0x46464952signature - EXE files:
0x00905A4Dsignature - XML files:
0x6D783F3Csignature
- WAVE files:
- Brute force recovery: Try all 256 possible keys using known patterns
#![allow(unused)]
fn main() {
// StormLib's key detection approach
fn detect_encryption_key(file: &MpqFile) -> Option<u32> {
// Try filename-based key
if let Some(key) = try_filename_key(file) {
return Some(key);
}
// Try sector size detection
if let Some(key) = detect_key_by_sector_size(file) {
return Some(key);
}
// Try known content patterns
if let Some(key) = detect_key_by_content(file) {
return Some(key);
}
None
}
}
wow-mpq: Basic Encryption
- Standard filename-based key calculation
- No automatic key detection
- No content-based recovery
5. Sector-Based File Reading
StormLib Optimizations
- Compression detection: Analyzes sector size to determine if compressed
- ADLER32 verification: Validates each sector’s checksum
- Dynamic loading: Loads sector offset tables on demand
- Caching: LRU cache for frequently accessed sectors
wow-mpq Implementation
- Basic sector reading and decompression
- No checksum verification
- Simple sequential processing
6. Special Behaviors and Edge Cases
StormLib: Game-Specific Handling
Map Type Detection
#![allow(unused)]
fn main() {
enum MapType {
NotRecognized,
AviFile, // Warcraft III cinematics
Starcraft, // .scm, .scx extensions
Warcraft3, // HM3W signature
Starcraft2, // .s2ma, .sc2map extensions
}
}
Protection Bypasses
- BOBA protector: Handles negative table offsets
- w3xMaster: Fixes invalid hi-word positions
- Integer overflow: Masks table sizes to prevent overflow
- Starcraft Beta: Special handling for specific archive sizes
wow-mpq: Standard Format Only
- No game-specific detection
- No protection bypass mechanisms
- Assumes well-formed archives
7. Memory Management and Performance
StormLib Optimizations
- Custom allocators: Specialized allocation for large tables
- Memory mapping:
MmapStreamfor large read-only archives - Sector caching: LRU cache with configurable size
- Thread safety: parking_lot mutexes
- Bit packing: Memory-efficient storage for table data
#![allow(unused)]
fn main() {
// StormLib's optimized approach
pub struct ThreadSafeMpqArchive {
archive: Arc<ParkingRwLock<MpqArchive>>,
sector_cache: Arc<Mutex<SectorCache>>,
mmap_stream: Option<MmapStream>,
}
}
wow-mpq: Standard Rust Patterns
- Standard
Arc<Mutex<>>for thread safety - No specialized memory mapping
- Basic caching strategies
- Standard allocators
8. Error Handling and Recovery
StormLib: Recovery
- Malformed archive detection: Marks archives as read-only
- Automatic recovery: Attempts to work with damaged archives
- Detailed error context: Extensive error information
- Graceful degradation: Continues operation with reduced functionality
wow-mpq: Fail-Fast Approach
- Returns errors on malformed data
- No automatic recovery mechanisms
- Clear error messages
- Predictable behavior
9. File Operations
StormLib: Full Read/Write Support
Advanced Write Features
- Atomic writes: File writer with Drop trait finalization
- Free space management: Finds optimal positions for new files
- Incremental updates: Modifies archives without full rebuild
- Automatic compression: Chooses compression method
Metadata Support
- MD5 calculation during write
- CRC32 for integrity
- Timestamp preservation
- Extended attributes
wow-mpq: Read-Focused Design
- Full read support
- Basic write functionality
- Archive creation support
- Limited in-place modification
10. Additional StormLib Features
Features Not in wow-mpq
- Patch metadata: Special support for WoW patch chains
- Compile-time tables: Storm buffer as const array
- Extended attributes: File timestamps and custom metadata
- Sparse compression: RLE algorithm for sparse data
- LZMA compression: LZMA compression support
- RSA signatures: Digital signature verification
- Listfile management: Automatic listfile updates
- Weak signature verification: Support for older signatures
Implementation Considerations
If you need these features, consider:
- Implementing them as separate crates
- Using FFI to call StormLib for specific operations
- Contributing implementations to wow-mpq
Path Separator Handling
Automatic Conversion
Both StormLib and wow-mpq automatically convert forward slashes to backslashes:
#![allow(unused)]
fn main() {
// All of these work identically:
archive.find_file("Units/Human/Footman.mdx")?; // Forward slashes
archive.find_file("Units\\Human\\Footman.mdx")?; // Backslashes
archive.find_file("Units/Human\\Footman.mdx")?; // Mixed
// The hash functions normalize all paths to use backslashes internally
}
CLI Path Conversion
The CLI tools properly convert MPQ paths to system paths:
# Extract with proper path conversion
warcraft-rs mpq extract archive.mpq --output ./extracted
# Files like "Units\Human\Footman.mdx" become:
# - Linux/macOS: ./extracted/Units/Human/Footman.mdx
# - Windows: .\extracted\Units\Human\Footman.mdx
Migration Guide
From StormLib to wow-mpq
- Check feature requirements: Ensure wow-mpq supports your needs
- Update error handling: Adapt to Rust’s Result type
- Adjust for missing features: Implement workarounds or alternatives
- Test thoroughly: Especially with protected or malformed archives
Common Migration Issues
#![allow(unused)]
fn main() {
// StormLib style
HANDLE hMpq;
if (!SFileOpenArchive("archive.mpq", 0, 0, &hMpq)) {
// Handle error
}
// wow-mpq style
let archive = match Archive::open("archive.mpq") {
Ok(a) => a,
Err(e) => {
// Handle error
return;
}
};
}
Performance Comparison
| Feature | StormLib | wow-mpq |
|---|---|---|
| Memory usage | Lower (bit packing) | Higher (standard types) |
| Thread safety | Manual with callbacks | Built-in with Rust |
| Large archives | Memory mapped | Standard I/O |
| Caching | Configurable LRU | Basic |
| Startup time | Slower (more checks) | Faster |
FFI Compatibility Layer
Storm-FFI: StormLib API in Rust
The storm-ffi crate provides a complete StormLib-compatible C API, allowing C/C++ applications to use wow-mpq as a drop-in replacement for StormLib:
// Existing StormLib code works unchanged
HANDLE hMpq;
if (SFileOpenArchive("archive.mpq", 0, MPQ_OPEN_READ_WRITE, &hMpq)) {
// Add, remove, rename files
SFileAddFile(hMpq, "new.txt", "data/new.txt", MPQ_FILE_COMPRESS);
SFileRemoveFile(hMpq, "old.txt", 0);
SFileRenameFile(hMpq, "file.txt", "renamed.txt");
// Compact archive
SFileCompactArchive(hMpq, NULL, false);
SFileCloseArchive(hMpq);
}
Supported StormLib Functions
Archive Operations:
- ✅
SFileOpenArchive/SFileCloseArchive - ✅
SFileCreateArchive/SFileCreateArchive2 - ✅
SFileFlushArchive/SFileCompactArchive - ✅
SFileGetArchiveName/SFileGetFileInfo
File Operations:
- ✅
SFileOpenFileEx/SFileCloseFile - ✅
SFileReadFile/SFileGetFileSize - ✅
SFileAddFile/SFileAddFileEx - ✅
SFileRemoveFile/SFileRenameFile - ✅
SFileCreateFile/SFileWriteFile/SFileFinishFile
Search Operations:
- ✅
SFileFindFirstFile/SFileFindNextFile/SFileFindClose - ✅
SFileEnumFiles(callback-based enumeration) - ✅
SFileHasFile(check file existence)
Verification:
- ✅
SFileVerifyFile/SFileVerifyArchive - ✅
SFileExtractFile(extract to disk)
Attributes:
- ✅
SFileGetFileAttributes/SFileSetFileAttributes - ✅
SFileUpdateFileAttributes/SFileFlushAttributes
Implementation Differences
While the API is identical, there are some implementation differences:
- Thread Safety: Built-in thread safety without manual synchronization
- Error Handling: Thread-local error storage instead of global state
- Memory Management: Automatic memory management internally
- Performance: Generally faster due to Rust optimizations
Recommendations
Use StormLib when
- Working with protected archives
- Need game-specific features
- Require key recovery
- Handle malformed archives
- Need all compression methods
Use wow-mpq when
- Want memory safety
- Prefer Rust ecosystem
- Work with standard MPQ files
- Value API design
- Need async support (future)
Use storm-ffi when
- Migrating existing C/C++ code from StormLib
- Need drop-in StormLib replacement
- Want Rust’s safety with C compatibility
- Require specific StormLib API functions
Blizzard Archive Compatibility
Attributes File Deviation
All official Blizzard MPQ archives have a consistent deviation from the specification:
- Expected: Attributes files sized exactly for the archive’s file count
- Actual: Blizzard adds exactly 28 extra zero bytes at the end
- Handling: Both StormLib and wow-mpq gracefully handle this deviation
#![allow(unused)]
fn main() {
// wow-mpq automatically detects and handles Blizzard's format:
let archive = Archive::open("Data/patch.mpq")?;
// Warning logged: "Attributes file size mismatch: ... difference=-28"
// But archive works perfectly!
}
Tested WoW Versions
Full compatibility confirmed with official archives from:
- WoW 1.12.1 (Vanilla)
- WoW 2.4.3 (The Burning Crusade)
- WoW 3.3.5a (Wrath of the Lich King)
- WoW 4.3.4 (Cataclysm)
- WoW 5.4.8 (Mists of Pandaria)
Contributing Missing Features
If you need StormLib features in wow-mpq:
- Open an issue: Discuss the feature need
- Reference StormLib: Link to relevant StormLib code
- Propose design: Suggest Rust-idiomatic implementation
- Submit PR: Include tests and documentation