wow-mpq 0.6.2

High-performance parser for World of Warcraft MPQ archives with parallel processing support
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
//! # wow_mpq - MPQ Archive Library
//!
//! A high-performance, safe Rust implementation of the MPQ (Mo'PaQ) archive format
//! used by Blizzard Entertainment games.
//!
//! ## About the Name
//!
//! wow_mpq is named after the original format name "Mo'PaQ" (Mike O'Brien Pack),
//! which was later shortened to MPQ. This library provides the core MPQ functionality.
//!
//! ## Features
//!
//! - Support for all MPQ format versions (v1-v4)
//! - Full compatibility with StormLib API through FFI
//! - Multiple compression algorithms (zlib, bzip2, LZMA, etc.)
//! - Digital signature support (verification and generation)
//! - Strong security with signature verification
//! - Comprehensive error handling
//! - Optional memory-mapped files for high-performance access
//! - SIMD-accelerated operations for maximum performance
//!
//! ## Examples
//!
//! ### Basic Usage
//!
//! ```no_run
//! use wow_mpq::{Archive, OpenOptions};
//!
//! # fn main() -> Result<(), wow_mpq::Error> {
//! // Open an existing MPQ archive
//! let mut archive = Archive::open("example.mpq")?;
//!
//! // List files in the archive
//! for entry in archive.list()? {
//!     println!("{}", entry.name);
//! }
//!
//! // Extract a specific file
//! let data = archive.read_file("war3map.j")?;
//! # Ok(())
//! # }
//! ```
//!
//! ### Memory-Mapped High Performance Access
//!
//! ```no_run,ignore
//! # #[cfg(feature = "mmap")]
//! use wow_mpq::{Archive, OpenOptions};
//!
//! # #[cfg(feature = "mmap")]
//! # fn main() -> Result<(), wow_mpq::Error> {
//! // Open archive with memory mapping enabled for better performance
//! let mut archive = OpenOptions::new()
//!     .enable_memory_mapping()
//!     .open("large_archive.mpq")?;
//!
//! // High-performance file reading using memory mapping
//! let data = archive.read_file_mapped("large_file.m2")?;
//! println!("Read {} bytes using memory mapping", data.len());
//! # Ok(())
//! # }
//! # #[cfg(not(feature = "mmap"))]
//! # fn main() {}
//! ```
//!
//! ### Digital Signatures
//!
//! ```no_run
//! use wow_mpq::crypto::{generate_weak_signature, SignatureInfo, WEAK_SIGNATURE_FILE_SIZE};
//! use std::io::Cursor;
//!
//! # fn main() -> Result<(), wow_mpq::Error> {
//! // Generate a weak signature for an archive
//! let archive_data = std::fs::read("archive.mpq")?;
//! let archive_size = archive_data.len() as u64;
//!
//! let sig_info = SignatureInfo::new_weak(
//!     0,                               // Archive start offset
//!     archive_size,                    // Archive size
//!     archive_size,                    // Signature position (at end)
//!     WEAK_SIGNATURE_FILE_SIZE as u64, // Signature file size
//!     vec![],                          // Empty initially
//! );
//!
//! let cursor = Cursor::new(&archive_data);
//! let signature_file = generate_weak_signature(cursor, &sig_info)?;
//! # Ok(())
//! # }
//! ```
//!
//! ### SIMD-Accelerated Operations
//!
//! ```no_run,ignore
//! # #[cfg(feature = "simd")]
//! use wow_mpq::simd::SimdOps;
//!
//! # #[cfg(feature = "simd")]
//! # fn main() -> Result<(), wow_mpq::Error> {
//! // Create SIMD operations with runtime CPU detection
//! let simd = SimdOps::new();
//!
//! // Hardware-accelerated CRC32 calculation
//! let crc = simd.crc32(b"test data", 0);
//!
//! // SIMD-accelerated hash for large batch operations
//! let hash = simd.hash_string_simd(b"filename.mdx", 0);
//!
//! // Check available SIMD features
//! println!("SIMD support available: {}", simd.has_simd_support());
//! # Ok(())
//! # }
//! # #[cfg(not(feature = "simd"))]
//! # fn main() {}
//! ```

#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(
    missing_docs,
    missing_debug_implementations,
    rust_2018_idioms,
    unreachable_pub
)]

pub mod archive;
pub mod buffer_pool;
pub mod builder;
pub mod compare;
pub mod compression;
pub mod crypto;
pub mod database;
pub mod error;
pub mod header;
pub mod io;
pub mod modification;
pub mod parallel;
pub mod patch;
pub mod patch_chain;
pub mod path;
pub mod rebuild;
pub mod security;
pub mod single_archive_parallel;
pub mod special_files;
pub mod tables;

// SIMD optimizations (optional feature)
#[cfg(feature = "simd")]
#[cfg_attr(docsrs, doc(cfg(feature = "simd")))]
pub mod simd;

