use alloc::vec::Vec;
use uuid::Uuid;
use super::PeFile;
use crate::error::Result;
use crate::lazy::{Lazy, cached};
use crate::net::{
BlobHeap, GuidHeap, MetadataHeader, MetadataTablesHeader, StringsHeap, UserStringsHeap,
com_type_lib_id, module_version_ids, type_ref_hash,
};
#[derive(Debug, Default)]
pub(super) struct DotNetCache<'a> {
metadata_header: Lazy<Result<Option<MetadataHeader<'a>>>>,
tables_header: Lazy<Result<Option<MetadataTablesHeader<'a>>>>,
type_ref_hash: Lazy<Result<Option<[u8; 32]>>>,
module_version_ids: Lazy<Result<Option<Vec<Uuid>>>>,
com_type_lib_id: Lazy<Result<Option<Uuid>>>,
}
impl<'a> PeFile<'a> {
pub fn is_dot_net(&self) -> bool {
matches!(self.com_descriptor(), Ok(Some(_)))
}
pub fn metadata_header(&self) -> Result<Option<&MetadataHeader<'a>>> {
cached(&self.cache.dot_net.metadata_header, || {
let Some(com_descriptor) = self.com_descriptor()? else {
return Ok(None);
};
let rva = com_descriptor.meta_data().virtual_address();
if rva.get() == 0 {
return Ok(None);
}
let offset = self.section_headers()?.rva_to_offset(rva)?;
MetadataHeader::parse(self.data, offset.usize()).map(Some)
})
.map(Option::as_ref)
}
pub fn metadata_strings(&self) -> Result<Option<StringsHeap<'a>>> {
self.parse_stream("#Strings", StringsHeap::parse)
}
pub fn metadata_user_strings(&self) -> Result<Option<UserStringsHeap<'a>>> {
self.parse_stream("#US", UserStringsHeap::parse)
}
pub fn metadata_guids(&self) -> Result<Option<GuidHeap<'a>>> {
self.parse_stream("#GUID", GuidHeap::parse)
}
pub fn metadata_blob(&self) -> Result<Option<BlobHeap<'a>>> {
self.parse_stream("#Blob", BlobHeap::parse)
}
pub fn metadata_tables_header(&self) -> Result<Option<&MetadataTablesHeader<'a>>> {
cached(&self.cache.dot_net.tables_header, || {
self.parse_stream("#~", |data, offset, _| {
MetadataTablesHeader::parse(data, offset)
})
})
.map(Option::as_ref)
}
pub fn type_ref_hash(&self) -> Result<Option<[u8; 32]>> {
cached(&self.cache.dot_net.type_ref_hash, || {
let (Some(tables_header), Some(strings)) =
(self.metadata_tables_header()?, self.metadata_strings()?)
else {
return Ok(None);
};
type_ref_hash(tables_header, &strings).map(Some)
})
.copied()
}
pub fn clr_module_version_ids(&self) -> Result<Option<&[Uuid]>> {
cached(&self.cache.dot_net.module_version_ids, || {
let Some(tables_header) = self.metadata_tables_header()? else {
return Ok(None);
};
Ok(Some(module_version_ids(
tables_header.tables()?,
self.metadata_guids()?.as_ref(),
)))
})
.map(Option::as_deref)
}
pub fn clr_com_type_lib_id(&self) -> Result<Option<Uuid>> {
cached(&self.cache.dot_net.com_type_lib_id, || {
let (Some(tables_header), Some(strings), Some(blob)) = (
self.metadata_tables_header()?,
self.metadata_strings()?,
self.metadata_blob()?,
) else {
return Ok(None);
};
com_type_lib_id(tables_header.tables()?, &strings, &blob)
})
.copied()
}
fn parse_stream<T>(
&self,
name: &str,
parse: impl FnOnce(&'a [u8], usize, usize) -> Result<T>,
) -> Result<Option<T>> {
let Some(header) = self.metadata_header()? else {
return Ok(None);
};
let Some(stream) = header.stream(name) else {
return Ok(None);
};
let offset = header.offset.usize() + stream.stream_offset as usize;
parse(self.data, offset, stream.size as usize).map(Some)
}
}