dpdu-wrapper 0.1.3

High-level Rust wrapper for D-PDU implementations.
use neohook::detour_helper;
use parking_lot::RwLock;
use std::collections::HashSet;
use std::ffi::{CStr, c_void};
use std::sync::{LazyLock, OnceLock};
use tracing::error;
use windows::Win32::Foundation::HWND;
use windows::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA};
use windows::core::{PCSTR, s};

static CALLBACKS: LazyLock<RwLock<HashSet<MessageBoxACallback>>> =
    LazyLock::new(|| RwLock::default());

type MsgBoxAFn =
    unsafe extern "system" fn(hwnd: HWND, text: PCSTR, caption: PCSTR, style: u32) -> i32;

type MessageBoxACallback = fn(text: &str) -> bool;

unsafe extern "system" fn hooked_msg_box_a(
    hwnd: HWND,
    text: PCSTR,
    caption: PCSTR,
    style: u32,
) -> i32 {
    let trampoline = TRAMPOLINE
        .get()
        .expect("internal error: MessageBoxA detour is not initialized");

    let call_trampoline = || -> i32 { unsafe { trampoline(hwnd, text, caption, style) } };

    if !text.is_null() {
        let Ok(text) = unsafe { CStr::from_ptr(text.as_ptr() as _) }.to_str() else {
            return call_trampoline();
        };

        let callbacks = CALLBACKS.read();
        for callback in callbacks.iter() {
            if callback(text) {
                return 1;
            }
        }
    }

    call_trampoline()
}

static TRAMPOLINE: OnceLock<MsgBoxAFn> = OnceLock::new();

static DETOUR: LazyLock<Result<(), String>> = LazyLock::new(|| unsafe {
    let user32dll = LoadLibraryA(s!("user32.dll"))
        .map_err(|e| format!("LoadLibraryA(user32.dll) error: {}", e.message()))?;

    let fn_ptr = GetProcAddress(user32dll, s!("MessageBoxA"))
        .map(|v| v as *const c_void)
        .ok_or_else(|| "unable to take a pointer to the MessageBoxA() function".to_string())?;

    let hook_result = detour_helper!(TRAMPOLINE, fn_ptr, hooked_msg_box_a, MsgBoxAFn)
        .map_err(|e| format!("detour_helper!(...): {}", e.to_string()))?;

    Box::leak(Box::new(hook_result));

    Ok(())
});

pub(crate) fn hook_message_box_a() -> bool {
    match DETOUR.as_ref() {
        Ok(_) => true,
        Err(err) => {
            error!("MessageBoxA hook error: {err}");
            false
        }
    }
}

pub(crate) fn register_callback(callback: MessageBoxACallback) {
    let mut callbacks = CALLBACKS.write();
    callbacks.insert(callback);
}