use std::collections::{HashMap, HashSet};
use crate::{
cilassembly::{ChangeRef, HeapChanges, TableModifications},
metadata::{exports::UnifiedExportContainer, imports::UnifiedImportContainer, tables::TableId},
};
#[derive(Debug, Clone)]
pub struct AssemblyChanges {
pub table_changes: HashMap<TableId, TableModifications>,
pub string_heap_changes: HeapChanges<String>,
pub blob_heap_changes: HeapChanges<Vec<u8>>,
pub guid_heap_changes: HeapChanges<[u8; 16]>,
pub userstring_heap_changes: HeapChanges<String>,
pub referenced_string_offsets: HashSet<u32>,
pub native_imports: UnifiedImportContainer,
pub native_exports: UnifiedExportContainer,
pub method_bodies: HashMap<u32, Vec<u8>>,
pub next_method_placeholder: u32,
pub resource_data: Vec<u8>,
pub field_data: HashMap<u32, Vec<u8>>,
pub next_field_placeholder: u32,
}
impl AssemblyChanges {
pub fn new() -> Self {
Self {
table_changes: HashMap::new(),
string_heap_changes: HeapChanges::new_strings(),
blob_heap_changes: HeapChanges::new_blobs(),
guid_heap_changes: HeapChanges::new_guids(),
userstring_heap_changes: HeapChanges::new_userstrings(),
referenced_string_offsets: HashSet::new(),
native_imports: UnifiedImportContainer::new(),
native_exports: UnifiedExportContainer::new(),
method_bodies: HashMap::new(),
next_method_placeholder: 0xF000_0000, resource_data: Vec::new(),
field_data: HashMap::new(),
next_field_placeholder: 0xE000_0000, }
}
pub fn empty() -> Self {
Self::new()
}
pub fn has_changes(&self) -> bool {
!self.table_changes.is_empty()
|| self.string_heap_changes.has_changes()
|| self.blob_heap_changes.has_changes()
|| self.guid_heap_changes.has_changes()
|| self.userstring_heap_changes.has_changes()
|| !self.native_imports.is_empty()
|| !self.native_exports.is_empty()
|| !self.resource_data.is_empty()
}
pub fn modified_table_count(&self) -> usize {
self.table_changes.len()
}
pub fn string_additions_count(&self) -> usize {
self.string_heap_changes.additions_count()
}
pub fn blob_additions_count(&self) -> usize {
self.blob_heap_changes.additions_count()
}
pub fn guid_additions_count(&self) -> usize {
self.guid_heap_changes.additions_count()
}
pub fn userstring_additions_count(&self) -> usize {
self.userstring_heap_changes.additions_count()
}
pub fn modified_tables(&self) -> impl Iterator<Item = TableId> + '_ {
self.table_changes.keys().copied()
}
pub fn native_imports_mut(&mut self) -> &mut UnifiedImportContainer {
&mut self.native_imports
}
pub fn native_imports(&self) -> &UnifiedImportContainer {
&self.native_imports
}
pub fn native_exports_mut(&mut self) -> &mut UnifiedExportContainer {
&mut self.native_exports
}
pub fn native_exports(&self) -> &UnifiedExportContainer {
&self.native_exports
}
pub fn get_table_modifications(&self, table_id: TableId) -> Option<&TableModifications> {
self.table_changes.get(&table_id)
}
pub fn get_table_modifications_mut(
&mut self,
table_id: TableId,
) -> Option<&mut TableModifications> {
self.table_changes.get_mut(&table_id)
}
pub fn binary_heap_sizes(&self) -> (usize, usize, usize, usize) {
let string_size = self.string_heap_changes.binary_string_heap_size();
let blob_size = self.blob_heap_changes.binary_blob_heap_size();
let guid_size = self.guid_heap_changes.binary_guid_heap_size();
let userstring_size = self.userstring_heap_changes.binary_userstring_heap_size();
(string_size, blob_size, guid_size, userstring_size)
}
pub fn string_change_refs(&self) -> impl Iterator<Item = &super::ChangeRefRc> {
self.string_heap_changes
.appended_iter()
.map(|(_, change_ref)| change_ref)
}
pub fn blob_change_refs(&self) -> impl Iterator<Item = &super::ChangeRefRc> {
self.blob_heap_changes
.appended_iter()
.map(|(_, change_ref)| change_ref)
}
pub fn guid_change_refs(&self) -> impl Iterator<Item = &super::ChangeRefRc> {
self.guid_heap_changes
.appended_iter()
.map(|(_, change_ref)| change_ref)
}
pub fn userstring_change_refs(&self) -> impl Iterator<Item = &super::ChangeRefRc> {
self.userstring_heap_changes
.appended_iter()
.map(|(_, change_ref)| change_ref)
}
pub fn table_change_refs(
&self,
table_id: TableId,
) -> impl Iterator<Item = (&u32, &super::ChangeRefRc)> {
self.table_changes
.get(&table_id)
.map(crate::cilassembly::TableModifications::change_refs)
.into_iter()
.flatten()
}
pub fn all_table_change_refs(
&self,
) -> impl Iterator<Item = (TableId, &u32, &super::ChangeRefRc)> {
self.table_changes.iter().flat_map(|(table_id, mods)| {
mods.change_refs()
.map(move |(rid, change_ref)| (*table_id, rid, change_ref))
})
}
pub fn lookup_by_placeholder(&self, placeholder: u32) -> Option<&super::ChangeRefRc> {
if !ChangeRef::is_placeholder(placeholder) {
return None;
}
let id = ChangeRef::id_from_placeholder(placeholder)?;
for (_, change_ref) in self.string_heap_changes.appended_iter() {
if change_ref.id() == id {
return Some(change_ref);
}
}
for (_, change_ref) in self.blob_heap_changes.appended_iter() {
if change_ref.id() == id {
return Some(change_ref);
}
}
for (_, change_ref) in self.guid_heap_changes.appended_iter() {
if change_ref.id() == id {
return Some(change_ref);
}
}
for (_, change_ref) in self.userstring_heap_changes.appended_iter() {
if change_ref.id() == id {
return Some(change_ref);
}
}
for mods in self.table_changes.values() {
for (_, change_ref) in mods.change_refs() {
if change_ref.id() == id {
return Some(change_ref);
}
}
}
None
}
pub fn store_method_body(&mut self, body_bytes: Vec<u8>) -> u32 {
let placeholder_rva = self.next_method_placeholder;
self.method_bodies.insert(placeholder_rva, body_bytes);
self.next_method_placeholder += 1;
placeholder_rva
}
pub fn get_method_body(&self, placeholder_rva: u32) -> Option<&Vec<u8>> {
self.method_bodies.get(&placeholder_rva)
}
pub fn method_bodies_total_size(&self) -> crate::Result<u32> {
self.method_bodies
.values()
.map(|body| {
let size = u32::try_from(body.len())
.map_err(|_| malformed_error!("Method body size exceeds u32 range"))?;
Ok((size + 3) & !3)
})
.sum()
}
pub fn method_bodies(&self) -> impl Iterator<Item = (u32, &Vec<u8>)> + '_ {
self.method_bodies
.iter()
.map(|(placeholder_rva, body)| (*placeholder_rva, body))
}
pub fn is_method_body_placeholder(&self, rva: u32) -> bool {
rva >= 0xF000_0000 && self.method_bodies.contains_key(&rva)
}
pub fn store_resource_data(&mut self, data: &[u8]) -> u32 {
#[allow(clippy::cast_possible_truncation)]
let offset = self.resource_data.len() as u32;
#[allow(clippy::cast_possible_truncation)]
let len = data.len() as u32; self.resource_data.extend_from_slice(&len.to_le_bytes());
self.resource_data.extend_from_slice(data);
offset
}
pub fn resource_data_size(&self) -> usize {
self.resource_data.len()
}
pub fn has_resource_data(&self) -> bool {
!self.resource_data.is_empty()
}
pub fn resource_data_bytes(&self) -> &[u8] {
&self.resource_data
}
pub fn store_field_data(&mut self, data: Vec<u8>) -> u32 {
let placeholder_rva = self.next_field_placeholder;
self.field_data.insert(placeholder_rva, data);
self.next_field_placeholder += 1;
placeholder_rva
}
pub fn get_field_data(&self, placeholder_rva: u32) -> Option<&Vec<u8>> {
self.field_data.get(&placeholder_rva)
}
pub fn field_data_total_size(&self) -> crate::Result<u32> {
self.field_data
.values()
.map(|data| {
let size = u32::try_from(data.len())
.map_err(|_| malformed_error!("Field data size exceeds u32 range"))?;
Ok((size + 3) & !3)
})
.sum()
}
pub fn field_data_entries(&self) -> impl Iterator<Item = (u32, &Vec<u8>)> + '_ {
self.field_data
.iter()
.map(|(placeholder_rva, data)| (*placeholder_rva, data))
}
pub fn is_field_data_placeholder(&self, rva: u32) -> bool {
(0xE000_0000..0xF000_0000).contains(&rva) && self.field_data.contains_key(&rva)
}
pub fn has_field_data(&self) -> bool {
!self.field_data.is_empty()
}
}
impl Default for AssemblyChanges {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cilassembly::HeapChanges;
#[test]
fn test_assembly_changes_empty() {
let changes = AssemblyChanges::empty();
assert!(!changes.has_changes());
assert_eq!(changes.modified_table_count(), 0);
assert_eq!(changes.string_additions_count(), 0);
}
#[test]
fn test_binary_heap_sizes() {
let mut changes = AssemblyChanges::empty();
let (string_size, blob_size, guid_size, userstring_size) = changes.binary_heap_sizes();
assert_eq!(string_size, 0);
assert_eq!(blob_size, 0);
assert_eq!(guid_size, 0);
assert_eq!(userstring_size, 0);
let mut string_changes = HeapChanges::new_strings();
let _ = string_changes.append("Hello".to_string()); let _ = string_changes.append("World".to_string()); changes.string_heap_changes = string_changes;
let mut blob_changes = HeapChanges::new_blobs();
let _ = blob_changes.append(vec![1, 2, 3]); let _ = blob_changes.append(vec![4, 5, 6, 7, 8]); changes.blob_heap_changes = blob_changes;
let mut guid_changes = HeapChanges::new_guids();
let _ = guid_changes.append([1; 16]); let _ = guid_changes.append([2; 16]); changes.guid_heap_changes = guid_changes;
let (string_size, blob_size, guid_size, userstring_size) = changes.binary_heap_sizes();
assert_eq!(string_size, 12); assert_eq!(blob_size, 10); assert_eq!(guid_size, 32); assert_eq!(userstring_size, 0); }
}