wow_mpq/lib.rs
1//! # wow_mpq - MPQ Archive Library
2//!
3//! A high-performance, safe Rust implementation of the MPQ (Mo'PaQ) archive format
4//! used by Blizzard Entertainment games.
5//!
6//! ## About the Name
7//!
8//! wow_mpq is named after the original format name "Mo'PaQ" (Mike O'Brien Pack),
9//! which was later shortened to MPQ. This library provides the core MPQ functionality.
10//!
11//! ## Features
12//!
13//! - Support for all MPQ format versions (v1-v4)
14//! - Full compatibility with StormLib API through FFI
15//! - Multiple compression algorithms (zlib, bzip2, LZMA, etc.)
16//! - Digital signature support (verification and generation)
17//! - Strong security with signature verification
18//! - Comprehensive error handling
19//! - Optional memory-mapped files for high-performance access
20//! - SIMD-accelerated operations for maximum performance
21//!
22//! ## Examples
23//!
24//! ### Basic Usage
25//!
26//! ```no_run
27//! use wow_mpq::{Archive, OpenOptions};
28//!
29//! # fn main() -> Result<(), wow_mpq::Error> {
30//! // Open an existing MPQ archive
31//! let mut archive = Archive::open("example.mpq")?;
32//!
33//! // List files in the archive
34//! for entry in archive.list()? {
35//! println!("{}", entry.name);
36//! }
37//!
38//! // Extract a specific file
39//! let data = archive.read_file("war3map.j")?;
40//! # Ok(())
41//! # }
42//! ```
43//!
44//! ### Memory-Mapped High Performance Access
45//!
46//! ```no_run,ignore
47//! # #[cfg(feature = "mmap")]
48//! use wow_mpq::{Archive, OpenOptions};
49//!
50//! # #[cfg(feature = "mmap")]
51//! # fn main() -> Result<(), wow_mpq::Error> {
52//! // Open archive with memory mapping enabled for better performance
53//! let mut archive = OpenOptions::new()
54//! .enable_memory_mapping()
55//! .open("large_archive.mpq")?;
56//!
57//! // High-performance file reading using memory mapping
58//! let data = archive.read_file_mapped("large_file.m2")?;
59//! println!("Read {} bytes using memory mapping", data.len());
60//! # Ok(())
61//! # }
62//! # #[cfg(not(feature = "mmap"))]
63//! # fn main() {}
64//! ```
65//!
66//! ### Digital Signatures
67//!
68//! ```no_run
69//! use wow_mpq::crypto::{generate_weak_signature, SignatureInfo, WEAK_SIGNATURE_FILE_SIZE};
70//! use std::io::Cursor;
71//!
72//! # fn main() -> Result<(), wow_mpq::Error> {
73//! // Generate a weak signature for an archive
74//! let archive_data = std::fs::read("archive.mpq")?;
75//! let archive_size = archive_data.len() as u64;
76//!
77//! let sig_info = SignatureInfo::new_weak(
78//! 0, // Archive start offset
79//! archive_size, // Archive size
80//! archive_size, // Signature position (at end)
81//! WEAK_SIGNATURE_FILE_SIZE as u64, // Signature file size
82//! vec![], // Empty initially
83//! );
84//!
85//! let cursor = Cursor::new(&archive_data);
86//! let signature_file = generate_weak_signature(cursor, &sig_info)?;
87//! # Ok(())
88//! # }
89//! ```
90//!
91//! ### SIMD-Accelerated Operations
92//!
93//! ```no_run,ignore
94//! # #[cfg(feature = "simd")]
95//! use wow_mpq::simd::SimdOps;
96//!
97//! # #[cfg(feature = "simd")]
98//! # fn main() -> Result<(), wow_mpq::Error> {
99//! // Create SIMD operations with runtime CPU detection
100//! let simd = SimdOps::new();
101//!
102//! // Hardware-accelerated CRC32 calculation
103//! let crc = simd.crc32(b"test data", 0);
104//!
105//! // SIMD-accelerated hash for large batch operations
106//! let hash = simd.hash_string_simd(b"filename.mdx", 0);
107//!
108//! // Check available SIMD features
109//! println!("SIMD support available: {}", simd.has_simd_support());
110//! # Ok(())
111//! # }
112//! # #[cfg(not(feature = "simd"))]
113//! # fn main() {}
114//! ```
115
116#![cfg_attr(docsrs, feature(doc_cfg))]
117#![warn(
118 missing_docs,
119 missing_debug_implementations,
120 rust_2018_idioms,
121 unreachable_pub
122)]
123
124pub mod archive;
125pub mod buffer_pool;
126pub mod builder;
127pub mod compare;
128pub mod compression;
129pub mod crypto;
130pub mod database;
131pub mod error;
132pub mod header;
133pub mod io;
134pub mod modification;
135pub mod parallel;
136pub mod patch;
137pub mod patch_chain;
138pub mod path;
139pub mod rebuild;
140pub mod security;
141pub mod single_archive_parallel;
142pub mod special_files;
143pub mod tables;
144
145// SIMD optimizations (optional feature)
146#[cfg(feature = "simd")]
147#[cfg_attr(docsrs, doc(cfg(feature = "simd")))]
148pub mod simd;
149
150#[cfg(any(test, feature = "test-utils", doc))]
151pub mod test_utils;
152
153pub mod debug;
154
155// Re-export commonly used types
156pub use archive::{
157 Archive, ArchiveInfo, FileEntry, FileInfo, Md5Status, OpenOptions, SignatureStatus, TableInfo,
158 UserDataInfo,
159};
160pub use buffer_pool::{BufferPool, BufferSize, PoolConfig, PoolStatistics};
161pub use builder::{ArchiveBuilder, AttributesOption, ListfileOption};
162pub use compare::{
163 CompareOptions, ComparisonResult, ComparisonSummary, FileComparison, MetadataComparison,
164 compare_archives,
165};
166pub use error::{Error, Result};
167pub use header::{FormatVersion, MpqHeader};
168pub use modification::{AddFileOptions, MutableArchive};
169pub use patch_chain::{ChainInfo, PatchChain};
170pub use rebuild::{RebuildOptions, RebuildSummary, rebuild_archive};
171pub use tables::{BetFileInfo, BetTable, BlockEntry, BlockTable, HashEntry, HashTable, HetTable};
172
173// Re-export crypto for CLI usage
174pub use crypto::{
175 decrypt_block, decrypt_dword, encrypt_block, hash_string, hash_type, jenkins_hash,
176};
177
178// Re-export compression for testing
179pub use compression::{compress, decompress};
180
181// Re-export decryption for testing
182pub use archive::decrypt_file_data;
183
184// Re-export async types when async feature is enabled
185#[cfg(feature = "async")]
186#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
187pub use io::{
188 AsyncArchiveReader, AsyncConfig, AsyncDecompressionMonitor, AsyncMetrics, AsyncOperationStats,
189};
190
191// Re-export memory mapping types when mmap feature is enabled
192#[cfg(feature = "mmap")]
193#[cfg_attr(docsrs, doc(cfg(feature = "mmap")))]
194pub use io::{MemoryMapConfig, MemoryMapManager, MemoryMapStats, MemoryMappedArchive};
195
196// Re-export SIMD types when simd feature is enabled
197#[cfg(feature = "simd")]
198#[cfg_attr(docsrs, doc(cfg(feature = "simd")))]
199pub use simd::{CpuFeatures, SimdOps};
200
201// Re-export security types for configuration
202pub use security::{DecompressionMonitor, SecurityLimits, SessionTracker};
203
204/// MPQ signature constants
205pub mod signatures {
206 /// Standard MPQ archive signature ('MPQ\x1A')
207 pub const MPQ_ARCHIVE: u32 = 0x1A51504D;
208
209 /// MPQ user data signature ('MPQ\x1B')
210 pub const MPQ_USERDATA: u32 = 0x1B51504D;
211
212 /// HET table signature ('HET\x1A')
213 pub const HET_TABLE: u32 = 0x1A544548;
214
215 /// BET table signature ('BET\x1A')
216 pub const BET_TABLE: u32 = 0x1A544542;
217
218 /// Strong signature magic ('NGIS')
219 pub const STRONG_SIGNATURE: [u8; 4] = *b"NGIS";
220}
221
222/// Block size calculation
223#[inline]
224pub fn calculate_sector_size(block_size_shift: u16) -> usize {
225 512 << block_size_shift
226}
227
228/// Check if a value is a power of two
229#[inline]
230pub fn is_power_of_two(value: u32) -> bool {
231 value != 0 && (value & (value - 1)) == 0
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn test_calculate_sector_size() {
240 // Test standard sector sizes used in MPQ archives
241
242 // Block size 0: 512 bytes (minimum)
243 assert_eq!(calculate_sector_size(0), 512);
244
245 // Block size 1: 1024 bytes (1 KB)
246 assert_eq!(calculate_sector_size(1), 1024);
247
248 // Block size 2: 2048 bytes (2 KB)
249 assert_eq!(calculate_sector_size(2), 2048);
250
251 // Block size 3: 4096 bytes (4 KB) - Common default
252 assert_eq!(calculate_sector_size(3), 4096);
253
254 // Block size 4: 8192 bytes (8 KB)
255 assert_eq!(calculate_sector_size(4), 8192);
256
257 // Block size 5: 16384 bytes (16 KB)
258 assert_eq!(calculate_sector_size(5), 16384);
259
260 // Block size 6: 32768 bytes (32 KB)
261 assert_eq!(calculate_sector_size(6), 32768);
262
263 // Block size 7: 65536 bytes (64 KB)
264 assert_eq!(calculate_sector_size(7), 65536);
265
266 // Block size 8: 131072 bytes (128 KB)
267 assert_eq!(calculate_sector_size(8), 131072);
268
269 // Block size 9: 262144 bytes (256 KB)
270 assert_eq!(calculate_sector_size(9), 262144);
271
272 // Block size 10: 524288 bytes (512 KB)
273 assert_eq!(calculate_sector_size(10), 524288);
274
275 // Block size 11: 1048576 bytes (1 MB)
276 assert_eq!(calculate_sector_size(11), 1048576);
277
278 // Block size 12: 2097152 bytes (2 MB)
279 assert_eq!(calculate_sector_size(12), 2097152);
280
281 // Block size 13: 4194304 bytes (4 MB)
282 assert_eq!(calculate_sector_size(13), 4194304);
283
284 // Block size 14: 8388608 bytes (8 MB)
285 assert_eq!(calculate_sector_size(14), 8388608);
286
287 // Block size 15: 16777216 bytes (16 MB) - Maximum practical size
288 assert_eq!(calculate_sector_size(15), 16777216);
289 }
290
291 #[test]
292 fn test_calculate_sector_size_edge_cases() {
293 // Test with maximum u16 value (though this would be impractical)
294 // This would overflow on 32-bit systems, but Rust handles it gracefully
295 let max_shift = 16; // Reasonable maximum to test
296 let result = calculate_sector_size(max_shift);
297 assert_eq!(result, 512 << 16); // 33,554,432 bytes (32 MB)
298 }
299
300 #[test]
301 fn test_is_power_of_two() {
302 // Valid powers of two
303 assert!(is_power_of_two(1));
304 assert!(is_power_of_two(2));
305 assert!(is_power_of_two(4));
306 assert!(is_power_of_two(8));
307 assert!(is_power_of_two(16));
308 assert!(is_power_of_two(32));
309 assert!(is_power_of_two(64));
310 assert!(is_power_of_two(128));
311 assert!(is_power_of_two(256));
312 assert!(is_power_of_two(512));
313 assert!(is_power_of_two(1024));
314 assert!(is_power_of_two(2048));
315 assert!(is_power_of_two(4096));
316 assert!(is_power_of_two(8192));
317 assert!(is_power_of_two(16384));
318 assert!(is_power_of_two(32768));
319 assert!(is_power_of_two(65536));
320 assert!(is_power_of_two(0x100000)); // 1,048,576
321 assert!(is_power_of_two(0x80000000)); // 2^31
322
323 // Not powers of two
324 assert!(!is_power_of_two(0));
325 assert!(!is_power_of_two(3));
326 assert!(!is_power_of_two(5));
327 assert!(!is_power_of_two(6));
328 assert!(!is_power_of_two(7));
329 assert!(!is_power_of_two(9));
330 assert!(!is_power_of_two(10));
331 assert!(!is_power_of_two(15));
332 assert!(!is_power_of_two(100));
333 assert!(!is_power_of_two(127));
334 assert!(!is_power_of_two(255));
335 assert!(!is_power_of_two(1023));
336 assert!(!is_power_of_two(1025));
337 assert!(!is_power_of_two(0xFFFFFFFF));
338 }
339
340 #[test]
341 fn test_hash_table_size_validation() {
342 // Hash table sizes must be powers of two
343 // This test demonstrates how is_power_of_two would be used
344
345 let valid_sizes = [
346 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536,
347 ];
348
349 for size in &valid_sizes {
350 assert!(
351 is_power_of_two(*size),
352 "Hash table size {size} should be valid"
353 );
354 }
355
356 let invalid_sizes = [0, 3, 5, 7, 9, 15, 100, 1000, 1023, 1025, 4095, 4097];
357
358 for size in &invalid_sizes {
359 assert!(
360 !is_power_of_two(*size),
361 "Hash table size {size} should be invalid"
362 );
363 }
364 }
365
366 #[test]
367 fn test_typical_mpq_configurations() {
368 // Test typical MPQ configurations from various games
369
370 // Diablo II: Often uses block size 3 (4KB sectors)
371 let d2_sector_size = calculate_sector_size(3);
372 assert_eq!(d2_sector_size, 4096);
373
374 // Warcraft III: Typically uses block size 3-4 (4KB-8KB sectors)
375 let wc3_sector_size_small = calculate_sector_size(3);
376 let wc3_sector_size_large = calculate_sector_size(4);
377 assert_eq!(wc3_sector_size_small, 4096);
378 assert_eq!(wc3_sector_size_large, 8192);
379
380 // World of Warcraft: Uses various sizes, often 4-8 (8KB-128KB sectors)
381 let wow_sector_size_min = calculate_sector_size(4);
382 let wow_sector_size_typical = calculate_sector_size(6);
383 let wow_sector_size_max = calculate_sector_size(8);
384 assert_eq!(wow_sector_size_min, 8192);
385 assert_eq!(wow_sector_size_typical, 32768);
386 assert_eq!(wow_sector_size_max, 131072);
387
388 // StarCraft II: Can use larger sectors for HD assets
389 let sc2_sector_size = calculate_sector_size(9);
390 assert_eq!(sc2_sector_size, 262144); // 256 KB
391 }
392
393 #[test]
394 #[cfg(feature = "mmap")]
395 fn test_memory_mapping_config_availability() {
396 // Test that memory mapping types are available when feature is enabled
397 let _config = MemoryMapConfig::default();
398 let _stats = MemoryMapStats::default();
399 }
400
401 #[test]
402 #[cfg(feature = "simd")]
403 fn test_simd_ops_availability() {
404 // Test that SIMD types are available when feature is enabled
405 let simd = SimdOps::new();
406 let _features = simd.features();
407
408 // Should not panic during CPU feature detection
409 let _has_support = simd.has_simd_support();
410 }
411
412 #[test]
413 fn test_security_limits_availability() {
414 // Security types should always be available
415 let _limits = SecurityLimits::default();
416 let _tracker = SessionTracker::new();
417 }
418}