File

Struct File 

pub struct File { /* private fields */ }
Expand description

Provides access to low-level file and memory parsing utilities.

The crate::Parser type is used for decoding CIL bytecode and metadata streams.

§Usage Examples

use dotscope::{Parser, assembly::decode_instruction};
let code = [0x2A]; // ret
let mut parser = Parser::new(&code);
let instr = decode_instruction(&mut parser, 0x1000)?;
assert_eq!(instr.mnemonic, "ret");

Represents a loaded PE file.

This struct can load both .NET assemblies and native PE files. Use File::is_clr() to check if the file contains .NET metadata.

This struct contains the parsed PE information and provides methods for accessing headers, sections, data directories, and for converting between address spaces. It supports loading from both files and memory buffers.

The File struct is the main entry point for working with PE files. It can load both .NET assemblies and native PE executables/DLLs. Use File::is_clr() or File::clr() to determine if the loaded file contains .NET metadata.

§Examples

§Loading and checking for .NET metadata

use dotscope::File;
use std::path::Path;

let file = File::from_path(Path::new("example.exe"))?;
println!("Loaded PE with {} sections", file.sections().len());

// Check if it's a .NET assembly
if file.is_clr() {
    let (clr_rva, clr_size) = file.clr().unwrap();
    println!(".NET assembly - CLR at RVA 0x{:x}, size={}", clr_rva, clr_size);
} else {
    println!("Native PE executable (no .NET metadata)");
}

§Loading from memory

use dotscope::File;
use std::fs;

let data = fs::read("assembly.dll")?;
let file = File::from_mem(data)?;

// Access CLR metadata if present
if let Some((clr_rva, clr_size)) = file.clr() {
    println!("CLR header at RVA 0x{:x}, {} bytes", clr_rva, clr_size);
}

§Working with addresses

use dotscope::File;
use std::path::Path;

let file = File::from_path(Path::new("tests/samples/WindowsBase.dll"))?;

// Convert between address spaces
let entry_rva = file.header_optional().as_ref().unwrap()
    .standard_fields.address_of_entry_point as usize;
let entry_offset = file.rva_to_offset(entry_rva)?;

// Read entry point code
let entry_code = file.data_slice(entry_offset, 16)?;
println!("Entry point bytes: {:02x?}", entry_code);

Implementations§

§

impl File

pub fn from_path(path: impl AsRef<Path>) -> Result<File>

Loads a PE file from the given path.

This method opens a file from disk and parses it as a PE file. The file is memory-mapped for efficient access. Works with both .NET assemblies and native PE executables/DLLs.

§Arguments
  • path - Path to the PE file on disk. Accepts &Path, &str, String, or PathBuf.
§Errors

Returns an error if:

  • The file cannot be read or opened
  • The file is not a valid PE format
  • The file is empty
§Examples
use dotscope::File;
use std::path::Path;

// With Path
let file = File::from_path(Path::new("tests/samples/WindowsBase.dll"))?;

// With string slice
let file = File::from_path("tests/samples/WindowsBase.dll")?;

println!("Loaded {} bytes with {} sections",
         file.len(), file.sections().len());

// Check if it's a .NET assembly
if let Some((clr_rva, clr_size)) = file.clr() {
    println!("CLR runtime header: RVA=0x{:x}, size={}", clr_rva, clr_size);
}

pub fn from_mem(data: Vec<u8>) -> Result<File>

Loads a PE file from a memory buffer.

This method parses a PE file that’s already loaded into memory. Useful when working with embedded resources or downloaded files. Works with both .NET assemblies and native PE executables/DLLs.

§Arguments
  • data - The bytes of the PE file.
§Errors

Returns an error if:

  • The buffer is empty
  • The data is not a valid PE format
§Examples
use dotscope::File;
use std::fs;

// Load from downloaded or embedded data
let data = fs::read("tests/samples/WindowsBase.dll")?;
let file = File::from_mem(data)?;

// Inspect the assembly
println!("Assembly size: {} bytes", file.len());
println!("Image base: 0x{:x}", file.imagebase());

// Find specific sections
for section in file.sections() {
    let name = section.name.trim_end_matches('\0');
    if name == ".text" {
        println!("Code section at RVA 0x{:x}", section.virtual_address);
        break;
    }
}

pub fn from_std_file(file: File) -> Result<File>

Creates a File from an opened std::fs::File.

The file is memory-mapped for efficient access. This is useful when you already have a file handle open with specific permissions or from a special location.

§Arguments
  • file - An opened file handle
