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