use crate::{Result, error};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Type {
pub class: u16,
pub kind: u16,
pub rows: u16,
pub columns: u16,
pub elements: u16,
pub members: Vec<TypeMember>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TypeMember {
pub name: String,
pub ty: Type,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Constant {
pub word: usize,
pub name: String,
pub register_set: u16,
pub register: u16,
pub count: u16,
pub reserved: u16,
pub ty: Type,
pub default: Option<Vec<u8>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ConstantTable {
pub creator: String,
pub target: String,
pub version: u32,
pub flags: u32,
pub constants: Vec<Constant>,
}
pub(crate) struct Reader<'a> {
pub(crate) data: &'a [u8],
pub(crate) word: usize,
pub(crate) tag: &'static str,
}
impl Reader<'_> {
pub(crate) fn word_of(&self, at: usize) -> usize {
self.word.saturating_add(at / 4)
}
pub(crate) fn plus(&self, at: usize, by: usize) -> Result<usize> {
at.checked_add(by).ok_or_else(|| {
error(
self.word_of(at),
format!("{} offset {at}+{by} is past the largest offset", self.tag),
)
})
}
pub(crate) fn bytes(&self, at: usize, count: usize, size: usize) -> Result<usize> {
count.checked_mul(size).ok_or_else(|| {
error(
self.word_of(at),
format!("{} {count} entries of {size} bytes do not fit", self.tag),
)
})
}
pub(crate) fn range(&self, at: usize, len: usize) -> Result<&[u8]> {
at.checked_add(len)
.and_then(|end| self.data.get(at..end))
.ok_or_else(|| {
error(
self.word_of(at),
format!(
"{} range {at}+{len} outside the comment's {} bytes",
self.tag,
self.data.len()
),
)
})
}
pub(crate) fn u16(&self, at: usize) -> Result<u16> {
match self.range(at, 2)? {
&[a, b] => Ok(u16::from_le_bytes([a, b])),
_ => Err(error(
self.word_of(at),
format!("{} u16 outside comment", self.tag),
)),
}
}
pub(crate) fn u32(&self, at: usize) -> Result<u32> {
match self.range(at, 4)? {
&[a, b, c, d] => Ok(u32::from_le_bytes([a, b, c, d])),
_ => Err(error(
self.word_of(at),
format!("{} u32 outside comment", self.tag),
)),
}
}
pub(crate) fn offset(&self, at: usize) -> Result<usize> {
let offset = self.u32(at)?;
usize::try_from(offset).map_err(|_| {
error(
self.word_of(at),
format!("{} offset {offset} does not fit an address", self.tag),
)
})
}
pub(crate) fn string(&self, at: usize) -> Result<String> {
let bytes = self.range(at, self.data.len().saturating_sub(at))?;
let end = bytes
.iter()
.position(|&b| b == 0)
.ok_or_else(|| error(self.word_of(at), format!("{} string has no NUL", self.tag)))?;
let text = bytes.get(..end).unwrap_or_default();
std::str::from_utf8(text)
.map(str::to_owned)
.map_err(|_| error(self.word_of(at), format!("{} string is not UTF-8", self.tag)))
}
pub(crate) fn ty(&self, at: usize, depth: usize, budget: &mut usize) -> Result<Type> {
if depth > 32 || *budget == 0 {
return Err(error(
self.word_of(at),
format!("{} type nesting/cycle or 4096-node limit", self.tag),
));
}
*budget = budget.saturating_sub(1);
self.range(at, 16)?;
let count = usize::from(self.u16(self.plus(at, 10)?)?);
let members_at = self.offset(self.plus(at, 12)?)?;
self.range(members_at, self.bytes(members_at, count, 8)?)?;
let mut members = Vec::new();
for n in 0..count {
let member = self.plus(members_at, self.bytes(members_at, n, 8)?)?;
members.push(TypeMember {
name: self.string(self.offset(member)?)?,
ty: self.ty(
self.offset(self.plus(member, 4)?)?,
depth.saturating_add(1),
budget,
)?,
});
}
Ok(Type {
class: self.u16(at)?,
kind: self.u16(self.plus(at, 2)?)?,
rows: self.u16(self.plus(at, 4)?)?,
columns: self.u16(self.plus(at, 6)?)?,
elements: self.u16(self.plus(at, 8)?)?,
members,
})
}
}
pub(crate) fn read(data: &[u8], word: usize, expected_version: u32) -> Result<ConstantTable> {
let r = Reader {
data,
word,
tag: "CTAB",
};
if r.u32(0)? != 28 {
return Err(error(word, "CTAB header size is not 28"));
}
let version = r.u32(8)?;
if version != expected_version {
return Err(error(
r.word_of(8),
format!(
"CTAB version disagrees with the program's: {version:#010x}, where the program's version token is {expected_version:#010x}"
),
));
}
let count = r.offset(12)?;
if count > 1024 {
return Err(error(
r.word_of(12),
format!("CTAB has {count} constants, where it has at most 1024"),
));
}
let base = r.offset(16)?;
r.range(base, r.bytes(base, count, 20)?)?;
let mut constants: Vec<Constant> = Vec::new();
let mut budget = 4096;
for n in 0..count {
let at = r.plus(base, r.bytes(base, n, 20)?)?;
let name = r.string(r.offset(at)?)?;
let register_set = r.u16(r.plus(at, 4)?)?;
let register = r.u16(r.plus(at, 6)?)?;
let count = r.u16(r.plus(at, 8)?)?;
if register_set > 3 {
return Err(error(
r.word_of(at),
format!(
"CTAB register set {register_set}, where it is 0 (bool), 1 (int4), 2 (float4) or 3 (sampler)"
),
));
}
if count == 0 {
return Err(error(
r.word_of(at),
format!("CTAB register range c{register}+{count} is empty"),
));
}
if let Some(c) = constants.iter().find(|c| {
c.register_set == register_set
&& (c.name == name
|| (u32::from(c.register) < u32::from(register).saturating_add(u32::from(count))
&& register < c.register.saturating_add(c.count)))
}) {
let detail = if c.name == name {
format!("CTAB constant {name} appears twice in register set {register_set}")
} else {
format!(
"CTAB constant {name} overlaps {} in register set {register_set}",
c.name
)
};
return Err(error(r.word_of(at), detail));
}
let default_at = r.offset(r.plus(at, 16)?)?;
let default = if default_at == 0 {
None
} else {
if register_set == 3 {
return Err(error(r.word_of(at), "CTAB sampler default"));
}
let row = if register_set == 0 { 4 } else { 16 };
Some(
r.range(default_at, r.bytes(default_at, usize::from(count), row)?)?
.to_vec(),
)
};
constants.push(Constant {
word: r.word_of(at),
name,
register_set,
register,
count,
reserved: r.u16(r.plus(at, 10)?)?,
ty: r.ty(r.offset(r.plus(at, 12)?)?, 0, &mut budget)?,
default,
});
}
Ok(ConstantTable {
creator: r.string(r.offset(4)?)?,
version,
flags: r.u32(20)?,
target: r.string(r.offset(24)?)?,
constants,
})
}