wdext 0.1.0

A DbgEng wrapper framework
// SPDX-FileCopyrightText: 2026 takubokudori
// SPDX-License-Identifier: MIT OR Apache-2.0
//! A wrapper around the DbgEng API.

use crate::*;
use std::mem::MaybeUninit;
use windows::core::{HRESULT, Interface};
use windy::{ACPStr, ACPString, WStr, WString};

pub mod advanced;
pub mod breakpoint;
pub mod callbacks;
pub mod client;
pub mod control;
pub mod data_spaces;
pub mod output_stream;
pub mod plm_client;
pub mod registers;
pub mod symbol_group;
pub mod symbols;
pub mod system_objects;

pub use advanced::*;
pub use breakpoint::*;
pub use callbacks::*;
pub use client::*;
pub use control::*;
pub use data_spaces::*;
pub use output_stream::*;
pub use plm_client::*;
pub use registers::*;
pub use symbol_group::*;
pub use symbols::*;
pub use system_objects::*;

pub(crate) type WinResult<T> = Result<T, windows::core::Error>;

pub fn debug_create() -> WinResult<DebugClient> {
    unsafe { Ok(DebugCreate::<IDebugClient>()?.into()) }
}

pub fn debug_create_ex(dbgeng_options: u32) -> WinResult<DebugClient> {
    unsafe { Ok(DebugCreateEx::<IDebugClient>(dbgeng_options)?.into()) }
}

pub fn debug_connect(
    remote_options: impl AsRef<ACPStr>,
) -> WinResult<DebugClient> {
    let mut interface = std::ptr::null_mut();
    unsafe {
        DebugConnect(pca!(remote_options), &IDebugClient::IID, &mut interface)?;
    }
    unsafe { DebugClient::from_raw(interface) }
}

pub fn debug_connect_wide(
    remote_options: impl AsRef<WStr>,
) -> WinResult<DebugClient> {
    let mut interface = std::ptr::null_mut();
    unsafe {
        DebugConnectWide(
            pcw!(remote_options),
            &IDebugClient::IID,
            &mut interface,
        )?;
    }
    unsafe { DebugClient::from_raw(interface) }
}

/// Calls a DbgEng-style variable-size buffer API, retrying once with the
/// reported required size when the initial buffer is insufficient.
///
/// # Safety
///
/// On success, `f` must initialize exactly the number of elements written to
/// the size output. `S_FALSE` must mean that the buffer was insufficient and
/// the size output contains the required element count.
pub(crate) unsafe fn vec_with_capacity<T>(
    cap: usize,
    mut f: impl FnMut(&mut [MaybeUninit<T>], &mut u32) -> HRESULT,
) -> WinResult<Vec<T>> {
    let mut v = Vec::with_capacity(cap);
    let mut size1 = 0;
    let mut size2 = 0;

    unsafe {
        if hr!(f(v.spare_capacity_mut(), &mut size1))? {
            assert!(
                size1 as usize <= v.capacity(),
                "DbgEng returned a size larger than the supplied buffer"
            );
            v.set_len(size1 as usize);
            Ok(v)
        } else {
            let mut v = Vec::with_capacity(size1 as usize);
            assert!(
                hr!(f(v.spare_capacity_mut(), &mut size2))?,
                "DbgEng still reported an insufficient buffer after \
                 exact-size allocation"
            );
            assert_eq!(size1, size2);
            assert!(
                size2 as usize <= v.capacity(),
                "DbgEng returned a size larger than the supplied buffer"
            );
            v.set_len(size2 as usize);
            Ok(v)
        }
    }
}

/// Variant of [`vec_with_capacity`] that can use a different callback for the
/// exact-size retry.
///
/// # Safety
///
/// The callbacks have the same initialization and size-reporting requirements
/// as [`vec_with_capacity`].
pub(crate) unsafe fn vec_with_capacity2<T>(
    cap: usize,
    mut f1: impl FnMut(&mut [MaybeUninit<T>], &mut u32) -> HRESULT,
    mut f2: impl FnMut(&mut [MaybeUninit<T>], &mut u32) -> HRESULT,
) -> WinResult<Vec<T>> {
    let mut v = Vec::with_capacity(cap);
    let mut size1 = 0;
    let mut size2 = 0;

    unsafe {
        if hr!(f1(v.spare_capacity_mut(), &mut size1))? {
            assert!(
                size1 as usize <= v.capacity(),
                "DbgEng returned a size larger than the supplied buffer"
            );
            v.set_len(size1 as usize);
            Ok(v)
        } else {
            let mut v = Vec::with_capacity(size1 as usize);
            assert!(
                hr!(f2(v.spare_capacity_mut(), &mut size2))?,
                "DbgEng still reported an insufficient buffer after \
                 exact-size allocation"
            );
            assert_eq!(size1, size2);
            assert!(
                size2 as usize <= v.capacity(),
                "DbgEng returned a size larger than the supplied buffer"
            );
            v.set_len(size2 as usize);
            Ok(v)
        }
    }
}

/// First, attempts to write the ANSI string into an array with a capacity of `cap`.
/// If the string is truncated, adjusts the capacity to the appropriate size
/// and tries to retrieve the string again.
///
/// # Safety
///
/// `f` must return `S_FALSE` when the buffer size is insufficient.
pub(crate) unsafe fn astring_with_capacity(
    cap: usize,
    f: impl FnMut(&mut [MaybeUninit<u8>], &mut u32) -> HRESULT,
) -> WinResult<ACPString> {
    unsafe {
        Ok(ACPString::from_vec_with_nul_unchecked(vec_with_capacity(
            cap, f,
        )?))
    }
}

pub(crate) unsafe fn astring_with_capacity2(
    cap: usize,
    f1: impl FnMut(&mut [MaybeUninit<u8>], &mut u32) -> HRESULT,
    f2: impl FnMut(&mut [MaybeUninit<u8>], &mut u32) -> HRESULT,
) -> WinResult<ACPString> {
    unsafe {
        Ok(ACPString::from_vec_with_nul_unchecked(vec_with_capacity2(
            cap, f1, f2,
        )?))
    }
}

/// First, attempts to write the Uncode string into an array with a capacity of `cap`.
/// If the string is truncated, adjusts the capacity to the appropriate size
/// and tries to retrieve the string again.
///
/// # Safety
///
/// `f` must return `S_FALSE` when the buffer size is insufficient.
pub(crate) unsafe fn wstring_with_capacity(
    cap: usize,
    f: impl FnMut(&mut [MaybeUninit<u16>], &mut u32) -> HRESULT,
) -> WinResult<WString> {
    unsafe {
        Ok(WString::from_vec_with_nul_unchecked(vec_with_capacity(
            cap, f,
        )?))
    }
}

pub(crate) unsafe fn wstring_with_capacity2(
    cap: usize,
    f1: impl FnMut(&mut [MaybeUninit<u16>], &mut u32) -> HRESULT,
    f2: impl FnMut(&mut [MaybeUninit<u16>], &mut u32) -> HRESULT,
) -> WinResult<WString> {
    unsafe {
        Ok(WString::from_vec_with_nul_unchecked(vec_with_capacity2(
            cap, f1, f2,
        )?))
    }
}