dxcontainer
Reads the part directory of a DXBC container, the file format Direct3D shader programs are stored in (Shader Model 4 to 6), computes its digest, and writes a container again with the digest repaired.
let bytes = read?;
for part in chunks?
println!;
A shader-patching mod edits a part and writes the container back:
let bytes = read?;
let patched = replace_part?; // the other parts as they were, the digest repaired
write?;
Run it with cargo run -p dxcontainer --example parts -- shader.dxbc; the source is examples/parts.rs. The crate documentation has a self-contained example with a hand-built container.
What it reads
A header (DXBC, a 16-byte digest, version 1, the container's size, the number of parts), the directory of the parts'
offsets, then the parts one after another in the directory's order (each its four-character kind, its length and its
bytes), the last ending where the container does. Shader Model 6 parts are not padded, so an offset need not be a multiple
of 4. This layout holds for the 137 containers committed under dxbc-reader/tests/fixtures and dxil-reader/tests/fixtures:
the tests read each to its exact end and check that checksum equals the digest it stores.
serialize(&parts) writes parts as a container: the header, the directory, the parts in the order given, each exactly as given
with nothing between them (a part keeps the padding its producer wrote, or none), and the digest checksum computes.
serialize(&chunks(bytes)?) is bytes again for every container that stores the digest checksum computes, which includes
all 77,457 stock containers of five games. repair_digest rewrites the digest of a container edited in place, and
replace_part replaces (or appends) one part of a container and writes the result. A kind twice is refused, as the reader refuses it.
Outside tools agree: Microsoft's DXIL validator signs a container whose digest was zeroed with the digest repair_digest
writes, and vkd3d's vkd3d-dxbc accepts the containers written here (it checks the digest) and refuses the same bytes with one digest byte changed.
feature_flags reads the SFI0 part, the features the shader requires, as a u64 with the bytecode (SHDR/SHEX or
DXIL) that says what its bits mean; feature_name names a bit the way the compiler lists it. The names were read off
fxc and dxc output for programs that need one feature each (tests/fixtures/features, with the sources).
What it guarantees
The reader reads to the exact end and refuses, with an Error that names the byte, a part anywhere else (overlapping, or
after a gap), a byte after the last part, a part kind twice, a size other than the container's and a version other than 1.
The crate denies indexing and unchecked arithmetic at compile time, so untrusted bytes produce an Error, not a panic.
What it does not do
It does not verify the digest: the digest is not an authenticity check. checksum(bytes) computes it (MD5's block function
over the bytes from 20 on, padded as the compiler pads it), for a caller that wants to compare; on all 137 committed fixtures
it equals the stored digest, as it does on the 77,457 containers of five games' shipped shaders (none stores a zero
digest). It interprets no part but SFI0; dxbc-reader (Shader Model 4 and 5) and dxil-reader (Shader Model 6) do.
Tests
cargo test -p dxcontainer: containers built by hand for each refusal, every truncation of one, and the committed
fixtures of dxbc-reader and dxil-reader read to their end. No game files are needed.
License: GPL-3.0-or-later.