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 = read?; // a token stream, not a DXBC container
let program = read?;
println!;
for i in program.instructions
if let Some = &program.constants
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_0and the software profilesvs_2_swandvs_3_sw;ps_1_0tops_1_4,ps_2_0,ps_2_x,ps_3_0andps_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,texandtexcoord). - Instructions (
Instruction): the opcode (everyD3DSIOopcode, byOpcode), the control bits (atexld's project and bias forms, the comparison ofif_comp,break_compandsetp_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 (a0or the loop counteraL) and the swizzle as stored; in vertex 1.x, where the index is the implicita0.x,Relative::wordisNone. - Flow control:
if,if_comp,else,endif,loop,endloop,rep,endrep,break,break_comp,breakp,call,callnz,retandlabelare 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,defianddefb, with the literal as stored (float bits, integers, a bool word). - Comments (
Comment) with their words, and theCTABcomment read as aConstantTable: the creator, target, flags and each constant's name, register set, register range, type tree and default value, with the word of its record. TheDBUGcomment of anfxc /Zibuild is read as aDebugInfo: 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 itsEND(found by reading, so anEND-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.