use crate::decode::ProgramType;
use crate::{Result, chunk, error, parts};
const MAX_RECORDS: usize = 65536;
const MAX_NAME: usize = 4096;
const MAX_BUFFER_SIZE: usize = 65536;
struct View<'a>(&'a [u8]);
impl View<'_> {
fn bytes(&self, at: usize, len: usize) -> Result<&[u8]> {
let end = at.checked_add(len).ok_or_else(|| {
error(
at / 4,
format!("{len} bytes at byte {at} overflow the address space"),
)
})?;
self.0.get(at..end).ok_or_else(|| {
error(
at / 4,
format!(
"{len} bytes at byte {at} exceed the part's {} bytes",
self.0.len()
),
)
})
}
fn array<const N: usize>(&self, at: usize) -> Result<[u8; N]> {
self.bytes(at, N)?
.try_into()
.map_err(|_| error(at / 4, format!("{N} bytes at byte {at}")))
}
fn u32(&self, at: usize) -> Result<u32> {
Ok(u32::from_le_bytes(self.array(at)?))
}
fn u16(&self, at: usize) -> Result<u16> {
Ok(u16::from_le_bytes(self.array(at)?))
}
fn plus(&self, at: usize, delta: usize) -> Result<usize> {
at.checked_add(delta).ok_or_else(|| {
error(
at / 4,
format!("byte {at} plus {delta} overflows the address space"),
)
})
}
fn field(&self, at: usize, delta: usize) -> Result<u32> {
self.u32(self.plus(at, delta)?)
}
fn field16(&self, at: usize, delta: usize) -> Result<u16> {
self.u16(self.plus(at, delta)?)
}
fn record(&self, at: usize, i: usize, stride: usize) -> Result<usize> {
i.checked_mul(stride)
.and_then(|n| at.checked_add(n))
.ok_or_else(|| {
error(
at / 4,
format!("record {i} of {stride} bytes at byte {at} overflows"),
)
})
}
fn string(&self, at: usize) -> Result<String> {
let tail = self.0.get(at..).ok_or_else(|| {
error(
at / 4,
format!("a string at byte {at}, past the part's {} bytes", self.0.len()),
)
})?;
let end = tail.iter().position(|&b| b == 0).ok_or_else(|| {
error(
at / 4,
format!("the string at byte {at} has no terminating zero in the part"),
)
})?;
if end > MAX_NAME {
return Err(error(
at / 4,
format!("the string at byte {at} is {end} bytes, more than the {MAX_NAME} allowed"),
));
}
let text = tail
.get(..end)
.ok_or_else(|| error(at / 4, format!("the string at byte {at}")))?;
std::str::from_utf8(text)
.map(str::to_owned)
.map_err(|_| error(at / 4, format!("the string at byte {at} is not UTF-8")))
}
fn table(&self, at: usize, count: usize, stride: usize) -> Result<std::ops::Range<usize>> {
if count > MAX_RECORDS {
return Err(error(
at / 4,
format!("a table of {count} records at byte {at}, more than the {MAX_RECORDS} allowed"),
));
}
self.bytes(
at,
count.checked_mul(stride).ok_or_else(|| {
error(
at / 4,
format!("a table of {count} records of {stride} bytes overflows"),
)
})?,
)?;
Ok(0..count)
}
fn ty(&self, at: usize, stack: &mut Vec<usize>, budget: &mut usize) -> Result<RdefType> {
if stack.len() >= 32 || stack.contains(&at) || *budget == 0 {
return Err(error(
at / 4,
"the type at this word is part of a cycle, nests deeper than 32 or is one of more than 65536 types",
));
}
*budget = budget.saturating_sub(1);
self.bytes(at, 36)?;
stack.push(at);
let count = usize::from(self.field16(at, 10)?);
let offset = self.field(at, 12)? as usize;
let mut members = Vec::new();
for i in self.table(offset, count, 12)? {
let p = self.record(offset, i, 12)?;
members.push(RdefMember {
name: self.string(self.u32(p)? as usize)?,
offset: self.field(p, 8)?,
ty: self.ty(self.field(p, 4)? as usize, stack, budget)?,
});
}
stack.pop();
Ok(RdefType {
word: at / 4,
class: self.u16(at)?,
scalar: self.field16(at, 2)?,
rows: self.field16(at, 4)?,
columns: self.field16(at, 6)?,
elements: self.field16(at, 8)?,
members,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignatureElement {
pub name: String,
pub index: u32,
pub system_value: u32,
pub component_type: u32,
pub register: u32,
pub mask: u8,
pub read_write_mask: u8,
pub at: usize,
}
pub fn signature(bytes: &[u8], kind: &[u8; 4]) -> Result<Vec<SignatureElement>> {
let v = View(bytes);
let count = v.u32(0)? as usize;
let directory = v.u32(4)?;
if directory != 8 {
return Err(error(
1,
format!("expected the record directory at byte 8, found byte {directory}"),
));
}
let (skip, record) = match kind {
b"ISG1" | b"OSG1" => (4, 32),
b"OSG5" => (4, 28),
_ => (0, 24),
};
let mut result = Vec::new();
for i in v.table(8, count, record)? {
let p = v.record(8, i, record)?;
let q = v.plus(p, skip)?;
let [mask, read_write_mask, _, _] = v.array::<4>(v.plus(q, 20)?)?;
result.push(SignatureElement {
name: v.string(v.u32(q)? as usize)?,
index: v.field(q, 4)?,
system_value: v.field(q, 8)?,
component_type: v.field(q, 12)?,
register: v.field(q, 16)?,
mask,
read_write_mask,
at: p / 4,
});
}
Ok(result)
}
pub fn patch_signature_elements(bytes: &[u8]) -> Result<Vec<SignatureElement>> {
let directory = parts(bytes)?;
signature(chunk(&directory, b"PCSG")?, b"PCSG")
}
fn signature_part<'a>(
directory: &[dxcontainer::Chunk<'a>],
tags: &[&[u8; 4]],
) -> Result<(&'a [u8], [u8; 4])> {
let tag = tags
.iter()
.find(|t| directory.iter().any(|c| &&c.kind == *t))
.or(tags.first())
.ok_or_else(|| error(0, "no signature part kinds to look for"))?;
Ok((chunk(directory, tag)?, **tag))
}
pub fn signature_elements(
bytes: &[u8],
) -> Result<(ProgramType, Vec<SignatureElement>, Vec<SignatureElement>)> {
let directory = parts(bytes)?;
let kind = program_type(&directory)?;
let read = |tags: &[&[u8; 4]]| -> Result<Vec<SignatureElement>> {
let (data, tag) = signature_part(&directory, tags)?;
signature(data, &tag)
};
Ok((
kind,
read(&[b"ISGN", b"ISG1"])?,
read(&[b"OSGN", b"OSG1", b"OSG5"])?,
))
}
pub fn program_type(directory: &[dxcontainer::Chunk<'_>]) -> Result<ProgramType> {
let code = directory
.iter()
.find(|c| matches!(&c.kind, b"SHDR" | b"SHEX"))
.ok_or_else(|| error(0, "the container has no SHDR or SHEX part"))?;
let token = View(code.data).u32(0)?;
match token >> 16 {
0 => Ok(ProgramType::Pixel),
1 => Ok(ProgramType::Vertex),
2 => Ok(ProgramType::Geometry),
3 => Ok(ProgramType::Hull),
4 => Ok(ProgramType::Domain),
5 => Ok(ProgramType::Compute),
kind => Err(error(
0,
format!("expected a program type from 0 (pixel) to 5 (compute), found {kind}"),
)),
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Rdef {
pub target: u32,
pub bindings: Vec<RdefBinding>,
pub buffers: Vec<RdefBuffer>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RdefBinding {
pub word: usize,
pub name: String,
pub kind: u32,
pub returns: u32,
pub dimension: u32,
pub samples: u32,
pub register: u32,
pub count: u32,
pub flags: u32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RdefBuffer {
pub word: usize,
pub name: String,
pub kind: u32,
pub size: u32,
pub variables: Vec<RdefVariable>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RdefVariable {
pub word: usize,
pub name: String,
pub offset: u32,
pub size: u32,
pub ty: RdefType,
pub default: Option<Vec<u8>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RdefType {
pub word: usize,
pub class: u16,
pub scalar: u16,
pub rows: u16,
pub columns: u16,
pub elements: u16,
pub members: Vec<RdefMember>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RdefMember {
pub name: String,
pub offset: u32,
pub ty: RdefType,
}
pub fn rdef_target(bytes: &[u8]) -> Result<Option<u32>> {
let directory = parts(bytes)?;
directory
.iter()
.find(|c| &c.kind == b"RDEF")
.map(|rdef| View(rdef.data).u32(16))
.transpose()
}
pub fn rdef(bytes: &[u8]) -> Result<Option<Rdef>> {
let directory = parts(bytes)?;
if !directory.iter().any(|c| &c.kind == b"RDEF") {
return Ok(None);
}
let v = View(chunk(&directory, b"RDEF")?);
let target = v.u32(16)?;
let tag = v.bytes(28, 4)?;
if tag != b"RD11" {
return Err(error(
7,
format!(
"expected the `RD11` tag of the Shader Model 5 RDEF layout, found {:?}",
String::from_utf8_lossy(tag)
),
));
}
for (i, size) in [60, 24, 32, 40, 36, 12].into_iter().enumerate() {
let at = v.record(32, i, 4)?;
let found = v.u32(at)?;
if found != size {
return Err(error(
at / 4,
format!("expected record size {size} (entry {i} of the RD11 size directory), found {found}"),
));
}
}
let mut bindings = Vec::new();
let offset = v.field(0, 12)? as usize;
for i in v.table(offset, v.field(0, 8)? as usize, 32)? {
let p = v.record(offset, i, 32)?;
bindings.push(RdefBinding {
word: p / 4,
name: v.string(v.u32(p)? as usize)?,
kind: v.field(p, 4)?,
returns: v.field(p, 8)?,
dimension: v.field(p, 12)?,
samples: v.field(p, 16)?,
register: v.field(p, 20)?,
count: v.field(p, 24)?,
flags: v.field(p, 28)?,
});
}
let mut buffers = Vec::new();
let offset = v.field(0, 4)? as usize;
let mut budget = MAX_RECORDS;
for i in v.table(offset, v.u32(0)? as usize, 24)? {
let p = v.record(offset, i, 24)?;
let name = v.string(v.u32(p)? as usize)?;
let kind = v.field(p, 20)?;
let size = v.field(p, 12)?;
if size as usize > MAX_BUFFER_SIZE {
return Err(error(
v.plus(p, 12)? / 4,
format!("constant buffer of {size} bytes, more than the {MAX_BUFFER_SIZE} allowed"),
));
}
let mut variables = Vec::new();
let entries = v.field(p, 8)? as usize;
for j in v.table(entries, v.field(p, 4)? as usize, 40)? {
let q = v.record(entries, j, 40)?;
let start = v.field(q, 4)?;
let len = v.field(q, 8)?;
if start.checked_add(len).is_none_or(|end| end > size) {
return Err(error(
v.plus(q, 4)? / 4,
format!(
"variable at byte {start} of {len} bytes lies outside its {size} byte constant buffer"
),
));
}
let default = v.field(q, 20)? as usize;
let ty = v.ty(v.field(q, 16)? as usize, &mut Vec::new(), &mut budget)?;
variables.push(RdefVariable {
word: q / 4,
name: v.string(v.u32(q)? as usize)?,
offset: start,
size: len,
ty,
default: if default == 0 {
None
} else {
Some(v.bytes(default, len as usize)?.to_vec())
},
});
}
buffers.push(RdefBuffer {
word: p / 4,
name,
kind,
size,
variables,
});
}
Ok(Some(Rdef {
target,
bindings,
buffers,
}))
}