use std::collections::BTreeMap;
use super::msf::Stream;
use crate::Result;
const LANES: [char; 4] = ['x', 'y', 'z', 'w'];
fn lane(n: u32) -> char {
LANES
.get(usize::try_from(n).unwrap_or(usize::MAX))
.copied()
.unwrap_or('w')
}
const MODIFIER: u16 = 0x1518;
const ALIAS: u16 = 0x150a;
const CLASS: u16 = 0x1504;
const STRUCTURE: u16 = 0x1505;
const FIELD_LIST: u16 = 0x1203;
const MEMBER: u16 = 0x150d;
const BASE_CLASS: u16 = 0x1400;
const BASE_INTERFACE: u16 = 0x151a;
const STATIC_MEMBER: u16 = 0x150e;
const METHOD: u16 = 0x150f;
const NESTED_TYPE: u16 = 0x1510;
const ONE_METHOD: u16 = 0x1511;
const STRIDED_ARRAY: u16 = 0x1516;
const HLSL: u16 = 0x1517;
const INTERFACE_POINTER: u16 = 0x200;
const VECTOR: u16 = 0x151b;
const MATRIX: u16 = 0x151c;
const STATIC: &str = "could not get UDT child offset in PDB";
const OVERRUN: &str = "offset overruns length for type in PDB";
const VECTOR_MEMBER: &str = "invalid vector member";
#[derive(Debug, Default, PartialEq, Eq)]
pub(crate) struct Path {
pub(crate) text: String,
pub(crate) error: Option<&'static str>,
}
pub(crate) struct Types<'a> {
stream: &'a Stream,
records: BTreeMap<u32, (u16, usize, usize)>,
}
impl<'a> Types<'a> {
pub(crate) fn read(stream: &'a Stream) -> Result<Self> {
let header = stream.size(4)?;
let mut index = stream.u32(8)?;
let end = stream.plus(header, stream.size(16)?)?;
let mut at = header;
let mut records = BTreeMap::new();
while at < end {
let len = usize::from(stream.u16(at)?);
let kind = stream.u16(stream.plus(at, 2)?)?;
let next = stream.plus(at, len.saturating_add(2))?;
if len < 2 || next > end {
return Err(stream.fail(at, format!("a type record of {len} bytes past the records' end")));
}
records.insert(index, (kind, at.saturating_add(4), next));
index = index.saturating_add(1);
at = next;
}
Ok(Self { stream, records })
}
fn numeric(&self, at: usize) -> Result<(u32, usize)> {
let s = self.stream;
match s.u16(at)? {
n if n < 0x8000 => Ok((u32::from(n), s.plus(at, 2)?)),
0x8002 => Ok((u32::from(s.u16(s.plus(at, 2)?)?), s.plus(at, 4)?)),
0x8004 => Ok((s.u32(s.plus(at, 2)?)?, s.plus(at, 6)?)),
leaf => Err(s.fail(
at,
format!("numeric leaf {leaf:#06x}, where 0x8002 and 0x8004 are the ones known"),
)),
}
}
fn underlying(&self, index: u32) -> Result<(u32, Option<(u16, usize)>)> {
let s = self.stream;
let mut index = index;
for _ in 0..64 {
if index < 0x1000 {
return Ok((index, None));
}
let &(kind, at, _) = self
.records
.get(&index)
.ok_or_else(|| s.fail(0, format!("type index {index:#x} has no record")))?;
match kind {
MODIFIER | ALIAS => index = s.u32(at)?,
_ => return Ok((index, Some((kind, at)))),
}
}
Err(s.fail(0, format!("type {index:#x} is modified more than 64 deep")))
}
fn length(&self, index: u32) -> Result<u32> {
let s = self.stream;
let (index, record) = self.underlying(index)?;
let Some((kind, at)) = record else {
return Ok(match index & 0xff {
0x41 | 0x76 | 0x77 => 8,
0x46 | 0x72 | 0x73 => 2,
_ => 4,
});
};
let at = match kind {
STRUCTURE | CLASS => {
return self
.numeric(s.plus(self.defined(index, kind, at)?, 16)?)
.map(|(n, _)| n);
}
VECTOR | HLSL => s.plus(at, 8)?,
STRIDED_ARRAY => s.plus(at, 12)?,
MATRIX => s.plus(at, 17)?,
_ => {
return Err(s.fail(
at,
format!("type {index:#x} of kind {kind:#06x}, which the listing does not name"),
));
}
};
Ok(self.numeric(at)?.0)
}
fn values(&self, index: u32) -> Result<u32> {
let s = self.stream;
let (resolved, record) = self.underlying(index)?;
let Some((kind, at)) = record else {
return Ok(1);
};
Ok(match kind {
VECTOR => s.u32(s.plus(at, 4)?)?,
MATRIX => s.u32(s.plus(at, 4)?)?.saturating_mul(s.u32(s.plus(at, 8)?)?),
STRIDED_ARRAY => {
let stride = s.u32(s.plus(at, 8)?)?;
let count = self
.length(resolved)?
.saturating_add(stride.saturating_sub(1))
.checked_div(stride);
count.unwrap_or(0).saturating_mul(self.values(s.u32(at)?)?)
}
HLSL if s.u16(s.plus(at, 4)?)? == INTERFACE_POINTER => 4,
STRUCTURE | CLASS => {
let list = s.u32(s.plus(self.defined(resolved, kind, at)?, 4)?)?;
let mut total = 0u32;
for ty in self.data_members(list)? {
total = total.saturating_add(self.values(ty)?);
}
total
}
_ => 1,
})
}
pub(crate) fn path(&self, index: u32, offset: u32) -> Result<Path> {
let mut out = Path::default();
if self.values(index)? == 1 {
return Ok(out);
}
let (mut index, mut offset) = (index, offset);
let s = self.stream;
for _ in 0..64 {
if offset >= self.length(index)? {
out.error = Some(OVERRUN);
return Ok(out);
}
let (resolved, record) = self.underlying(index)?;
let Some((kind, at)) = record else {
return Ok(out);
};
match kind {
VECTOR => {
let count = s.u32(s.plus(at, 4)?)?;
let component = self.length(index)?.checked_div(count).unwrap_or(0);
match (offset.checked_div(component), offset.checked_rem(component)) {
(Some(n), Some(0)) => {
out.text.push('.');
out.text.push(lane(n));
}
_ => out.error = Some(VECTOR_MEMBER),
}
return Ok(out);
}
STRUCTURE | CLASS => {
let list = s.u32(s.plus(self.defined(resolved, kind, at)?, 4)?)?;
match self.member(list, offset)? {
Ok((member, ty, start)) => {
if let Some(member) = member {
out.text.push('.');
out.text.push_str(&member);
}
index = ty;
offset = offset.saturating_sub(start);
}
Err(error) => {
out.error = Some(error);
return Ok(out);
}
}
}
MATRIX => {
let stride = s.u32(s.plus(at, 12)?)?;
let row_major = s.u8(s.plus(at, 16)?)? & 1 != 0;
let major = offset.checked_div(stride).unwrap_or(0);
let minor = offset.checked_rem(stride).unwrap_or(0) / 4;
let (row, column) = if row_major { (major, minor) } else { (minor, major) };
out.text.push_str(&format!("._m{row}{column}"));
return Ok(out);
}
HLSL => {
match s.u16(s.plus(at, 4)?)? {
INTERFACE_POINTER => {
out.text.push('.');
out.text.push(lane(offset / 4));
}
other => out
.text
.push_str(&format!("<unknown HLSL built-in {other} offset {offset}>")),
}
return Ok(out);
}
STRIDED_ARRAY => {
let element = s.u32(at)?;
let stride = s.u32(s.plus(at, 8)?)?;
out.text
.push_str(&format!("[{}]", offset.checked_div(stride).unwrap_or(0)));
offset = offset.checked_rem(stride).unwrap_or(0);
index = element;
}
_ => {
return Err(s.fail(
at,
format!("type {resolved:#x} of kind {kind:#06x}, which the listing does not name"),
));
}
}
}
Err(s.fail(0, format!("type {index:#x} nests more than 64 deep")))
}
fn defined(&self, index: u32, kind: u16, at: usize) -> Result<usize> {
let s = self.stream;
if s.u16(s.plus(at, 2)?)? & 0x80 == 0 {
return Ok(at);
}
let name = |at: usize| -> Result<String> {
let (_, name_at) = self.numeric(s.plus(at, 16)?)?;
Ok(s.string(name_at)?.0)
};
let wanted = name(at)?;
for &(other, data, _) in self.records.values() {
if other == kind && s.u16(s.plus(data, 2)?)? & 0x80 == 0 && name(data)? == wanted {
return Ok(data);
}
}
Err(s.fail(
at,
format!("type {index:#x} is a forward reference to {wanted}, which no record defines"),
))
}
fn data_members(&self, list: u32) -> Result<Vec<u32>> {
let s = self.stream;
let &(kind, mut at, end) = self
.records
.get(&list)
.ok_or_else(|| s.fail(0, format!("field list {list:#x} has no record")))?;
if kind != FIELD_LIST {
return Err(s.fail(
at,
format!("type {list:#x} is of kind {kind:#06x}, where a field list is 0x1203"),
));
}
let mut out = Vec::new();
while at < end {
let leaf = s.u16(at)?;
let mut next = self.member_end(at, leaf)?;
if matches!(leaf, MEMBER | BASE_CLASS) {
out.push(s.u32(s.plus(at, 4)?)?);
}
while next < end && s.u8(next)? >= 0xf0 {
next = next.saturating_add(1);
}
at = next;
}
Ok(out)
}
fn member_end(&self, at: usize, leaf: u16) -> Result<usize> {
let s = self.stream;
match leaf {
MEMBER => s.string(self.numeric(s.plus(at, 8)?)?.1).map(|(_, after)| after),
BASE_CLASS | BASE_INTERFACE => self.numeric(s.plus(at, 8)?).map(|(_, after)| after),
STATIC_MEMBER | METHOD | NESTED_TYPE => Ok(s.string(s.plus(at, 8)?)?.1),
ONE_METHOD => {
let virtual_offset = matches!((s.u16(s.plus(at, 2)?)? >> 2) & 7, 4 | 6);
Ok(s.string(s.plus(at, if virtual_offset { 12 } else { 8 })?)?.1)
}
_ => Err(s.fail(
at,
format!(
"field list member of kind {leaf:#06x}, where 0x1400, 0x150d to 0x1511 and 0x151a are the ones \
known"
),
)),
}
}
#[allow(clippy::type_complexity)]
fn member(
&self,
list: u32,
offset: u32,
) -> Result<std::result::Result<(Option<String>, u32, u32), &'static str>> {
let s = self.stream;
let &(kind, mut at, end) = self
.records
.get(&list)
.ok_or_else(|| s.fail(0, format!("field list {list:#x} has no record")))?;
if kind != FIELD_LIST {
return Err(s.fail(
at,
format!("type {list:#x} is of kind {kind:#06x}, where a field list is 0x1203"),
));
}
let mut before = None;
while at < end {
let leaf = s.u16(at)?;
let mut next = self.member_end(at, leaf)?;
match leaf {
MEMBER | BASE_CLASS => {
let ty = s.u32(s.plus(at, 4)?)?;
let (start, after) = self.numeric(s.plus(at, 8)?)?;
let name = match leaf {
MEMBER => Some(s.string(after)?.0),
_ => None,
};
if let Some(into) = offset.checked_sub(start) {
if into < self.length(ty)? {
return Ok(Ok((name, ty, start)));
}
before = Some((name, ty, start));
}
}
STATIC_MEMBER => return Ok(Err(STATIC)),
_ => {}
}
while next < end && s.u8(next)? >= 0xf0 {
next = next.saturating_add(1);
}
at = next;
}
before.map(Ok).ok_or_else(|| {
s.fail(
at,
format!("field list {list:#x} has no member at offset {offset}"),
)
})
}
}