§Errors

Returns an error if:

  • The file cannot be memory-mapped
  • The data is not a valid PE format
§Examples
use dotscope::File;
use std::fs::File as StdFile;

let std_file = StdFile::open("assembly.dll")?;
let pe_file = File::from_std_file(std_file)?;

pub fn from_reader<R: Read>(reader: R) -> Result<File>

Creates a File from any type implementing Read.

This is the most flexible constructor, allowing Files to be created from network streams, archives, cursors, or any custom reader.

§Arguments
  • reader - Any type implementing Read
§Errors

Returns an error if:

  • The reader cannot be read
  • The data is not a valid PE format
§Examples
use dotscope::File;
use std::io::Cursor;

let data = vec![/* PE bytes */];
let cursor = Cursor::new(data);
let file = File::from_reader(cursor)?;

pub fn len(&self) -> usize

Returns the total size of the loaded file in bytes.

§Examples
use dotscope::File;
use std::path::Path;

let file = File::from_path(Path::new("tests/samples/WindowsBase.dll"))?;
println!("File size: {} bytes", file.len());

pub fn is_empty(&self) -> bool

Returns true if the file has a length of zero.

§Examples
use dotscope::File;
use std::path::Path;

let file = File::from_path(Path::new("tests/samples/WindowsBase.dll"))?;
assert!(!file.is_empty()); // Valid PE files are never empty

pub fn imagebase(&self) -> u64

Returns the image base address of the loaded PE file.

The image base is the preferred virtual address where the PE file should be loaded in memory. This is used for calculating virtual addresses.

§Examples
use dotscope::File;
use std::path::Path;

let file = File::from_path(Path::new("tests/samples/WindowsBase.dll"))?;
let base = file.imagebase();
println!("Image base: 0x{:x}", base);

pub fn header(&self) -> &CoffHeader

Returns a reference to the COFF header.

The COFF header contains essential metadata about the executable, including the machine type, number of sections, and timestamp.

§Examples
use dotscope::File;
use std::path::Path;

let file = File::from_path(Path::new("tests/samples/WindowsBase.dll"))?;
let header = file.header();
println!("Machine type: 0x{:x}", header.machine);
println!("Number of sections: {}", header.number_of_sections);

pub fn header_dos(&self) -> &DosHeader

Returns a reference to the DOS header.

The DOS header is the first part of a PE file and contains the DOS stub that displays the “This program cannot be run in DOS mode” message.

§Examples
use dotscope::File;
use std::path::Path;

let file = File::from_path(Path::new("tests/samples/WindowsBase.dll"))?;
let dos_header = file.header_dos();
println!("DOS signature: 0x{:x}", dos_header.signature);
println!("Number of bytes on last page: {}", dos_header.bytes_on_last_page);

pub fn header_optional(&self) -> &Option<OptionalHeader>

Returns a reference to the optional header, if present.

This is always Some for valid .NET assemblies since they require an optional header to define data directories and other metadata.

§Examples
use dotscope::File;
use std::path::Path;

let file = File::from_path(Path::new("tests/samples/WindowsBase.dll"))?;
let optional_header = file.header_optional().as_ref().unwrap();
println!("Entry point: 0x{:x}", optional_header.standard_fields.address_of_entry_point);
println!("Subsystem: {:?}", optional_header.windows_fields.subsystem);

pub fn clr(&self) -> Option<(usize, usize)>

Returns the RVA and size of the CLR runtime header, if present.

The CLR runtime header contains metadata about the .NET runtime, including pointers to metadata tables and other runtime structures.

§Returns
  • Some((rva, size)) if this PE file contains a CLR runtime header (.NET assembly)
  • None if this is a native PE file without .NET metadata
§Examples
use dotscope::File;
use std::path::Path;

let file = File::from_path(Path::new("example.exe"))?;
match file.clr() {
    Some((rva, size)) => println!(".NET assembly: CLR at RVA 0x{:x}", rva),
    None => println!("Native PE file (no .NET metadata)"),
}

pub fn is_clr(&self) -> bool

Returns true if this PE file contains a CLR runtime header (.NET assembly).

This is a convenience method equivalent to file.clr().is_some().

§Examples
use dotscope::File;
use std::path::Path;

let file = File::from_path(Path::new("example.exe"))?;
if file.is_clr() {
    println!("This is a .NET assembly");
} else {
    println!("This is a native PE file");
}

pub fn sections(&self) -> &[SectionTable]

Returns a slice of the section headers of the PE file.

