Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Feature Flags

crustywad uses Cargo feature flags to keep the default dependency footprint small while allowing callers to opt in to additional capabilities.

Summary

FeatureDefaultPurpose
mmapnoMemory-mapped file loading via memmap2
freedoom-testsnoIntegration tests against local Freedoom WAD fixtures
writenoWAD serialization — WadBuilder, WriteError, WriteOptions, WriteWarning

mmap

Enables: Wad::from_path_mapped and Wad::from_path_mapped_with_options

Adds dependency: memmap2

Memory-maps the WAD file instead of reading it into a Vec<u8>. On large WADs this avoids a heap allocation equal to the file size and lets the OS page in only the bytes that are actually accessed. The tradeoff is a small amount of unsafe code in mmap.rs (the only unsafe in the library crate) to call memmap2::MmapOptions::map.

Wad::from_path (the non-mapped variant) always reads the whole file into memory regardless of whether this feature is enabled.

Usage

# Cargo.toml
crustywad = { version = "0.1", features = ["mmap"] }
#![allow(unused)]
fn main() {
use crustywad::{Wad, ParseOptions};

// Zero-copy load from disk:
let _wad = Wad::from_path_mapped("doom.wad")?;

// Zero-copy load with options:
let _wad = Wad::from_path_mapped_with_options("doom.wad", ParseOptions::lenient())?;
Ok::<(), crustywad::ParseError>(())
}

When to use mmap

Memory-mapped loading is useful for large WADs when you only need to access a subset of lumps. The OS maps the file into the address space without copying all bytes into heap memory upfront — pages are faulted in on demand.

For small WADs or when you will access most lumps, Wad::from_path (which reads into a Vec<u8>) is equally fast and has simpler lifetime semantics.

Platform notes

memmap2 is supported on all tier-1 Rust targets (Linux, macOS, Windows). Memory-mapped files are read-only; there is no risk of accidentally writing to the underlying file.

Warning: the WAD file must not be truncated or replaced by another process while the Wad is alive. On Unix, truncation from another process triggers a SIGBUS on the next lump data access, which will abort the process. On Windows the mapping prevents truncation but concurrent writes by another process may expose inconsistent data. Use Wad::from_path if the file may be modified externally while in use.


freedoom-tests

Enables: integration tests in crates/crustywad/tests/freedoom.rs

Adds dependency: none (test-only fixture files on disk)

Gates optional tests that parse real Freedoom WAD files. Tests skip gracefully when CRUSTYWAD_FREEDOOM_DIR is not set or when the expected WAD files are not present in that directory — they do not fail.

Fetching fixtures

# Default version (configured in tests/fixtures/fetch_freedoom.py):
just fetch-fixtures

# Specific Freedoom release:
just fetch-fixtures version=v0.14.0

Running the tests

# Using just — defaults CRUSTYWAD_FREEDOOM_DIR to tests/fixtures/freedoom:
just test-freedoom

# Override the fixture directory:
just test-freedoom dir=/path/to/freedoom

# Or run cargo directly:
CRUSTYWAD_FREEDOOM_DIR=tests/fixtures/freedoom \
  cargo test -p crustywad --features freedoom-tests

CI

CI runs cargo test --workspace --all-features, which enables the freedoom-tests feature flag. The tests skip gracefully when CRUSTYWAD_FREEDOOM_DIR is not set — and CI never sets it because the fixture WADs are gitignored and not downloaded in the standard CI pipeline.


write

Enables: WadBuilder, WriteError, WriteWarning, WriteOptions, and Wad::to_builder

Adds dependency: none (uses binrw already in the dependency tree)

Adds WAD serialization support. WadBuilder accumulates lumps and serializes them to a Vec<u8> in the canonical Doom WAD layout: [12-byte header][lump data blobs][16-byte directory entries].

Usage

# Cargo.toml
crustywad = { version = "0.1", features = ["write"] }
#![allow(unused)]
fn main() {
use crustywad::{WadBuilder, WadKind};

// Build a new PWAD from scratch:
let bytes = WadBuilder::new(WadKind::Pwad)
    .add_lump("MAP01", b"data")
    .build()
    .unwrap();

assert!(crustywad::Wad::from_bytes(bytes).is_ok());
}

Round-tripping a parsed WAD

#![allow(unused)]
fn main() {
use crustywad::{Wad, WadBuilder, WadKind};

let mut source = Vec::new();
source.extend_from_slice(b"PWAD");
source.extend_from_slice(&0_i32.to_le_bytes());
source.extend_from_slice(&12_i32.to_le_bytes());
let wad = Wad::from_bytes(source).unwrap();
let rebuilt = wad.to_builder().build().unwrap();
}

Validation and error handling

WadBuilder::build uses strict mode by default. Use build_with_options with WriteOptions::lenient() to collect recoverable issues as WriteWarning values instead:

  • Names with NUL bytes or non-ASCII bytes always error in both modes.
  • Names longer than 8 bytes: strict mode returns WriteError::NameTooLong; lenient mode truncates and emits WriteWarning::NameTruncated.
  • WadKind::Unknown magic: strict mode returns WriteError::UnknownMagicStrict; lenient mode writes the raw 4-byte magic.

Common cargo invocations

GoalCommand
Build with all featurescargo build --workspace --all-features
Build with mmap onlycargo build -p crustywad --features mmap
Test with all featurescargo test --workspace --all-features
Test with mmap onlycargo test -p crustywad --features mmap
Test with Freedoom fixturesCRUSTYWAD_FREEDOOM_DIR=… cargo test -p crustywad --features freedoom-tests
Build with writecargo build -p crustywad --features write
Test with writecargo test -p crustywad --features write
Full CI checkjust ci

See the justfile for available just recipes including feature-specific aliases.