use crate::reflection::{rdef, signature};
use crate::{Result, chunk, code, decode, error, parts, statistics};
mod header;
mod level9;
mod number;
mod program;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Options {
pub header: bool,
pub numbering: bool,
pub offsets: bool,
pub hex_literals: bool,
pub debug_info: bool,
}
impl Default for Options {
fn default() -> Self {
Self {
header: true,
numbering: false,
offsets: false,
hex_literals: false,
debug_info: true,
}
}
}
impl Options {
pub fn from_flags(flags: u32) -> Result<Self> {
for (bit, name) in [
(0x01, "D3D_DISASM_ENABLE_COLOR_CODE (an HTML listing)"),
(
0x08,
"D3D_DISASM_ENABLE_INSTRUCTION_CYCLE, which Microsoft's disassembler refuses too",
),
] {
if flags & bit != 0 {
return Err(error(0, format!("flag 0x{bit:02x}, {name}, is not written")));
}
}
let unknown = flags & !0xf6;
if unknown != 0 {
return Err(error(
0,
format!(
"flag bit 0x{:x} is not a D3D_DISASM flag",
1u32 << unknown.trailing_zeros()
),
));
}
Ok(Self {
header: flags & 0x40 == 0,
numbering: flags & 0x04 != 0,
offsets: flags & 0x20 != 0,
hex_literals: flags & 0x80 != 0,
debug_info: flags & 0x10 == 0,
})
}
}
pub fn disassemble(container: &[u8]) -> Result<String> {
disassemble_with(container, &Options::default())
}
pub fn disassemble_with(container: &[u8], options: &Options) -> Result<String> {
let directory = parts(container)?;
let program = decode(&code(container)?)?;
let reflection = rdef(container)?;
let read = |tags: &[&[u8; 4]]| -> Result<Option<(Vec<crate::SignatureElement>, bool)>> {
match tags.iter().find(|t| directory.iter().any(|c| &&c.kind == *t)) {
Some(tag) => {
let elements = signature(chunk(&directory, tag)?, tag)?;
let streams = elements.iter().any(|e| e.stream != 0);
Ok(Some((elements, streams)))
}
None => Ok(None),
}
};
let inputs = read(&[b"ISGN", b"ISG1"])?;
let outputs = read(&[b"OSGN", b"OSG1", b"OSG5"])?;
let patch = read(&[b"PCSG", b"PSG1"])?;
let stat = statistics(container)?;
let mut out = String::new();
if options.header {
header::write(
&mut out,
&header::Header {
program: &program,
rdef: reflection.as_ref(),
features: dxcontainer::feature_flags(container)
.map_err(|e| error(e.offset / 4, e.detail))?
.map_or(0, |f| f.bits),
stat: stat.as_ref(),
inputs: elements(&inputs),
outputs: elements(&outputs),
patch: elements(&patch),
interfaces: crate::interfaces(container)?.as_ref(),
debug_layer: dxcontainer::checksum(container).as_ref().map(|d| &d[..])
!= container.get(4..20),
},
);
if let Some(part) = crate::level9(container)? {
level9::write(&mut out, &part, options)?;
}
}
let annotations = match directory.iter().any(|c| &c.kind == b"SPDB") && options.debug_info {
true => Some(crate::spdb::annotations(chunk(&directory, b"SPDB")?)?),
false => None,
};
program::write(&mut out, &program, options, annotations.as_ref());
if !options.header {
return Ok(out);
}
let slots = stat.and_then(|s| s.instructions()).unwrap_or(0);
out.push_str(&format!("// Approximately {slots} instruction slots used\n"));
Ok(out)
}
fn elements(part: &Option<(Vec<crate::SignatureElement>, bool)>) -> Option<header::Elements<'_>> {
part.as_ref().map(|(elements, streams)| header::Elements {
elements,
streams: *streams,
})
}