Skip to main content

whiteout/
mpq.rs

1// SPDX-License-Identifier: BSD-3-Clause
2// Copyright (c) 2026 Fernando Sahmkow
3// AUTOGENERATED by tools/codegen/emit_rust.py — do not edit.
4// Regenerate via:  python -m tools.codegen.codegen mpq --backend rust
5
6#![allow(clippy::too_many_arguments)]
7
8// Which of these a module needs depends on its shapes; the modules that
9// have no span accessors would otherwise trip the unused-import lint.
10#[allow(unused_imports)]
11use crate::support::{BorrowedSlice, Bytes};
12
13/// MPQ format version.
14#[repr(i32)]
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16pub enum FormatVersion {
17    /// Original format (up to 4 GB archives).
18    V1 = 0,
19    /// Extended format (>4 GB archives, hi-block table).
20    V2 = 1,
21}
22
23impl TryFrom<i32> for FormatVersion {
24    type Error = crate::Error;
25    fn try_from(v: i32) -> Result<Self, crate::Error> {
26        match v {
27            0 => Ok(FormatVersion::V1),
28            1 => Ok(FormatVersion::V2),
29            other => Err(crate::Error::UnknownEnum {
30                name: "FormatVersion",
31                value: other,
32            }),
33        }
34    }
35}
36
37/// Compression algorithm for writing files.
38#[repr(i32)]
39#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
40pub enum Compression {
41    /// No compression; data stored verbatim.
42    None = 0,
43    /// Huffman coding (used for audio in older Blizzard games).
44    Huffman = 1,
45    /// zlib / DEFLATE compression (most common MPQ codec).
46    Zlib = 2,
47    /// PKware DCL (implode) compression.
48    PKware = 8,
49    /// bzip2 compression.
50    BZip2 = 16,
51    /// Sparse / RLE compression.
52    Sparse = 32,
53    /// IMA ADPCM mono (used for mono audio).
54    AdpcmMono = 64,
55    /// IMA ADPCM stereo (used for stereo audio).
56    AdpcmStereo = -128,
57}
58
59impl TryFrom<i32> for Compression {
60    type Error = crate::Error;
61    fn try_from(v: i32) -> Result<Self, crate::Error> {
62        match v {
63            0 => Ok(Compression::None),
64            1 => Ok(Compression::Huffman),
65            2 => Ok(Compression::Zlib),
66            8 => Ok(Compression::PKware),
67            16 => Ok(Compression::BZip2),
68            32 => Ok(Compression::Sparse),
69            64 => Ok(Compression::AdpcmMono),
70            -128 => Ok(Compression::AdpcmStereo),
71            other => Err(crate::Error::UnknownEnum {
72                name: "Compression",
73                value: other,
74            }),
75        }
76    }
77}
78
79/// Bit flags. Combine with `|`, test with [`FileFlags::contains`].
80#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
81pub struct FileFlags(pub i32);
82
83impl FileFlags {
84    pub const NONE: Self = Self(0);
85    /// File uses sector compression.
86    pub const COMPRESSED: Self = Self(512);
87    /// File data is encrypted.
88    pub const ENCRYPTED: Self = Self(65536);
89    /// File stored as a single unit (no sector splitting).
90    pub const SINGLE_UNIT: Self = Self(16777216);
91    /// Slot is occupied by a real file.
92    pub const EXISTS: Self = Self(-2147483648);
93
94    #[inline]
95    pub const fn contains(self, other: Self) -> bool {
96        (self.0 & other.0) == other.0
97    }
98
99    #[inline]
100    pub const fn is_empty(self) -> bool {
101        self.0 == 0
102    }
103}
104
105impl core::ops::BitOr for FileFlags {
106    type Output = Self;
107    #[inline]
108    fn bitor(self, rhs: Self) -> Self {
109        Self(self.0 | rhs.0)
110    }
111}
112
113impl core::ops::BitAnd for FileFlags {
114    type Output = Self;
115    #[inline]
116    fn bitand(self, rhs: Self) -> Self {
117        Self(self.0 & rhs.0)
118    }
119}
120
121impl core::ops::Not for FileFlags {
122    type Output = Self;
123    #[inline]
124    fn not(self) -> Self {
125        Self(!self.0)
126    }
127}
128
129impl core::fmt::Debug for FileFlags {
130    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
131        write!(f, "FileFlags({:#x})", self.0)
132    }
133}
134
135/// Information about a single file in the archive.
136pub struct FileInfo {
137    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_MpqFileInfo>,
138}
139
140impl Drop for FileInfo {
141    fn drop(&mut self) {
142        // SAFETY: `raw` came from a native constructor and Drop runs once.
143        unsafe { ffi::whiteout_mpq_MpqFileInfo_delete(self.raw.as_ptr()) }
144    }
145}
146
147impl FileInfo {
148    /// # Safety
149    /// `raw` must be a live handle this value takes ownership of.
150    #[allow(dead_code)] // used by whichever methods return this type
151    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_MpqFileInfo) -> Option<Self> {
152        core::ptr::NonNull::new(raw).map(|raw| FileInfo { raw })
153    }
154}
155
156// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
157// is deliberately NOT implemented — the C++ types make no documented
158// guarantee about concurrent use, and claiming one we haven't verified
159// would be unsound. See `@bind thread_safe` in the plan.
160unsafe impl Send for FileInfo {}
161
162impl core::fmt::Debug for FileInfo {
163    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
164        f.debug_struct("FileInfo").finish_non_exhaustive()
165    }
166}
167
168impl FileInfo {
169    /// # Panics
170    /// Panics if the native allocation fails.
171    pub fn new() -> Self {
172        // SAFETY: the native constructor returns a live handle; a null here
173        // means the library is unusable.
174        unsafe {
175            let raw = ffi::whiteout_mpq_MpqFileInfo_new();
176            Self::from_raw(raw).expect("native FileInfo allocation failed")
177        }
178    }
179
180    /// Filename (from listfile or hash table lookup).
181    pub fn name(&self) -> String {
182        // SAFETY: the native side hands over an owned CString.
183        unsafe {
184            crate::support::take_string(ffi::whiteout_mpq_MpqFileInfo_get_name(self.raw.as_ptr()))
185        }
186    }
187
188    pub fn set_name(&mut self, value: &str) {
189        let value = std::ffi::CString::new(value).unwrap_or_default();
190        // SAFETY: the pointer outlives the call.
191        unsafe { ffi::whiteout_mpq_MpqFileInfo_set_name(self.raw.as_ptr(), value.as_ptr()) }
192    }
193
194    /// Compressed storage size in bytes.
195    pub fn compressed_size(&self) -> u32 {
196        // SAFETY: plain scalar read through a live handle.
197        unsafe { ffi::whiteout_mpq_MpqFileInfo_get_compressedSize(self.raw.as_ptr()) }
198    }
199
200    pub fn set_compressed_size(&mut self, value: u32) {
201        // SAFETY: plain scalar write through a live handle.
202        unsafe { ffi::whiteout_mpq_MpqFileInfo_set_compressedSize(self.raw.as_ptr(), value) }
203    }
204
205    /// Uncompressed (original) file size in bytes.
206    pub fn uncompressed_size(&self) -> u32 {
207        // SAFETY: plain scalar read through a live handle.
208        unsafe { ffi::whiteout_mpq_MpqFileInfo_get_uncompressedSize(self.raw.as_ptr()) }
209    }
210
211    pub fn set_uncompressed_size(&mut self, value: u32) {
212        // SAFETY: plain scalar write through a live handle.
213        unsafe { ffi::whiteout_mpq_MpqFileInfo_set_uncompressedSize(self.raw.as_ptr(), value) }
214    }
215
216    /// Block entry flags (see FileFlags enum).
217    pub fn flags(&self) -> FileFlags {
218        // SAFETY: scalar read; a flag set accepts any bits.
219        FileFlags(unsafe { ffi::whiteout_mpq_MpqFileInfo_get_flags(self.raw.as_ptr()) })
220    }
221
222    pub fn set_flags(&mut self, value: FileFlags) {
223        // SAFETY: scalar write through a live handle.
224        unsafe { ffi::whiteout_mpq_MpqFileInfo_set_flags(self.raw.as_ptr(), value.0) }
225    }
226
227    /// Locale ID (typically Locale::Neutral).
228    pub fn locale(&self) -> u16 {
229        // SAFETY: plain scalar read through a live handle.
230        unsafe { ffi::whiteout_mpq_MpqFileInfo_get_locale(self.raw.as_ptr()) }
231    }
232
233    pub fn set_locale(&mut self, value: u16) {
234        // SAFETY: plain scalar write through a live handle.
235        unsafe { ffi::whiteout_mpq_MpqFileInfo_set_locale(self.raw.as_ptr(), value) }
236    }
237}
238
239impl Default for FileInfo {
240    fn default() -> Self {
241        Self::new()
242    }
243}
244
245/// Summary information about the archive.
246pub struct ArchiveInfo {
247    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_MpqArchiveInfo>,
248}
249
250impl Drop for ArchiveInfo {
251    fn drop(&mut self) {
252        // SAFETY: `raw` came from a native constructor and Drop runs once.
253        unsafe { ffi::whiteout_mpq_MpqArchiveInfo_delete(self.raw.as_ptr()) }
254    }
255}
256
257impl ArchiveInfo {
258    /// # Safety
259    /// `raw` must be a live handle this value takes ownership of.
260    #[allow(dead_code)] // used by whichever methods return this type
261    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_MpqArchiveInfo) -> Option<Self> {
262        core::ptr::NonNull::new(raw).map(|raw| ArchiveInfo { raw })
263    }
264}
265
266// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
267// is deliberately NOT implemented — the C++ types make no documented
268// guarantee about concurrent use, and claiming one we haven't verified
269// would be unsound. See `@bind thread_safe` in the plan.
270unsafe impl Send for ArchiveInfo {}
271
272impl core::fmt::Debug for ArchiveInfo {
273    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
274        f.debug_struct("ArchiveInfo").finish_non_exhaustive()
275    }
276}
277
278impl ArchiveInfo {
279    /// # Panics
280    /// Panics if the native allocation fails.
281    pub fn new() -> Self {
282        // SAFETY: the native constructor returns a live handle; a null here
283        // means the library is unusable.
284        unsafe {
285            let raw = ffi::whiteout_mpq_MpqArchiveInfo_new();
286            Self::from_raw(raw).expect("native ArchiveInfo allocation failed")
287        }
288    }
289
290    /// 0 = V1, 1 = V2.
291    pub fn format_version(&self) -> u16 {
292        // SAFETY: plain scalar read through a live handle.
293        unsafe { ffi::whiteout_mpq_MpqArchiveInfo_get_formatVersion(self.raw.as_ptr()) }
294    }
295
296    pub fn set_format_version(&mut self, value: u16) {
297        // SAFETY: plain scalar write through a live handle.
298        unsafe { ffi::whiteout_mpq_MpqArchiveInfo_set_formatVersion(self.raw.as_ptr(), value) }
299    }
300
301    /// Hash table capacity (always a power of 2).
302    pub fn hash_table_entries(&self) -> u32 {
303        // SAFETY: plain scalar read through a live handle.
304        unsafe { ffi::whiteout_mpq_MpqArchiveInfo_get_hashTableEntries(self.raw.as_ptr()) }
305    }
306
307    pub fn set_hash_table_entries(&mut self, value: u32) {
308        // SAFETY: plain scalar write through a live handle.
309        unsafe { ffi::whiteout_mpq_MpqArchiveInfo_set_hashTableEntries(self.raw.as_ptr(), value) }
310    }
311
312    /// Number of occupied block table entries.
313    pub fn block_table_entries(&self) -> u32 {
314        // SAFETY: plain scalar read through a live handle.
315        unsafe { ffi::whiteout_mpq_MpqArchiveInfo_get_blockTableEntries(self.raw.as_ptr()) }
316    }
317
318    pub fn set_block_table_entries(&mut self, value: u32) {
319        // SAFETY: plain scalar write through a live handle.
320        unsafe { ffi::whiteout_mpq_MpqArchiveInfo_set_blockTableEntries(self.raw.as_ptr(), value) }
321    }
322
323    /// Sector size in bytes (512 << sectorSizeShift).
324    pub fn sector_size(&self) -> u32 {
325        // SAFETY: plain scalar read through a live handle.
326        unsafe { ffi::whiteout_mpq_MpqArchiveInfo_get_sectorSize(self.raw.as_ptr()) }
327    }
328
329    pub fn set_sector_size(&mut self, value: u32) {
330        // SAFETY: plain scalar write through a live handle.
331        unsafe { ffi::whiteout_mpq_MpqArchiveInfo_set_sectorSize(self.raw.as_ptr(), value) }
332    }
333
334    /// Total archive size in bytes.
335    pub fn archive_size(&self) -> u64 {
336        // SAFETY: plain scalar read through a live handle.
337        unsafe { ffi::whiteout_mpq_MpqArchiveInfo_get_archiveSize(self.raw.as_ptr()) }
338    }
339
340    pub fn set_archive_size(&mut self, value: u64) {
341        // SAFETY: plain scalar write through a live handle.
342        unsafe { ffi::whiteout_mpq_MpqArchiveInfo_set_archiveSize(self.raw.as_ptr(), value) }
343    }
344}
345
346impl Default for ArchiveInfo {
347    fn default() -> Self {
348        Self::new()
349    }
350}
351
352/// Options for writing a file into the archive.
353pub struct WriteOptions {
354    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_MpqWriteOptions>,
355}
356
357impl Drop for WriteOptions {
358    fn drop(&mut self) {
359        // SAFETY: `raw` came from a native constructor and Drop runs once.
360        unsafe { ffi::whiteout_mpq_MpqWriteOptions_delete(self.raw.as_ptr()) }
361    }
362}
363
364impl WriteOptions {
365    /// # Safety
366    /// `raw` must be a live handle this value takes ownership of.
367    #[allow(dead_code)] // used by whichever methods return this type
368    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_MpqWriteOptions) -> Option<Self> {
369        core::ptr::NonNull::new(raw).map(|raw| WriteOptions { raw })
370    }
371}
372
373// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
374// is deliberately NOT implemented — the C++ types make no documented
375// guarantee about concurrent use, and claiming one we haven't verified
376// would be unsound. See `@bind thread_safe` in the plan.
377unsafe impl Send for WriteOptions {}
378
379impl core::fmt::Debug for WriteOptions {
380    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
381        f.debug_struct("WriteOptions").finish_non_exhaustive()
382    }
383}
384
385impl WriteOptions {
386    /// # Panics
387    /// Panics if the native allocation fails.
388    pub fn new() -> Self {
389        // SAFETY: the native constructor returns a live handle; a null here
390        // means the library is unusable.
391        unsafe {
392            let raw = ffi::whiteout_mpq_MpqWriteOptions_new();
393            Self::from_raw(raw).expect("native WriteOptions allocation failed")
394        }
395    }
396
397    /// Compression algorithm to apply.
398    pub fn compression(&self) -> Compression {
399        // SAFETY: scalar read; the discriminant is validated below.
400        unsafe { ffi::whiteout_mpq_MpqWriteOptions_get_compression(self.raw.as_ptr()) }
401            .try_into()
402            .expect("unknown enum discriminant from the native library")
403    }
404
405    pub fn set_compression(&mut self, value: Compression) {
406        // SAFETY: scalar write through a live handle.
407        unsafe {
408            ffi::whiteout_mpq_MpqWriteOptions_set_compression(self.raw.as_ptr(), value as i32)
409        }
410    }
411
412    /// Locale ID for the hash table slot.
413    pub fn locale(&self) -> u16 {
414        // SAFETY: plain scalar read through a live handle.
415        unsafe { ffi::whiteout_mpq_MpqWriteOptions_get_locale(self.raw.as_ptr()) }
416    }
417
418    pub fn set_locale(&mut self, value: u16) {
419        // SAFETY: plain scalar write through a live handle.
420        unsafe { ffi::whiteout_mpq_MpqWriteOptions_set_locale(self.raw.as_ptr(), value) }
421    }
422
423    /// Encrypt file data with a derived key.
424    pub fn encrypt(&self) -> bool {
425        // SAFETY: plain scalar read through a live handle.
426        unsafe { ffi::whiteout_mpq_MpqWriteOptions_get_encrypt(self.raw.as_ptr()) != 0 }
427    }
428
429    pub fn set_encrypt(&mut self, value: bool) {
430        // SAFETY: plain scalar write through a live handle.
431        unsafe {
432            ffi::whiteout_mpq_MpqWriteOptions_set_encrypt(
433                self.raw.as_ptr(),
434                if value { 1 } else { 0 },
435            )
436        }
437    }
438
439    /// Store the file as a single unpartitioned unit.
440    pub fn single_unit(&self) -> bool {
441        // SAFETY: plain scalar read through a live handle.
442        unsafe { ffi::whiteout_mpq_MpqWriteOptions_get_singleUnit(self.raw.as_ptr()) != 0 }
443    }
444
445    pub fn set_single_unit(&mut self, value: bool) {
446        // SAFETY: plain scalar write through a live handle.
447        unsafe {
448            ffi::whiteout_mpq_MpqWriteOptions_set_singleUnit(
449                self.raw.as_ptr(),
450                if value { 1 } else { 0 },
451            )
452        }
453    }
454}
455
456impl Default for WriteOptions {
457    fn default() -> Self {
458        Self::new()
459    }
460}
461
462/// Options for creating a new archive.
463pub struct CreateOptions {
464    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_MpqCreateOptions>,
465}
466
467impl Drop for CreateOptions {
468    fn drop(&mut self) {
469        // SAFETY: `raw` came from a native constructor and Drop runs once.
470        unsafe { ffi::whiteout_mpq_MpqCreateOptions_delete(self.raw.as_ptr()) }
471    }
472}
473
474impl CreateOptions {
475    /// # Safety
476    /// `raw` must be a live handle this value takes ownership of.
477    #[allow(dead_code)] // used by whichever methods return this type
478    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_MpqCreateOptions) -> Option<Self> {
479        core::ptr::NonNull::new(raw).map(|raw| CreateOptions { raw })
480    }
481}
482
483// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
484// is deliberately NOT implemented — the C++ types make no documented
485// guarantee about concurrent use, and claiming one we haven't verified
486// would be unsound. See `@bind thread_safe` in the plan.
487unsafe impl Send for CreateOptions {}
488
489impl core::fmt::Debug for CreateOptions {
490    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
491        f.debug_struct("CreateOptions").finish_non_exhaustive()
492    }
493}
494
495impl CreateOptions {
496    /// # Panics
497    /// Panics if the native allocation fails.
498    pub fn new() -> Self {
499        // SAFETY: the native constructor returns a live handle; a null here
500        // means the library is unusable.
501        unsafe {
502            let raw = ffi::whiteout_mpq_MpqCreateOptions_new();
503            Self::from_raw(raw).expect("native CreateOptions allocation failed")
504        }
505    }
506
507    /// Archive format version (V1 or V2).
508    pub fn version(&self) -> FormatVersion {
509        // SAFETY: scalar read; the discriminant is validated below.
510        unsafe { ffi::whiteout_mpq_MpqCreateOptions_get_version(self.raw.as_ptr()) }
511            .try_into()
512            .expect("unknown enum discriminant from the native library")
513    }
514
515    pub fn set_version(&mut self, value: FormatVersion) {
516        // SAFETY: scalar write through a live handle.
517        unsafe { ffi::whiteout_mpq_MpqCreateOptions_set_version(self.raw.as_ptr(), value as i32) }
518    }
519
520    /// Initial hash table capacity; rounded up to the next power of 2.
521    pub fn hash_table_size(&self) -> u32 {
522        // SAFETY: plain scalar read through a live handle.
523        unsafe { ffi::whiteout_mpq_MpqCreateOptions_get_hashTableSize(self.raw.as_ptr()) }
524    }
525
526    pub fn set_hash_table_size(&mut self, value: u32) {
527        // SAFETY: plain scalar write through a live handle.
528        unsafe { ffi::whiteout_mpq_MpqCreateOptions_set_hashTableSize(self.raw.as_ptr(), value) }
529    }
530
531    /// Sector size = 512 << shift (default 3 → 4096 bytes).
532    pub fn sector_size_shift(&self) -> u16 {
533        // SAFETY: plain scalar read through a live handle.
534        unsafe { ffi::whiteout_mpq_MpqCreateOptions_get_sectorSizeShift(self.raw.as_ptr()) }
535    }
536
537    pub fn set_sector_size_shift(&mut self, value: u16) {
538        // SAFETY: plain scalar write through a live handle.
539        unsafe { ffi::whiteout_mpq_MpqCreateOptions_set_sectorSizeShift(self.raw.as_ptr(), value) }
540    }
541}
542
543impl Default for CreateOptions {
544    fn default() -> Self {
545        Self::new()
546    }
547}
548
549/// RAII wrapper for MPQ archive access
550///
551/// Provides full CRUD operations on MPQ archives.  Modifications are held in a virtual overlay until save() is called, which writes a complete new archive atomically (write to temp file, then rename).
552///
553/// All public methods are thread-safe: read operations acquire a shared lock; write and persist operations acquire an exclusive lock.
554pub struct Storage {
555    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_MpqStorage>,
556}
557
558impl Drop for Storage {
559    fn drop(&mut self) {
560        // SAFETY: `raw` came from a native constructor and Drop runs once.
561        unsafe { ffi::whiteout_mpq_MpqStorage_delete(self.raw.as_ptr()) }
562    }
563}
564
565impl Storage {
566    /// # Safety
567    /// `raw` must be a live handle this value takes ownership of.
568    #[allow(dead_code)] // used by whichever methods return this type
569    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_MpqStorage) -> Option<Self> {
570        core::ptr::NonNull::new(raw).map(|raw| Storage { raw })
571    }
572}
573
574// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
575// is deliberately NOT implemented — the C++ types make no documented
576// guarantee about concurrent use, and claiming one we haven't verified
577// would be unsound. See `@bind thread_safe` in the plan.
578unsafe impl Send for Storage {}
579
580impl core::fmt::Debug for Storage {
581    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
582        f.debug_struct("Storage").finish_non_exhaustive()
583    }
584}
585
586impl Storage {
587    /// Open an existing MPQ archive. Memory-maps the file and parses tables. @param path  Path to the .mpq file. @param pool  Optional WorkerPool for parallel compress/decompress (non-owning). @return A valid Storage, or std::nullopt on failure.
588    pub fn open(path: &str, pool: Option<&crate::interfaces::HostWorkerPool>) -> Option<Storage> {
589        let path_cstr = std::ffi::CString::new(path).unwrap_or_default();
590        // SAFETY: handle is live for the duration of the call.
591        unsafe {
592            Storage::from_raw(ffi::whiteout_mpq_MpqStorage_open(
593                path_cstr.as_ptr(),
594                pool.map_or(core::ptr::null_mut(), |v| v.as_ptr()),
595            ))
596        }
597    }
598
599    /// Create a new empty archive in memory. No file on disk until save(path).
600    pub fn create(
601        opts: &CreateOptions,
602        pool: Option<&crate::interfaces::HostWorkerPool>,
603    ) -> Option<Storage> {
604        // SAFETY: handle is live for the duration of the call.
605        unsafe {
606            Storage::from_raw(ffi::whiteout_mpq_MpqStorage_create(
607                opts.raw.as_ptr(),
608                pool.map_or(core::ptr::null_mut(), |v| v.as_ptr()),
609            ))
610        }
611    }
612
613    /// Release all resources. Same effect as letting the Storage go out of scope.
614    pub fn close(&mut self) {
615        // SAFETY: handle is live for the duration of the call.
616        unsafe {
617            ffi::whiteout_mpq_MpqStorage_close(self.raw.as_ptr());
618        }
619    }
620
621    /// Read a file from the archive. Checks the overlay first, then the source archive. @return File contents, or std::nullopt if not found or deleted.
622    pub fn read_file(&self, name: &str) -> Option<Bytes> {
623        let name_cstr = std::ffi::CString::new(name).unwrap_or_default();
624        // SAFETY: handle is live for the duration of the call.
625        unsafe {
626            Bytes::from_raw(ffi::whiteout_mpq_MpqStorage_readFile(
627                self.raw.as_ptr(),
628                name_cstr.as_ptr(),
629            ))
630        }
631    }
632
633    /// Read a file with a specific locale.
634    pub fn read_file_name_locale(&self, name: &str, locale: u16) -> Option<Bytes> {
635        let name_cstr = std::ffi::CString::new(name).unwrap_or_default();
636        // SAFETY: handle is live for the duration of the call.
637        unsafe {
638            Bytes::from_raw(ffi::whiteout_mpq_MpqStorage_readFile_name_locale(
639                self.raw.as_ptr(),
640                name_cstr.as_ptr(),
641                locale,
642            ))
643        }
644    }
645
646    /// Check if a file exists in the archive (including overlay).
647    pub fn file_exists(&self, name: &str) -> bool {
648        let name_cstr = std::ffi::CString::new(name).unwrap_or_default();
649        // SAFETY: handle is live for the duration of the call.
650        unsafe {
651            ffi::whiteout_mpq_MpqStorage_fileExists(self.raw.as_ptr(), name_cstr.as_ptr()) != 0
652        }
653    }
654
655    /// Get information about a file.
656    pub fn file_info(&self, name: &str) -> Option<FileInfo> {
657        let name_cstr = std::ffi::CString::new(name).unwrap_or_default();
658        // SAFETY: handle is live for the duration of the call.
659        unsafe {
660            FileInfo::from_raw(ffi::whiteout_mpq_MpqStorage_fileInfo(
661                self.raw.as_ptr(),
662                name_cstr.as_ptr(),
663            ))
664        }
665    }
666
667    /// Get summary information about the archive.
668    pub fn archive_info(&self) -> Option<ArchiveInfo> {
669        // SAFETY: handle is live for the duration of the call.
670        unsafe {
671            ArchiveInfo::from_raw(ffi::whiteout_mpq_MpqStorage_archiveInfo(self.raw.as_ptr()))
672        }
673    }
674
675    /// List all known filenames (from listfile + overlay additions − deletions).
676    pub fn list_files(&self) -> Vec<String> {
677        // SAFETY: index stays below the reported count.
678        unsafe {
679            let n = ffi::whiteout_mpq_MpqStorage_listFiles_count(self.raw.as_ptr());
680            (0..n)
681                .map(|i| {
682                    crate::support::take_string(ffi::whiteout_mpq_MpqStorage_listFiles_at(
683                        self.raw.as_ptr(),
684                        i,
685                    ))
686                })
687                .collect()
688        }
689    }
690
691    /// Write or overwrite a file. Data is held in overlay until save(). @return true on success, false if the hash table is full.
692    pub fn write_file(&mut self, name: &[u8], data: &[u8], opts: &WriteOptions) -> bool {
693        // SAFETY: handle is live for the duration of the call.
694        unsafe {
695            ffi::whiteout_mpq_MpqStorage_writeFile(
696                self.raw.as_ptr(),
697                name.as_ptr(),
698                name.len(),
699                data.as_ptr(),
700                data.len(),
701                opts.raw.as_ptr(),
702            ) != 0
703        }
704    }
705
706    /// Delete a file from the archive. @return true if the file was found (in source or overlay), false otherwise.
707    pub fn delete_file(&mut self, name: &str) -> bool {
708        let name_cstr = std::ffi::CString::new(name).unwrap_or_default();
709        // SAFETY: handle is live for the duration of the call.
710        unsafe {
711            ffi::whiteout_mpq_MpqStorage_deleteFile(self.raw.as_ptr(), name_cstr.as_ptr()) != 0
712        }
713    }
714
715    /// Save the archive to its original path (temp file + atomic rename). @return false if this Storage was created via create() with no prior save(path).
716    pub fn save(&mut self) -> bool {
717        // SAFETY: handle is live for the duration of the call.
718        unsafe { ffi::whiteout_mpq_MpqStorage_save(self.raw.as_ptr()) != 0 }
719    }
720
721    /// Save the archive to a specific path. After saving, the new file becomes the source archive and the overlay is cleared.
722    pub fn save_path(&mut self, path: &str) -> bool {
723        let path_cstr = std::ffi::CString::new(path).unwrap_or_default();
724        // SAFETY: handle is live for the duration of the call.
725        unsafe {
726            ffi::whiteout_mpq_MpqStorage_save_path(self.raw.as_ptr(), path_cstr.as_ptr()) != 0
727        }
728    }
729}
730
731/// VirtualPathFileSystem implementation backed by an MPQ archive.
732///
733/// The Storage must outlive this object — MpqFileSystem holds a non-owning reference to it.
734///
735/// Path separators: both '/' and '\\' are accepted and treated identically. Filename comparison is case-insensitive, matching MPQ archive semantics.
736///
737/// Requires the `whiteout_mpq` CMake target.
738///
739/// Example: auto storage = mpq::Storage::open("War3.mpq"); utils::MpqFileSystem fs(*storage); auto data = fs.readFile("units\\orc\\grunt\\grunt.mdx");
740pub struct FileSystem {
741    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_MpqFileSystem>,
742}
743
744impl Drop for FileSystem {
745    fn drop(&mut self) {
746        // SAFETY: `raw` came from a native constructor and Drop runs once.
747        unsafe { ffi::whiteout_mpq_MpqFileSystem_delete(self.raw.as_ptr()) }
748    }
749}
750
751impl FileSystem {
752    /// # Safety
753    /// `raw` must be a live handle this value takes ownership of.
754    #[allow(dead_code)] // used by whichever methods return this type
755    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_MpqFileSystem) -> Option<Self> {
756        core::ptr::NonNull::new(raw).map(|raw| FileSystem { raw })
757    }
758}
759
760// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
761// is deliberately NOT implemented — the C++ types make no documented
762// guarantee about concurrent use, and claiming one we haven't verified
763// would be unsound. See `@bind thread_safe` in the plan.
764unsafe impl Send for FileSystem {}
765
766impl core::fmt::Debug for FileSystem {
767    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
768        f.debug_struct("FileSystem").finish_non_exhaustive()
769    }
770}
771
772impl FileSystem {
773    /// Read a file from the archive. Returns an empty vector if not found.
774    pub fn read_file(&self, path: &str) -> Bytes {
775        let path_cstr = std::ffi::CString::new(path).unwrap_or_default();
776        // SAFETY: handle is live for the duration of the call.
777        unsafe {
778            Bytes::from_raw(ffi::whiteout_mpq_MpqFileSystem_readFile(
779                self.raw.as_ptr(),
780                path_cstr.as_ptr(),
781            ))
782            .unwrap_or_else(Bytes::empty)
783        }
784    }
785
786    /// Write a file into the archive overlay. Changes are not persisted to disk until storage.save() is called on the underlying Storage.
787    pub fn write_file(&mut self, path: &str, data: &[u8]) -> bool {
788        let path_cstr = std::ffi::CString::new(path).unwrap_or_default();
789        // SAFETY: handle is live for the duration of the call.
790        unsafe {
791            ffi::whiteout_mpq_MpqFileSystem_writeFile(
792                self.raw.as_ptr(),
793                path_cstr.as_ptr(),
794                data.as_ptr(),
795                data.len(),
796            ) != 0
797        }
798    }
799
800    /// Check if a file exists in the archive (including the write overlay).
801    pub fn file_exists(&self, path: &str) -> bool {
802        let path_cstr = std::ffi::CString::new(path).unwrap_or_default();
803        // SAFETY: handle is live for the duration of the call.
804        unsafe {
805            ffi::whiteout_mpq_MpqFileSystem_fileExists(self.raw.as_ptr(), path_cstr.as_ptr()) != 0
806        }
807    }
808}
809
810#[doc(hidden)]
811pub mod ffi {
812    #![allow(missing_debug_implementations)]
813
814    #[allow(unused_imports)]
815    use crate::support::{RawBytes, RawCString};
816
817    #[repr(C)]
818    pub struct whiteout_MpqFileInfo {
819        _private: [u8; 0],
820    }
821    #[repr(C)]
822    pub struct whiteout_MpqArchiveInfo {
823        _private: [u8; 0],
824    }
825    #[repr(C)]
826    pub struct whiteout_MpqWriteOptions {
827        _private: [u8; 0],
828    }
829    #[repr(C)]
830    pub struct whiteout_MpqCreateOptions {
831        _private: [u8; 0],
832    }
833    #[repr(C)]
834    pub struct whiteout_MpqStorage {
835        _private: [u8; 0],
836    }
837    #[repr(C)]
838    pub struct whiteout_MpqFileSystem {
839        _private: [u8; 0],
840    }
841
842    extern "C" {
843        // FileInfo
844        pub fn whiteout_mpq_MpqFileInfo_new() -> *mut whiteout_MpqFileInfo;
845        pub fn whiteout_mpq_MpqFileInfo_delete(self_: *mut whiteout_MpqFileInfo);
846        pub fn whiteout_mpq_MpqFileInfo_get_name(self_: *mut whiteout_MpqFileInfo) -> RawCString;
847        pub fn whiteout_mpq_MpqFileInfo_set_name(
848            self_: *mut whiteout_MpqFileInfo,
849            value: *const core::ffi::c_char,
850        );
851        pub fn whiteout_mpq_MpqFileInfo_get_compressedSize(self_: *mut whiteout_MpqFileInfo)
852            -> u32;
853        pub fn whiteout_mpq_MpqFileInfo_set_compressedSize(
854            self_: *mut whiteout_MpqFileInfo,
855            value: u32,
856        );
857        pub fn whiteout_mpq_MpqFileInfo_get_uncompressedSize(
858            self_: *mut whiteout_MpqFileInfo,
859        ) -> u32;
860        pub fn whiteout_mpq_MpqFileInfo_set_uncompressedSize(
861            self_: *mut whiteout_MpqFileInfo,
862            value: u32,
863        );
864        pub fn whiteout_mpq_MpqFileInfo_get_flags(self_: *mut whiteout_MpqFileInfo) -> i32;
865        pub fn whiteout_mpq_MpqFileInfo_set_flags(self_: *mut whiteout_MpqFileInfo, value: i32);
866        pub fn whiteout_mpq_MpqFileInfo_get_locale(self_: *mut whiteout_MpqFileInfo) -> u16;
867        pub fn whiteout_mpq_MpqFileInfo_set_locale(self_: *mut whiteout_MpqFileInfo, value: u16);
868        // ArchiveInfo
869        pub fn whiteout_mpq_MpqArchiveInfo_new() -> *mut whiteout_MpqArchiveInfo;
870        pub fn whiteout_mpq_MpqArchiveInfo_delete(self_: *mut whiteout_MpqArchiveInfo);
871        pub fn whiteout_mpq_MpqArchiveInfo_get_formatVersion(
872            self_: *mut whiteout_MpqArchiveInfo,
873        ) -> u16;
874        pub fn whiteout_mpq_MpqArchiveInfo_set_formatVersion(
875            self_: *mut whiteout_MpqArchiveInfo,
876            value: u16,
877        );
878        pub fn whiteout_mpq_MpqArchiveInfo_get_hashTableEntries(
879            self_: *mut whiteout_MpqArchiveInfo,
880        ) -> u32;
881        pub fn whiteout_mpq_MpqArchiveInfo_set_hashTableEntries(
882            self_: *mut whiteout_MpqArchiveInfo,
883            value: u32,
884        );
885        pub fn whiteout_mpq_MpqArchiveInfo_get_blockTableEntries(
886            self_: *mut whiteout_MpqArchiveInfo,
887        ) -> u32;
888        pub fn whiteout_mpq_MpqArchiveInfo_set_blockTableEntries(
889            self_: *mut whiteout_MpqArchiveInfo,
890            value: u32,
891        );
892        pub fn whiteout_mpq_MpqArchiveInfo_get_sectorSize(
893            self_: *mut whiteout_MpqArchiveInfo,
894        ) -> u32;
895        pub fn whiteout_mpq_MpqArchiveInfo_set_sectorSize(
896            self_: *mut whiteout_MpqArchiveInfo,
897            value: u32,
898        );
899        pub fn whiteout_mpq_MpqArchiveInfo_get_archiveSize(
900            self_: *mut whiteout_MpqArchiveInfo,
901        ) -> u64;
902        pub fn whiteout_mpq_MpqArchiveInfo_set_archiveSize(
903            self_: *mut whiteout_MpqArchiveInfo,
904            value: u64,
905        );
906        // WriteOptions
907        pub fn whiteout_mpq_MpqWriteOptions_new() -> *mut whiteout_MpqWriteOptions;
908        pub fn whiteout_mpq_MpqWriteOptions_delete(self_: *mut whiteout_MpqWriteOptions);
909        pub fn whiteout_mpq_MpqWriteOptions_get_compression(
910            self_: *mut whiteout_MpqWriteOptions,
911        ) -> i32;
912        pub fn whiteout_mpq_MpqWriteOptions_set_compression(
913            self_: *mut whiteout_MpqWriteOptions,
914            value: i32,
915        );
916        pub fn whiteout_mpq_MpqWriteOptions_get_locale(self_: *mut whiteout_MpqWriteOptions)
917            -> u16;
918        pub fn whiteout_mpq_MpqWriteOptions_set_locale(
919            self_: *mut whiteout_MpqWriteOptions,
920            value: u16,
921        );
922        pub fn whiteout_mpq_MpqWriteOptions_get_encrypt(
923            self_: *mut whiteout_MpqWriteOptions,
924        ) -> i32;
925        pub fn whiteout_mpq_MpqWriteOptions_set_encrypt(
926            self_: *mut whiteout_MpqWriteOptions,
927            value: i32,
928        );
929        pub fn whiteout_mpq_MpqWriteOptions_get_singleUnit(
930            self_: *mut whiteout_MpqWriteOptions,
931        ) -> i32;
932        pub fn whiteout_mpq_MpqWriteOptions_set_singleUnit(
933            self_: *mut whiteout_MpqWriteOptions,
934            value: i32,
935        );
936        // CreateOptions
937        pub fn whiteout_mpq_MpqCreateOptions_new() -> *mut whiteout_MpqCreateOptions;
938        pub fn whiteout_mpq_MpqCreateOptions_delete(self_: *mut whiteout_MpqCreateOptions);
939        pub fn whiteout_mpq_MpqCreateOptions_get_version(
940            self_: *mut whiteout_MpqCreateOptions,
941        ) -> i32;
942        pub fn whiteout_mpq_MpqCreateOptions_set_version(
943            self_: *mut whiteout_MpqCreateOptions,
944            value: i32,
945        );
946        pub fn whiteout_mpq_MpqCreateOptions_get_hashTableSize(
947            self_: *mut whiteout_MpqCreateOptions,
948        ) -> u32;
949        pub fn whiteout_mpq_MpqCreateOptions_set_hashTableSize(
950            self_: *mut whiteout_MpqCreateOptions,
951            value: u32,
952        );
953        pub fn whiteout_mpq_MpqCreateOptions_get_sectorSizeShift(
954            self_: *mut whiteout_MpqCreateOptions,
955        ) -> u16;
956        pub fn whiteout_mpq_MpqCreateOptions_set_sectorSizeShift(
957            self_: *mut whiteout_MpqCreateOptions,
958            value: u16,
959        );
960        // Storage
961        pub fn whiteout_mpq_MpqStorage_delete(self_: *mut whiteout_MpqStorage);
962        pub fn whiteout_mpq_MpqStorage_open(
963            path: *const core::ffi::c_char,
964            pool: *mut core::ffi::c_void,
965        ) -> *mut whiteout_MpqStorage;
966        pub fn whiteout_mpq_MpqStorage_create(
967            opts: *mut whiteout_MpqCreateOptions,
968            pool: *mut core::ffi::c_void,
969        ) -> *mut whiteout_MpqStorage;
970        pub fn whiteout_mpq_MpqStorage_close(self_: *mut whiteout_MpqStorage);
971        pub fn whiteout_mpq_MpqStorage_readFile(
972            self_: *mut whiteout_MpqStorage,
973            name: *const core::ffi::c_char,
974        ) -> RawBytes;
975        pub fn whiteout_mpq_MpqStorage_readFile_name_locale(
976            self_: *mut whiteout_MpqStorage,
977            name: *const core::ffi::c_char,
978            locale: u16,
979        ) -> RawBytes;
980        pub fn whiteout_mpq_MpqStorage_fileExists(
981            self_: *mut whiteout_MpqStorage,
982            name: *const core::ffi::c_char,
983        ) -> i32;
984        pub fn whiteout_mpq_MpqStorage_fileInfo(
985            self_: *mut whiteout_MpqStorage,
986            name: *const core::ffi::c_char,
987        ) -> *mut whiteout_MpqFileInfo;
988        pub fn whiteout_mpq_MpqStorage_archiveInfo(
989            self_: *mut whiteout_MpqStorage,
990        ) -> *mut whiteout_MpqArchiveInfo;
991        pub fn whiteout_mpq_MpqStorage_listFiles_count(self_: *mut whiteout_MpqStorage) -> usize;
992        pub fn whiteout_mpq_MpqStorage_listFiles_at(
993            self_: *mut whiteout_MpqStorage,
994            index: usize,
995        ) -> RawCString;
996        pub fn whiteout_mpq_MpqStorage_writeFile(
997            self_: *mut whiteout_MpqStorage,
998            name: *const u8,
999            name_size: usize,
1000            data: *const u8,
1001            data_size: usize,
1002            opts: *mut whiteout_MpqWriteOptions,
1003        ) -> i32;
1004        pub fn whiteout_mpq_MpqStorage_deleteFile(
1005            self_: *mut whiteout_MpqStorage,
1006            name: *const core::ffi::c_char,
1007        ) -> i32;
1008        pub fn whiteout_mpq_MpqStorage_save(self_: *mut whiteout_MpqStorage) -> i32;
1009        pub fn whiteout_mpq_MpqStorage_save_path(
1010            self_: *mut whiteout_MpqStorage,
1011            path: *const core::ffi::c_char,
1012        ) -> i32;
1013        // FileSystem
1014        pub fn whiteout_mpq_MpqFileSystem_new_storage(
1015            _0: *mut core::ffi::c_void,
1016        ) -> *mut whiteout_MpqFileSystem;
1017        pub fn whiteout_mpq_MpqFileSystem_delete(self_: *mut whiteout_MpqFileSystem);
1018        pub fn whiteout_mpq_MpqFileSystem_readFile(
1019            self_: *mut whiteout_MpqFileSystem,
1020            path: *const core::ffi::c_char,
1021        ) -> RawBytes;
1022        pub fn whiteout_mpq_MpqFileSystem_writeFile(
1023            self_: *mut whiteout_MpqFileSystem,
1024            path: *const core::ffi::c_char,
1025            data: *const u8,
1026            data_size: usize,
1027        ) -> i32;
1028        pub fn whiteout_mpq_MpqFileSystem_fileExists(
1029            self_: *mut whiteout_MpqFileSystem,
1030            path: *const core::ffi::c_char,
1031        ) -> i32;
1032    }
1033}