#![allow(non_upper_case_globals)]
use std::{cell::SyncUnsafeCell, ffi::c_void};
use bon::Builder;
use ib_hook::inline::InlineHook;
use serde::{Deserialize, Serialize};
use tracing::trace;
use widestring::U16CStr;
use windows::{
Win32::{
Foundation::{PROPERTYKEY, S_OK},
Storage::EnhancedStorage::PKEY_Size,
System::Com::StructuredStorage::PROPVARIANT,
UI::Shell::PropertiesSystem::{IPropertySystem, PDFF_ALWAYSKB, PROPDESC_FORMAT_FLAGS},
},
core::{HRESULT, Interface, PWSTR},
};
use crate::{hook::prop::magic, prop::system::PropertySystem, string::prefix_u16cstr_ptr};
#[derive(Default, Serialize, Deserialize, Clone, Builder, Debug)]
pub struct PropertySystemHookConfig {
#[builder(default)]
size_no_alwayskb: bool,
#[builder(default)]
size_max_bar: bool,
}
type FormatForDisplayAlloc = unsafe extern "system" fn(
*mut c_void,
*const PROPERTYKEY,
*const PROPVARIANT,
PROPDESC_FORMAT_FLAGS,
*mut PWSTR,
) -> HRESULT;
pub(crate) struct PropertySystemHook {
format_for_display_alloc: Option<InlineHook<FormatForDisplayAlloc>>,
config: PropertySystemHookConfig,
}
impl PropertySystemHook {
pub fn new(config: PropertySystemHookConfig) -> anyhow::Result<Self> {
let system = IPropertySystem::new()?;
let format_for_display_alloc = if config.size_max_bar || config.size_no_alwayskb {
let format_for_display_alloc = system.vtable().FormatForDisplayAlloc;
InlineHook::new_enabled(format_for_display_alloc, format_for_display_alloc_detour).ok()
} else {
None
};
Ok(Self {
format_for_display_alloc,
config,
})
}
}
pub(crate) static HOOK: SyncUnsafeCell<Option<PropertySystemHook>> = SyncUnsafeCell::new(None);
pub fn apply(config: Option<PropertySystemHookConfig>) -> anyhow::Result<()> {
let hook = unsafe { &mut *HOOK.get() };
*hook = match config {
Some(config) => Some(PropertySystemHook::new(config)?),
None => None,
};
Ok(())
}
#[allow(non_snake_case)]
unsafe extern "system" fn format_for_display_alloc_detour(
this: *mut c_void,
key: *const PROPERTYKEY,
propvar: *const PROPVARIANT,
pdff: PROPDESC_FORMAT_FLAGS,
ppszDisplay: *mut PWSTR,
) -> HRESULT {
trace!(?key, ?propvar, ?pdff);
let hook = unsafe { &*HOOK.get() }.as_ref().unwrap();
let trampoline = hook.format_for_display_alloc.as_ref().unwrap().trampoline();
let real = || unsafe { trampoline(this, key, propvar, pdff, ppszDisplay) };
let pkey = unsafe { &*key };
match *pkey {
PKEY_Size => {
if hook.config.size_no_alwayskb || hook.config.size_max_bar {
let r = unsafe {
trampoline(
this,
key,
propvar,
if hook.config.size_no_alwayskb {
pdff & !PDFF_ALWAYSKB
} else {
pdff
},
ppszDisplay,
)
};
if r.is_err() {
return r;
}
}
if hook.config.size_max_bar
&& let Some(size) = TryInto::<u64>::try_into(unsafe { &*propvar }).ok()
&& let Some(max) = magic::ui8_read_u64(propvar)
{
let s = unsafe { U16CStr::from_ptr_str((*ppszDisplay).0) };
let mut bar = crate::string::bar::StringBar::builder()
.value(size)
.max(max)
.min_bar(true)
.build()
.to_utf16_string();
bar.push(' ');
let prefix = bar.as_slice();
unsafe { *ppszDisplay = prefix_u16cstr_ptr(s, &prefix) };
};
return S_OK;
}
_ => (),
}
real()
}