injectorpp 0.5.1

Injectorpp is a powerful tool designed to facilitate the writing of unit tests without the need to introduce traits solely for testing purposes. It streamlines the testing process by providing a seamless and efficient way to abstract dependencies, ensuring that your code remains clean and maintainable.
Documentation
use crate::injector_core::common::*;

use super::patch_trait::PatchTrait;

#[cfg(target_arch = "x86_64")]
use super::patch_amd64::PatchAmd64;
#[cfg(target_arch = "aarch64")]
use super::patch_arm64::PatchArm64;
#[cfg(target_arch = "arm")]
use super::patch_arm::PatchArm;

#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "arm"))]
use super::thread_local_registry;

#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "arm"))]
use super::thread_local_registry::ThreadRegistration;

/// An internal builder for patching a function. Not exposed publicly.
pub(crate) struct WhenCalled {
    func_ptr: FuncPtrInternal,
}

impl WhenCalled {
    pub(crate) fn new(func: FuncPtrInternal) -> Self {
        Self { func_ptr: func }
    }

    /// Patches the target function with a direct JMP to the replacement (0.4.0-style global patching).
    /// All threads see the fake because the function's code bytes are overwritten.
    /// Used by `when_called_globally()`.
    pub(crate) fn will_execute_guard(self, target: FuncPtrInternal) -> PatchGuard {
        #[cfg(target_arch = "x86_64")]
        {
            PatchAmd64::replace_function_with_other_function(self.func_ptr, target)
        }

        #[cfg(target_arch = "aarch64")]
        {
            PatchArm64::replace_function_with_other_function(self.func_ptr, target)
        }

        #[cfg(target_arch = "arm")]
        {
            PatchArm::replace_function_with_other_function(self.func_ptr, target)
        }
    }

    /// Patches the target function using thread-local dispatch (x86_64 only).
    /// The original function is patched to a dispatcher that routes calls
    /// to per-thread replacement functions.
    #[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "arm"))]
    pub(crate) fn will_execute_thread_local(
        self,
        target: FuncPtrInternal,
    ) -> ThreadRegistration {
        let replacement_addr = target.as_ptr() as usize;
        thread_local_registry::register_replacement(&self.func_ptr, replacement_addr, None)
    }

    /// Patches the target function to return a boolean using thread-local dispatch.
    #[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "arm"))]
    pub(crate) fn will_return_boolean_thread_local(self, value: bool) -> ThreadRegistration {
        // Generate a small JIT block that returns the boolean value
        #[cfg(target_arch = "x86_64")]
        let (jit_size, asm_code_vec) = {
            let code: [u8; 8] = [
                0x48, 0xC7, 0xC0, // mov rax, imm32
                value as u8, 0x00, 0x00, 0x00, // imm32
                0xC3, // ret
            ];
            (8usize, code.to_vec())
        };

        #[cfg(target_arch = "aarch64")]
        let (jit_size, asm_code_vec) = {
            use super::arm64_codegenerator::*;
            use super::utils::{bool_array_to_u32, u8_to_bits};

            let mut value_bits = [false; 16];
            value_bits[0] = value;
            let movz = emit_movz(value_bits, true, u8_to_bits::<2>(0), u8_to_bits::<5>(0));
            let ret = emit_ret_x30();

            let mut code = Vec::with_capacity(8);
            code.extend_from_slice(&bool_array_to_u32(movz).to_le_bytes());
            code.extend_from_slice(&bool_array_to_u32(ret).to_le_bytes());
            (8usize, code)
        };

        #[cfg(target_arch = "arm")]
        let (jit_size, asm_code_vec) = {
            // ARM mode: MOV r0, #value; BX lr
            let mov_r0: u32 = 0xE3A00000 | (value as u32); // MOV r0, #0 or #1
            let bx_lr: u32 = 0xE12FFF1E; // BX lr
            let mut code = Vec::with_capacity(8);
            code.extend_from_slice(&mov_r0.to_le_bytes());
            code.extend_from_slice(&bx_lr.to_le_bytes());
            (8usize, code)
        };

        let jit_memory = allocate_jit_memory(&self.func_ptr, jit_size);

        unsafe {
            inject_asm_code(&asm_code_vec, jit_memory);
        }

        let replacement_addr = jit_memory as usize;
        thread_local_registry::register_replacement(
            &self.func_ptr,
            replacement_addr,
            Some((jit_memory, jit_size)),
        )
    }

    /// Patches the target function to return a fixed boolean via direct JMP (0.4.0-style).
    /// All threads see the fake. Used by `when_called_globally().will_return_boolean()`.
    pub(crate) fn will_return_boolean_guard(self, value: bool) -> PatchGuard {
        #[cfg(target_arch = "x86_64")]
        {
            PatchAmd64::replace_function_return_boolean(self.func_ptr, value)
        }

        #[cfg(target_arch = "aarch64")]
        {
            PatchArm64::replace_function_return_boolean(self.func_ptr, value)
        }

        #[cfg(target_arch = "arm")]
        {
            PatchArm::replace_function_return_boolean(self.func_ptr, value)
        }
    }
}