dotscope 0.6.0

A high-performance, cross-platform framework for analyzing and reverse engineering .NET PE executables
Documentation
//! Assembly writer module for .NET binary generation.
//!
//! This module provides PE file generation capabilities for writing .NET assemblies.
//! It uses a streaming generation approach that builds complete PE files efficiently.
//!
//! # Architecture
//!
//! The writer uses a streaming approach with the `PeGenerator`:
//!
//! ```text
//! ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
//! │   CilAssembly   │───▶│   PeGenerator   │───▶│  Output (mmap)  │
//! │   (source)      │    │   (streaming)   │    │  or Vec<u8>     │
//! └─────────────────┘    └─────────────────┘    └─────────────────┘
//! ```
//!
//! # Usage
//!
//! The primary API is through `CilAssembly` methods:
//!
//! ```rust,no_run
//! use dotscope::prelude::*;
//! use std::path::Path;
//!
//! let view = CilAssemblyView::from_path(Path::new("input.dll"))?;
//! let mut assembly = view.to_owned();
//!
//! // Write to file
//! assembly.to_file("output.dll")?;
//!
//! // Or generate to memory
//! let bytes = assembly.to_memory()?;
//! # Ok::<(), dotscope::Error>(())
//! ```
//!
//! # Error Handling
//!
//! This module returns errors in the following cases:
//!
//! - Layout calculation fails due to invalid assembly state
//! - File I/O operations fail (permissions, disk space, path issues)
//! - Memory mapping operations fail on the output file
//!
//! # References
//!
//! - [ECMA-335 Common Language Infrastructure (CLI)](https://www.ecma-international.org/publications/standards/Ecma-335.htm)
//! - [PE Format Specification](https://docs.microsoft.com/en-us/windows/win32/debug/pe-format)

mod context;
mod fields;
mod fixups;
mod generator;
mod heaps;
mod methods;
mod output;
mod relocations;
mod remapper;
mod signatures;
mod sizes;
mod tables;

// Re-export for use by other crate modules
pub(crate) use tables::ResolvePlaceholders;

// Internal use by CilAssembly
pub(super) use generator::PeGenerator;

// Public re-exports
pub use generator::GeneratorConfig;

#[cfg(test)]
mod tests {
    use std::path::Path;

    use tempfile::NamedTempFile;

    use crate::{metadata::tables::TableId, CilAssemblyView};

    #[test]
    fn test_assembly_to_file_basic() {
        let view = CilAssemblyView::from_path(Path::new("tests/samples/crafted_2.exe"))
            .expect("Failed to load test assembly");
        let mut assembly = view.to_owned();

        let temp_file = NamedTempFile::new().expect("Failed to create temp file");
        let result = assembly.to_file(temp_file.path());

        assert!(
            result.is_ok(),
            "Basic assembly writing should succeed: {:?}",
            result
        );
    }

    #[test]
    fn test_assembly_roundtrip_preserves_basic_structure() {
        // Test that writing and reloading preserves basic structure
        let view1 = CilAssemblyView::from_path(Path::new("tests/samples/crafted_2.exe"))
            .expect("Failed to load test assembly");

        let original_type_count = view1
            .tables()
            .map(|t| t.table_row_count(TableId::TypeDef))
            .unwrap_or(0);
        let original_method_count = view1
            .tables()
            .map(|t| t.table_row_count(TableId::MethodDef))
            .unwrap_or(0);

        let mut assembly = view1.to_owned();

        let temp_file = NamedTempFile::new().expect("Failed to create temp file");
        assembly
            .to_file(temp_file.path())
            .expect("Write should succeed");

        // Reload and verify structure
        let reloaded_view = CilAssemblyView::from_path(temp_file.path())
            .expect("Should be able to reload written assembly");

        let reloaded_type_count = reloaded_view
            .tables()
            .map(|t| t.table_row_count(TableId::TypeDef))
            .unwrap_or(0);
        let reloaded_method_count = reloaded_view
            .tables()
            .map(|t| t.table_row_count(TableId::MethodDef))
            .unwrap_or(0);

        assert_eq!(
            original_type_count, reloaded_type_count,
            "Type count should be preserved"
        );
        assert_eq!(
            original_method_count, reloaded_method_count,
            "Method count should be preserved"
        );
    }

    #[test]
    fn test_assembly_to_memory() {
        let view = CilAssemblyView::from_path(Path::new("tests/samples/crafted_2.exe"))
            .expect("Failed to load test assembly");
        let mut assembly = view.to_owned();

        let result = assembly.to_memory();
        assert!(
            result.is_ok(),
            "In-memory generation should succeed: {:?}",
            result.err()
        );

        let bytes = result.unwrap();
        assert!(!bytes.is_empty(), "Generated bytes should not be empty");
        assert_eq!(&bytes[0..2], b"MZ", "Should have MZ signature");
    }
}