use crate::{Result, error, parts};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ConstantMapping {
pub buffer: u16,
pub start: u16,
pub count: u16,
pub target: u16,
pub conversion: [u8; 4],
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ScalarMapping {
pub buffer: u16,
pub source: u16,
pub component: u16,
pub target: u16,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SamplerMapping {
pub resource: u8,
pub sampler: u8,
pub target: u8,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RuntimeConstant {
pub kind: u16,
pub target: u16,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Level9 {
pub version: u32,
pub program: Vec<u8>,
pub program_offset: u32,
pub constants: Vec<ConstantMapping>,
pub integers: Vec<ScalarMapping>,
pub booleans: Vec<ScalarMapping>,
pub samplers: Vec<SamplerMapping>,
pub runtime: Vec<RuntimeConstant>,
}
struct Part<'a> {
data: &'a [u8],
}
impl Part<'_> {
fn u16(&self, at: usize) -> Result<u16> {
self.data
.get(at..at.saturating_add(2))
.and_then(|b| b.try_into().ok())
.map(u16::from_le_bytes)
.ok_or_else(|| error(at / 4, format!("the Aon9 part ends before byte {at}")))
}
fn u32(&self, at: usize) -> Result<u32> {
self.data
.get(at..at.saturating_add(4))
.and_then(|b| b.try_into().ok())
.map(u32::from_le_bytes)
.ok_or_else(|| error(at / 4, format!("the Aon9 part ends before byte {at}")))
}
fn byte(&self, at: usize) -> Result<u8> {
self.data
.get(at)
.copied()
.ok_or_else(|| error(at / 4, format!("the Aon9 part ends before byte {at}")))
}
fn rows(&self, at: usize, row: usize) -> Result<Vec<usize>> {
let count = usize::from(self.u16(at)?);
let offset = usize::from(self.u16(at.saturating_add(2))?);
let end = count.checked_mul(row).and_then(|n| n.checked_add(offset));
if end.is_none_or(|end| end > self.data.len()) {
return Err(error(
at / 4,
format!(
"a table of {count} rows of {row} bytes at byte {offset} runs past the Aon9 part's {} bytes",
self.data.len()
),
));
}
Ok((0..count)
.map(|n| offset.saturating_add(n.saturating_mul(row)))
.collect())
}
fn scalars(&self, at: usize) -> Result<Vec<ScalarMapping>> {
self.rows(at, 8)?
.into_iter()
.map(|r| {
Ok(ScalarMapping {
buffer: self.u16(r)?,
source: self.u16(r.saturating_add(2))?,
component: self.u16(r.saturating_add(4))?,
target: self.u16(r.saturating_add(6))?,
})
})
.collect()
}
}
pub fn level9(bytes: &[u8]) -> Result<Option<Level9>> {
let directory = parts(bytes)?;
let Some(chunk) = directory.iter().find(|c| &c.kind == b"Aon9") else {
return Ok(None);
};
let part = Part { data: chunk.data };
let size = part.u32(0)?;
if usize::try_from(size).ok() != Some(part.data.len()) {
return Err(error(
0,
format!("the Aon9 part states {size} bytes but has {}", part.data.len()),
));
}
let length = usize::try_from(part.u32(8)?).unwrap_or(usize::MAX);
let program_offset = part.u32(12)?;
let offset = usize::try_from(program_offset).unwrap_or(usize::MAX);
let program = offset
.checked_add(length)
.and_then(|end| part.data.get(offset..end))
.ok_or_else(|| {
error(
2,
format!(
"a program of {length} bytes at byte {offset} runs past the Aon9 part's {} bytes",
part.data.len()
),
)
})?;
let constants = part
.rows(16, 12)?
.into_iter()
.map(|r| {
Ok(ConstantMapping {
buffer: part.u16(r)?,
start: part.u16(r.saturating_add(2))?,
count: part.u16(r.saturating_add(4))?,
target: part.u16(r.saturating_add(6))?,
conversion: [
part.byte(r.saturating_add(8))?,
part.byte(r.saturating_add(9))?,
part.byte(r.saturating_add(10))?,
part.byte(r.saturating_add(11))?,
],
})
})
.collect::<Result<_>>()?;
let samplers = part
.rows(28, 4)?
.into_iter()
.map(|r| {
let fourth = part.byte(r.saturating_add(3))?;
if fourth != 0 {
return Err(error(
r / 4,
format!(
"byte {} of a sampler mapping is {fourth}, where every one seen is 0",
r.saturating_add(3)
),
));
}
Ok(SamplerMapping {
resource: part.byte(r)?,
sampler: part.byte(r.saturating_add(1))?,
target: part.byte(r.saturating_add(2))?,
})
})
.collect::<Result<_>>()?;
let runtime = part
.rows(32, 4)?
.into_iter()
.map(|r| {
Ok(RuntimeConstant {
kind: part.u16(r)?,
target: part.u16(r.saturating_add(2))?,
})
})
.collect::<Result<_>>()?;
Ok(Some(Level9 {
version: part.u32(4)?,
program: program.to_vec(),
program_offset,
constants,
integers: part.scalars(20)?,
booleans: part.scalars(24)?,
samplers,
runtime,
}))
}