Skip to main content

ib_shell_item/hook/prop/
mod.rs

1use std::{cell::SyncUnsafeCell, ffi::c_void, mem::MaybeUninit, ptr};
2
3use bon::Builder;
4use serde::{Deserialize, Serialize};
5use tracing::{debug, error};
6use windows::{
7    Win32::UI::Shell::{
8        IShellItem2,
9        PropertiesSystem::{GETPROPERTYSTOREFLAGS, IPropertyStore},
10    },
11    core::{GUID, HRESULT, Interface},
12};
13
14use crate::hook::HOOK_CONFIG;
15
16pub mod magic;
17pub mod system;
18pub mod value;
19
20#[derive(Default, Serialize, Deserialize, Clone, Builder, Debug)]
21#[builder(on(Vec<u16>, into))]
22pub struct PropertyHookConfig {
23    /// Mainly for testing.
24    str_prefix: Option<Vec<u16>>,
25
26    #[cfg(feature = "everything")]
27    #[builder(default)]
28    size_from_everything: bool,
29
30    system: Option<system::PropertySystemHookConfig>,
31}
32
33pub(crate) type GetPropertyStoreFn = unsafe extern "system" fn(
34    *mut c_void,
35    GETPROPERTYSTOREFLAGS,
36    *const GUID,
37    *mut *mut c_void,
38) -> HRESULT;
39
40/// Store original and real GetPropertyStore function pointers (lazy initialized)
41pub(crate) struct GetPropertyStoreState {
42    pub original_get_store: Option<GetPropertyStoreFn>,
43    pub real_get_store: MaybeUninit<GetPropertyStoreFn>,
44}
45
46pub(crate) static GET_PROPERTY_STORE_STATE: SyncUnsafeCell<GetPropertyStoreState> =
47    SyncUnsafeCell::new(GetPropertyStoreState {
48        original_get_store: None,
49        real_get_store: MaybeUninit::uninit(),
50    });
51
52/// Hooked GetPropertyStore function
53///
54/// [IShellItem2::GetPropertyStore (shobjidl_core.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ishellitem2-getpropertystore)
55pub(crate) unsafe extern "system" fn get_property_store(
56    this: *mut c_void,
57    flags: GETPROPERTYSTOREFLAGS,
58    riid: *const GUID,
59    ppv: *mut *mut c_void,
60) -> HRESULT {
61    let state = GET_PROPERTY_STORE_STATE.get();
62    let real = || unsafe { (*state).real_get_store.assume_init()(this, flags, riid, ppv) };
63
64    let config = HOOK_CONFIG.read().unwrap();
65    let Some(_config) = &config.property else {
66        return real();
67    };
68
69    // Call original function
70    let result = real();
71
72    if result.is_ok() {
73        debug!(?flags, "GetPropertyStore called");
74        let store = unsafe { IPropertyStore::from_raw_borrowed(&*ppv) }.unwrap();
75        if let Err(e) = value::enable_hook(store) {
76            error!(%e, "Failed to hook GetValue");
77        }
78    }
79
80    result
81}
82
83fn hook(enable: bool) -> windows::core::Result<()> {
84    let state = GET_PROPERTY_STORE_STATE.get();
85    let res = unsafe {
86        slim_detours_sys::SlimDetoursInlineHook(
87            enable as _,
88            (*state).real_get_store.as_mut_ptr().cast(),
89            get_property_store as _,
90        )
91    };
92    windows::core::HRESULT(res).ok()
93}
94
95pub(crate) fn enable_hook(item2: &IShellItem2) -> windows::core::Result<()> {
96    let get_property_store = item2.vtable().GetPropertyStore;
97
98    // Check if already initialized with the same function
99    let state = unsafe { &mut *GET_PROPERTY_STORE_STATE.get() };
100    match state.original_get_store {
101        Some(f) if ptr::fn_addr_eq(f, get_property_store) => Ok(()),
102        None => {
103            let config = HOOK_CONFIG.read().unwrap();
104            if let Err(e) = system::apply(config.property.as_ref().and_then(|p| p.system.clone())) {
105                error!(?e, "system");
106            };
107
108            // Not yet initialized, write the state
109            state.original_get_store = Some(get_property_store);
110            (*state).real_get_store.write(get_property_store);
111            debug!(?get_property_store, "Hooking GetPropertyStore");
112            hook(true)
113        }
114        Some(f) => {
115            // TODO
116            error!(?f, ?get_property_store, "Multi GetPropertyStore");
117            windows::core::HRESULT(1).ok()
118        }
119    }
120}
121
122pub(crate) fn disable_hook() -> windows::core::Result<()> {
123    _ = system::apply(None);
124
125    let state = GET_PROPERTY_STORE_STATE.get();
126    if unsafe { (*state).original_get_store.is_some() } {
127        // Unhook and restore original
128        debug!("Unhooking GetPropertyStore");
129        hook(false)?;
130    }
131    value::disable_hook()
132}