Skip to main content

d3d9_reader/
ctab.rs

1//! The constant table D3DX writes as a comment (`CTAB`, `D3DXSHADER_CONSTANTTABLE`): offsets are relative to the
2//! header after the FourCC.
3use crate::{Result, error};
4
5/// A CTAB type. Numeric class/type values are D3DXPARAMETER_CLASS/TYPE, kept alongside their dimensions.
6#[derive(Clone, Debug, PartialEq, Eq)]
7pub struct Type {
8    /// D3DXPARAMETER_CLASS as stored (u16 at offset 0 of the type record): scalar, vector, row or column matrix, object,
9    /// struct.
10    pub class: u16,
11    /// D3DXPARAMETER_TYPE as stored (u16 at offset 2): bool, int, float, sampler and so on.
12    pub kind: u16,
13    /// Rows (u16 at offset 4).
14    pub rows: u16,
15    /// Columns (u16 at offset 6).
16    pub columns: u16,
17    /// Array length (u16 at offset 8); 1 for a type that is not an array.
18    pub elements: u16,
19    /// Members of a struct type in the order stored (the u16 count at offset 10 says how many); empty for any other
20    /// class.
21    pub members: Vec<TypeMember>,
22}
23
24/// One member of a struct type.
25#[derive(Clone, Debug, PartialEq, Eq)]
26pub struct TypeMember {
27    /// The member's name.
28    pub name: String,
29    /// The member's type.
30    pub ty: Type,
31}
32
33/// A named allocation, including the original type even when optimization shortened its register range.
34#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct Constant {
36    /// The word of the program that the constant's record (`D3DXSHADER_CONSTANTINFO`) starts in.
37    pub word: usize,
38    /// The constant's name, from the NUL-terminated UTF-8 string its record points at.
39    pub name: String,
40    /// D3DXREGISTER_SET: BOOL=0, INT4=1, FLOAT4=2, SAMPLER=3.
41    pub register_set: u16,
42    /// First register of the allocation (u16 at offset 6 of the constant record), in the bank `register_set` names.
43    pub register: u16,
44    /// Registers allocated (u16 at offset 8). Reading refuses 0; how many registers a bank has is the device's and the
45    /// version's, which reading does not check.
46    pub count: u16,
47    /// Compiler-reserved word of D3DXSHADER_CONSTANTINFO. Compilers write nonzero values;
48    /// it is retained verbatim and does not change the explicit register allocation.
49    pub reserved: u16,
50    /// The declared type; it can be larger than `count` registers when the compiler shortened the allocation.
51    pub ty: Type,
52    /// Raw register defaults (BOOL rows are 4 bytes, other numeric rows 16); no defaults on samplers.
53    pub default: Option<Vec<u8>>,
54}
55
56/// The constant table (CTAB comment) of a program: who compiled it, for which target, and the named register allocations.
57#[derive(Clone, Debug, PartialEq, Eq)]
58pub struct ConstantTable {
59    /// The creator string the header's offset at 4 points at (the compiler's name and version).
60    pub creator: String,
61    /// The target profile string (`ps_3_0`) the header's offset at 24 points at.
62    pub target: String,
63    /// The shader version token (u32 at offset 8 of the header): `0xffff0300` for `ps_3_0`, `0xfffe0300` for `vs_3_0`.
64    /// Reading refuses a table whose version is not the program's.
65    pub version: u32,
66    /// Compile flags (u32 at offset 20 of the header), as stored.
67    pub flags: u32,
68    /// The allocations in the order stored: at most 1024, none overlapping another in the same register set, no name
69    /// twice in one register set (a uniform read as both an integer and a float has a range in each).
70    pub constants: Vec<Constant>,
71}
72
73/// Bounds-checked reads of a comment's bytes after its FourCC, shared by the `CTAB` and `DBUG` readers: errors name
74/// the word of the program and start with the FourCC.
75pub(crate) struct Reader<'a> {
76    pub(crate) data: &'a [u8],
77    /// The word of the program that byte 0 lies in.
78    pub(crate) word: usize,
79    /// The FourCC, for errors.
80    pub(crate) tag: &'static str,
81}
82impl Reader<'_> {
83    /// The word of the program that byte `at` of the table lies in, for an error.
84    pub(crate) fn word_of(&self, at: usize) -> usize {
85        self.word.saturating_add(at / 4)
86    }
87    /// `at + by`, an error naming `at` when it does not fit.
88    pub(crate) fn plus(&self, at: usize, by: usize) -> Result<usize> {
89        at.checked_add(by).ok_or_else(|| {
90            error(
91                self.word_of(at),
92                format!("{} offset {at}+{by} is past the largest offset", self.tag),
93            )
94        })
95    }
96    /// `count * size` bytes, an error naming `at` when it does not fit.
97    pub(crate) fn bytes(&self, at: usize, count: usize, size: usize) -> Result<usize> {
98        count.checked_mul(size).ok_or_else(|| {
99            error(
100                self.word_of(at),
101                format!("{} {count} entries of {size} bytes do not fit", self.tag),
102            )
103        })
104    }
105    pub(crate) fn range(&self, at: usize, len: usize) -> Result<&[u8]> {
106        at.checked_add(len)
107            .and_then(|end| self.data.get(at..end))
108            .ok_or_else(|| {
109                error(
110                    self.word_of(at),
111                    format!(
112                        "{} range {at}+{len} outside the comment's {} bytes",
113                        self.tag,
114                        self.data.len()
115                    ),
116                )
117            })
118    }
119    pub(crate) fn u16(&self, at: usize) -> Result<u16> {
120        match self.range(at, 2)? {
121            &[a, b] => Ok(u16::from_le_bytes([a, b])),
122            _ => Err(error(
123                self.word_of(at),
124                format!("{} u16 outside comment", self.tag),
125            )),
126        }
127    }
128    pub(crate) fn u32(&self, at: usize) -> Result<u32> {
129        match self.range(at, 4)? {
130            &[a, b, c, d] => Ok(u32::from_le_bytes([a, b, c, d])),
131            _ => Err(error(
132                self.word_of(at),
133                format!("{} u32 outside comment", self.tag),
134            )),
135        }
136    }
137    pub(crate) fn offset(&self, at: usize) -> Result<usize> {
138        let offset = self.u32(at)?;
139        usize::try_from(offset).map_err(|_| {
140            error(
141                self.word_of(at),
142                format!("{} offset {offset} does not fit an address", self.tag),
143            )
144        })
145    }
146    pub(crate) fn string(&self, at: usize) -> Result<String> {
147        let bytes = self.range(at, self.data.len().saturating_sub(at))?;
148        let end = bytes
149            .iter()
150            .position(|&b| b == 0)
151            .ok_or_else(|| error(self.word_of(at), format!("{} string has no NUL", self.tag)))?;
152        let text = bytes.get(..end).unwrap_or_default();
153        std::str::from_utf8(text)
154            .map(str::to_owned)
155            .map_err(|_| error(self.word_of(at), format!("{} string is not UTF-8", self.tag)))
156    }
157    pub(crate) fn ty(&self, at: usize, depth: usize, budget: &mut usize) -> Result<Type> {
158        if depth > 32 || *budget == 0 {
159            return Err(error(
160                self.word_of(at),
161                format!("{} type nesting/cycle or 4096-node limit", self.tag),
162            ));
163        }
164        *budget = budget.saturating_sub(1);
165        self.range(at, 16)?;
166        let count = usize::from(self.u16(self.plus(at, 10)?)?);
167        let members_at = self.offset(self.plus(at, 12)?)?;
168        self.range(members_at, self.bytes(members_at, count, 8)?)?;
169        let mut members = Vec::new();
170        for n in 0..count {
171            let member = self.plus(members_at, self.bytes(members_at, n, 8)?)?;
172            members.push(TypeMember {
173                name: self.string(self.offset(member)?)?,
174                ty: self.ty(
175                    self.offset(self.plus(member, 4)?)?,
176                    depth.saturating_add(1),
177                    budget,
178                )?,
179            });
180        }
181        Ok(Type {
182            class: self.u16(at)?,
183            kind: self.u16(self.plus(at, 2)?)?,
184            rows: self.u16(self.plus(at, 4)?)?,
185            columns: self.u16(self.plus(at, 6)?)?,
186            elements: self.u16(self.plus(at, 8)?)?,
187            members,
188        })
189    }
190}
191
192pub(crate) fn read(data: &[u8], word: usize, expected_version: u32) -> Result<ConstantTable> {
193    let r = Reader {
194        data,
195        word,
196        tag: "CTAB",
197    };
198    if r.u32(0)? != 28 {
199        return Err(error(word, "CTAB header size is not 28"));
200    }
201    let version = r.u32(8)?;
202    if version != expected_version {
203        return Err(error(
204            r.word_of(8),
205            format!(
206                "CTAB version disagrees with the program's: {version:#010x}, where the program's version token is {expected_version:#010x}"
207            ),
208        ));
209    }
210    let count = r.offset(12)?;
211    if count > 1024 {
212        return Err(error(
213            r.word_of(12),
214            format!("CTAB has {count} constants, where it has at most 1024"),
215        ));
216    }
217    let base = r.offset(16)?;
218    r.range(base, r.bytes(base, count, 20)?)?;
219    let mut constants: Vec<Constant> = Vec::new();
220    let mut budget = 4096;
221    for n in 0..count {
222        let at = r.plus(base, r.bytes(base, n, 20)?)?;
223        let name = r.string(r.offset(at)?)?;
224        let register_set = r.u16(r.plus(at, 4)?)?;
225        let register = r.u16(r.plus(at, 6)?)?;
226        let count = r.u16(r.plus(at, 8)?)?;
227        if register_set > 3 {
228            return Err(error(
229                r.word_of(at),
230                format!(
231                    "CTAB register set {register_set}, where it is 0 (bool), 1 (int4), 2 (float4) or 3 (sampler)"
232                ),
233            ));
234        }
235        if count == 0 {
236            return Err(error(
237                r.word_of(at),
238                format!("CTAB register range c{register}+{count} is empty"),
239            ));
240        }
241        // one uniform can take a range in each register set it is read from (an `int2` as a loop count in `i#` and a
242        // float in `c#`), so a name repeats only across sets
243        if let Some(c) = constants.iter().find(|c| {
244            c.register_set == register_set
245                && (c.name == name
246                    || (u32::from(c.register) < u32::from(register).saturating_add(u32::from(count))
247                        && register < c.register.saturating_add(c.count)))
248        }) {
249            let detail = if c.name == name {
250                format!("CTAB constant {name} appears twice in register set {register_set}")
251            } else {
252                format!(
253                    "CTAB constant {name} overlaps {} in register set {register_set}",
254                    c.name
255                )
256            };
257            return Err(error(r.word_of(at), detail));
258        }
259        let default_at = r.offset(r.plus(at, 16)?)?;
260        let default = if default_at == 0 {
261            None
262        } else {
263            if register_set == 3 {
264                return Err(error(r.word_of(at), "CTAB sampler default"));
265            }
266            let row = if register_set == 0 { 4 } else { 16 };
267            Some(
268                r.range(default_at, r.bytes(default_at, usize::from(count), row)?)?
269                    .to_vec(),
270            )
271        };
272        constants.push(Constant {
273            word: r.word_of(at),
274            name,
275            register_set,
276            register,
277            count,
278            reserved: r.u16(r.plus(at, 10)?)?,
279            ty: r.ty(r.offset(r.plus(at, 12)?)?, 0, &mut budget)?,
280            default,
281        });
282    }
283    Ok(ConstantTable {
284        creator: r.string(r.offset(4)?)?,
285        version,
286        flags: r.u32(20)?,
287        target: r.string(r.offset(24)?)?,
288        constants,
289    })
290}