use std::borrow::Cow;
use scroll::{ctx::TryFromCtx, Endian, Pread};
use crate::common::*;
use crate::msf::Stream;
const PDB_NMT_HDR: u32 = 0xEFFE_EFFE;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum StringTableHashVersion {
LongHash = 1,
LongHashV2 = 2,
}
impl StringTableHashVersion {
fn parse_u32(value: u32) -> Result<Self> {
match value {
1 => Ok(Self::LongHash),
2 => Ok(Self::LongHashV2),
_ => Err(Error::UnimplementedFeature(
"unknown string table hash version",
)),
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
struct StringTableHeader {
magic: u32,
hash_version: u32,
names_size: u32,
}
impl<'t> TryFromCtx<'t, Endian> for StringTableHeader {
type Error = scroll::Error;
fn try_from_ctx(this: &'t [u8], le: Endian) -> scroll::Result<(Self, usize)> {
let mut offset = 0;
let data = Self {
magic: this.gread_with(&mut offset, le)?,
hash_version: this.gread_with(&mut offset, le)?,
names_size: this.gread_with(&mut offset, le)?,
};
Ok((data, offset))
}
}
impl StringTableHeader {
fn names_start(self) -> usize {
std::mem::size_of::<Self>()
}
fn names_end(self) -> usize {
self.names_start() + self.names_size as usize
}
}
#[derive(Debug)]
pub struct StringTable<'s> {
header: StringTableHeader,
#[allow(dead_code)] hash_version: StringTableHashVersion,
stream: Stream<'s>,
}
impl<'s> StringTable<'s> {
pub(crate) fn parse(stream: Stream<'s>) -> Result<Self> {
let mut buf = stream.parse_buffer();
let header = buf.parse::<StringTableHeader>()?;
if header.magic != PDB_NMT_HDR {
return Err(Error::UnimplementedFeature(
"invalid string table signature",
));
}
if buf.len() < header.names_end() {
return Err(Error::UnexpectedEof);
}
let hash_version = StringTableHashVersion::parse_u32(header.hash_version)?;
Ok(StringTable {
header,
hash_version,
stream,
})
}
}
impl<'s> StringTable<'s> {
pub fn get(&self, offset: StringRef) -> Result<RawString<'_>> {
if offset.0 >= self.header.names_size {
return Err(Error::UnexpectedEof);
}
let string_offset = self.header.names_start() + offset.0 as usize;
let data = &self.stream.as_slice()[string_offset..self.header.names_end()];
ParseBuffer::from(data).parse_cstring()
}
}
impl StringRef {
pub fn to_raw_string<'s>(self, strings: &'s StringTable<'_>) -> Result<RawString<'s>> {
strings.get(self)
}
pub fn to_string_lossy<'s>(self, strings: &'s StringTable<'_>) -> Result<Cow<'s, str>> {
strings.get(self).map(|r| r.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem;
#[test]
fn test_string_table_header() {
assert_eq!(mem::size_of::<StringTableHeader>(), 12);
assert_eq!(mem::align_of::<StringTableHeader>(), 4);
}
}