whiteoutlib 0.1.6

Read and write Blizzard game assets from Rust: models (MDX, M2, M3), textures (BLP, DDS, PNG, JPEG, BMP, TGA, TIFF, GIF) and archives (CASC, MPQ).
Documentation
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2026 Fernando Sahmkow
//
// Hand-written runtime support shared by every generated module. The
// analogue of `bindings/csharp/Whiteout/Common/`.

use core::ffi::{c_char, c_void};
use core::marker::PhantomData;
use core::ops::Deref;

/// Mirror of the C `whiteout_Bytes`.
///
/// `_owner` is the ownership discriminator: non-null means the C++ side
/// heap-allocated a buffer for us and we must free it; null means either a
/// borrowed view into memory somebody else owns, or "absent".
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct RawBytes {
    pub data: *const u8,
    pub size: usize,
    pub owner: *mut c_void,
}

/// Mirror of the C `whiteout_CString`.
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct RawCString {
    pub chars: *const c_char,
    pub length: usize,
    pub owner: *mut c_void,
}

extern "C" {
    fn whiteout_Bytes_free(buf: RawBytes);
    fn whiteout_CString_free(s: RawCString);
}

/// An owned byte buffer produced by the native library.
///
/// Derefs to `[u8]`, so it behaves like a slice; the backing memory stays
/// in C++ and is released on drop. Nothing is copied unless you ask for it
/// with [`Bytes::to_vec`].
pub struct Bytes {
    raw: RawBytes,
}

impl Bytes {
    /// # Safety
    /// `raw` must have come from a native call that transfers ownership
    /// (i.e. `raw.owner` is non-null), and must not be freed elsewhere.
    pub(crate) unsafe fn from_raw(raw: RawBytes) -> Option<Self> {
        // `owner == null` is the library's "no value" signal. It is *not*
        // the same as an empty buffer: a present-but-empty vector still
        // carries a non-null owner. Keying on `data` instead — as the C#
        // binding does — reports an empty file as missing.
        if raw.owner.is_null() {
            None
        } else {
            Some(Bytes { raw })
        }
    }

    /// An empty buffer that owns nothing.
    ///
    /// Used where the native call reports failure by handing back a buffer
    /// with no owner — the caller still gets a valid, empty slice.
    pub(crate) fn empty() -> Self {
        Bytes {
            raw: RawBytes {
                data: core::ptr::null(),
                size: 0,
                owner: core::ptr::null_mut(),
            },
        }
    }

    pub fn to_vec(&self) -> Vec<u8> {
        self.as_ref().to_vec()
    }

    pub fn is_empty(&self) -> bool {
        self.raw.size == 0
    }

    pub fn len(&self) -> usize {
        self.raw.size
    }
}

impl Deref for Bytes {
    type Target = [u8];
    fn deref(&self) -> &[u8] {
        if self.raw.data.is_null() || self.raw.size == 0 {
            return &[];
        }
        // SAFETY: the native side guarantees `data`/`size` describe one
        // allocation, kept alive by `owner` until we free it in `drop`.
        unsafe { core::slice::from_raw_parts(self.raw.data, self.raw.size) }
    }
}

impl AsRef<[u8]> for Bytes {
    fn as_ref(&self) -> &[u8] {
        self
    }
}

impl Drop for Bytes {
    fn drop(&mut self) {
        if self.raw.owner.is_null() {
            return; // `Bytes::empty` — nothing was ever allocated.
        }
        // SAFETY: `from_raw` only constructs a Bytes for an owning buffer,
        // and Drop runs exactly once.
        unsafe { whiteout_Bytes_free(self.raw) }
    }
}

impl core::fmt::Debug for Bytes {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("Bytes").field("len", &self.len()).finish()
    }
}

// SAFETY: the buffer is a plain heap allocation with no thread affinity;
// `Bytes` owns it exclusively.
unsafe impl Send for Bytes {}
unsafe impl Sync for Bytes {}

