Skip to main content

dxbc_reader/
reflection.rs

1//! The reflection (`RDEF`) and signature (`ISGN`, `OSGN`, `ISG1`, `OSG1`, `OSG5`, `PCSG`, `PSG1`) parts of a Shader
2//! Model 4 or 5 container, as the records they store: resource bindings, constant buffers with their variables and type
3//! trees, and signature elements, each with D3D's own numbers (`D3D_SHADER_INPUT_TYPE`, `D3D_NAME`, ...) and the word
4//! it starts at. What the numbers mean for a graphics API is the caller's to decide.
5//!
6//! Every table is bounded before it is read, every string is checked to end inside its part and be UTF-8, and a type
7//! tree is refused where it nests deeper than 32 or refers to itself.
8use crate::decode::ProgramType;
9use crate::{Result, chunk, error, parts};
10
11/// The most records one table may state.
12const MAX_RECORDS: usize = 65536;
13/// The longest name, in bytes.
14const MAX_NAME: usize = 4096;
15/// The largest constant buffer, in bytes.
16const MAX_BUFFER_SIZE: usize = 65536;
17
18pub(crate) struct View<'a>(pub(crate) &'a [u8]);
19impl View<'_> {
20    pub(crate) fn bytes(&self, at: usize, len: usize) -> Result<&[u8]> {
21        let end = at.checked_add(len).ok_or_else(|| {
22            error(
23                at / 4,
24                format!("{len} bytes at byte {at} overflow the address space"),
25            )
26        })?;
27        self.0.get(at..end).ok_or_else(|| {
28            error(
29                at / 4,
30                format!(
31                    "{len} bytes at byte {at} exceed the part's {} bytes",
32                    self.0.len()
33                ),
34            )
35        })
36    }
37    pub(crate) fn array<const N: usize>(&self, at: usize) -> Result<[u8; N]> {
38        self.bytes(at, N)?
39            .try_into()
40            .map_err(|_| error(at / 4, format!("{N} bytes at byte {at}")))
41    }
42    pub(crate) fn u32(&self, at: usize) -> Result<u32> {
43        Ok(u32::from_le_bytes(self.array(at)?))
44    }
45    pub(crate) fn u16(&self, at: usize) -> Result<u16> {
46        Ok(u16::from_le_bytes(self.array(at)?))
47    }
48    /// The byte `delta` bytes after `at`.
49    pub(crate) fn plus(&self, at: usize, delta: usize) -> Result<usize> {
50        at.checked_add(delta).ok_or_else(|| {
51            error(
52                at / 4,
53                format!("byte {at} plus {delta} overflows the address space"),
54            )
55        })
56    }
57    /// The 32-bit field `delta` bytes after `at`.
58    pub(crate) fn field(&self, at: usize, delta: usize) -> Result<u32> {
59        self.u32(self.plus(at, delta)?)
60    }
61    /// The 16-bit field `delta` bytes after `at`.
62    pub(crate) fn field16(&self, at: usize, delta: usize) -> Result<u16> {
63        self.u16(self.plus(at, delta)?)
64    }
65    /// The start of record `i` of a table at `at` with records of `stride` bytes.
66    pub(crate) fn record(&self, at: usize, i: usize, stride: usize) -> Result<usize> {
67        i.checked_mul(stride)
68            .and_then(|n| at.checked_add(n))
69            .ok_or_else(|| {
70                error(
71                    at / 4,
72                    format!("record {i} of {stride} bytes at byte {at} overflows"),
73                )
74            })
75    }
76    /// The string at byte `at`, which ends inside the part.
77    pub(crate) fn string(&self, at: usize) -> Result<String> {
78        let tail = self.0.get(at..).ok_or_else(|| {
79            error(
80                at / 4,
81                format!("a string at byte {at}, past the part's {} bytes", self.0.len()),
82            )
83        })?;
84        let end = tail.iter().position(|&b| b == 0).ok_or_else(|| {
85            error(
86                at / 4,
87                format!("the string at byte {at} has no terminating zero in the part"),
88            )
89        })?;
90        if end > MAX_NAME {
91            return Err(error(
92                at / 4,
93                format!("the string at byte {at} is {end} bytes, more than the {MAX_NAME} allowed"),
94            ));
95        }
96        let text = tail
97            .get(..end)
98            .ok_or_else(|| error(at / 4, format!("the string at byte {at}")))?;
99        std::str::from_utf8(text)
100            .map(str::to_owned)
101            .map_err(|_| error(at / 4, format!("the string at byte {at} is not UTF-8")))
102    }
103    /// The indices of the `count` records of `stride` bytes at `at`, once the table is known to lie in the part.
104    pub(crate) fn table(&self, at: usize, count: usize, stride: usize) -> Result<std::ops::Range<usize>> {
105        if count > MAX_RECORDS {
106            return Err(error(
107                at / 4,
108                format!("a table of {count} records at byte {at}, more than the {MAX_RECORDS} allowed"),
109            ));
110        }
111        self.bytes(
112            at,
113            count.checked_mul(stride).ok_or_else(|| {
114                error(
115                    at / 4,
116                    format!("a table of {count} records of {stride} bytes overflows"),
117                )
118            })?,
119        )?;
120        Ok(0..count)
121    }
122    /// The type record at byte `at` (of `layout`'s size) and its members' types, depth first.
123    fn ty(&self, at: usize, layout: Layout, stack: &mut Vec<usize>, budget: &mut usize) -> Result<RdefType> {
124        if stack.len() >= 32 || stack.contains(&at) || *budget == 0 {
125            return Err(error(
126                at / 4,
127                "the type at this word is part of a cycle, nests deeper than 32 or is one of more than 65536 types",
128            ));
129        }
130        *budget = budget.saturating_sub(1);
131        self.bytes(at, layout.ty)?;
132        stack.push(at);
133        let count = usize::from(self.field16(at, 10)?);
134        let offset = self.field(at, 12)? as usize;
135        let mut members = Vec::new();
136        for i in self.table(offset, count, 12)? {
137            let p = self.record(offset, i, 12)?;
138            members.push(RdefMember {
139                name: self.string(self.u32(p)? as usize)?,
140                offset: self.field(p, 8)?,
141                ty: self.ty(self.field(p, 4)? as usize, layout, stack, budget)?,
142            });
143        }
144        stack.pop();
145        // Shader Model 5 type records name the type in their last field
146        let name = if layout.ty >= 36 {
147            match self.field(at, 32)? as usize {
148                0 => None,
149                offset => Some(self.string(offset)?),
150            }
151        } else {
152            None
153        };
154        Ok(RdefType {
155            word: at / 4,
156            name,
157            class: self.u16(at)?,
158            scalar: self.field16(at, 2)?,
159            rows: self.field16(at, 4)?,
160            columns: self.field16(at, 6)?,
161            elements: self.field16(at, 8)?,
162            members,
163        })
164    }
165}
166
167/// The record sizes of an `RDEF` part's layout that differ. The constant buffer (24 bytes) and struct member (12) records
168/// are the same in all three; Shader Model 5's variable and type records end in fields Shader Model 4's do not have, and
169/// Shader Model 5.1's binding records end in the register space and the resource's id.
170#[derive(Clone, Copy)]
171struct Layout {
172    /// The size of a resource binding record.
173    binding: usize,
174    /// The size of a variable record.
175    variable: usize,
176    /// The size of a type record.
177    ty: usize,
178}
179
180/// Shader Model 4: the 28-byte header, then the tables.
181const SM4: Layout = Layout {
182    binding: 32,
183    variable: 24,
184    ty: 16,
185};
186/// Shader Model 5.0: the header, the `RD11` tag and the six record sizes it states.
187const SM50: Layout = Layout {
188    binding: 32,
189    variable: 40,
190    ty: 36,
191};
192/// Shader Model 5.1: the header, the tag `13 13 44 25` and the six record sizes it states.
193const SM51: Layout = Layout {
194    binding: 40,
195    variable: 40,
196    ty: 36,
197};
198
199/// The tag of the Shader Model 5.1 layout, the word `0x25441313`.
200const SM51_TAG: &[u8; 4] = &[0x13, 0x13, 0x44, 0x25];
201
202/// One element of a signature as stored.
203#[derive(Clone, Debug, PartialEq, Eq)]
204pub struct SignatureElement {
205    /// The semantic name (`POSITION`, `TEXCOORD`, ...).
206    pub name: String,
207    /// The semantic index (the `1` of `TEXCOORD1`).
208    pub index: u32,
209    /// `D3D_NAME`.
210    pub system_value: u32,
211    /// `D3D_REGISTER_COMPONENT_TYPE`.
212    pub component_type: u32,
213    /// The register the element is packed into.
214    pub register: u32,
215    /// The components the element occupies, one bit each (x is bit 0).
216    pub mask: u8,
217    /// The components a program reads (inputs) or never writes (outputs), as stored.
218    pub read_write_mask: u8,
219    /// Where the record starts, in words of the chunk.
220    pub at: usize,
221    /// The output stream of a geometry program's element (`OSG5`, `OSG1`); 0 in the other layouts.
222    pub stream: u32,
223    /// `D3D_MIN_PRECISION` (`ISG1`, `OSG1`, `PSG1`); 0 (default) in the other layouts.
224    pub min_precision: u32,
225}
226
227impl SignatureElement {
228    /// The element's `D3D_NAME`: as stored, but for a pixel program's outputs, which store none; theirs is the one
229    /// their semantic names (`SV_Target` is 64, `SV_Depth` 65, ...). `output` says which signature the element is in.
230    pub fn system_value_for(&self, output: bool, kind: ProgramType) -> u32 {
231        if self.system_value != 0 || !output || kind != ProgramType::Pixel {
232            return self.system_value;
233        }
234        match self.name.to_ascii_lowercase().as_str() {
235            "sv_target" => 64,
236            "sv_depth" => 65,
237            "sv_coverage" => 66,
238            "sv_depthgreaterequal" => 67,
239            "sv_depthlessequal" => 68,
240            "sv_stencilref" => 69,
241            "sv_innercoverage" => 70,
242            _ => 0,
243        }
244    }
245
246    /// [`Self::system_value_for`] as Microsoft's disassembler names it in the SysValue column (`POS`, `TARGET`; `SV`
247    /// and the number for one it has no name for).
248    pub fn system_value_name(&self, output: bool, kind: ProgramType) -> String {
249        match self.system_value_for(output, kind) {
250            0 => "NONE",
251            1 => "POS",
252            2 => "CLIPDST",
253            3 => "CULLDST",
254            4 => "RTINDEX",
255            5 => "VPINDEX",
256            6 => "VERTID",
257            7 => "PRIMID",
258            8 => "INSTID",
259            9 => "FFACE",
260            10 => "SAMPLE",
261            11 => "QUADEDGE",
262            12 => "QUADINT",
263            13 => "TRIEDGE",
264            14 => "TRIINT",
265            15 => "LINEDET",
266            16 => "LINEDEN",
267            64 => "TARGET",
268            65 => "DEPTH",
269            66 => "COVERAGE",
270            67 => "DEPTHGE",
271            68 => "DEPTHLE",
272            69 => "STENCILREF",
273            70 => "INNERCOV",
274            other => return format!("SV{other}"),
275        }
276        .to_owned()
277    }
278
279    /// The component type as Microsoft's disassembler names it in the Format column, or the minimum precision where
280    /// the element has one (`float`, `min16f`).
281    pub fn format_name(&self) -> &'static str {
282        match (self.min_precision, self.component_type) {
283            (1, _) => "min16f",
284            (2, _) => "min2_8f",
285            (4, _) => "min16i",
286            (5, _) => "min16u",
287            (_, 1) => "uint",
288            (_, 2) => "int",
289            (_, 3) => "float",
290            _ => "unknown",
291        }
292    }
293}
294
295/// A signature part's records. Layouts: `ISGN`/`OSGN`/`PCSG` (name, index, system value, component type, register,
296/// masks), `ISG1`/`OSG1`/`PSG1` (a stream and a minimum precision besides) and `OSG5` (a stream besides). `kind` is the
297/// part's tag, which says which layout `bytes` has.
298pub fn signature(bytes: &[u8], kind: &[u8; 4]) -> Result<Vec<SignatureElement>> {
299    let v = View(bytes);
300    let count = v.u32(0)? as usize;
301    let directory = v.u32(4)?;
302    if directory != 8 {
303        return Err(error(
304            1,
305            format!("expected the record directory at byte 8, found byte {directory}"),
306        ));
307    }
308    // bytes before the name offset, and the record size
309    let (skip, record) = match kind {
310        b"ISG1" | b"OSG1" | b"PSG1" => (4, 32),
311        b"OSG5" => (4, 28),
312        _ => (0, 24),
313    };
314    let mut result = Vec::new();
315    for i in v.table(8, count, record)? {
316        let p = v.record(8, i, record)?;
317        let q = v.plus(p, skip)?;
318        let [mask, read_write_mask, _, _] = v.array::<4>(v.plus(q, 20)?)?;
319        result.push(SignatureElement {
320            name: v.string(v.u32(q)? as usize)?,
321            index: v.field(q, 4)?,
322            system_value: v.field(q, 8)?,
323            component_type: v.field(q, 12)?,
324            register: v.field(q, 16)?,
325            mask,
326            read_write_mask,
327            at: p / 4,
328            stream: if skip == 4 { v.u32(p)? } else { 0 },
329            min_precision: if record == 32 { v.field(q, 24)? } else { 0 },
330        });
331    }
332    Ok(result)
333}
334
335/// A hull or domain program's patch-constant signature elements as stored (the `PCSG` part, or the `PSG1` part where
336/// the program uses minimum precision).
337pub fn patch_signature_elements(bytes: &[u8]) -> Result<Vec<SignatureElement>> {
338    let directory = parts(bytes)?;
339    let (data, tag) = signature_part(&directory, &[b"PCSG", b"PSG1"])?;
340    signature(data, &tag)
341}
342
343/// The part among `tags` the container has (the first tag where it has none, so that is the one reported missing).
344fn signature_part<'a>(
345    directory: &[dxcontainer::Chunk<'a>],
346    tags: &[&[u8; 4]],
347) -> Result<(&'a [u8], [u8; 4])> {
348    let tag = tags
349        .iter()
350        .find(|t| directory.iter().any(|c| &&c.kind == *t))
351        .or(tags.first())
352        .ok_or_else(|| error(0, "no signature part kinds to look for"))?;
353    Ok((chunk(directory, tag)?, **tag))
354}
355
356/// A container's program type and its input and output signature elements as stored.
357pub fn signature_elements(
358    bytes: &[u8],
359) -> Result<(ProgramType, Vec<SignatureElement>, Vec<SignatureElement>)> {
360    let directory = parts(bytes)?;
361    let kind = program_type(&directory)?;
362    let read = |tags: &[&[u8; 4]]| -> Result<Vec<SignatureElement>> {
363        let (data, tag) = signature_part(&directory, tags)?;
364        signature(data, &tag)
365    };
366    Ok((
367        kind,
368        read(&[b"ISGN", b"ISG1"])?,
369        read(&[b"OSGN", b"OSG1", b"OSG5"])?,
370    ))
371}
372
373/// The type of a container's program: its code part's version token names it in the high 16 bits.
374pub fn program_type(directory: &[dxcontainer::Chunk<'_>]) -> Result<ProgramType> {
375    let code = crate::code_part(directory)?;
376    let token = View(code.data).u32(0)?;
377    match token >> 16 {
378        0 => Ok(ProgramType::Pixel),
379        1 => Ok(ProgramType::Vertex),
380        2 => Ok(ProgramType::Geometry),
381        3 => Ok(ProgramType::Hull),
382        4 => Ok(ProgramType::Domain),
383        5 => Ok(ProgramType::Compute),
384        kind => Err(error(
385            0,
386            format!("expected a program type from 0 (pixel) to 5 (compute), found {kind}"),
387        )),
388    }
389}
390
391/// A container's `RDEF` part as stored: what it says the program binds and the layouts of its buffers.
392#[derive(Clone, Debug, PartialEq, Eq)]
393pub struct Rdef {
394    /// The part's own version token (`0xffff0500`: an SM5.0 pixel program, `0xfffe0500`: vertex, ...).
395    pub target: u32,
396    /// `D3DCOMPILE_*` flags the program was compiled with, as stored.
397    pub flags: u32,
398    /// The compiler that wrote the part (`Microsoft (R) HLSL Shader Compiler 10.1`).
399    pub creator: String,
400    /// The resource bindings, in the order the part lists them.
401    pub bindings: Vec<RdefBinding>,
402    /// The constant buffers, in the order the part lists them.
403    pub buffers: Vec<RdefBuffer>,
404}
405
406/// One resource binding record as stored.
407#[derive(Clone, Debug, PartialEq, Eq)]
408pub struct RdefBinding {
409    /// Where the record starts, in words of the part.
410    pub word: usize,
411    /// The resource's name.
412    pub name: String,
413    /// `D3D_SHADER_INPUT_TYPE`.
414    pub kind: u32,
415    /// `D3D_RESOURCE_RETURN_TYPE`.
416    pub returns: u32,
417    /// `D3D_SRV_DIMENSION`.
418    pub dimension: u32,
419    /// A multisampled texture's sample count; a structured buffer's stride.
420    pub samples: u32,
421    /// The first register the resource is bound to.
422    pub register: u32,
423    /// How many consecutive registers it is bound to.
424    pub count: u32,
425    /// `D3D_SHADER_INPUT_FLAGS`.
426    pub flags: u32,
427    /// The register space; 0 in the layouts before Shader Model 5.1, which have no spaces.
428    pub space: u32,
429    /// The resource's id, where the layout stores one (Shader Model 5.1): the number the program's declarations and
430    /// instructions name the resource by in place of its register.
431    pub id: Option<u32>,
432}
433
434impl RdefBinding {
435    /// `D3D_SHADER_INPUT_TYPE` as Microsoft's disassembler names it in the Type column (`cbuffer`, `texture`,
436    /// `sampler_c` for a comparison sampler, `UAV`).
437    pub fn type_name(&self) -> &'static str {
438        match self.kind {
439            0 => "cbuffer",
440            1 => "tbuffer",
441            // D3D_SIF_COMPARISON_SAMPLER
442            3 if self.flags & 2 != 0 => "sampler_c",
443            3 => "sampler",
444            4 | 6 | 8..=11 | 13 => "UAV",
445            _ => "texture",
446        }
447    }
448
449    /// What the binding returns, as Microsoft's disassembler names it in the Format column (`float4`, `struct`,
450    /// `byte`; `NA` for a buffer or sampler, which return none).
451    pub fn format_name(&self) -> String {
452        match self.kind {
453            0 | 1 | 3 => "NA".to_owned(),
454            5 | 6 | 9..=11 => "struct".to_owned(),
455            7 | 8 => "byte".to_owned(),
456            _ => {
457                let name = match self.returns {
458                    1 => "unorm",
459                    2 => "snorm",
460                    3 => "sint",
461                    4 => "uint",
462                    5 => "float",
463                    6 => "mixed",
464                    7 => "double",
465                    8 => "continued",
466                    _ => "NA",
467                };
468                // D3D_SIF_TEXTURE_COMPONENTS: the number of components less one
469                let components = ((self.flags >> 2) & 3).saturating_add(1);
470                if components > 1 {
471                    format!("{name}{components}")
472                } else {
473                    name.to_owned()
474                }
475            }
476        }
477    }
478
479    /// The resource's shape, as Microsoft's disassembler names it in the Dim column (`2d`, `2dMS4`, `r/w`, `NA`).
480    pub fn dimension_name(&self) -> String {
481        match self.kind {
482            0 | 1 | 3 => return "NA".to_owned(),
483            5 | 7 => return "r/o".to_owned(),
484            6 | 8 => return "r/w".to_owned(),
485            9 => return "append".to_owned(),
486            10 => return "consume".to_owned(),
487            11 => return "r/w+cnt".to_owned(),
488            _ => {}
489        }
490        let name = match self.dimension {
491            1 | 11 => "buf",
492            2 => "1d",
493            3 => "1darray",
494            4 => "2d",
495            5 => "2darray",
496            6 => "2dMS",
497            7 => "2darrayMS",
498            8 => "3d",
499            9 => "cube",
500            10 => "cubearray",
501            _ => "NA",
502        };
503        if matches!(self.dimension, 6 | 7) && self.samples != 0 && self.samples != u32::MAX {
504            format!("{name}{}", self.samples)
505        } else {
506            name.to_owned()
507        }
508    }
509
510    /// The register file the binding is in: `cb`, `s`, `u` or `t`.
511    pub fn register_prefix(&self) -> &'static str {
512        match self.type_name() {
513            "cbuffer" => "cb",
514            "sampler" | "sampler_c" => "s",
515            "UAV" => "u",
516            _ => "t",
517        }
518    }
519}
520
521/// One buffer (`D3D11_SHADER_BUFFER_DESC`) as stored, with its variables.
522#[derive(Clone, Debug, PartialEq, Eq)]
523pub struct RdefBuffer {
524    /// Where the record starts, in words of the part.
525    pub word: usize,
526    /// The buffer's name.
527    pub name: String,
528    /// `D3D_CBUFFER_TYPE`: 0 a cbuffer, 1 a tbuffer, 2 interface pointers, 3 a structured buffer's element.
529    pub kind: u32,
530    /// The buffer's size in bytes, as the part stores it.
531    pub size: u32,
532    /// The variables the buffer holds.
533    pub variables: Vec<RdefVariable>,
534}
535
536/// One variable of a buffer as stored: its byte range in the buffer, its type and its default value.
537#[derive(Clone, Debug, PartialEq, Eq)]
538pub struct RdefVariable {
539    /// Where the record starts, in words of the part.
540    pub word: usize,
541    /// The variable's name.
542    pub name: String,
543    /// Its first byte in the buffer.
544    pub offset: u32,
545    /// Its size in bytes.
546    pub size: u32,
547    /// `D3D_SHADER_VARIABLE_FLAGS`: bit 2 (`D3D_SVF_USED`) is set where the program reads the variable.
548    pub flags: u32,
549    /// Its type.
550    pub ty: RdefType,
551    /// The stored default value's `size` bytes, where the variable has one.
552    pub default: Option<Vec<u8>>,
553    /// The first texture register and the number of them a class instance holds (Shader Model 5 records only;
554    /// `[u32::MAX, 0]` where it holds none, and for Shader Model 4 records).
555    pub textures: [u32; 2],
556    /// The first sampler register and the number of them a class instance holds, as `textures`.
557    pub samplers: [u32; 2],
558}
559
560/// A variable's type record (`D3D11_SHADER_TYPE_DESC`) as stored, and its members' types.
561#[derive(Clone, Debug, PartialEq, Eq)]
562pub struct RdefType {
563    /// Where the record starts, in words of the part.
564    pub word: usize,
565    /// `D3D_SHADER_VARIABLE_CLASS`: 0 scalar, 1 vector, 2 row-major matrix, 3 column-major matrix, 4 object, 5 struct.
566    pub class: u16,
567    /// `D3D_SHADER_VARIABLE_TYPE`.
568    pub scalar: u16,
569    /// The matrix's rows (1 for a scalar or vector).
570    pub rows: u16,
571    /// The vector's or matrix's columns.
572    pub columns: u16,
573    /// The array length; 0 where it isn't an array.
574    pub elements: u16,
575    /// The struct's members, in order.
576    pub members: Vec<RdefMember>,
577    /// The type's name (`float4`, `float3x4`, a struct's own name), where the layout stores one (Shader Model 5).
578    pub name: Option<String>,
579}
580
581impl RdefType {
582    /// `D3D_SHADER_VARIABLE_TYPE` as HLSL names it (`float`, `uint`, `Texture2D`; `type` and the number for one it
583    /// has no name for).
584    pub fn scalar_name(&self) -> String {
585        match self.scalar {
586            0 => "void",
587            1 => "bool",
588            2 => "int",
589            3 => "float",
590            4 => "string",
591            5 => "texture",
592            6 => "Texture1D",
593            7 => "Texture2D",
594            8 => "Texture3D",
595            9 => "TextureCube",
596            10 => "SamplerState",
597            19 => "uint",
598            20 => "uint8",
599            25 => "Buffer",
600            26 => "cbuffer",
601            27 => "tbuffer",
602            28 => "Texture1DArray",
603            29 => "Texture2DArray",
604            32 => "Texture2DMS",
605            33 => "Texture2DMSArray",
606            34 => "TextureCubeArray",
607            39 => "double",
608            40 => "RWTexture1D",
609            41 => "RWTexture1DArray",
610            42 => "RWTexture2D",
611            43 => "RWTexture2DArray",
612            44 => "RWTexture3D",
613            45 => "RWBuffer",
614            46 => "ByteAddressBuffer",
615            47 => "RWByteAddressBuffer",
616            48 => "StructuredBuffer",
617            49 => "RWStructuredBuffer",
618            50 => "AppendStructuredBuffer",
619            51 => "ConsumeStructuredBuffer",
620            52 => "min8float",
621            53 => "min10float",
622            54 => "min16float",
623            55 => "min12int",
624            56 => "min16int",
625            57 => "min16uint",
626            other => return format!("type{other}"),
627        }
628        .to_owned()
629    }
630
631    /// The type's name as Microsoft's disassembler writes it before a variable's name: `float4`, `row_major float3x4`,
632    /// `interface ILight`. Built from the class and scalar type, as the disassembler does (the stored name of a `uint`
633    /// is `dword`); an interface's from its stored name. A struct is `struct` (the disassembler opens a block of its
634    /// members instead).
635    pub fn type_name(&self) -> String {
636        let scalar = self.scalar_name();
637        match self.class {
638            1 => format!("{scalar}{}", self.columns),
639            2 => format!("row_major {scalar}{}x{}", self.rows, self.columns),
640            3 => format!("{scalar}{}x{}", self.rows, self.columns),
641            5 => "struct".to_owned(),
642            // an interface class (6) or pointer (7)
643            6 | 7 => format!("interface {}", self.name.as_deref().unwrap_or("")),
644            _ => scalar,
645        }
646    }
647}
648
649/// A struct member: its name, its byte offset in the struct and its type.
650#[derive(Clone, Debug, PartialEq, Eq)]
651pub struct RdefMember {
652    /// The member's name.
653    pub name: String,
654    /// Its first byte in the struct.
655    pub offset: u32,
656    /// Its type.
657    pub ty: RdefType,
658}
659
660/// The version token of a container's first `RDEF` part (`None` where it has none), read without the rest of the part:
661/// a caller can tell which program model's layout it holds before it asks [`rdef`] for the records.
662pub fn rdef_target(bytes: &[u8]) -> Result<Option<u32>> {
663    let directory = parts(bytes)?;
664    directory
665        .iter()
666        .find(|c| &c.kind == b"RDEF")
667        .map(|rdef| View(rdef.data).u32(16))
668        .transpose()
669}
670
671/// The tag and record-size directory a Shader Model 5 `RDEF` part has after its 28-byte header, and the layout they
672/// state: `RD11` for 5.0, `13 13 44 25` for 5.1.
673fn read_directory(v: &View<'_>) -> Result<Layout> {
674    let tag = v.bytes(28, 4)?;
675    let (layout, name) = if tag == b"RD11" {
676        (SM50, "RD11")
677    } else if tag == SM51_TAG {
678        (SM51, "5.1")
679    } else {
680        return Err(error(
681            7,
682            format!(
683                "expected the `RD11` tag of the Shader Model 5.0 RDEF layout or the bytes 13 13 44 25 of the 5.1 one,                  found {:02x?} ({:?})",
684                tag,
685                String::from_utf8_lossy(tag)
686            ),
687        ));
688    };
689    for (i, size) in [60, 24, layout.binding, layout.variable, layout.ty, 12]
690        .into_iter()
691        .enumerate()
692    {
693        let at = v.record(32, i, 4)?;
694        let found = v.u32(at)? as usize;
695        if found != size {
696            return Err(error(
697                at / 4,
698                format!(
699                    "expected record size {size} (entry {i} of the {name} size directory), found {found}"
700                ),
701            ));
702        }
703    }
704    Ok(layout)
705}
706
707/// A container's `RDEF` part (`None` where it has none): the Shader Model 4 layout where its target is a 4.x one (a
708/// 28-byte header, then the tables), the Shader Model 5.0 or 5.1 layout (the record-size directory its tag names) where
709/// it is a 5.x one.
710pub fn rdef(bytes: &[u8]) -> Result<Option<Rdef>> {
711    let directory = parts(bytes)?;
712    if !directory.iter().any(|c| &c.kind == b"RDEF") {
713        return Ok(None);
714    }
715    let v = View(chunk(&directory, b"RDEF")?);
716    let target = v.u32(16)?;
717    let layout = match (target >> 8) & 0xff {
718        4 => SM4,
719        _ => read_directory(&v)?,
720    };
721    let mut bindings = Vec::new();
722    let offset = v.field(0, 12)? as usize;
723    for i in v.table(offset, v.field(0, 8)? as usize, layout.binding)? {
724        let p = v.record(offset, i, layout.binding)?;
725        let spaced = layout.binding == SM51.binding;
726        bindings.push(RdefBinding {
727            word: p / 4,
728            name: v.string(v.u32(p)? as usize)?,
729            kind: v.field(p, 4)?,
730            returns: v.field(p, 8)?,
731            dimension: v.field(p, 12)?,
732            samples: v.field(p, 16)?,
733            register: v.field(p, 20)?,
734            count: v.field(p, 24)?,
735            flags: v.field(p, 28)?,
736            space: if spaced { v.field(p, 32)? } else { 0 },
737            id: if spaced { Some(v.field(p, 36)?) } else { None },
738        });
739    }
740    let mut buffers = Vec::new();
741    let offset = v.field(0, 4)? as usize;
742    let mut budget = MAX_RECORDS;
743    for i in v.table(offset, v.u32(0)? as usize, 24)? {
744        let p = v.record(offset, i, 24)?;
745        let name = v.string(v.u32(p)? as usize)?;
746        let kind = v.field(p, 20)?;
747        let size = v.field(p, 12)?;
748        if size as usize > MAX_BUFFER_SIZE {
749            return Err(error(
750                v.plus(p, 12)? / 4,
751                format!("constant buffer of {size} bytes, more than the {MAX_BUFFER_SIZE} allowed"),
752            ));
753        }
754        let mut variables = Vec::new();
755        let entries = v.field(p, 8)? as usize;
756        for j in v.table(entries, v.field(p, 4)? as usize, layout.variable)? {
757            let q = v.record(entries, j, layout.variable)?;
758            let start = v.field(q, 4)?;
759            let len = v.field(q, 8)?;
760            // an interface pointer buffer (2) counts its variables in slots, not bytes, and gives an unused one no
761            // slot (u32::MAX) and no size; its buffer size bounds nothing
762            if kind != 2 && start.checked_add(len).is_none_or(|end| end > size) {
763                return Err(error(
764                    v.plus(q, 4)? / 4,
765                    format!(
766                        "variable at byte {start} of {len} bytes lies outside its {size} byte constant buffer"
767                    ),
768                ));
769            }
770            let default = v.field(q, 20)? as usize;
771            let ty = v.ty(v.field(q, 16)? as usize, layout, &mut Vec::new(), &mut budget)?;
772            variables.push(RdefVariable {
773                word: q / 4,
774                name: v.string(v.u32(q)? as usize)?,
775                offset: start,
776                size: len,
777                flags: v.field(q, 12)?,
778                ty,
779                default: if default == 0 {
780                    None
781                } else {
782                    Some(v.bytes(default, len as usize)?.to_vec())
783                },
784                textures: if layout.variable >= 40 {
785                    [v.field(q, 24)?, v.field(q, 28)?]
786                } else {
787                    [u32::MAX, 0]
788                },
789                samplers: if layout.variable >= 40 {
790                    [v.field(q, 32)?, v.field(q, 36)?]
791                } else {
792                    [u32::MAX, 0]
793                },
794            });
795        }
796        buffers.push(RdefBuffer {
797            word: p / 4,
798            name,
799            kind,
800            size,
801            variables,
802        });
803    }
804    Ok(Some(Rdef {
805        target,
806        flags: v.u32(20)?,
807        creator: v.string(v.u32(24)? as usize)?,
808        bindings,
809        buffers,
810    }))
811}