use std::io::{self, Write};
use crate::{
formatting::{
attributes,
helpers::{data_label_for_rva, find_section_for_rva, hex_bytes, quote_identifier},
security, FormatterOptions,
},
metadata::{
identity::Identity,
signatures::TypeSignature,
tables::{AssemblyFlags, FileAttributes, HashAlgorithmId, TableId, TypeAttributes},
typesystem::CilTypeReference,
},
CilObject,
};
fn write_assembly_arch_flags(w: &mut dyn Write, flags: AssemblyFlags) -> io::Result<()> {
let kw = flags.processor_architecture_keyword();
if !kw.is_empty() {
write!(w, " {kw}")?;
}
if flags.content_type() == AssemblyFlags::CONTENT_TYPE_WINDOWS_RUNTIME {
write!(w, " windowsruntime")?;
}
Ok(())
}
pub(super) fn format_header(
opts: &FormatterOptions,
w: &mut dyn Write,
asm: &CilObject,
) -> io::Result<()> {
if let Some(asm_meta) = asm.assembly() {
write!(w, ".assembly")?;
write_assembly_arch_flags(w, asm_meta.flags)?;
writeln!(w, " '{}' {{", asm_meta.name)?;
if asm_meta.hash_alg_id != HashAlgorithmId::NONE {
writeln!(w, " .hash algorithm 0x{:08X}", asm_meta.hash_alg_id.bits())?;
}
writeln!(
w,
" .ver {}:{}:{}:{}",
asm_meta.major_version,
asm_meta.minor_version,
asm_meta.build_number,
asm_meta.revision_number
)?;
if opts.show_custom_attributes {
attributes::format_custom_attributes(w, &asm_meta.custom_attributes, " ", asm)?;
}
if opts.show_security {
if let Some(sec) = asm_meta.security.get() {
security::format_security(w, sec, " ")?;
}
}
writeln!(w, "}}")?;
}
if let Some(module) = asm.module() {
writeln!(w, ".module {}", module.name)?;
}
let flags = asm.cor20header().flags;
writeln!(w, ".corflags 0x{:08X} // {flags}", flags.bits())?;
if let Some(oh) = &asm.file().pe().optional_header {
let wf = &oh.windows_fields;
writeln!(w, ".imagebase 0x{:08X}", wf.image_base)?;
writeln!(w, ".file alignment 0x{:08X}", wf.file_alignment)?;
writeln!(w, ".stackreserve 0x{:08X}", wf.size_of_stack_reserve)?;
writeln!(
w,
".subsystem 0x{:04X} // {}",
wf.subsystem.bits(),
wf.subsystem
)?;
}
writeln!(w)?;
Ok(())
}
pub(super) fn format_assembly_refs(w: &mut dyn Write, asm: &CilObject) -> io::Result<()> {
for entry in asm.refs_assembly().iter() {
let aref = entry.value();
write!(w, ".assembly extern")?;
if aref.flags.contains(AssemblyFlags::RETARGETABLE) {
write!(w, " retargetable")?;
}
write_assembly_arch_flags(w, aref.flags)?;
writeln!(w, " '{}' {{", aref.name)?;
if let Some(ref ident) = aref.identifier {
match ident {
Identity::Token(token) => {
let bytes = token.to_le_bytes();
writeln!(w, " .publickeytoken = ({})", hex_bytes(&bytes))?;
}
Identity::PubKey(key) => {
writeln!(w, " .publickey = ({})", hex_bytes(key))?;
}
Identity::EcmaKey(key) => {
writeln!(w, " .publickey = ({})", hex_bytes(key))?;
}
}
}
if let Some(ref hash) = aref.hash {
let hash_data = hash.data();
if !hash_data.is_empty() {
writeln!(w, " .hash = ({})", hex_bytes(hash_data))?;
}
}
if let Some(ref culture) = aref.culture {
if !culture.is_empty() {
writeln!(w, " .culture \"{culture}\"")?;
}
}
writeln!(
w,
" .ver {}:{}:{}:{}",
aref.major_version, aref.minor_version, aref.build_number, aref.revision_number
)?;
writeln!(w, "}}")?;
}
Ok(())
}
pub(super) fn format_module_refs(w: &mut dyn Write, asm: &CilObject) -> io::Result<()> {
for entry in asm.refs_module().iter() {
let mref = entry.value();
writeln!(w, ".module extern '{}'", mref.name)?;
}
Ok(())
}
pub(super) fn format_file_directives(w: &mut dyn Write, asm: &CilObject) -> io::Result<()> {
for entry in asm.refs_file().iter() {
let file = entry.value();
write!(w, ".file")?;
if file.flags.contains(FileAttributes::CONTAINS_NO_META_DATA) {
write!(w, " nometadata")?;
}
write!(w, " '{}'", file.name)?;
let hash_data = file.hash_value.data();
if !hash_data.is_empty() {
write!(w, " .hash = ({})", hex_bytes(hash_data))?;
}
writeln!(w)?;
}
Ok(())
}
pub(super) fn format_exported_types(
opts: &FormatterOptions,
w: &mut dyn Write,
asm: &CilObject,
) -> io::Result<()> {
let cil_exports = asm.exports().cil();
for entry in cil_exports.iter() {
let exported = entry.value();
let flags = exported.flags;
let vis = match flags & TypeAttributes::VISIBILITY_MASK {
TypeAttributes::PUBLIC => "public",
TypeAttributes::NESTED_PUBLIC => "nested public",
TypeAttributes::NESTED_PRIVATE => "nested private",
TypeAttributes::NESTED_FAMILY => "nested family",
TypeAttributes::NESTED_ASSEMBLY => "nested assembly",
TypeAttributes::NESTED_FAM_AND_ASSEM => "nested famandassem",
TypeAttributes::NESTED_FAM_OR_ASSEM => "nested famorassem",
_ => "private",
};
let is_forwarder = flags.contains(TypeAttributes::FORWARDER);
let fullname = match &exported.namespace {
Some(ns) if !ns.is_empty() => format!("{ns}.{}", exported.name),
_ => exported.name.clone(),
};
if is_forwarder {
writeln!(
w,
".class extern forwarder {} {{",
quote_identifier(&fullname)
)?;
} else {
writeln!(w, ".class extern {vis} {} {{", quote_identifier(&fullname))?;
}
if let Some(implementation) = exported.get_implementation() {
match implementation {
CilTypeReference::File(file) => {
writeln!(w, " .file '{}'", file.name)?;
}
CilTypeReference::AssemblyRef(aref) => {
writeln!(w, " .assembly extern '{}'", aref.name)?;
}
CilTypeReference::ExportedType(parent) => {
let parent_fullname = match &parent.namespace {
Some(ns) if !ns.is_empty() => format!("{ns}.{}", parent.name),
_ => parent.name.clone(),
};
writeln!(w, " .class extern {}", quote_identifier(&parent_fullname))?;
}
_ => {}
}
}
if opts.show_custom_attributes {
attributes::format_custom_attributes(w, &exported.custom_attributes, " ", asm)?;
}
writeln!(w, "}}")?;
}
Ok(())
}
pub(super) fn format_data_directives(w: &mut dyn Write, asm: &CilObject) -> io::Result<()> {
let file = asm.file();
let sections = file.sections();
let mut data_entries: Vec<(u32, usize, Option<Vec<u8>>)> = Vec::new();
for cil_type in asm
.query_types()
.filter(|t| t.token.is_table(TableId::TypeDef))
.find_all()
{
for (_, field) in cil_type.fields.iter() {
let Some(&rva) = field.rva.get() else {
continue;
};
let type_token = match &field.signature.base {
TypeSignature::ValueType(token) | TypeSignature::Class(token) => Some(*token),
_ => None,
};
let size = type_token
.and_then(|token| asm.types().get(&token))
.and_then(|t| t.class_size.get().copied())
.unwrap_or(0) as usize;
if size == 0 {
continue;
}
let is_initialized = find_section_for_rva(sections, rva).is_none_or(|s| {
(rva as u64) < u64::from(s.virtual_address) + u64::from(s.size_of_raw_data)
});
if is_initialized {
let Ok(offset) = file.rva_to_offset(rva as usize) else {
continue;
};
let Ok(bytes) = file.data_slice(offset, size) else {
continue;
};
data_entries.push((rva, size, Some(bytes.to_vec())));
} else {
data_entries.push((rva, size, None));
}
}
}
data_entries.sort_by_key(|&(rva, _, _)| rva);
data_entries.dedup_by_key(|entry| entry.0);
for (rva, size, bytes_opt) in &data_entries {
let (prefix, qualifier) = data_label_for_rva(sections, *rva);
match bytes_opt {
Some(bytes) => {
write!(w, ".data{qualifier} {prefix}{rva:08X} = bytearray (")?;
for (i, b) in bytes.iter().enumerate() {
if i > 0 {
write!(w, " ")?;
}
write!(w, "{b:02X}")?;
}
writeln!(w, ")")?;
}
None => {
writeln!(w, ".data{qualifier} {prefix}{rva:08X} = int8[{size}]")?;
}
}
}
if !data_entries.is_empty() {
writeln!(w)?;
}
Ok(())
}