1use std::collections::BTreeMap;
4use std::fmt::Write;
5
6use crate::{
7 Comparison, Constant, ConstantTable, DebugInfo, Declared, Destination, Error, Instruction, Item, Literal,
8 Modifier, Opcode, Program, Register, RegisterType, Relative, Result, Source, SourceLine, Stage,
9 TextureType, Type, read,
10};
11
12#[derive(Clone, Debug, PartialEq, Eq)]
14#[non_exhaustive]
15pub struct Options {
16 pub header: bool,
19 pub numbering: bool,
23 pub offsets: bool,
26 pub hex_literals: bool,
28 pub debug_info: bool,
31}
32
33impl Default for Options {
34 fn default() -> Self {
35 Self {
36 header: true,
37 numbering: false,
38 offsets: false,
39 hex_literals: false,
40 debug_info: true,
41 }
42 }
43}
44
45impl Options {
46 pub fn from_flags(flags: u32) -> Result<Self> {
49 let refuse = |detail: String| Error { word: 0, detail };
50 if flags & 0x01 != 0 {
51 return Err(refuse(
52 "flag 0x01, D3D_DISASM_ENABLE_COLOR_CODE (an HTML listing), is not written".into(),
53 ));
54 }
55 if flags & 0x08 != 0 {
56 return Err(refuse(
57 "flag 0x08, D3D_DISASM_ENABLE_INSTRUCTION_CYCLE, which Microsoft's disassembler refuses too, is not \
58 written"
59 .into(),
60 ));
61 }
62 let unknown = flags & !0xf6;
63 if unknown != 0 {
64 return Err(refuse(format!(
65 "flag bit 0x{:x} is not a D3D_DISASM flag",
66 1u32 << unknown.trailing_zeros()
67 )));
68 }
69 Ok(Self {
70 header: flags & 0x40 == 0,
71 numbering: flags & 0x04 != 0,
72 offsets: flags & 0x20 != 0,
73 hex_literals: flags & 0x80 != 0,
74 debug_info: flags & 0x10 == 0,
75 })
76 }
77}
78
79const LANES: [char; 4] = ['x', 'y', 'z', 'w'];
80
81pub fn disassemble(bytes: &[u8]) -> Result<String> {
85 disassemble_with(bytes, &Options::default())
86}
87
88pub fn disassemble_with(bytes: &[u8], options: &Options) -> Result<String> {
90 let program = read(bytes)?;
91 let mut out = String::new();
92 if let Some(table) = program.constants.as_ref().filter(|_| options.header) {
94 header(&mut out, table);
95 }
96 let _ = writeln!(out, " {}", program.version);
97 let debug = program.debug.as_ref().filter(|_| options.debug_info);
98 let notes = debug.map(variables).unwrap_or_default();
99 let records: BTreeMap<u32, usize> = debug
101 .map(|d| d.lines.iter().enumerate().map(|(n, l)| (l.offset, n)).collect())
102 .unwrap_or_default();
103 let mut source: Option<(u16, u16)> = None;
105 let mut depth: usize = 0;
106 let mut slot: u32 = 0;
108 for item in &program.items {
109 if matches!(item, Item::Comment(_)) {
110 continue;
111 }
112 let record = u32::try_from(item.word().saturating_mul(4))
113 .ok()
114 .and_then(|offset| records.get(&offset))
115 .copied();
116 let line = record.and_then(|n| debug.and_then(|d| d.lines.get(n)));
118 if let (
119 Some(d),
120 Some(SourceLine {
121 line,
122 file: Some(file),
123 ..
124 }),
125 ) = (debug, line)
126 {
127 match source {
128 Some((f, l)) if f == *file => {
129 if line.abs_diff(l) > 2 {
130 let _ = writeln!(out, "\n#line {line}");
131 }
132 }
133 _ => {
134 let path = d.files.get(usize::from(*file)).map_or("", String::as_str);
135 let _ = writeln!(out, "\n#line {line} \"{path}\"");
136 }
137 }
138 source = Some((*file, *line));
139 }
140 let mut prefix = String::new();
142 if options.numbering {
143 match item {
144 Item::Instruction(i) if !i.coissue && i.opcode != Opcode::Nop => {
145 let colon = if options.offsets { ' ' } else { ':' };
146 let _ = write!(prefix, "{slot:>4}{colon} ");
147 slot = slot.saturating_add(slots(i, &program).1);
148 }
149 _ => prefix.push_str(" "),
150 }
151 }
152 if options.offsets {
153 let _ = write!(prefix, "0x{:08X}: ", item.word().saturating_mul(4));
154 }
155 let indent = match item {
157 Item::Instruction(i) if i.coissue => " + ",
158 _ => " ",
159 };
160 let line = match item {
161 Item::Comment(_) => continue,
162 Item::Declaration(d) => declaration(d, &program),
163 Item::Definition(d) => definition(&d.literal, d.destination.register, &program, options),
164 Item::Instruction(i) => {
165 if matches!(
166 i.opcode,
167 Opcode::Else | Opcode::EndIf | Opcode::EndLoop | Opcode::EndRep
168 ) {
169 depth = depth.saturating_sub(1);
170 }
171 let text = instruction(i, &program);
172 let line = format!("{}{text}", " ".repeat(depth));
173 if matches!(
174 i.opcode,
175 Opcode::If | Opcode::IfC | Opcode::Else | Opcode::Loop | Opcode::Rep
176 ) {
177 depth = depth.saturating_add(1);
178 }
179 line
180 }
181 };
182 let _ = write!(out, "{prefix}{indent}{}", line.trim_end());
183 if let Some(written) = record.and_then(|n| notes.get(n)).filter(|w| !w.is_empty()) {
184 let _ = write!(out, " // {}", written.join(", "));
185 }
186 out.push('\n');
187 }
188 out.push('\n');
189 footer(&mut out, &program);
190 Ok(out)
191}
192
193fn variables(debug: &DebugInfo) -> Vec<Vec<String>> {
198 let mut notes = vec![Vec::new(); debug.lines.len()];
199 for v in &debug.variables {
200 let name = match &v.scope {
201 Some(scope) if *scope == debug.entry => v.name.clone(),
202 Some(scope) => format!("{scope}::{}", v.name),
203 None => format!("::{}", v.name),
204 };
205 for l in &v.locations {
206 let components: Vec<String> = l.components.iter().flatten().map(u16::to_string).collect();
207 if let Some(n) = usize::try_from(l.instruction).ok().and_then(|n| notes.get_mut(n)) {
208 n.push(format!("{name}<{}>", components.join(",")));
209 }
210 }
211 }
212 notes
213}
214
215fn type_kind(kind: u16) -> &'static str {
217 match kind {
218 0 => "void",
219 1 => "bool",
220 2 => "int",
221 3 => "float",
222 4 => "string",
223 5 => "texture",
224 6 => "texture1D",
225 7 => "texture2D",
226 8 => "texture3D",
227 9 => "textureCUBE",
228 10 => "sampler",
229 11 => "sampler1D",
230 12 => "sampler2D",
231 13 => "sampler3D",
232 14 => "samplerCUBE",
233 _ => "unknown",
234 }
235}
236
237fn type_name(ty: &Type) -> String {
240 let kind = type_kind(ty.kind);
241 match ty.class {
242 1 => format!("{kind}{}", ty.columns),
243 2 => format!("row_major {kind}{}x{}", ty.rows, ty.columns),
244 3 => format!("{kind}{}x{}", ty.rows, ty.columns),
245 _ => kind.to_owned(),
246 }
247}
248
249fn bank(set: u16) -> &'static str {
251 match set {
252 0 => "b",
253 1 => "i",
254 3 => "s",
255 _ => "c",
256 }
257}
258
259fn header(out: &mut String, table: &ConstantTable) {
262 let _ = writeln!(out, "//\n// Generated by {}", table.creator);
263 let mut constants: Vec<&Constant> = table.constants.iter().collect();
264 if constants.is_empty() {
265 return;
266 }
267 constants.sort_by(|a, b| a.name.as_bytes().cmp(b.name.as_bytes()));
268 out.push_str("//\n// Parameters:\n//\n");
269 let mut parameters = constants.clone();
271 parameters.dedup_by(|a, b| a.name == b.name);
272 for c in ¶meters {
273 parameter(out, 3, &c.ty, &c.name);
274 }
275 out.push_str("//\n//\n// Registers:\n//\n");
276 constants.sort_by_key(|c| (c.register_set, c.register));
277 let width = constants.iter().map(|c| c.name.len()).max().unwrap_or(0).max(12);
279 let _ = writeln!(out, "// {:<width$} Reg Size", "Name");
280 let _ = writeln!(out, "// {} ----- ----", "-".repeat(width));
281 for c in &constants {
282 let register = format!("{}{}", bank(c.register_set), c.register);
283 let _ = writeln!(out, "// {:<width$} {register:<5} {:>4}", c.name, c.count);
284 }
285 out.push_str("//\n\n");
286}
287
288fn parameter(out: &mut String, indent: usize, ty: &Type, name: &str) {
301 let pad = " ".repeat(indent);
302 let array = if ty.elements > 1 {
303 format!("[{}]", ty.elements)
304 } else {
305 String::new()
306 };
307 if ty.class == 5 {
308 let blank = format!("//{pad}\n");
309 if !out.ends_with(&blank) {
310 out.push_str(&blank);
311 }
312 let _ = write!(out, "//{pad}struct\n//{pad}{{\n");
313 for member in &ty.members {
314 parameter(out, indent.saturating_add(4), &member.ty, &member.name);
315 }
316 let _ = write!(out, "//\n//{pad}}} {name}{array};\n//{pad}\n");
317 } else {
318 let _ = writeln!(out, "//{pad}{} {name}{array};", type_name(ty));
319 }
320}
321
322fn register(r: Register, program: &Program) -> String {
323 let n = r.number;
324 let vertex3 = program.version.stage == Stage::Vertex && program.version.major == 3;
325 let indexed = |names: &[&str]| -> String {
326 usize::try_from(n)
327 .ok()
328 .and_then(|i| names.get(i))
329 .map_or_else(|| format!("?{n}"), |s| (*s).to_owned())
330 };
331 match r.kind {
332 RegisterType::Temp => format!("r{n}"),
333 RegisterType::Input => format!("v{n}"),
334 RegisterType::Const => format!("c{n}"),
335 RegisterType::Const2 => format!("c{}", n.saturating_add(2048)),
336 RegisterType::Const3 => format!("c{}", n.saturating_add(4096)),
337 RegisterType::Const4 => format!("c{}", n.saturating_add(6144)),
338 RegisterType::Address if program.version.stage == Stage::Pixel => format!("t{n}"),
339 RegisterType::Address => "a0".into(),
340 RegisterType::RasterizerOut => indexed(&["oPos", "oFog", "oPts"]),
341 RegisterType::AttributeOut => format!("oD{n}"),
342 RegisterType::Output if vertex3 => format!("o{n}"),
343 RegisterType::Output => format!("oT{n}"),
344 RegisterType::ConstInt => format!("i{n}"),
345 RegisterType::ColorOut => format!("oC{n}"),
346 RegisterType::DepthOut => "oDepth".into(),
347 RegisterType::Sampler => format!("s{n}"),
348 RegisterType::ConstBool => format!("b{n}"),
349 RegisterType::Loop => "aL".into(),
350 RegisterType::Misc => indexed(&["vPos", "vFace"]),
351 RegisterType::Label => format!("l{n}"),
352 RegisterType::Predicate => format!("p{n}"),
353 RegisterType::Texture => format!("t{n}"),
354 }
355}
356
357fn lane(l: u8) -> char {
358 LANES.get(usize::from(l)).copied().unwrap_or('?')
359}
360
361fn swizzle(s: [u8; 4]) -> String {
363 match s {
364 [0, 1, 2, 3] => String::new(),
365 [a, b, c, d] if a == b && b == c && c == d => format!(".{}", lane(a)),
366 _ => format!(".{}", s.iter().map(|&l| lane(l)).collect::<String>()),
367 }
368}
369
370fn relative(r: Option<Relative>, program: &Program) -> String {
371 r.map_or(String::new(), |r| {
372 format!("[{}{}]", register(r.register, program), swizzle(r.swizzle))
373 })
374}
375
376fn source(s: &Source, program: &Program) -> String {
377 let name = format!(
378 "{}{}",
379 register(s.register, program),
380 relative(s.relative, program)
381 );
382 let body = match s.modifier {
383 Modifier::None | Modifier::Negate | Modifier::Not => name,
384 Modifier::Bias | Modifier::BiasNegate => format!("{name}_bias"),
385 Modifier::Sign | Modifier::SignNegate => format!("{name}_bx2"),
386 Modifier::Complement => format!("1-{name}"),
387 Modifier::X2 | Modifier::X2Negate => format!("{name}_x2"),
388 Modifier::DivideZ => format!("{name}_dz"),
389 Modifier::DivideW => format!("{name}_dw"),
390 Modifier::Abs | Modifier::AbsNegate => format!("{name}_abs"),
391 };
392 let sign = match s.modifier {
393 Modifier::Negate
394 | Modifier::BiasNegate
395 | Modifier::SignNegate
396 | Modifier::X2Negate
397 | Modifier::AbsNegate => "-",
398 Modifier::Not => "!",
399 _ => "",
400 };
401 let swizzled = if s.register.kind == RegisterType::Sampler {
402 String::new()
403 } else {
404 swizzle(s.swizzle)
405 };
406 format!("{sign}{body}{swizzled}")
407}
408
409fn destination(d: &Destination, program: &Program) -> String {
410 let mask = if d.mask == 15 {
411 String::new()
412 } else {
413 let lanes: String = (0..4u8).filter(|n| d.mask & (1 << n) != 0).map(lane).collect();
414 format!(".{lanes}")
415 };
416 format!(
417 "{}{}{mask}",
418 register(d.register, program),
419 relative(d.relative, program)
420 )
421}
422
423fn suffixes(d: Option<&Destination>) -> String {
425 let Some(d) = d else { return String::new() };
426 let mut s = match d.shift {
427 1 => "_x2",
428 2 => "_x4",
429 3 => "_x8",
430 -1 => "_d2",
431 -2 => "_d4",
432 -3 => "_d8",
433 _ => "",
434 }
435 .to_owned();
436 if d.saturate {
437 s.push_str("_sat");
438 }
439 if d.partial_precision {
440 s.push_str("_pp");
441 }
442 if d.centroid {
443 s.push_str("_centroid");
444 }
445 s
446}
447
448fn mnemonic(i: &Instruction, program: &Program) -> String {
449 let v = program.version;
450 let from14 = v.stage == Stage::Pixel && (v.major > 1 || v.minor == 4);
451 let comparison = i.comparison().map_or("", |c| match c {
452 Comparison::Greater => "_gt",
453 Comparison::Equal => "_eq",
454 Comparison::GreaterEqual => "_ge",
455 Comparison::Less => "_lt",
456 Comparison::NotEqual => "_ne",
457 Comparison::LessEqual => "_le",
458 });
459 match i.opcode {
460 Opcode::Tex if from14 => match i.control {
461 0 => "texld",
462 1 => "texldp",
463 _ => "texldb",
464 }
465 .to_owned(),
466 Opcode::TexCoord if from14 => "texcrd".into(),
467 Opcode::IfC => format!("if{comparison}"),
468 Opcode::BreakC => format!("break{comparison}"),
469 Opcode::SetP => format!("setp{comparison}"),
470 other => other.name().into(),
471 }
472}
473
474fn instruction(i: &Instruction, program: &Program) -> String {
475 let mut args = Vec::new();
476 if let Some(d) = &i.destination {
477 args.push(destination(d, program));
478 }
479 args.extend(i.sources.iter().map(|s| source(s, program)));
480 let predicate = i
481 .predicate
482 .as_ref()
483 .map_or(String::new(), |p| format!("({}) ", source(p, program)));
484 format!(
485 "{predicate}{}{} {}",
486 mnemonic(i, program),
487 suffixes(i.destination.as_ref()),
488 args.join(", ")
489 )
490}
491
492const USAGES: [&str; 14] = [
494 "position",
495 "blendweight",
496 "blendindices",
497 "normal",
498 "psize",
499 "texcoord",
500 "tangent",
501 "binormal",
502 "tessfactor",
503 "positiont",
504 "color",
505 "fog",
506 "depth",
507 "sample",
508];
509
510fn declaration(d: &crate::Declaration, program: &Program) -> String {
511 let what = match d.declared {
512 Declared::Sampler(TextureType::D2) => "_2d".to_owned(),
513 Declared::Sampler(TextureType::Cube) => "_cube".to_owned(),
514 Declared::Sampler(TextureType::Volume) => "_volume".to_owned(),
515 Declared::Semantic { .. }
516 if matches!(
517 d.destination.register.kind,
518 RegisterType::Texture | RegisterType::Misc
519 ) || (program.version.stage == Stage::Pixel && program.version.major == 2) =>
520 {
521 String::new()
522 }
523 Declared::Semantic { usage, index } => {
524 let index = if index == 0 {
525 String::new()
526 } else {
527 index.to_string()
528 };
529 let usage = USAGES.get(usize::from(usage)).copied().unwrap_or("unknown");
530 format!("_{usage}{index}")
531 }
532 };
533 format!(
534 "dcl{what}{} {}",
535 suffixes(Some(&d.destination)),
536 destination(&d.destination, program)
537 )
538}
539
540fn definition(literal: &Literal, register_: Register, program: &Program, options: &Options) -> String {
541 let (op, values) = match literal {
542 Literal::Float(f) => (
543 "def",
544 f.iter()
545 .map(|&w| {
546 if options.hex_literals {
547 format!("0x{w:08x}")
548 } else {
549 general(f32::from_bits(w))
550 }
551 })
552 .collect::<Vec<_>>()
553 .join(", "),
554 ),
555 Literal::Integer(i) => (
556 "defi",
557 i.iter().map(ToString::to_string).collect::<Vec<_>>().join(", "),
558 ),
559 Literal::Bool(b) => ("defb", (if *b != 0 { "true" } else { "false" }).to_owned()),
560 };
561 format!("{op} {}, {values}", register(register_, program))
562}
563
564fn general(value: f32) -> String {
567 let value = f64::from(value);
568 if value == 0.0 {
569 return if value.is_sign_negative() { "-0" } else { "0" }.to_owned();
570 }
571 if !value.is_finite() {
572 return format!("{value}");
573 }
574 let scientific = format!("{value:.8e}");
575 let (mantissa, exponent) = scientific.split_once('e').unwrap_or((&scientific, "0"));
576 let exponent: i32 = exponent.parse().unwrap_or(0);
577 let trim = |text: &str| -> String {
578 if text.contains('.') {
579 text.trim_end_matches('0').trim_end_matches('.').to_owned()
580 } else {
581 text.to_owned()
582 }
583 };
584 if !(-4..9).contains(&exponent) {
585 let sign = if exponent < 0 { '-' } else { '+' };
586 return format!("{}e{sign}{:03}", trim(mantissa), exponent.unsigned_abs());
587 }
588 let decimals = usize::try_from(8i32.saturating_sub(exponent)).unwrap_or(0);
589 trim(&format!("{value:.decimals$}"))
590}
591
592fn slots(i: &Instruction, program: &Program) -> (u32, u32) {
596 if i.coissue || i.opcode == Opcode::Nop {
598 return (0, 0);
599 }
600 let pixel = program.version.stage == Stage::Pixel;
601 match i.opcode {
602 Opcode::Tex => (1, 0),
603 Opcode::TexLdl => (2, 0),
604 Opcode::TexLdd => (1, 2),
605 Opcode::SinCos => (0, 8),
606 Opcode::M4x4 | Opcode::M3x4 => (0, 4),
607 Opcode::Nrm | Opcode::Pow | Opcode::M4x3 | Opcode::M3x3 => (0, 3),
608 Opcode::If
609 | Opcode::IfC
610 | Opcode::BreakC
611 | Opcode::BreakP
612 | Opcode::CallNz
613 | Opcode::Rep
614 | Opcode::Loop => (0, 3),
615 Opcode::EndRep
616 | Opcode::EndLoop
617 | Opcode::Call
618 | Opcode::M3x2
619 | Opcode::Crs
620 | Opcode::Dsx
621 | Opcode::Dsy => (0, 2),
622 Opcode::Dp2Add if pixel => (0, 2),
623 Opcode::Label | Opcode::Phase => (0, 0),
624 _ => (0, 1),
625 }
626}
627
628fn footer(out: &mut String, program: &Program) {
630 let (texture, arithmetic) = program.instructions().fold((0u32, 0u32), |(t, a), i| {
631 let (ti, ai) = slots(i, program);
632 (t.saturating_add(ti), a.saturating_add(ai))
633 });
634 let total = texture.saturating_add(arithmetic);
635 let slots = if total == 1 { "slot" } else { "slots" };
636 let _ = write!(out, "// approximately {total} instruction {slots} used");
637 if texture > 0 {
638 let _ = write!(out, " ({texture} texture, {arithmetic} arithmetic)");
639 }
640 out.push('\n');
641}