Skip to main content

dxbc_reader/disasm/
mod.rs

1//! A text listing of a Shader Model 4 or 5 container in the dialect of Microsoft's disassembler (`D3DDisassemble` with
2//! no flags, `fxc /dumpbin`): the reflection and signature tables as comments, the program's declarations and
3//! instructions, and the instruction count of the `STAT` part.
4use crate::reflection::{rdef, signature};
5use crate::{Result, chunk, code, decode, error, parts, statistics};
6
7mod header;
8mod level9;
9mod number;
10mod program;
11
12/// How the listing is written: the switches of Microsoft's `D3D_DISASM_*` flags that this disassembler writes.
13/// [`Options::default`] is `D3DDisassemble` with no flags.
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15#[non_exhaustive]
16pub struct Options {
17    /// The comment block before the program and the instruction count after it; off, only the program's lines
18    /// (`D3D_DISASM_INSTRUCTION_ONLY`, 0x40).
19    pub header: bool,
20    /// Each instruction after its number, `  12: `, other lines after as many spaces; before an offset, `  12  `
21    /// (`D3D_DISASM_ENABLE_INSTRUCTION_NUMBERING`, 0x04).
22    pub numbering: bool,
23    /// Each line after the byte offset of its token in the program, `0x0000001C: `, after the number
24    /// (`D3D_DISASM_ENABLE_INSTRUCTION_OFFSET`, 0x20).
25    pub offsets: bool,
26    /// Every 32-bit value as eight hex digits, `l(0x3f800000)` (`D3D_DISASM_PRINT_HEX_LITERALS`, 0x80).
27    pub hex_literals: bool,
28    /// A `/Zi` build's source lines and variable locations: from the `SPDB` part, `#line` lines, the variables'
29    /// initial locations before the first instruction with a line, and after an instruction the locations that start
30    /// at the next; from a feature level 9 container's Direct3D 9 program, its own (cleared by
31    /// `D3D_DISASM_DISABLE_DEBUG_INFO`, 0x10).
32    pub debug_info: bool,
33}
34
35impl Default for Options {
36    fn default() -> Self {
37        Self {
38            header: true,
39            numbering: false,
40            offsets: false,
41            hex_literals: false,
42            debug_info: true,
43        }
44    }
45}
46
47impl Options {
48    /// The options of a `D3D_DISASM_*` flags word. `D3D_DISASM_ENABLE_DEFAULT_VALUE_PRINTS` (0x02) changes nothing
49    /// (the default values are written either way). Refuses
50    /// `D3D_DISASM_ENABLE_COLOR_CODE` (0x01, an HTML listing), `D3D_DISASM_ENABLE_INSTRUCTION_CYCLE` (0x08, which
51    /// Microsoft's disassembler refuses too) and bits it does not define, naming the bit.
52    pub fn from_flags(flags: u32) -> Result<Self> {
53        for (bit, name) in [
54            (0x01, "D3D_DISASM_ENABLE_COLOR_CODE (an HTML listing)"),
55            (
56                0x08,
57                "D3D_DISASM_ENABLE_INSTRUCTION_CYCLE, which Microsoft's disassembler refuses too",
58            ),
59        ] {
60            if flags & bit != 0 {
61                return Err(error(0, format!("flag 0x{bit:02x}, {name}, is not written")));
62            }
63        }
64        let unknown = flags & !0xf6;
65        if unknown != 0 {
66            return Err(error(
67                0,
68                format!(
69                    "flag bit 0x{:x} is not a D3D_DISASM flag",
70                    1u32 << unknown.trailing_zeros()
71                ),
72            ));
73        }
74        Ok(Self {
75            header: flags & 0x40 == 0,
76            numbering: flags & 0x04 != 0,
77            offsets: flags & 0x20 != 0,
78            hex_literals: flags & 0x80 != 0,
79            debug_info: flags & 0x10 == 0,
80        })
81    }
82}
83
84/// The listing of a `DXBC` container's Shader Model 4 or 5 program as Microsoft's disassembler writes it with no
85/// flags, its lines ending in `\n` (the disassembler's blob also ends in a zero byte, which is not part of the text).
86///
87/// Refuses a container [`parts`] refuses, one without a `SHDR` or `SHEX` part, a `DXIL` container and a program
88/// [`decode()`] refuses.
89pub fn disassemble(container: &[u8]) -> Result<String> {
90    disassemble_with(container, &Options::default())
91}
92
93/// The listing of a `DXBC` container as [`disassemble`] writes it, with `options`.
94pub fn disassemble_with(container: &[u8], options: &Options) -> Result<String> {
95    let directory = parts(container)?;
96    let program = decode(&code(container)?)?;
97    let reflection = rdef(container)?;
98    let read = |tags: &[&[u8; 4]]| -> Result<Option<(Vec<crate::SignatureElement>, bool)>> {
99        match tags.iter().find(|t| directory.iter().any(|c| &&c.kind == *t)) {
100            Some(tag) => {
101                let elements = signature(chunk(&directory, tag)?, tag)?;
102                // the rows name their streams where any is not stream 0
103                let streams = elements.iter().any(|e| e.stream != 0);
104                Ok(Some((elements, streams)))
105            }
106            None => Ok(None),
107        }
108    };
109    let inputs = read(&[b"ISGN", b"ISG1"])?;
110    let outputs = read(&[b"OSGN", b"OSG1", b"OSG5"])?;
111    let patch = read(&[b"PCSG", b"PSG1"])?;
112    let stat = statistics(container)?;
113    let mut out = String::new();
114    if options.header {
115        header::write(
116            &mut out,
117            &header::Header {
118                program: &program,
119                rdef: reflection.as_ref(),
120                features: dxcontainer::feature_flags(container)
121                    .map_err(|e| error(e.offset / 4, e.detail))?
122                    .map_or(0, |f| f.bits),
123                stat: stat.as_ref(),
124                inputs: elements(&inputs),
125                outputs: elements(&outputs),
126                patch: elements(&patch),
127                interfaces: crate::interfaces(container)?.as_ref(),
128                debug_layer: dxcontainer::checksum(container).as_ref().map(|d| &d[..])
129                    != container.get(4..20),
130            },
131        );
132        if let Some(part) = crate::level9(container)? {
133            level9::write(&mut out, &part, options)?;
134        }
135    }
136    let annotations = match directory.iter().any(|c| &c.kind == b"SPDB") && options.debug_info {
137        true => Some(crate::spdb::annotations(chunk(&directory, b"SPDB")?)?),
138        false => None,
139    };
140    program::write(&mut out, &program, options, annotations.as_ref());
141    if !options.header {
142        return Ok(out);
143    }
144    // Microsoft's disassembler writes the count of the STAT part, 0 without one
145    let slots = stat.and_then(|s| s.instructions()).unwrap_or(0);
146    out.push_str(&format!("// Approximately {slots} instruction slots used\n"));
147    Ok(out)
148}
149
150/// A signature part as the header reads it.
151fn elements(part: &Option<(Vec<crate::SignatureElement>, bool)>) -> Option<header::Elements<'_>> {
152    part.as_ref().map(|(elements, streams)| header::Elements {
153        elements,
154        streams: *streams,
155    })
156}