#[cfg(any(test, feature = "test-utils", doc))]
pub mod test_utils;

pub mod debug;

// Re-export commonly used types
pub use archive::{
    Archive, ArchiveInfo, FileEntry, FileInfo, Md5Status, OpenOptions, SignatureStatus, TableInfo,
    UserDataInfo,
};
pub use buffer_pool::{BufferPool, BufferSize, PoolConfig, PoolStatistics};
pub use builder::{ArchiveBuilder, AttributesOption, ListfileOption};
pub use compare::{
    CompareOptions, ComparisonResult, ComparisonSummary, FileComparison, MetadataComparison,
    compare_archives,
};
pub use error::{Error, Result};
pub use header::{FormatVersion, MpqHeader};
pub use modification::{AddFileOptions, MutableArchive};
pub use patch_chain::{ChainInfo, PatchChain};
pub use rebuild::{RebuildOptions, RebuildSummary, rebuild_archive};
pub use tables::{BetFileInfo, BetTable, BlockEntry, BlockTable, HashEntry, HashTable, HetTable};

// Re-export crypto for CLI usage
pub use crypto::{
    decrypt_block, decrypt_dword, encrypt_block, hash_string, hash_type, jenkins_hash,
};

// Re-export compression for testing
pub use compression::{compress, decompress};

// Re-export decryption for testing
pub use archive::decrypt_file_data;

// Re-export async types when async feature is enabled
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub use io::{
    AsyncArchiveReader, AsyncConfig, AsyncDecompressionMonitor, AsyncMetrics, AsyncOperationStats,
};

// Re-export memory mapping types when mmap feature is enabled
#[cfg(feature = "mmap")]
#[cfg_attr(docsrs, doc(cfg(feature = "mmap")))]
pub use io::{MemoryMapConfig, MemoryMapManager, MemoryMapStats, MemoryMappedArchive};

// Re-export SIMD types when simd feature is enabled
#[cfg(feature = "simd")]
#[cfg_attr(docsrs, doc(cfg(feature = "simd")))]
pub use simd::{CpuFeatures, SimdOps};

// Re-export security types for configuration
pub use security::{DecompressionMonitor, SecurityLimits, SessionTracker};

/// MPQ signature constants
pub mod signatures {
    /// Standard MPQ archive signature ('MPQ\x1A')
    pub const MPQ_ARCHIVE: u32 = 0x1A51504D;

    /// MPQ user data signature ('MPQ\x1B')
    pub const MPQ_USERDATA: u32 = 0x1B51504D;

    /// HET table signature ('HET\x1A')
    pub const HET_TABLE: u32 = 0x1A544548;

    /// BET table signature ('BET\x1A')
    pub const BET_TABLE: u32 = 0x1A544542;

    /// Strong signature magic ('NGIS')
    pub const STRONG_SIGNATURE: [u8; 4] = *b"NGIS";
}

/// Block size calculation
#[inline]
pub fn calculate_sector_size(block_size_shift: u16) -> usize {
    512 << block_size_shift
}