Sections contain the actual code and data of the PE file, such as .text (executable code), .data (initialized data), and .rsrc (resources).

§Examples
use dotscope::File;
use std::path::Path;

let file = File::from_path(Path::new("tests/samples/WindowsBase.dll"))?;
for section in file.sections() {
    println!("Section: {} at RVA 0x{:x}, size: {} bytes",
             section.name, section.virtual_address, section.virtual_size);
}

pub fn directories(&self) -> Vec<(DataDirectoryType, DataDirectory)>

Returns the data directories of the PE file.

§Examples
use dotscope::File;
use std::path::Path;

let file = File::from_path(Path::new("tests/samples/WindowsBase.dll"))?;
for (dir_type, directory) in file.directories() {
    println!("Directory: {:?}, RVA: 0x{:x}", dir_type, directory.virtual_address);
}

pub fn get_data_directory( &self, dir_type: DataDirectoryType, ) -> Option<(u32, u32)>

Returns the RVA and size of a specific data directory entry.

This method provides unified access to PE data directory entries by type. It returns the virtual address and size if the directory exists and is valid, or None if the directory doesn’t exist or has zero address/size.

§Arguments
  • dir_type - The type of data directory to retrieve
§Returns
  • Some((rva, size)) if the directory exists with non-zero address and size
  • None if the directory doesn’t exist or has zero address/size
§Examples
use dotscope::File;
use dotscope::DataDirectoryType;
use std::path::Path;

let file = File::from_path(Path::new("example.dll"))?;

// Check for import table
if let Some((import_rva, import_size)) = file.get_data_directory(DataDirectoryType::ImportTable) {
    println!("Import table at RVA 0x{:x}, size: {} bytes", import_rva, import_size);
}

// Check for export table
if let Some((export_rva, export_size)) = file.get_data_directory(DataDirectoryType::ExportTable) {
    println!("Export table at RVA 0x{:x}, size: {} bytes", export_rva, export_size);
}

pub fn imports(&self) -> Option<&Vec<Import>>

Returns the parsed import data from the PE file.

Uses goblin’s PE parsing to extract import table information including DLL dependencies and imported functions. Returns the parsed import data if an import directory exists.

§Returns
  • Some(imports) if import directory exists and was successfully parsed
  • None if no import directory exists or parsing failed
§Examples
use dotscope::File;
use std::path::Path;

let file = File::from_path(Path::new("example.dll"))?;
if let Some(imports) = file.imports() {
    for import in imports {
        println!("DLL: {}", import.dll);
        if let Some(ref name) = import.name {
            if !name.is_empty() {
                println!("  Function: {}", name);
            }
        } else if let Some(ordinal) = import.ordinal {
            println!("  Ordinal: {}", ordinal);
        }
    }
}

pub fn exports(&self) -> Option<&Vec<Export>>

Returns the parsed export data from the PE file.

Uses goblin’s PE parsing to extract export table information including exported functions and their addresses. Returns the parsed export data if an export directory exists.

§Returns
  • Some(exports) if export directory exists and was successfully parsed
  • None if no export directory exists or parsing failed
§Examples
use dotscope::File;
use std::path::Path;

let file = File::from_path(Path::new("example.dll"))?;
if let Some(exports) = file.exports() {
    for export in exports {
        if let Some(name) = &export.name {
            println!("Export: {} -> 0x{:X}", name, export.rva);
        }
    }
}

pub fn data(&self) -> &[u8]

Returns the raw data of the loaded file.

This provides access to the entire PE file contents as a byte slice. Useful for reading specific offsets or when you need direct access to the binary data.

§Examples
use dotscope::File;
use std::path::Path;

let file = File::from_path(Path::new("tests/samples/WindowsBase.dll"))?;
let data = file.data();

// Check DOS signature (MZ)
assert_eq!(&data[0..2], b"MZ");

// Access PE signature offset
let pe_offset = u32::from_le_bytes([data[60], data[61], data[62], data[63]]) as usize;
assert_eq!(&data[pe_offset..pe_offset + 4], b"PE\0\0");

pub fn data_slice(&self, offset: usize, len: usize) -> Result<&[u8]>

Returns a slice of the file data at the given offset and length.

This is a safe way to access specific portions of the PE file data with bounds checking to prevent buffer overflows.

§Arguments
  • offset - The offset to start the slice from.
  • len - The length of the slice.
§Errors

Returns an error if the requested range is out of bounds.

§Examples
use dotscope::File;
use std::path::Path;

