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        // a sample count as a signed number: `Texture2DMS<float4, -1>` stores 0xffffffff, written `2dMS-1`
504        if matches!(self.dimension, 6 | 7) && self.samples != 0 {
505            format!("{name}{}", i32::from_le_bytes(self.samples.to_le_bytes()))
506        } else {
507            name.to_owned()
508        }
509    }
510
511    /// The register file the binding is in: `cb`, `s`, `u` or `t`.
512    pub fn register_prefix(&self) -> &'static str {
513        match self.type_name() {
514            "cbuffer" => "cb",
515            "sampler" | "sampler_c" => "s",
516            "UAV" => "u",
517            _ => "t",
518        }
519    }
520}
521
522/// One buffer (`D3D11_SHADER_BUFFER_DESC`) as stored, with its variables.
523#[derive(Clone, Debug, PartialEq, Eq)]
524pub struct RdefBuffer {
525    /// Where the record starts, in words of the part.
526    pub word: usize,
527    /// The buffer's name.
528    pub name: String,
529    /// `D3D_CBUFFER_TYPE`: 0 a cbuffer, 1 a tbuffer, 2 interface pointers, 3 a structured buffer's element.
530    pub kind: u32,
531    /// The buffer's size in bytes, as the part stores it.
532    pub size: u32,
533    /// The variables the buffer holds.
534    pub variables: Vec<RdefVariable>,
535}
536
537/// One variable of a buffer as stored: its byte range in the buffer, its type and its default value.
538#[derive(Clone, Debug, PartialEq, Eq)]
539pub struct RdefVariable {
540    /// Where the record starts, in words of the part.
541    pub word: usize,
542    /// The variable's name.
543    pub name: String,
544    /// Its first byte in the buffer.
545    pub offset: u32,
546    /// Its size in bytes.
547    pub size: u32,
548    /// `D3D_SHADER_VARIABLE_FLAGS`: bit 2 (`D3D_SVF_USED`) is set where the program reads the variable.
549    pub flags: u32,
550    /// Its type.
551    pub ty: RdefType,
552    /// The stored default value's `size` bytes, where the variable has one.
553    pub default: Option<Vec<u8>>,
554    /// The first texture register and the number of them a class instance holds (Shader Model 5 records only;
555    /// `[u32::MAX, 0]` where it holds none, and for Shader Model 4 records).
556    pub textures: [u32; 2],
557    /// The first sampler register and the number of them a class instance holds, as `textures`.
558    pub samplers: [u32; 2],
559}
560
561/// A variable's type record (`D3D11_SHADER_TYPE_DESC`) as stored, and its members' types.
562#[derive(Clone, Debug, PartialEq, Eq)]
563pub struct RdefType {
564    /// Where the record starts, in words of the part.
565    pub word: usize,
566    /// `D3D_SHADER_VARIABLE_CLASS`: 0 scalar, 1 vector, 2 row-major matrix, 3 column-major matrix, 4 object, 5 struct.
567    pub class: u16,
568    /// `D3D_SHADER_VARIABLE_TYPE`.
569    pub scalar: u16,
570    /// The matrix's rows (1 for a scalar or vector).
571    pub rows: u16,
572    /// The vector's or matrix's columns.
573    pub columns: u16,
574    /// The array length; 0 where it isn't an array.
575    pub elements: u16,
576    /// The struct's members, in order.
577    pub members: Vec<RdefMember>,
578    /// The type's name (`float4`, `float3x4`, a struct's own name), where the layout stores one (Shader Model 5).
579    pub name: Option<String>,
580}
581
582impl RdefType {
583    /// `D3D_SHADER_VARIABLE_TYPE` as HLSL names it (`float`, `uint`, `Texture2D`; `type` and the number for one it
584    /// has no name for).
585    pub fn scalar_name(&self) -> String {
586        match self.scalar {
587            0 => "void",
588            1 => "bool",
589            2 => "int",
590            3 => "float",
591            4 => "string",
592            5 => "texture",
593            6 => "Texture1D",
594            7 => "Texture2D",
595            8 => "Texture3D",
596            9 => "TextureCube",
597            10 => "SamplerState",
598            19 => "uint",
599            20 => "uint8",
600            25 => "Buffer",
601            26 => "cbuffer",
602            27 => "tbuffer",
603            28 => "Texture1DArray",
604            29 => "Texture2DArray",
605            32 => "Texture2DMS",
606            33 => "Texture2DMSArray",
607            34 => "TextureCubeArray",
608            39 => "double",
609            40 => "RWTexture1D",
610            41 => "RWTexture1DArray",
611            42 => "RWTexture2D",
612            43 => "RWTexture2DArray",
613            44 => "RWTexture3D",
614            45 => "RWBuffer",
615            46 => "ByteAddressBuffer",
616            47 => "RWByteAddressBuffer",
617            48 => "StructuredBuffer",
618            49 => "RWStructuredBuffer",
619            50 => "AppendStructuredBuffer",
620            51 => "ConsumeStructuredBuffer",
621            52 => "min8float",
622            53 => "min10float",
623            54 => "min16float",
624            55 => "min12int",
625            56 => "min16int",
626            57 => "min16uint",
627            other => return format!("type{other}"),
628        }
629        .to_owned()
630    }
631
632    /// The type's name as Microsoft's disassembler writes it before a variable's name: `float4`, `row_major float3x4`,
633    /// `interface ILight`. Built from the class and scalar type, as the disassembler does (the stored name of a `uint`
634    /// is `dword`); an interface's from its stored name. A struct is `struct` (the disassembler opens a block of its
635    /// members instead).
636    pub fn type_name(&self) -> String {
637        let scalar = self.scalar_name();
638        match self.class {
639            1 => format!("{scalar}{}", self.columns),
640            2 => format!("row_major {scalar}{}x{}", self.rows, self.columns),
641            3 => format!("{scalar}{}x{}", self.rows, self.columns),
642            5 => "struct".to_owned(),
643            // an interface class (6) or pointer (7)
644            6 | 7 => format!("interface {}", self.name.as_deref().unwrap_or("")),
645            _ => scalar,
646        }
647    }
648}
649
650/// A struct member: its name, its byte offset in the struct and its type.
651#[derive(Clone, Debug, PartialEq, Eq)]
652pub struct RdefMember {
653    /// The member's name.
654    pub name: String,
655    /// Its first byte in the struct.
656    pub offset: u32,
657    /// Its type.
658    pub ty: RdefType,
659}
660
661/// The version token of a container's first `RDEF` part (`None` where it has none), read without the rest of the part:
662/// a caller can tell which program model's layout it holds before it asks [`rdef`] for the records.
663pub fn rdef_target(bytes: &[u8]) -> Result<Option<u32>> {
664    let directory = parts(bytes)?;
665    directory
666        .iter()
667        .find(|c| &c.kind == b"RDEF")
668        .map(|rdef| View(rdef.data).u32(16))
669        .transpose()
670}
671
672/// The tag and record-size directory a Shader Model 5 `RDEF` part has after its 28-byte header, and the layout they
673/// state: `RD11` for 5.0, `13 13 44 25` for 5.1.
674fn read_directory(v: &View<'_>) -> Result<Layout> {
675    let tag = v.bytes(28, 4)?;
676    let (layout, name) = if tag == b"RD11" {
677        (SM50, "RD11")
678    } else if tag == SM51_TAG {
679        (SM51, "5.1")
680    } else {
681        return Err(error(
682            7,
683            format!(
684                "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?} ({:?})",
685                tag,
686                String::from_utf8_lossy(tag)
687            ),
688        ));
689    };
690    for (i, size) in [60, 24, layout.binding, layout.variable, layout.ty, 12]
691        .into_iter()
692        .enumerate()
693    {
694        let at = v.record(32, i, 4)?;
695        let found = v.u32(at)? as usize;
696        if found != size {
697            return Err(error(
698                at / 4,
699                format!(
700                    "expected record size {size} (entry {i} of the {name} size directory), found {found}"
701                ),
702            ));
703        }
704    }
705    Ok(layout)
706}
707
708/// A container's `RDEF` part (`None` where it has none): the Shader Model 4 layout where its target is a 4.x one (a
709/// 28-byte header, then the tables), the Shader Model 5.0 or 5.1 layout (the record-size directory its tag names) where
710/// it is a 5.x one.
711pub fn rdef(bytes: &[u8]) -> Result<Option<Rdef>> {
712    let directory = parts(bytes)?;
713    if !directory.iter().any(|c| &c.kind == b"RDEF") {
714        return Ok(None);
715    }
716    let v = View(chunk(&directory, b"RDEF")?);
717    let target = v.u32(16)?;
718    let layout = match (target >> 8) & 0xff {
719        4 => SM4,
720        _ => read_directory(&v)?,
721    };
722    let mut bindings = Vec::new();
723    let offset = v.field(0, 12)? as usize;
724    for i in v.table(offset, v.field(0, 8)? as usize, layout.binding)? {
725        let p = v.record(offset, i, layout.binding)?;
726        let spaced = layout.binding == SM51.binding;
727        bindings.push(RdefBinding {
728            word: p / 4,
729            name: v.string(v.u32(p)? as usize)?,
730            kind: v.field(p, 4)?,
731            returns: v.field(p, 8)?,
732            dimension: v.field(p, 12)?,
733            samples: v.field(p, 16)?,
734            register: v.field(p, 20)?,
735            count: v.field(p, 24)?,
736            flags: v.field(p, 28)?,
737            space: if spaced { v.field(p, 32)? } else { 0 },
738            id: if spaced { Some(v.field(p, 36)?) } else { None },
739        });
740    }
741    let mut buffers = Vec::new();
742    let offset = v.field(0, 4)? as usize;
743    let mut budget = MAX_RECORDS;
744    for i in v.table(offset, v.u32(0)? as usize, 24)? {
745        let p = v.record(offset, i, 24)?;
746        let name = v.string(v.u32(p)? as usize)?;
747        let kind = v.field(p, 20)?;
748        let size = v.field(p, 12)?;
749        if size as usize > MAX_BUFFER_SIZE {
750            return Err(error(
751                v.plus(p, 12)? / 4,
752                format!("constant buffer of {size} bytes, more than the {MAX_BUFFER_SIZE} allowed"),
753            ));
754        }
755        let mut variables = Vec::new();
756        let entries = v.field(p, 8)? as usize;
757        for j in v.table(entries, v.field(p, 4)? as usize, layout.variable)? {
758            let q = v.record(entries, j, layout.variable)?;
759            let start = v.field(q, 4)?;
760            let len = v.field(q, 8)?;
761            // an interface pointer buffer (2) counts its variables in slots, not bytes, and gives an unused one no
762            // slot (u32::MAX) and no size; its buffer size bounds nothing
763            if kind != 2 && start.checked_add(len).is_none_or(|end| end > size) {
764                return Err(error(
765                    v.plus(q, 4)? / 4,
766                    format!(
767                        "variable at byte {start} of {len} bytes lies outside its {size} byte constant buffer"
768                    ),
769                ));
770            }
771            let default = v.field(q, 20)? as usize;
772            let ty = v.ty(v.field(q, 16)? as usize, layout, &mut Vec::new(), &mut budget)?;
773            variables.push(RdefVariable {
774                word: q / 4,
775                name: v.string(v.u32(q)? as usize)?,
776                offset: start,
777                size: len,
778                flags: v.field(q, 12)?,
779                ty,
780                default: if default == 0 {
781                    None
782                } else {
783                    Some(v.bytes(default, len as usize)?.to_vec())
784                },
785                textures: if layout.variable >= 40 {
786                    [v.field(q, 24)?, v.field(q, 28)?]
787                } else {
788                    [u32::MAX, 0]
789                },
790                samplers: if layout.variable >= 40 {
791                    [v.field(q, 32)?, v.field(q, 36)?]
792                } else {
793                    [u32::MAX, 0]
794                },
795            });
796        }
797        buffers.push(RdefBuffer {
798            word: p / 4,
799            name,
800            kind,
801            size,
802            variables,
803        });
804    }
805    Ok(Some(Rdef {
806        target,
807        flags: v.u32(20)?,
808        creator: v.string(v.u32(24)? as usize)?,
809        bindings,
810        buffers,
811    }))
812}