Skip to main content

dxbc_reader/
level9.rs

1//! The `Aon9` part of a container compiled for a Direct3D 9 feature level (`ps_4_0_level_9_1` and the like): the
2//! Shader Model 2 program the runtime runs on such hardware, and the tables that map the Shader Model 4 program's
3//! constant buffers, samplers and resources onto Direct3D 9 registers.
4//!
5//! The layout below was read from containers fxc 10.0.26100.4654 built for every combination the tables have (no
6//! constants, float/int/uint/bool constants from several buffers, loops over int constants, branches on bool
7//! constants, several texture and sampler pairs, vertex and pixel programs at levels 9_1 and 9_3), each checked
8//! against `D3DDisassemble`'s printing of the same tables. Words, from the start of the part:
9//!
10//! - 0: the part's size in bytes;
11//! - 1: a Direct3D 9 version token, the program's version at the feature level's minimum (`vs_2_0` where the program
12//!   is `vs_2_x`);
13//! - 2 and 3: the program's size and its offset in the part, in bytes;
14//! - 4 to 8: five tables, each a `u16` count then a `u16` byte offset: constant buffer ranges (12 bytes a row), int
15//!   constants (8), bool constants (8), samplers (4), constants the runtime fills (4).
16use crate::{Result, error, parts};
17
18/// One range of a constant buffer copied into float constant registers.
19#[derive(Clone, Debug, PartialEq, Eq)]
20pub struct ConstantMapping {
21    /// The constant buffer (`cb#`).
22    pub buffer: u16,
23    /// The buffer's first register copied.
24    pub start: u16,
25    /// The registers copied.
26    pub count: u16,
27    /// The first float constant register (`c#`) they go to.
28    pub target: u16,
29    /// How each component converts, x to w: 0 float, 1 bool, 2 int, 3 uint.
30    pub conversion: [u8; 4],
31}
32
33/// One component of a constant buffer copied into an int (`i#`) or bool (`b#`) constant register.
34#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct ScalarMapping {
36    /// The constant buffer (`cb#`).
37    pub buffer: u16,
38    /// The buffer's register.
39    pub source: u16,
40    /// The register's component, 0 to 3 for x to w.
41    pub component: u16,
42    /// The `i#` or `b#` register it goes to.
43    pub target: u16,
44}
45
46/// A Direct3D 9 sampler register and the Shader Model 4 sampler and resource it stands for.
47#[derive(Clone, Debug, PartialEq, Eq)]
48pub struct SamplerMapping {
49    /// The Shader Model 4 resource (`t#`).
50    pub resource: u8,
51    /// The Shader Model 4 sampler (`s#`).
52    pub sampler: u8,
53    /// The Direct3D 9 sampler register (`s#`).
54    pub target: u8,
55}
56
57/// A float constant register the runtime fills itself.
58#[derive(Clone, Debug, PartialEq, Eq)]
59pub struct RuntimeConstant {
60    /// What it holds: 0 is the vertex program's position offset, the only kind seen.
61    pub kind: u16,
62    /// The float constant register (`c#`).
63    pub target: u16,
64}
65
66/// The `Aon9` part.
67#[derive(Clone, Debug, PartialEq, Eq)]
68pub struct Level9 {
69    /// The version token of word 1: the program's version at the feature level's minimum.
70    pub version: u32,
71    /// The Direct3D 9 program, from its version token to `END`, for `d3d9_reader`.
72    pub program: Vec<u8>,
73    /// The program's offset in the part, in bytes (word 3).
74    pub program_offset: u32,
75    /// Constant buffer ranges copied into float constant registers.
76    pub constants: Vec<ConstantMapping>,
77    /// Constant buffer components copied into int constant registers.
78    pub integers: Vec<ScalarMapping>,
79    /// Constant buffer components copied into bool constant registers.
80    pub booleans: Vec<ScalarMapping>,
81    /// Sampler registers and what they stand for.
82    pub samplers: Vec<SamplerMapping>,
83    /// Constant registers the runtime fills.
84    pub runtime: Vec<RuntimeConstant>,
85}
86
87struct Part<'a> {
88    data: &'a [u8],
89}
90
91impl Part<'_> {
92    fn u16(&self, at: usize) -> Result<u16> {
93        self.data
94            .get(at..at.saturating_add(2))
95            .and_then(|b| b.try_into().ok())
96            .map(u16::from_le_bytes)
97            .ok_or_else(|| error(at / 4, format!("the Aon9 part ends before byte {at}")))
98    }
99    fn u32(&self, at: usize) -> Result<u32> {
100        self.data
101            .get(at..at.saturating_add(4))
102            .and_then(|b| b.try_into().ok())
103            .map(u32::from_le_bytes)
104            .ok_or_else(|| error(at / 4, format!("the Aon9 part ends before byte {at}")))
105    }
106    fn byte(&self, at: usize) -> Result<u8> {
107        self.data
108            .get(at)
109            .copied()
110            .ok_or_else(|| error(at / 4, format!("the Aon9 part ends before byte {at}")))
111    }
112    /// The byte offsets of the rows of the table whose count and offset are at byte `at`, each checked to lie in the
113    /// part.
114    fn rows(&self, at: usize, row: usize) -> Result<Vec<usize>> {
115        let count = usize::from(self.u16(at)?);
116        let offset = usize::from(self.u16(at.saturating_add(2))?);
117        let end = count.checked_mul(row).and_then(|n| n.checked_add(offset));
118        if end.is_none_or(|end| end > self.data.len()) {
119            return Err(error(
120                at / 4,
121                format!(
122                    "a table of {count} rows of {row} bytes at byte {offset} runs past the Aon9 part's {} bytes",
123                    self.data.len()
124                ),
125            ));
126        }
127        Ok((0..count)
128            .map(|n| offset.saturating_add(n.saturating_mul(row)))
129            .collect())
130    }
131    fn scalars(&self, at: usize) -> Result<Vec<ScalarMapping>> {
132        self.rows(at, 8)?
133            .into_iter()
134            .map(|r| {
135                Ok(ScalarMapping {
136                    buffer: self.u16(r)?,
137                    source: self.u16(r.saturating_add(2))?,
138                    component: self.u16(r.saturating_add(4))?,
139                    target: self.u16(r.saturating_add(6))?,
140                })
141            })
142            .collect()
143    }
144}
145
146/// The `Aon9` part: `None` for a container without one. Refuses a container [`parts`] refuses, a part whose stated
147/// size is not its own, and a program or table that runs past the part; errors name the part's word.
148pub fn level9(bytes: &[u8]) -> Result<Option<Level9>> {
149    let directory = parts(bytes)?;
150    let Some(chunk) = directory.iter().find(|c| &c.kind == b"Aon9") else {
151        return Ok(None);
152    };
153    let part = Part { data: chunk.data };
154    let size = part.u32(0)?;
155    if usize::try_from(size).ok() != Some(part.data.len()) {
156        return Err(error(
157            0,
158            format!("the Aon9 part states {size} bytes but has {}", part.data.len()),
159        ));
160    }
161    let length = usize::try_from(part.u32(8)?).unwrap_or(usize::MAX);
162    let program_offset = part.u32(12)?;
163    let offset = usize::try_from(program_offset).unwrap_or(usize::MAX);
164    let program = offset
165        .checked_add(length)
166        .and_then(|end| part.data.get(offset..end))
167        .ok_or_else(|| {
168            error(
169                2,
170                format!(
171                    "a program of {length} bytes at byte {offset} runs past the Aon9 part's {} bytes",
172                    part.data.len()
173                ),
174            )
175        })?;
176    let constants = part
177        .rows(16, 12)?
178        .into_iter()
179        .map(|r| {
180            Ok(ConstantMapping {
181                buffer: part.u16(r)?,
182                start: part.u16(r.saturating_add(2))?,
183                count: part.u16(r.saturating_add(4))?,
184                target: part.u16(r.saturating_add(6))?,
185                conversion: [
186                    part.byte(r.saturating_add(8))?,
187                    part.byte(r.saturating_add(9))?,
188                    part.byte(r.saturating_add(10))?,
189                    part.byte(r.saturating_add(11))?,
190                ],
191            })
192        })
193        .collect::<Result<_>>()?;
194    let samplers = part
195        .rows(28, 4)?
196        .into_iter()
197        .map(|r| {
198            let fourth = part.byte(r.saturating_add(3))?;
199            if fourth != 0 {
200                return Err(error(
201                    r / 4,
202                    format!(
203                        "byte {} of a sampler mapping is {fourth}, where every one seen is 0",
204                        r.saturating_add(3)
205                    ),
206                ));
207            }
208            Ok(SamplerMapping {
209                resource: part.byte(r)?,
210                sampler: part.byte(r.saturating_add(1))?,
211                target: part.byte(r.saturating_add(2))?,
212            })
213        })
214        .collect::<Result<_>>()?;
215    let runtime = part
216        .rows(32, 4)?
217        .into_iter()
218        .map(|r| {
219            Ok(RuntimeConstant {
220                kind: part.u16(r)?,
221                target: part.u16(r.saturating_add(2))?,
222            })
223        })
224        .collect::<Result<_>>()?;
225    Ok(Some(Level9 {
226        version: part.u32(4)?,
227        program: program.to_vec(),
228        program_offset,
229        constants,
230        integers: part.scalars(20)?,
231        booleans: part.scalars(24)?,
232        samplers,
233        runtime,
234    }))
235}