d3d9-reader 0.5.0

Direct3D 9 shader bytecode parser: reads vs/ps 1.x, 2.x and 3.0 token streams into typed declarations, definitions and instructions, with the CTAB constant table
Documentation

d3d9-reader

A bounded reader of Direct3D 9 shader bytecode: a vertex or pixel program of Shader Model 1.x, 2.0, 2.x or 3.0, from its version token to END, as typed declarations, definitions, instructions and comments, with the CTAB constant table.

let bytes = std::fs::read("shader.bin")?; // a token stream, not a DXBC container
let program = d3d9_reader::read(&bytes)?;
println!("{} with {} instructions", program.version, program.instructions().count());
for i in program.instructions() {
    println!("word {}: {} with {} sources", i.word, i.opcode.name(), i.sources.len());
}
if let Some(table) = &program.constants {
    for c in &table.constants {
        println!("{} in register set {} at {}+{}", c.name, c.register_set, c.register, c.count);
    }
}

disassemble writes a program's listing as Microsoft's disassembler does with no flags, byte for byte: the constant table's parameters and registers, the program, and the instruction slot count; for a /Zi build, the #line lines and the variables each line writes (mul r0, v0, c0 // ::o<0,1,2,3>). dx disasm runs it over files.

What it reads

  • Versions: vs_1_0, vs_1_1, vs_2_0, vs_2_x, vs_3_0 and the software profiles vs_2_sw and vs_3_sw; ps_1_0 to ps_1_4, ps_2_0, ps_2_x, ps_3_0 and ps_2_sw, ps_3_sw. Each program is read under its own version's rules: which opcodes and register types exist, and how many operands an instruction takes where that changed (sincos, sgn, tex and texcoord).
  • Instructions (Instruction): the opcode (every D3DSIO opcode, by Opcode), the control bits (a texld's project and bias forms, the comparison of if_comp, break_comp and setp_comp), the predicate of a predicated instruction, co-issue in pixel 1.x, the destination with its write mask, saturate, partial precision, centroid and pixel 1.x result shift, and the sources with their swizzles and modifiers (negate, bias, sign, complement, _x2, _dz, _dw, abs, not). Relative addressing keeps its address register (a0 or the loop counter aL) and the swizzle as stored; in vertex 1.x, where the index is the implicit a0.x, Relative::word is None.
  • Flow control: if, if_comp, else, endif, loop, endloop, rep, endrep, break, break_comp, breakp, call, callnz, ret and label are read as instructions with their operands; the reader does not check that blocks nest.
  • Declarations (Declaration): a sampler's texture type (2D, cube, volume) or an input's or output's usage and usage index, with the declared register and its modifiers.
  • Definitions (Definition): def, defi and defb, with the literal as stored (float bits, integers, a bool word).
  • Comments (Comment) with their words, and the CTAB comment read as a ConstantTable: the creator, target, flags and each constant's name, register set, register range, type tree and default value, with the word of its record. The DBUG comment of an fxc /Zi build is read as a DebugInfo: the source files, each record's source line and byte offset, and each variable's scope, name, type and the components each instruction writes.
  • Framing (frame): a program at the start of a larger buffer, read to its END (found by reading, so an END-shaped word inside a comment or a literal is not taken for it), and the bytes it spans.
  • One record at a time (records): the same reading as an iterator, so a caller can act on each record in order and stop at the first it refuses.

Numbers are Direct3D's own (D3DSIO_*, D3DSPR_*, D3DDECLUSAGE, D3DXREGISTER_SET, D3DXPARAMETER_CLASS), as stored.

What it guarantees

Whatever the stream states (a comment's or an instruction's length, an operand count, a table offset) is checked against the words that exist before it is read, and the reader does not index or do unchecked arithmetic (both denied at compile time). Every refusal is an Error naming the 32-bit word where it happened and what was expected there: an opcode or a register type the program's version does not have, reserved bits, an instruction whose stated length disagrees with its operands, a relative address without its address token, a definition naming another bank's register, a CTAB whose version is not the program's, whose ranges overlap or that names a constant twice in one register set, words after END.

What it does not do

It does not execute, translate, validate or assemble programs. It reads what one instruction says, not what a program means: it does not check that flow control nests, that a register is written before it is read, that a declaration comes before the code, or that an index is under a device's register count (the counts are the device's and the version's caps; CTAB ranges are checked to be nonempty and not to overlap). Texture-stage programs from before vs/ps 1.0, effects (fx_2_0) and the DXBC container are not its subject.

Tests

cargo test -p d3d9-reader: token streams laid out by hand for every version (the vertex and pixel 1.x texture instructions, co-issue and result shifts, the 2.x and 3.0 flow control, predication, aL, vertex texture fetch, vPos and vFace), the constant table, framing, and the refusals by word; every truncation of a program is refused and every single-bit change of one is read or refused without a panic. tests/disasm.rs compares the listing of each fixture in tests/disasm with D3DDisassemble's under 23 flag words (1,035 listings of 45 programs, 10 of them /Zi builds). 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 every program of Shaun White Skateboarding's three SDB2 bundles (7,306) and three more of its programs; the inputs are in tests/inputs/README.md, and each test skips where its input is not configured.

The reader was also checked, outside the repository, against the 173 programs of Wine's d3dx9 assembler tests (vs_1_1 to ps_3_0; each read, and its disassembly the same as Wine's apart from spelling), and against the 7,308 distinct vs_3_0 and ps_3_0 programs of Shaun White Skateboarding's three shader bundles, whose 783,092 records equal those of an independent parser. Those programs use none of the flow control, predication or aL; the hand-laid tests are what checks them.

Check the examples

From the repository root, cargo test -p d3d9-reader --doc checks the crate documentation, and cargo build -p d3d9-reader --examples builds the example program, which reads the caller's files.

License: GPL-3.0-or-later.