#![deny(missing_docs)]
#![deny(clippy::disallowed_methods, clippy::disallowed_types)]
#![deny(
clippy::arithmetic_side_effects,
clippy::indexing_slicing,
clippy::cast_possible_truncation
)]
use primase_foundations::bytes::View;
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
#[error("DXBC container at byte {offset}: {detail}")]
pub struct Error {
pub offset: usize,
pub detail: String,
}
fn error(offset: usize, detail: impl Into<String>) -> Error {
Error {
offset,
detail: detail.into(),
}
}
#[derive(Clone, Copy, Debug)]
pub struct Chunk<'a> {
pub kind: [u8; 4],
pub data: &'a [u8],
}
pub fn chunks(bytes: &[u8]) -> Result<Vec<Chunk<'_>>, Error> {
let view = View::new(bytes);
let word = |at: usize| -> Result<usize, Error> {
let word = view.array::<4>(at).map_err(|e| error(at, e.to_string()))?;
usize::try_from(u32::from_le_bytes(word)).map_err(|_| error(at, "a word past the address space"))
};
if bytes.get(..4) != Some(b"DXBC") {
return Err(error(0, "the magic is not DXBC"));
}
if word(20)? != 1 {
return Err(error(20, "the container's version is not 1"));
}
if word(24)? != bytes.len() {
return Err(error(
24,
format!(
"the container states a size of {}, it has {} bytes",
word(24)?,
bytes.len()
),
));
}
let count = word(28)?;
let directory_end = count
.checked_mul(4)
.and_then(|n| n.checked_add(32))
.filter(|end| *end <= bytes.len())
.ok_or_else(|| {
error(
28,
format!("a directory of {count} parts does not fit the container"),
)
})?;
let mut parts: Vec<Chunk<'_>> = Vec::new();
let mut next = directory_end;
for i in 0..count {
let entry = i
.checked_mul(4)
.and_then(|n| n.checked_add(32))
.ok_or_else(|| error(28, "a directory past the address space"))?;
let start = word(entry)?;
if start != next {
let shown = view.array::<4>(start).map_or_else(
|_| "past the end".to_owned(),
|kind| String::from_utf8_lossy(&kind).into_owned(),
);
return Err(error(
entry,
if start < next {
format!(
"overlapping parts: part {i} ({shown}) starts at {start}, before the previous part ends at {next}"
)
} else {
format!(
"{} bytes between parts: part {i} ({shown}) starts at {start}, where the previous part ends at {next}",
start.saturating_sub(next)
)
},
));
}
let kind = view.array::<4>(start).map_err(|e| error(start, e.to_string()))?;
let shown = String::from_utf8_lossy(&kind).into_owned();
let length = word(
start
.checked_add(4)
.ok_or_else(|| error(start, "a part past the address space"))?,
)?;
let data_at = start
.checked_add(8)
.ok_or_else(|| error(start, "a part past the address space"))?;
let data = view
.slice(data_at, length)
.map_err(|e| error(start, format!("part {i} ({shown}): {e}")))?;
if parts.iter().any(|p| p.kind == kind) {
return Err(error(start, format!("a part kind twice: {shown}")));
}
next = data_at
.checked_add(length)
.ok_or_else(|| error(start, "a part past the address space"))?;
parts.push(Chunk { kind, data });
}
if next != bytes.len() {
return Err(error(
next,
format!("{} bytes after the last part", bytes.len().saturating_sub(next)),
));
}
Ok(parts)
}
pub fn checksum(container: &[u8]) -> Option<[u8; 16]> {
let data = container.get(20..)?;
let bits = u32::try_from(data.len()).ok()?.wrapping_mul(8);
let (blocks, rest) = data.as_chunks::<64>();
let mut state = primase_foundations::hash::MD5_INIT;
for block in blocks {
primase_foundations::hash::md5_block(&mut state, block);
}
let mut last = [0u8; 64];
if rest.len() < 56 {
last.get_mut(4..)?.get_mut(..rest.len())?.copy_from_slice(rest);
*last.get_mut(rest.len().checked_add(4)?)? = 0x80;
} else {
let mut first = [0u8; 64];
first.get_mut(..rest.len())?.copy_from_slice(rest);
*first.get_mut(rest.len())? = 0x80;
primase_foundations::hash::md5_block(&mut state, &first);
}
last.get_mut(..4)?.copy_from_slice(&bits.to_le_bytes());
last.get_mut(60..)?
.copy_from_slice(&((bits >> 2) | 1).to_le_bytes());
primase_foundations::hash::md5_block(&mut state, &last);
Some(primase_foundations::hash::md5_bytes(state))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Bytecode {
Dxbc,
Dxil,
}
impl Bytecode {
pub fn of(parts: &[Chunk<'_>]) -> Option<Self> {
let has = |kinds: &[&[u8; 4]]| parts.iter().any(|p| kinds.contains(&&p.kind));
match (has(&[b"SHDR", b"SHEX"]), has(&[b"DXIL"])) {
(true, false) => Some(Self::Dxbc),
(false, true) => Some(Self::Dxil),
_ => None,
}
}
}
pub fn feature_name(bytecode: Bytecode, bit: u32) -> Option<&'static str> {
let shared = match bit {
0 => "Double-precision floating point",
2 => "UAVs at every shader stage",
3 => "64 UAV slots",
4 => "Minimum-precision data types",
5 => "Double-precision extensions for 11.1",
6 => "Shader extensions for 11.1",
8 => "Tiled resources",
9 => "PS Output Stencil Ref",
10 => "PS Inner Coverage",
11 => "Typed UAV Load Additional Formats",
12 => "Raster Ordered UAVs",
13 => "SV_RenderTargetArrayIndex or SV_ViewportArrayIndex from any shader feeding rasterizer",
_ => "",
};
if !shared.is_empty() {
return Some(shared);
}
match bytecode {
Bytecode::Dxbc => match bit {
1 => Some("Raw and Structured buffers"),
7 => Some("Comparison filtering for feature level 9"),
_ => None,
},
Bytecode::Dxil => match bit {
14 => Some("Wave level operations"),
15 => Some("64-Bit integer"),
16 => Some("View Instancing"),
17 => Some("Barycentrics"),
18 => Some("Use native low precision"),
19 => Some("Shading Rate"),
20 => Some("Raytracing tier 1.1 features"),
22 => Some("64-bit Atomics on Typed Resources"),
23 => Some("64-bit Atomics on Group Shared"),
24 => Some("Derivatives in mesh and amplification shaders"),
25 => Some("Resource descriptor heap indexing"),
26 => Some("Sampler descriptor heap indexing"),
28 => Some("64-bit Atomics on Heap Resources"),
29 => Some("Advanced Texture Ops"),
30 => Some("Writeable MSAA Textures"),
_ => None,
},
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FeatureFlags {
pub bytecode: Bytecode,
pub bits: u64,
}
impl FeatureFlags {
pub fn has(self, bit: u32) -> bool {
self.bits.checked_shr(bit).is_some_and(|rest| rest & 1 == 1)
}
pub fn names(self) -> Vec<&'static str> {
(0..u64::BITS)
.filter(|&b| self.has(b))
.filter_map(|b| feature_name(self.bytecode, b))
.collect()
}
pub fn unnamed(self) -> u64 {
(0..u64::BITS)
.filter(|&b| self.has(b) && feature_name(self.bytecode, b).is_none())
.fold(0, |mask, b| mask | 1u64.checked_shl(b).unwrap_or(0))
}
}
pub fn feature_flags(container: &[u8]) -> Result<Option<FeatureFlags>, Error> {
let parts = chunks(container)?;
let Some(part) = parts.iter().find(|p| &p.kind == b"SFI0") else {
return Ok(None);
};
let at = part
.data
.as_ptr()
.addr()
.saturating_sub(container.as_ptr().addr());
let bits = <[u8; 8]>::try_from(part.data)
.map_err(|_| error(at, format!("the SFI0 part has {} bytes, not 8", part.data.len())))?;
let bytecode = Bytecode::of(&parts).ok_or_else(|| {
error(
at,
"an SFI0 part in a container with no single kind of program part",
)
})?;
Ok(Some(FeatureFlags {
bytecode,
bits: u64::from_le_bytes(bits),
}))
}