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

Writing WAD Files

WadBuilder, behind the write feature flag, builds a new WAD from scratch or re-serializes an existing one.

crustywad = { version = "0.9.0", features = ["write"] }

Building from scratch

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

let bytes = WadBuilder::new(WadKind::Pwad)
    .add_lump("MAP01", b"")
    .add_lump("TEST", vec![1, 2, 3, 4])
    .build()
    .unwrap();

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

Lumps are added in order with add_lump(name, data). Name and size validation, along with offset (filepos, infotableofs) computation, are deferred entirely to build() / build_with_options() — callers never supply offsets directly.

Round-tripping an existing WAD

Use Wad::to_builder() to load a WAD, modify it, and re-serialize:

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

let mut bytes = Vec::new();
bytes.extend_from_slice(b"IWAD");
bytes.extend_from_slice(&1_i32.to_le_bytes());
bytes.extend_from_slice(&16_i32.to_le_bytes());
bytes.extend_from_slice(&[1, 2, 3, 4]);
bytes.extend_from_slice(&12_i32.to_le_bytes());
bytes.extend_from_slice(&4_i32.to_le_bytes());
bytes.extend_from_slice(b"TEST\0\0\0\0");
let wad = Wad::from_bytes(bytes)?;

let mut builder = wad.to_builder();
builder.add_lump("EXTRA", b"more data");
let rebuilt = builder.build()?;

assert_eq!(Wad::from_bytes(rebuilt)?.lump_count(), 2);
Ok::<(), Box<dyn std::error::Error>>(())
}

All lump data is copied into the builder during the conversion, so memory usage roughly doubles for the duration.

Writing UDMF maps

Use write_udmf() to serialize an assembled Map into a UDMF TEXTMAP string, or add_udmf_map() to add a complete map group to a WadBuilder. Both are available with the write feature:

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

// Assemble a Map to write out (here from a small in-memory UDMF WAD).
let textmap = concat!(
    "namespace = \"doom\";\n",
    "vertex { x = 0; y = 0; }\n",
    "vertex { x = 8; y = 0; }\n",
    "linedef { v1 = 0; v2 = 1; sidefront = 0; }\n",
    "sidedef { sector = 0; }\n",
    "sector { texturefloor = \"F\"; textureceiling = \"C\"; }\n",
);
let mut src = WadBuilder::new(WadKind::Pwad);
src.add_lump("MAP01", b"");
src.add_lump("TEXTMAP", textmap.as_bytes().to_vec());
src.add_lump("ENDMAP", b"");
let wad = Wad::from_bytes(src.build()?)?;
let group = wad.map_group("MAP01").unwrap();
let map: Map = Map::assemble(&wad, &group)?;

// Serialize the map to a UDMF TEXTMAP string:
let (textmap_out, _warnings) = write_udmf(&map, &WriteOptions::strict())?;
assert!(textmap_out.starts_with("namespace"));

// Or add a complete map group (MAP01 + TEXTMAP + ENDMAP) to a builder:
let mut builder = WadBuilder::new(WadKind::Pwad);
add_udmf_map(&mut builder, "MAP01", &map, &WriteOptions::strict())?;
let bytes = builder.build()?;
assert!(!bytes.is_empty());
Ok::<(), Box<dyn std::error::Error>>(())
}

Fields are emitted only when they differ from UDMF spec defaults; float coordinates are narrowed to integer form when whole (e.g. 64.0 is written as 64). The Strict vs. lenient write validation section below covers the WriteOptions modes; see Map records for the Map graph types these APIs consume.

Strict vs. lenient write validation

build() always uses strict validation. build_with_options() takes a WriteOptions for either mode, and returns any collected WriteWarnings alongside the bytes:

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

let (bytes, warnings) = WadBuilder::new(WadKind::Pwad)
    .add_lump("VERYLONGNAME", b"data")
    .build_with_options(&WriteOptions::lenient())
    .unwrap();

assert!(!warnings.is_empty()); // name was truncated to 8 bytes
assert!(crustywad::Wad::from_bytes(bytes).is_ok());
}
ConditionStrictLenient
Lump name longer than 8 bytesWriteError::NameTooLongWriteWarning::NameTruncated, truncated to 8 bytes
Name contains a NUL byteWriteError::NulInNameSame (both modes)
Non-ASCII nameWriteError::NonAsciiNameSame (both modes)
WadKind::Unknown magicWriteError::UnknownMagicStrictWriteWarning::UnknownMagic, written unchanged
Lump data larger than i32::MAX bytesWriteError::LumpTooLargeSame (both modes)
Lump count exceeds i32::MAXWriteError::TooManyLumpsSame (both modes)
Computed offset exceeds i32::MAXWriteError::OffsetOverflowSame (both modes)

Error handling

build() returns Result<Vec<u8>, WriteError>. build_with_options() returns Result<(Vec<u8>, Vec<WriteWarning>), WriteError> — the warnings vector is only ever non-empty in lenient mode.

See Data flow for the write pipeline flowchart and the strict/lenient write mode comparison, and Data model for how WadBuilder and its supporting types relate to Wad.

Runnable example

crates/crustywad/examples/write_wad.rs runs the scenarios above end to end:

cargo run -p crustywad --example write_wad --features write