let file = File::from_path(Path::new("tests/samples/WindowsBase.dll"))?;

// Read the DOS header (first 64 bytes)
let dos_header = file.data_slice(0, 64)?;
assert_eq!(&dos_header[0..2], b"MZ");

// Read PE signature
let pe_offset = u32::from_le_bytes([dos_header[60], dos_header[61],
                                    dos_header[62], dos_header[63]]) as usize;
let pe_sig = file.data_slice(pe_offset, 4)?;
assert_eq!(pe_sig, b"PE\0\0");

pub fn va_to_offset(&self, va: usize) -> Result<usize>

Converts a virtual address (VA) to a file offset.

Virtual addresses are absolute addresses where the PE file would be loaded in memory. This method converts them to file offsets for reading data from the actual file.

§Arguments
  • va - The virtual address to convert.
§Errors

Returns an error if the VA is out of bounds or cannot be mapped.

§Examples
use dotscope::File;
use std::path::Path;

let file = File::from_path(Path::new("tests/samples/WindowsBase.dll"))?;

// Convert entry point VA to file offset
let entry_point_va = file.header_optional().as_ref().unwrap().standard_fields.address_of_entry_point as usize;
let image_base = file.imagebase() as usize;
let full_va = image_base + entry_point_va;

let offset = file.va_to_offset(full_va)?;
println!("Entry point at file offset: 0x{:x}", offset);

pub fn rva_to_offset(&self, rva: usize) -> Result<usize>

Converts a relative virtual address (RVA) to a file offset.

RVAs are addresses relative to the image base. This is the most common address format used within PE files for referencing data and code.

§Arguments
  • rva - The RVA to convert.
§Errors

Returns an error if the RVA cannot be mapped to a file offset.

§Examples
use dotscope::File;
use std::path::Path;

let file = File::from_path(Path::new("tests/samples/WindowsBase.dll"))?;

// Convert CLR header RVA to file offset
let Some((clr_rva, _)) = file.clr() else {
    panic!("No CLR header found");
};
let clr_offset = file.rva_to_offset(clr_rva)?;

// Read CLR header data
let clr_data = file.data_slice(clr_offset, 72)?; // CLR header is 72 bytes
println!("CLR header starts with: {:02x?}", &clr_data[0..8]);

pub fn offset_to_rva(&self, offset: usize) -> Result<usize>

Converts a file offset to a relative virtual address (RVA).

This is the inverse of rva_to_offset(). Given a file offset, it calculates what RVA that offset corresponds to when the PE file is loaded in memory.

§Arguments
  • offset - The file offset to convert.
§Errors

Returns an error if the offset cannot be mapped to an RVA.

§Examples
use dotscope::File;
use std::path::Path;

let file = File::from_path(Path::new("tests/samples/WindowsBase.dll"))?;

// Find what RVA corresponds to file offset 0x1000
let rva = file.offset_to_rva(0x1000)?;
println!("File offset 0x1000 maps to RVA 0x{:x}", rva);

// Verify round-trip conversion
let back_to_offset = file.rva_to_offset(rva)?;
assert_eq!(back_to_offset, 0x1000);

pub fn section_contains_metadata(&self, section_name: &str) -> bool

Determines if a section contains .NET metadata by checking the actual metadata RVA.

This method reads the CLR runtime header to get the metadata RVA and checks if it falls within the specified section’s address range. This is more accurate than name-based heuristics since metadata can technically be located in any section.

§Arguments
  • section_name - The name of the section to check (e.g., “.text”)
§Returns

Returns true if the section contains .NET metadata, false otherwise.

§Examples
use dotscope::File;
use std::path::Path;

let file = File::from_path(Path::new("example.dll"))?;

if file.section_contains_metadata(".text") {
    println!("The .text section contains .NET metadata");
}

pub fn file_alignment(&self) -> Result<u32>

Gets the file alignment value from the PE header.

This method extracts the file alignment value from the PE optional header. This is typically 512 bytes for most .NET assemblies.

§Returns

Returns the file alignment value in bytes.

§Errors

Returns crate::Error::LayoutFailed if the PE header cannot be accessed.

pub fn section_alignment(&self) -> Result<u32>

Gets the section alignment value from the PE header.

This method extracts the section alignment value from the PE optional header. This is typically 4096 bytes (page size) for most .NET assemblies.

§Returns

Returns the section alignment value in bytes.

§Errors

Returns crate::Error::LayoutFailed if the PE header cannot be accessed.

