Skip to main content

whiteout/
lib.rs

1// SPDX-License-Identifier: BSD-3-Clause
2// Copyright (c) 2026 Fernando Sahmkow
3
4//! Rust bindings for [WhiteoutLib](https://github.com/Sahmkow/WhiteoutLib) —
5//! parsers and writers for Blizzard model and texture formats.
6//!
7//! # Layout contract
8//!
9//! Value types such as [`math::Vector3f`] are `#[repr(C)]` mirrors of their
10//! C++ counterparts and cross the FFI boundary with no conversion. Their
11//! sizes are pinned by `const` assertions at compile time, and
12//! [`math::check_abi`] re-verifies them against the library actually linked:
13//!
14//! ```no_run
15//! whiteout::math::check_abi().expect("native library layout mismatch");
16//! ```
17//!
18//! # Errors
19//!
20//! The underlying C++ library does not throw, and signals absence with
21//! `std::optional`. This binding follows that: operations that can simply
22//! find nothing return [`Option`], and [`Result`] is reserved for the few
23//! calls that produce a real diagnostic.
24//!
25//! # Zero-copy pixel access
26//!
27//! [`textures::Texture::data`] and `data_mut` borrow the C++ buffer
28//! directly — nothing is copied in either direction. That is safe because
29//! the slice borrows the texture, so the compiler rejects any use that
30//! could dangle. This must not compile:
31//!
32//! ```compile_fail
33//! use whiteout::textures::{PixelFormat, Texture};
34//! let mut tex = Texture::create_2d(PixelFormat::RGBA8, 4, 4, 1).unwrap();
35//! let pixels = tex.data_mut();
36//! drop(tex);            // owner released while `pixels` is still alive
37//! pixels[0] = 1;
38//! ```
39//!
40//! Neither may a shared and a mutable view coexist:
41//!
42//! ```compile_fail
43//! use whiteout::textures::{PixelFormat, Texture};
44//! let mut tex = Texture::create_2d(PixelFormat::RGBA8, 4, 4, 1).unwrap();
45//! let shared = tex.data();
46//! let unique = tex.data_mut();   // second borrow, one of them mutable
47//! let _ = (shared[0], unique[0]);
48//! ```
49//!
50//! C# and C++ can only document these hazards; here they are compile
51//! errors, which is what makes handing out the raw buffer reasonable.
52
53// The README is the crate docs, so its examples are compiled.
54#![doc = include_str!("../README.md")]
55#![deny(unsafe_op_in_unsafe_fn)]
56#![warn(missing_debug_implementations)]
57
58#[cfg(feature = "casc")]
59pub mod casc;
60pub mod host;
61pub mod interfaces;
62pub mod m2;
63pub mod m3;
64pub mod math;
65pub mod mdx;
66#[cfg(feature = "mpq")]
67pub mod mpq;
68mod support;
69pub mod textures;
70
71pub use support::{BorrowedSlice, Bytes, Ref, RefMut};
72
73use core::fmt;
74
75/// Errors that can cross the binding boundary.
76///
77/// Deliberately small: the C++ library reports "not found" through
78/// `std::optional`, which this binding surfaces as [`Option`] rather than
79/// as an error.
80#[non_exhaustive]
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub enum Error {
83    /// A `std::string*` diagnostic from the native side.
84    Native(String),
85    /// The linked library's layout disagrees with what this crate was
86    /// generated against — see [`math::check_abi`].
87    Layout {
88        what: &'static str,
89        expected: usize,
90        actual: usize,
91    },
92    /// A cargo feature is enabled but the linked library was built without
93    /// the matching `WHITEOUT_ENABLE_*`.
94    FeatureDisabled(&'static str),
95    /// The native library produced an enum discriminant this crate does not
96    /// know. Indicates version skew rather than bad input.
97    UnknownEnum { name: &'static str, value: i32 },
98}
99
100impl fmt::Display for Error {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        match self {
103            Error::Native(msg) => write!(f, "{msg}"),
104            Error::Layout {
105                what,
106                expected,
107                actual,
108            } => write!(
109                f,
110                "native layout mismatch for {what}: this crate expects {expected}, \
111                 the linked library reports {actual}"
112            ),
113            Error::FeatureDisabled(feat) => write!(
114                f,
115                "the `{feat}` feature is enabled but the linked whiteout_native \
116                 was built without it"
117            ),
118            Error::UnknownEnum { name, value } => write!(
119                f,
120                "the native library returned {value} for {name}, which this \
121                 crate does not know — the linked library is newer"
122            ),
123        }
124    }
125}
126
127impl std::error::Error for Error {}