d3d9_reader/lib.rs
1//! A reader of Direct3D 9 shader bytecode: the token stream a Direct3D 9 compiler or assembler writes for a vertex or
2//! pixel program of Shader Model 1.x, 2.x or 3.0, from its version token to `END`, with no container around it.
3//!
4//! [`read`] decodes the stream into typed records in stream order ([`Program::items`]): every comment, every
5//! declaration (`dcl`), every literal definition (`def`, `defi`, `defb`) and every instruction with its opcode, control
6//! bits, co-issue and predicate, destination (register, write mask, saturate, partial precision, centroid, the pixel 1.x
7//! result shift) and sources (register, swizzle, modifier, relative address). The `CTAB` comment D3DX writes is decoded
8//! as well ([`ConstantTable`]), and so is the `DBUG` comment of a `/Zi` build ([`DebugInfo`]). [`frame`] reads the first program of a buffer that holds more after it.
9//!
10//! It reads and checks bytes and never interprets them for an API: registers are Direct3D's register types and numbers,
11//! modifiers and usages Direct3D's own values. An instruction, register type or operand flag of another version (a
12//! pixel instruction in a vertex program, `i0` in vs_1_1, predication in 2.0) is read, as Microsoft's disassembler lists
13//! it. Whatever the format does not define (an opcode or register type with no name, a version that is none of 1.0 to
14//! 3.0, a reserved bit set, an instruction whose length disagrees with its operands) is an [`Error`] naming the 32-bit
15//! word where it is, where Microsoft's disassembler lists such a stream as garbage (`???`, `ps_2_254`); it never panics
16//! on untrusted input.
17//!
18//! ```
19//! # fn main() -> Result<(), d3d9_reader::Error> {
20//! // ps_2_0: dcl t0.xy; dcl_2d s0; texld r0, t0, s0; mov oC0, r0; end
21//! let words: [u32; 14] = [
22//! 0xffff_0200,
23//! 0x0200_001f, 0x8000_0000, 0xb003_0000,
24//! 0x0200_001f, 0x9000_0000, 0xa00f_0800,
25//! 0x0300_0042, 0x800f_0000, 0xb0e4_0000, 0xa0e4_0800,
26//! 0x0200_0001, 0x800f_0800, 0x80e4_0000,
27//! ];
28//! let bytes: Vec<u8> = words.iter().chain(&[0xffff]).flat_map(|w| w.to_le_bytes()).collect();
29//! let program = d3d9_reader::read(&bytes)?;
30//! assert_eq!(program.version.to_string(), "ps_2_0");
31//! assert_eq!(program.declarations().count(), 2);
32//! let names: Vec<_> = program.instructions().map(|i| i.opcode.name()).collect();
33//! assert_eq!(names, ["tex", "mov"]);
34//! # Ok(()) }
35//! ```
36#![deny(
37 clippy::arithmetic_side_effects,
38 clippy::indexing_slicing,
39 clippy::cast_possible_truncation
40)]
41#![deny(missing_docs)]
42#![deny(clippy::disallowed_methods, clippy::disallowed_types)]
43
44mod ctab;
45mod debug;
46mod disasm;
47mod opcodes;
48mod program;
49
50pub use ctab::{Constant, ConstantTable, Type, TypeMember};
51pub use debug::{DebugInfo, Location, SourceLine, Variable};
52pub use disasm::{Options, disassemble, disassemble_with};
53pub use opcodes::Opcode;
54pub use program::{
55 Comment, Comparison, Declaration, Declared, Definition, Destination, Instruction, Item, Literal,
56 Modifier, Precision, Program, Records, Register, RegisterType, Relative, Source, TextureType, frame,
57 read, records,
58};
59
60/// Something the reader can't read: the 32-bit word of the token stream where it is (the version token is word 0), and
61/// what was expected there or found instead.
62#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
63#[error("D3D9 at word {word}: {detail}")]
64pub struct Error {
65 /// The word offset from the version token.
66 pub word: usize,
67 /// What was expected there, or what was found instead.
68 pub detail: String,
69}
70
71/// The reader's result: an [`Error`] naming the word where reading failed.
72pub type Result<T> = std::result::Result<T, Error>;
73
74pub(crate) fn error(word: usize, detail: impl Into<String>) -> Error {
75 Error {
76 word,
77 detail: detail.into(),
78 }
79}
80
81/// The stage a program is for: the high 16 bits of its version token, `0xfffe` vertex and `0xffff` pixel.
82#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
83pub enum Stage {
84 /// A vertex program (`vs_*`).
85 Vertex,
86 /// A pixel program (`ps_*`).
87 Pixel,
88}
89
90/// A program's version token: the stage, and the major and minor version in its low 16 bits.
91///
92/// The versions read are vertex 1.0, 1.1, 2.0, 2.1 (`vs_2_x`, which `vs_2_a` compiles to), 3.0 and the software
93/// versions 2.255 and 3.255 (`vs_2_sw`, `vs_3_sw`), and pixel 1.0 to 1.4, 2.0, 2.1 (`ps_2_x`: `ps_2_a` and `ps_2_b`
94/// compile to it), 3.0, 2.255 and 3.255.
95#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
96pub struct Version {
97 /// Vertex or pixel.
98 pub stage: Stage,
99 /// Bits 8 to 15 of the token.
100 pub major: u8,
101 /// Bits 0 to 7 of the token; 255 for a software version.
102 pub minor: u8,
103}
104
105impl Version {
106 /// The version of a version token, `None` for a token that is not one of the versions this reader reads.
107 pub fn from_token(token: u32) -> Option<Self> {
108 let [minor, major, ..] = token.to_le_bytes();
109 let stage = match token >> 16 {
110 0xfffe => Stage::Vertex,
111 0xffff => Stage::Pixel,
112 _ => return None,
113 };
114 let known = matches!(
115 (stage, major, minor),
116 (Stage::Vertex, 1, 0 | 1) | (Stage::Pixel, 1, 0..=4) | (_, 2, 0 | 1 | 255) | (_, 3, 0 | 255)
117 );
118 known.then_some(Self { stage, major, minor })
119 }
120
121 /// The version token.
122 pub fn token(self) -> u32 {
123 let stage: u32 = match self.stage {
124 Stage::Vertex => 0xfffe,
125 Stage::Pixel => 0xffff,
126 };
127 (stage << 16) | (u32::from(self.major) << 8) | u32::from(self.minor)
128 }
129}
130
131impl std::fmt::Display for Version {
132 /// The profile name as Microsoft's disassembler writes it: `vs_1_1`, `ps_2_x`, `vs_3_sw`.
133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 let stage = match self.stage {
135 Stage::Vertex => "vs",
136 Stage::Pixel => "ps",
137 };
138 match (self.major, self.minor) {
139 (2, 1) => write!(f, "{stage}_2_x"),
140 (major, 255) => write!(f, "{stage}_{major}_sw"),
141 (major, minor) => write!(f, "{stage}_{major}_{minor}"),
142 }
143 }
144}