# dxbc-reader
A bounded reader of Direct3D 10 and 11 shader bytecode (DXBC, Shader Model 4 and 5): the token stream of any stage as typed
declarations and instructions, and the `RDEF` reflection and signature parts as the records they store.
```rust
let container = std::fs::read("shader.dxbc")?;
let code = dxbc_reader::code(&container)?; // the SHDR/SHEX part as 32-bit words
let program = dxbc_reader::decode(&code)?;
println!("{} with {} instructions", program.target(), program.instructions.len());
// Reflection and signatures straight from the container:
if let Some(rdef) = dxbc_reader::rdef(&container)? {
for binding in &rdef.bindings {
println!("{} at register {}", binding.name, binding.register);
}
}
let (stage, inputs, outputs) = dxbc_reader::signature_elements(&container)?;
println!("{stage:?}: {} inputs, {} outputs", inputs.len(), outputs.len());
```
Run it with `cargo run -p dxbc-reader --example inspect_dxbc -- <files>`; the source is [`examples/inspect_dxbc.rs`](examples/inspect_dxbc.rs).
## What it reads
- **Program** (`decode`): the version token (vertex, hull, domain, geometry, pixel or compute, version 4.x or 5.x),
declarations typed where this reader knows them (temps, indexable temps, global flags, constant buffers, samplers,
resources, UAVs, thread groups, group-shared memory, inputs and outputs, the immediate constant buffer) and every
instruction with its opcode, control bits, extended tokens (sample offset, resource dimension, return type) and operands
(register kind, component selection, indices including relative ones, immediates, modifiers, minimum precision).
Opcode names are those of Microsoft's tokenized program format (`opcode_name`).
- **Operand formats** (`operand_format`, `Instruction::format`, `Instruction::destinations_and_sources`): for each
instruction opcode, how many destination and source operands it takes and what each carries (float, double, integer, a
register with no data of its own such as a resource, sampler, view or stream, or data typed by the resource). `decode`
checks every instruction against it. The table is the one vkd3d's `tpf.c` and DXVK's `dxbc_defs.cpp` both publish (their
operand counts agree for every opcode both list); `call`, `callc`, `label` and `interface_call` have none. All 3,415,319
instructions of the 7,729 programs of Sekiro, Riders Republic and Sledding Game have the count and the register class
their opcode's format gives (109 of the 180 opcodes with a format occur in them).
- **Control bits** (`Instruction::precise_mask`, `sync_flags`, `info_return`; `Declaration::global_flags` and the
accessors of the geometry and tessellation declarations): the bits of the opcode token that the programs of the three
games use, read as the fields the token format gives them: the `precise` mask (4,960 instructions of Riders Republic's
programs), the flags of a `sync` (245 in Riders Republic), the return type of `resinfo` and `sample_info`, the global
flags (bits 0 to 5 occur), a geometry program's input primitive, output topology and vertex count, and a hull and
domain program's control point counts, domain, partitioning, output primitive, largest factor and phase instance
counts. The values are Direct3D's own numbers, as stored.
- **Texture reads** (`texture_reads`): every `sample`, `gather4`, `lod` and `ld` family instruction that names a `t`
register, with the texture register, the sampler register (none for a load, which uses no sampler), whether the
instruction compares, and the mode the program declares the sampler with. In Sekiro's 9,204 distinct programs (the
renderer, effect, material and character bundles) a texture register is read with exactly one sampler register in all
but 11 of 89,699 sampled slots, and an instruction compares exactly when its sampler is declared a comparison sampler.
`cargo run -p dxbc-reader --example pairs_dxbc -- <files or folders>` prints the pairs of each program.
- **Reflection** (`rdef`, `rdef_target`): the `RDEF` part of the Shader Model 4 layout (no tag), the Shader Model 5.0
one (`RD11`) and the 5.1 one (the tag `13 13 44 25`, whose bindings carry a register space and an id): resource
bindings, constant buffers with their variables and their used flags, default values and type trees.
- **Signatures** (`signature`, `signature_elements`, `patch_signature_elements`): `ISGN`, `OSGN`, `ISG1`, `OSG1`, `OSG5`,
`PCSG` and `PSG1` parts.
- **Statistics** (`statistics`): the `STAT` part, the compiler's counters, with accessors for the words checked against
`D3DReflect`.
- **Class linkage** (`interfaces`): the `IFCE` part of a program with class linkage, its class types, class instances
and interface slot records.
- **Feature level 9** (`level9`): the `Aon9` part of a `_level_9_x` container, its Direct3D 9 program and the mappings
of its constant buffers, samplers and runtime constants.
- **Listing** (`disassemble`, `disassemble_with`, `Options`): a Shader Model 4, 5 or 5.1 container's text listing as
Microsoft's disassembler writes it (`D3DDisassemble` of d3dcompiler_47 10.0.26100.4654, `fxc /dumpbin`), byte for
byte: the header (buffer definitions, resource bindings, class tables, signatures, functionality notes,
tessellation tables), the program and its instruction count; a level 9 container's as well, with the Direct3D 9
program of its `Aon9` part. `Options::from_flags` takes the `D3D_DISASM_*` flags word: `0x40` leaves out the
header, `0x04` numbers the instructions, `0x20` writes their offsets, `0x80` writes literals in hex, `0x10` leaves
out the debug information (`0x02` changes nothing in the text this writes); it refuses `0x01` (an HTML listing) and
`0x08`, which Microsoft's disassembler refuses too. A container with an `SPDB` part (the PDB `fxc /Zi` embeds) is
written with the source lines and variable locations Microsoft's listing interleaves: `#line` lines, the
variables' initial locations, and after an instruction the locations that start at the next (`r0.xyz <- c.rgb`; an
inlined call's variables only after an instruction in the code the call spans).
The PDB is read without file access, from the part's bytes: the MSF streams, the line and inlinee tables, the
symbols and the types the locations name.
- **Container** (`parts`, over the `dxcontainer` crate).
Numbers are Direct3D's own (`D3D_NAME`, `D3D_SHADER_INPUT_TYPE`, `D3D_REGISTER_COMPONENT_TYPE`, ...), as stored.
## What it guarantees
Reflection tables are bounded before they are read, strings must end inside their part and be UTF-8, and a type tree is
refused where it nests deeper than 32 or refers to itself. Operands nest at most three deep. Whatever cannot be read
(a truncated instruction, an out-of-range table, an operand register type it has no name for, an unexpected number of index
dimensions, an operand count other than the opcode's format) is an `Error` naming the 32-bit word where it happened.
## What it does not do
It does not execute, translate or validate programs. Declarations of the tessellation and geometry stages are kept as
`Declared::Other` with their raw words (`Declaration`'s accessors read their values). Shader Model
5.1's binding declarations and operands (the id, the register range and the space) are read as fxc's disassembly of
the `sm51` fixtures gives them; no game program checks them yet, the D3D11 games' being 4.0 and 5.0 (Direct3D 11 does
not load 5.1). Nothing in the 7,729 programs of Sekiro, Riders Republic and Sledding Game needs more: they hold no
64-bit immediate or 64-bit index, no operand register type the decoder lacks, no
extended operand token but the modifier, no `interface_call` or interface declaration, and no signature element with a
nonzero stream or minimum-precision column (all 691,630 elements of the `ISG1`, `OSG1`, `OSG5` and `PSG1` parts of the five
games' containers state stream 0 and minimum precision 0, so the signature records do not carry them); the decoder
refuses each of the other cases by name rather than reading it. `RDEF` parts of other record layouts than
Shader Model 4's, 5.0's and 5.1's are refused. Shader Model 6 is `dxil-reader`'s.
## Tests
`cargo test -p dxbc-reader`: token streams assembled by hand (a vertex program, typed declarations, an instruction's
controls and extended tokens, the refusals by name) and the committed fixtures in `tests/fixtures` and `tests/disasm`,
compiled by `fxc` from the HLSL beside each (`.od` files with `/Od`), each read to its end; two in `tests/fixtures`
are builds of `tests/disasm` edited into programs fxc does not write (`examples/patched_fixtures.rs` makes them). `tests/disasm.rs` compares
the listing of each with `D3DDisassemble`'s, stored in `tests/disasm/listings` under no flags and the flags that
change the text alone, in pairs and all together (`tests/disasm/generate.py` writes them; 4,807 listings of 209
programs, 106 of them `/Zi` builds), and the listings of the 19 test programs of t7_dxbc (`tests/third-party`).
`tests/spdb.rs` changes every byte of two `/Zi` builds' `SPDB` parts and checks each is written or refused without a
panic. No game files are needed for these.
With `GAME_DATA` naming a real-data configuration, `tests/corpora.rs` compares the listing with Microsoft's for whole
games: Riders Republic's 2,443 programs, Sekiro's 432 under 23 flag words and Sledding Game's Unity player's 4,854;
the inputs are in [`tests/inputs/README.md`](tests/inputs/README.md), and each test skips where its input is not
configured.
## Check the examples
From the repository root, `cargo test -p dxbc-reader --doc` checks the crate documentation, and
`cargo build -p dxbc-reader --examples` builds its example programs. Examples that read paths need the caller's files; the repository ships no game assets.
License: GPL-3.0-or-later.
`sample_info` and `sample_pos` can query the rasterizer register (operand type 14), retained as the token format names it.