pub fn is_pe32_plus_format(&self) -> Result<bool>

Determines if this is a PE32+ format file.

Returns true for PE32+ (64-bit) format, false for PE32 (32-bit) format. This affects the size of ILT/IAT entries and ordinal import bit positions.

§Returns

Returns true if PE32+ format, false if PE32 format.

§Errors

Returns crate::Error::LayoutFailed if the PE format cannot be determined.

pub fn text_section_rva(&self) -> Result<u32>

Gets the RVA of the .text section.

Locates the .text section (or .text-prefixed section) which typically contains .NET metadata and executable code.

§Returns

Returns the RVA (Relative Virtual Address) of the .text section.

§Errors

Returns crate::Error::LayoutFailed if no .text section is found.

pub fn text_section_file_offset(&self) -> Result<u64>

Gets the file offset of the .text section.

This method finds the .text section in the PE file and returns its file offset. This is needed for calculating absolute file offsets for metadata components.

§Returns

Returns the file offset of the .text section.

§Errors

Returns crate::Error::LayoutFailed if no .text section is found.

pub fn text_section_raw_size(&self) -> Result<u32>

Gets the raw size of the .text section.

This method finds the .text section and returns its raw data size. This is needed for calculating metadata expansion requirements.

§Returns

Returns the raw size of the .text section in bytes.

§Errors

Returns crate::Error::LayoutFailed if no .text section is found.

pub fn file_size(&self) -> u64

Gets the total size of the file.

Returns the size of the underlying file data in bytes.

§Returns

Returns the file size in bytes.

pub fn pe_signature_offset(&self) -> Result<u64>

Gets the PE signature offset from the DOS header.

Reads the PE offset from the DOS header at offset 0x3C to locate the PE signature (“PE\0\0”) within the file.

§Returns

Returns the file offset where the PE signature is located.

§Errors

Returns crate::Error::LayoutFailed if the file is too small to contain a valid DOS header.

pub fn pe_headers_size(&self) -> Result<u64>

Calculates the size of PE headers (including optional header).

Computes the total size of PE signature, COFF header, and optional header by reading the optional header size from the COFF header.

§Returns

Returns the total size in bytes of all PE headers.

§Errors

Returns crate::Error::LayoutFailed if the file is too small or headers are malformed.

pub fn align_to_file_alignment(&self, offset: u64) -> Result<u64>

Aligns an offset to this file’s PE file alignment boundary.

PE files require data to be aligned to specific boundaries for optimal loading. This method uses the actual file alignment value from the PE header rather than assuming a hardcoded value.

§Arguments
  • offset - The offset to align
§Returns

Returns the offset rounded up to the next file alignment boundary.

§Errors

Returns crate::Error::LayoutFailed if the PE header cannot be accessed.

pub fn pe(&self) -> &Pe

Returns a reference to the internal Pe structure.

This provides access to the owned PE data structures for operations that need to work directly with PE components, such as size calculations and header updates during write operations.

§Returns

Reference to the internal Pe structure

pub fn pe_mut(&mut self) -> &mut Pe

Returns a mutable reference to the internal Pe structure.

This provides mutable access to the owned PE data structures, enabling direct modifications to PE headers, sections, and data directories. Use this when you need to modify the PE structure in-place rather than creating copies.

§Returns

Mutable reference to the internal Pe structure

§Examples
// Add a new section to the PE file
let mut file = File::from_path(path)?;
let new_section = SectionTable::from_layout_info(
    ".meta".to_string(),
    0x4000,
    0x1000,
    0x2000,
    0x1000,
    0x40000040,
)?;
file.pe_mut().add_section(new_section);

// Update CLR data directory
file.pe_mut().update_clr_data_directory(0x4000, 72)?;

pub fn into_data(self) -> Vec<u8>

Consumes the File and returns the underlying data as an owned Vec.

For files loaded via from_mem(), this transfers ownership without copying. For files loaded via from_file(), this makes a complete copy of the memory-mapped data.

§Examples
use dotscope::File;
use std::path::Path;

// From memory - no copy
let original_data = vec![/* PE bytes */];
let file = File::from_mem(original_data)?;
let recovered_data = file.into_data();

// From file - copies the data
let file = File::from_path(Path::new("assembly.dll"))?;
let data_copy = file.into_data();

Auto Trait Implementations§

§

impl Freeze for File

§

impl !RefUnwindSafe for File

§

impl Send for File

§

impl Sync for File

§

impl Unpin for File

§

impl !UnwindSafe for File

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.