/// Consume a native `whiteout_CString` into an owned `String`.
///
/// # Safety
/// `raw` must come from a native call that transfers ownership.
pub(crate) unsafe fn take_string(raw: RawCString) -> String {
    if raw.chars.is_null() {
        // Still hand it back: freeing a null-owner CString is a no-op, and
        // this keeps the caller from having to special-case the empty case.
        unsafe { whiteout_CString_free(raw) };
        return String::new();
    }
    // SAFETY: `chars`/`length` describe a valid UTF-8 run owned by the
    // native side until we free it below.
    let bytes = unsafe { core::slice::from_raw_parts(raw.chars as *const u8, raw.length) };
    let out = String::from_utf8_lossy(bytes).into_owned();
    unsafe { whiteout_CString_free(raw) };
    out
}

/// Same, but distinguishes "absent" from "present and empty".
///
/// # Safety
/// As [`take_string`].
pub(crate) unsafe fn take_string_opt(raw: RawCString) -> Option<String> {
    if raw.owner.is_null() {
        return None;
    }
    Some(unsafe { take_string(raw) })
}

/// A borrowed view into a buffer owned by a native object.
///
/// This is what makes zero-copy pixel access safe: the lifetime is tied to
/// a borrow of the owning handle, so the compiler rejects any use after the
/// owner is dropped, resized, or mutably re-borrowed.
pub struct BorrowedSlice<'a> {
    ptr: *const u8,
    len: usize,
    _owner: PhantomData<&'a ()>,
}

impl<'a> BorrowedSlice<'a> {
    /// # Safety
    /// `ptr`/`len` must describe a buffer that stays valid and immutable
    /// for `'a`.
    pub(crate) unsafe fn new(ptr: *const u8, len: usize) -> Self {
        BorrowedSlice {
            ptr,
            len,
            _owner: PhantomData,
        }
    }
}

impl Deref for BorrowedSlice<'_> {
    type Target = [u8];
    fn deref(&self) -> &[u8] {
        if self.ptr.is_null() || self.len == 0 {
            return &[];
        }
        // SAFETY: guaranteed by the contract on `new`.
        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
    }
}

impl core::fmt::Debug for BorrowedSlice<'_> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("BorrowedSlice")
            .field("len", &self.len)
            .finish()
    }
}

/// A borrowed handle: a view of an object owned by something else.
///
/// The C ABI hands out interior pointers for struct fields and vector
/// elements (`&self->field`, `&self->vec[i]`). Those must never be freed,
/// so they cannot be represented by the owning handle types, which all
/// implement `Drop`. `Ref` wraps one in `ManuallyDrop` and ties it to the
/// parent's lifetime, so it neither frees nor outlives its owner.
///
/// Derefs to the underlying type, so it is used exactly like `&T`.
pub struct Ref<'a, T> {
    inner: core::mem::ManuallyDrop<T>,
    _owner: PhantomData<&'a T>,
}

impl<'a, T> Ref<'a, T> {
    /// # Safety
    /// `value` must wrap a pointer that stays valid for `'a` and is owned
    /// by something other than the returned `Ref`.
    pub(crate) unsafe fn new(value: T) -> Self {
        Ref {
            inner: core::mem::ManuallyDrop::new(value),
            _owner: PhantomData,
        }
    }
}

impl<T> Deref for Ref<'_, T> {
    type Target = T;
    fn deref(&self) -> &T {
        &self.inner
    }
}

impl<T: core::fmt::Debug> core::fmt::Debug for Ref<'_, T> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        (**self).fmt(f)
    }
}

/// The mutable counterpart of [`Ref`].
///
/// Borrowing the parent mutably is what makes in-place edits safe: the
/// compiler rejects a resize or a second view while this one is alive.
pub struct RefMut<'a, T> {
    inner: core::mem::ManuallyDrop<T>,
    _owner: PhantomData<&'a mut T>,
}

impl<'a, T> RefMut<'a, T> {
    /// # Safety
    /// As [`Ref::new`], plus: no other view of the same object may exist.
    pub(crate) unsafe fn new(value: T) -> Self {
        RefMut {
            inner: core::mem::ManuallyDrop::new(value),
            _owner: PhantomData,
        }
    }
}

impl<T> Deref for RefMut<'_, T> {
    type Target = T;
    fn deref(&self) -> &T {
        &self.inner
    }
}

impl<T> core::ops::DerefMut for RefMut<'_, T> {
    fn deref_mut(&mut self) -> &mut T {
        &mut self.inner
    }
}

impl<T: core::fmt::Debug> core::fmt::Debug for RefMut<'_, T> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        (**self).fmt(f)
    }
}