/// Check if a value is a power of two
#[inline]
pub fn is_power_of_two(value: u32) -> bool {
    value != 0 && (value & (value - 1)) == 0
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_calculate_sector_size() {
        // Test standard sector sizes used in MPQ archives

        // Block size 0: 512 bytes (minimum)
        assert_eq!(calculate_sector_size(0), 512);

        // Block size 1: 1024 bytes (1 KB)
        assert_eq!(calculate_sector_size(1), 1024);

        // Block size 2: 2048 bytes (2 KB)
        assert_eq!(calculate_sector_size(2), 2048);

        // Block size 3: 4096 bytes (4 KB) - Common default
        assert_eq!(calculate_sector_size(3), 4096);

        // Block size 4: 8192 bytes (8 KB)
        assert_eq!(calculate_sector_size(4), 8192);

        // Block size 5: 16384 bytes (16 KB)
        assert_eq!(calculate_sector_size(5), 16384);

        // Block size 6: 32768 bytes (32 KB)
        assert_eq!(calculate_sector_size(6), 32768);

        // Block size 7: 65536 bytes (64 KB)
        assert_eq!(calculate_sector_size(7), 65536);

        // Block size 8: 131072 bytes (128 KB)
        assert_eq!(calculate_sector_size(8), 131072);

        // Block size 9: 262144 bytes (256 KB)
        assert_eq!(calculate_sector_size(9), 262144);

        // Block size 10: 524288 bytes (512 KB)
        assert_eq!(calculate_sector_size(10), 524288);

        // Block size 11: 1048576 bytes (1 MB)
        assert_eq!(calculate_sector_size(11), 1048576);

        // Block size 12: 2097152 bytes (2 MB)
        assert_eq!(calculate_sector_size(12), 2097152);

        // Block size 13: 4194304 bytes (4 MB)
        assert_eq!(calculate_sector_size(13), 4194304);

        // Block size 14: 8388608 bytes (8 MB)
        assert_eq!(calculate_sector_size(14), 8388608);

        // Block size 15: 16777216 bytes (16 MB) - Maximum practical size
        assert_eq!(calculate_sector_size(15), 16777216);
    }

    #[test]
    fn test_calculate_sector_size_edge_cases() {
        // Test with maximum u16 value (though this would be impractical)
        // This would overflow on 32-bit systems, but Rust handles it gracefully
        let max_shift = 16; // Reasonable maximum to test
        let result = calculate_sector_size(max_shift);
        assert_eq!(result, 512 << 16); // 33,554,432 bytes (32 MB)
    }

    #[test]
    fn test_is_power_of_two() {
        // Valid powers of two
        assert!(is_power_of_two(1));
        assert!(is_power_of_two(2));
        assert!(is_power_of_two(4));
        assert!(is_power_of_two(8));
        assert!(is_power_of_two(16));
        assert!(is_power_of_two(32));
        assert!(is_power_of_two(64));
        assert!(is_power_of_two(128));
        assert!(is_power_of_two(256));
        assert!(is_power_of_two(512));
        assert!(is_power_of_two(1024));
        assert!(is_power_of_two(2048));
        assert!(is_power_of_two(4096));
        assert!(is_power_of_two(8192));
        assert!(is_power_of_two(16384));
        assert!(is_power_of_two(32768));
        assert!(is_power_of_two(65536));
        assert!(is_power_of_two(0x100000)); // 1,048,576
        assert!(is_power_of_two(0x80000000)); // 2^31

        // Not powers of two
        assert!(!is_power_of_two(0));
        assert!(!is_power_of_two(3));
        assert!(!is_power_of_two(5));
        assert!(!is_power_of_two(6));
        assert!(!is_power_of_two(7));
        assert!(!is_power_of_two(9));
        assert!(!is_power_of_two(10));
        assert!(!is_power_of_two(15));
        assert!(!is_power_of_two(100));
        assert!(!is_power_of_two(127));
        assert!(!is_power_of_two(255));
        assert!(!is_power_of_two(1023));
        assert!(!is_power_of_two(1025));
        assert!(!is_power_of_two(0xFFFFFFFF));
    }

    #[test]
    fn test_hash_table_size_validation() {
        // Hash table sizes must be powers of two
        // This test demonstrates how is_power_of_two would be used

        let valid_sizes = [
            4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536,
        ];

        for size in &valid_sizes {
            assert!(
                is_power_of_two(*size),
                "Hash table size {size} should be valid"
            );
        }

        let invalid_sizes = [0, 3, 5, 7, 9, 15, 100, 1000, 1023, 1025, 4095, 4097];

        for size in &invalid_sizes {
            assert!(
                !is_power_of_two(*size),
                "Hash table size {size} should be invalid"
            );
        }
    }

    #[test]
    fn test_typical_mpq_configurations() {
        // Test typical MPQ configurations from various games

        // Diablo II: Often uses block size 3 (4KB sectors)
        let d2_sector_size = calculate_sector_size(3);
        assert_eq!(d2_sector_size, 4096);

        // Warcraft III: Typically uses block size 3-4 (4KB-8KB sectors)
        let wc3_sector_size_small = calculate_sector_size(3);
        let wc3_sector_size_large = calculate_sector_size(4);
        assert_eq!(wc3_sector_size_small, 4096);
        assert_eq!(wc3_sector_size_large, 8192);

        // World of Warcraft: Uses various sizes, often 4-8 (8KB-128KB sectors)
        let wow_sector_size_min = calculate_sector_size(4);
        let wow_sector_size_typical = calculate_sector_size(6);
        let wow_sector_size_max = calculate_sector_size(8);
        assert_eq!(wow_sector_size_min, 8192);
        assert_eq!(wow_sector_size_typical, 32768);
        assert_eq!(wow_sector_size_max, 131072);

        // StarCraft II: Can use larger sectors for HD assets
        let sc2_sector_size = calculate_sector_size(9);
        assert_eq!(sc2_sector_size, 262144); // 256 KB
    }

    #[test]
    #[cfg(feature = "mmap")]
    fn test_memory_mapping_config_availability() {
        // Test that memory mapping types are available when feature is enabled
        let _config = MemoryMapConfig::default();
        let _stats = MemoryMapStats::default();
    }

    #[test]
    #[cfg(feature = "simd")]
    fn test_simd_ops_availability() {
        // Test that SIMD types are available when feature is enabled
        let simd = SimdOps::new();
        let _features = simd.features();

        // Should not panic during CPU feature detection
        let _has_support = simd.has_simd_support();
    }

    #[test]
    fn test_security_limits_availability() {
        // Security types should always be available
        let _limits = SecurityLimits::default();
        let _tracker = SessionTracker::new();
    }
}