From ec12221e2435bd0b53e837e3ee65ded1dd5da3a3 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 7 May 2026 22:40:05 +0000 Subject: [PATCH] Combine DEVICE_STATE and DEVICE_STATE_LOCK into a single RwLock Continues removing static muts. DEVICE_STATE was a `*mut DeviceState` guarded by a separate marker `RwLock<()>` (DEVICE_STATE_LOCK), which was used inconsistently and often skipped entirely. This collapses the two into a single `RwLock`, where DeviceStatePtr is a Send/Sync wrapper around the raw pointer, so every read/write goes through the same lock. API changes in the device_state crate: - `dev_state()` and `dev_state_d3d11_nolock()` are gone. Both used to hand out a `&'static mut`, which can't be reconciled with a guard lifetime, so every call site was migrated. - `dev_state_write()` / `dev_state_read()` return `Option<(guard, &/&mut DeviceState)>`. - `dev_state_d3d11_write()` and `dev_state_d3d11_read()` keep their shape, but `_read` now returns `&` instead of `&mut` (the previous `_read` was unsound). - All accessors fail safely on lock poisoning by logging and returning None rather than panicking, so a poison can't take the game down. Re-entrancy: `std::sync::RwLock` is not reentrant, so several call sites had to be reworked to drop the guard before invoking real D3D fns (which can re-enter our hooks on the same thread). Notably: - D3D9 `hook_present`, `hook_release`, `hook_set_texture`, `hook_set_stream_source`, `hook_reset`, `hook_draw_indexed_primitive`, `hook_create_device`, `hook_CreateTexture`, `hook_UpdateTexture`, and `create_d3d9` now extract the real fn pointer under a brief read guard, drop the guard, then invoke. The release path also threads `ref_count` updates through scoped write guards. - `HookDirect3D11Context` and friends now derive Copy so `get_hook_context` returns a copy and callers don't need to hold the dev_state lock to invoke the real fn pointers. - DX11 `hook_draw_indexed` was restructured so dev_state guards are acquired only briefly: the geom check / vb_state snapshot under a read guard, mod work outside any guard, and metrics under a final write guard. `ensure_vb_checksum_dx11`, `hook_snapshot::take`, and `with_dev_ptr` are no longer called with the lock held. - `init_device_state_once` now mutates the pointer through the write guard rather than via static mut. Tests serialize on a separate `TEST_DEV_STATE_LOCK` because the new init path takes the dev_state write lock internally. --- Native/device_state/src/device_state.rs | 134 ++++---- Native/hook_core/src/hook_device.rs | 231 ++++++++------ Native/hook_core/src/hook_device_d3d11.rs | 90 +++--- Native/hook_core/src/hook_render.rs | 258 +++++++++------ Native/hook_core/src/hook_render_d3d11.rs | 367 ++++++++++++---------- Native/hook_core/src/input_commands.rs | 6 +- Native/hook_snapshot/src/hook_snapshot.rs | 22 +- Native/mod_load/src/mod_load.rs | 41 ++- Native/shared_dx/src/types_dx11.rs | 3 + 9 files changed, 667 insertions(+), 485 deletions(-) diff --git a/Native/device_state/src/device_state.rs b/Native/device_state/src/device_state.rs index 5a11e16e..364494c4 100644 --- a/Native/device_state/src/device_state.rs +++ b/Native/device_state/src/device_state.rs @@ -1,72 +1,100 @@ use shared_dx::types::{DeviceState, HookD3D11State, HookDeviceState}; -use std::{ptr::null_mut, sync::{RwLockWriteGuard, RwLockReadGuard}}; +use std::{ptr::null_mut, sync::{RwLock, RwLockWriteGuard, RwLockReadGuard}}; use shared_dx::util::write_log_file; -// At some point need to make this private and just use accessor functions (especially with locks) -pub static mut DEVICE_STATE: *mut DeviceState = null_mut(); +/// Newtype wrapping the raw `DeviceState` pointer so it can live inside a +/// `static RwLock`. The pointer itself is essentially set once at hook +/// install time and torn down only by tests, so the `Send`/`Sync` impls +/// reflect the actual access pattern: the `RwLock` controls concurrent +/// access to the pointee, while the pointer field is only mutated under +/// the write guard during init/cleanup. +pub struct DeviceStatePtr(pub *mut DeviceState); +unsafe impl Send for DeviceStatePtr {} +unsafe impl Sync for DeviceStatePtr {} -/// This is primarily used by tests at the moment that need the device state. In general MM -/// is not currently thread safe, as all supported games use only a single render -/// thread, but that might change in the future. The perf overhead of locking inside -/// draw primitive is probably too great but a thread local might be viable, however I tried -/// that before and didn't like it, but maybe I was just wrong (prev commit is in -/// dc46643366ba6f44306ee79448afd73aec5038aa ). -/// Note: if you are using this and the log lock in a test, lock the log first ^_^ -pub static mut DEVICE_STATE_LOCK: std::sync::RwLock<()> = std::sync::RwLock::new(()); - -pub fn dev_state() -> &'static mut DeviceState { - unsafe { - if DEVICE_STATE == null_mut() { - write_log_file("accessing null device state pointer, this 'should never happen'. we gonna crash boys"); - panic!("Aborting because I'm about to dereference a null device state pointer."); - } - &mut (*DEVICE_STATE) - } +impl DeviceStatePtr { + pub fn is_null(&self) -> bool { self.0.is_null() } + pub fn as_ptr(&self) -> *mut DeviceState { self.0 } } -/// As `dev_state()` but only returns the d3d11 state. No locking. Returns None if no state -/// or current state is not d3d11. -pub unsafe fn dev_state_d3d11_nolock<'a>() -> Option<&'a mut HookD3D11State> { - match dev_state().hook { - Some(HookDeviceState::D3D11(ref mut h)) => { - Some(h) - }, - _ => None - } -} +/// Combined replacement for the previous `DEVICE_STATE` raw pointer and the +/// separate `DEVICE_STATE_LOCK` marker. The pointer is owned by the lock, so +/// every reader/writer goes through it. +/// +/// In general MM is currently driven from a single render thread, but locking +/// here allows safe access from the deferred mod load thread and from tests. +/// Note: if you are using this and the log lock in a test, lock the log first ^_^ +pub static DEVICE_STATE: RwLock = RwLock::new(DeviceStatePtr(null_mut())); -// TODO11 benchmark this and use it where needed -// As `dev_state()` but also returns only the d3d11 state and locks. -pub unsafe fn dev_state_d3d11_write<'a>() -> Option<(RwLockWriteGuard<'a, ()>, &'a mut HookD3D11State)> { - match DEVICE_STATE_LOCK.write() { - Ok(lock) => { - match dev_state().hook { - Some(HookDeviceState::D3D11(ref mut h)) => { - Some((lock,h)) - }, - _ => None +/// Acquire a write guard on the device state, returning both the guard and a +/// `&mut DeviceState`. Returns `None` if the lock is poisoned or the pointer +/// is null. On poison this logs and proceeds in fail-safe mode rather than +/// crashing the host process. +pub fn dev_state_write<'a>() -> Option<(RwLockWriteGuard<'a, DeviceStatePtr>, &'a mut DeviceState)> { + match DEVICE_STATE.write() { + Ok(mut lock) => { + if lock.0.is_null() { + return None; } - }, - Err(_) => { - write_log_file(&format!("dev_state_d3d11_write: failed to get lock")); + // SAFETY: the write guard provides exclusive access to the pointer + // and (by convention in this module) to the pointee. The lifetime + // is tied to the guard via the function signature. + let ptr = lock.0; + let r: &mut DeviceState = unsafe { &mut *ptr }; + // Suppress unused-mut: the binding must be `mut` so callers + // (after destructuring) can mutate through the guard if needed. + let _ = &mut lock; + Some((lock, r)) + } + Err(e) => { + write_log_file(&format!("dev_state_write: lock poisoned: {}", e)); None } } } -pub unsafe fn dev_state_d3d11_read<'a>() -> Option<(RwLockReadGuard<'a, ()>, &'a mut HookD3D11State)> { - match DEVICE_STATE_LOCK.read() { +/// Acquire a read guard on the device state, returning both the guard and a +/// `&DeviceState`. Returns `None` if the lock is poisoned or the pointer is +/// null. On poison this logs and proceeds in fail-safe mode. +pub fn dev_state_read<'a>() -> Option<(RwLockReadGuard<'a, DeviceStatePtr>, &'a DeviceState)> { + match DEVICE_STATE.read() { Ok(lock) => { - match dev_state().hook { - Some(HookDeviceState::D3D11(ref mut h)) => { - Some((lock,h)) - }, - _ => None + if lock.0.is_null() { + return None; } - }, - Err(_) => { - write_log_file(&format!("dev_state_d3d11_write: failed to get lock")); + // SAFETY: the read guard ensures no writer is active. We only + // hand out a shared reference to the pointee. + let r: &DeviceState = unsafe { &*lock.0 }; + Some((lock, r)) + } + Err(e) => { + write_log_file(&format!("dev_state_read: lock poisoned: {}", e)); None } } } + +/// As `dev_state_write` but only returns the d3d11 state. Returns None if no +/// state is initialized, the current state is not d3d11, or the lock is +/// poisoned. +pub fn dev_state_d3d11_write<'a>() -> Option<(RwLockWriteGuard<'a, DeviceStatePtr>, &'a mut HookD3D11State)> { + let (lock, ds) = dev_state_write()?; + match ds.hook { + Some(HookDeviceState::D3D11(ref mut h)) => Some((lock, h)), + _ => None, + } +} + +/// As `dev_state_read` but only returns the d3d11 state, immutably. Returns +/// None if no state is initialized, the current state is not d3d11, or the +/// lock is poisoned. +/// +/// Note: unlike the previous version of this function, this returns `&` not +/// `&mut`. Use `dev_state_d3d11_write` if mutation is needed. +pub fn dev_state_d3d11_read<'a>() -> Option<(RwLockReadGuard<'a, DeviceStatePtr>, &'a HookD3D11State)> { + let (lock, ds) = dev_state_read()?; + match ds.hook { + Some(HookDeviceState::D3D11(ref h)) => Some((lock, h)), + _ => None, + } +} diff --git a/Native/hook_core/src/hook_device.rs b/Native/hook_core/src/hook_device.rs index 39123dfa..c59afa52 100644 --- a/Native/hook_core/src/hook_device.rs +++ b/Native/hook_core/src/hook_device.rs @@ -17,7 +17,7 @@ use util; use util::*; use global_state::{GLOBAL_STATE, GLOBAL_STATE_LOCK, VBChecksumStatus}; -use device_state::{DEVICE_STATE, dev_state}; +use device_state::{DEVICE_STATE, dev_state_read, dev_state_write}; use crate::hook_render::{hook_present, hook_draw_indexed_primitive, hook_release, hook_reset, hook_set_stream_source}; use crate::hook_render_d3d11::HOOK_DRAW_PERIODIC_CALLS; use crate::hook_device_d3d11::query_and_set_runconf_in_globalstate; @@ -167,11 +167,17 @@ unsafe extern "system" fn hook_create_texture( ppTexture: *mut *mut IDirect3DTexture9, pSharedHandle: *mut winapi::um::winnt::HANDLE, ) -> HRESULT { - let real_fn = match (dev_state()).hook { - Some(HookDeviceState::D3D9(HookD3D9State { d3d9: _, device: Some(ref dev) })) => { - dev.real_create_texture + let real_fn = match dev_state_read() { + Some((_lck, ds)) => match &ds.hook { + Some(HookDeviceState::D3D9(HookD3D9State { d3d9: _, device: Some(dev) })) => { + dev.real_create_texture + }, + _ => { + write_log_file("hook_CreateTexture: no device state, returning E_FAIL"); + return E_FAIL; + } }, - _ => { + None => { write_log_file("hook_CreateTexture: no device state, returning E_FAIL"); return E_FAIL; } @@ -228,11 +234,17 @@ unsafe extern "system" fn hook_update_texture( pSourceTexture: *mut IDirect3DBaseTexture9, pDestinationTexture: *mut IDirect3DBaseTexture9, ) -> HRESULT { - let real_fn = match (dev_state()).hook { - Some(HookDeviceState::D3D9(HookD3D9State { d3d9: _, device: Some(ref dev) })) => { - dev.real_update_texture + let real_fn = match dev_state_read() { + Some((_lck, ds)) => match &ds.hook { + Some(HookDeviceState::D3D9(HookD3D9State { d3d9: _, device: Some(dev) })) => { + dev.real_update_texture + }, + _ => { + write_log_file("hook_UpdateTexture: no device state, returning E_FAIL"); + return E_FAIL; + } }, - _ => { + None => { write_log_file("hook_UpdateTexture: no device state, returning E_FAIL"); return E_FAIL; } @@ -455,64 +467,67 @@ unsafe fn create_and_hook_device( .lock() .map_err(|_err| HookError::GlobalLockError)?; - if DEVICE_STATE == null_mut() { - return Err(HookError::BadStateError("no device state pointer??".to_owned())); - } - // Query run configuration (e.g. force_tex_cpu_read) so DX9 hooks can use it query_and_set_runconf_in_globalstate(true); - (*DEVICE_STATE) - .hook - .as_mut() - .ok_or(HookError::Direct3D9InstanceNotFound) - .and_then(|hook| { - match hook { - HookDeviceState::D3D9(ds) if ds.d3d9.is_some() => Ok(ds), - _ => Err(HookError::D3D9HookFailed) - } - }) - .and_then(|hd3d9| { - write_log_file(&format!("calling real create device")); - if BehaviorFlags & D3DCREATE_MULTITHREADED == D3DCREATE_MULTITHREADED { - write_log_file(&format!( - "Notice: device being created with D3DCREATE_MULTITHREADED" - )); - } - // option is_some() checked earlier - let result = (hd3d9.d3d9.as_ref().unwrap().real_create_device)( - THIS, - Adapter, - DeviceType, - hFocusWindow, - BehaviorFlags, - pPresentationParameters, - ppReturnedDeviceInterface, - ); - if result != S_OK { - write_log_file(&format!("create device FAILED: {}", result)); - return Err(HookError::CreateDeviceFailed(result)); - } - (*DEVICE_STATE).d3d_window = hFocusWindow; - hook_d3d9_device(*ppReturnedDeviceInterface, &lock) - }) - .and_then(|hook_d3d9device| { - match (*DEVICE_STATE).hook { - Some(HookDeviceState::D3D9(ref mut d3d9)) => d3d9.device = Some(hook_d3d9device), - _ => () - }; - write_log_file(&format!( - "hooked device on thread {:?}", - std::thread::current().id() - )); - Ok(()) - }) - .or_else(|err| { - if ppReturnedDeviceInterface != null_mut() && *ppReturnedDeviceInterface != null_mut() { - (*(*ppReturnedDeviceInterface)).Release(); + // Pull the real_create_device fn ptr out under a read guard, then drop + // the guard before calling it. hook_d3d9_device and the post-write also + // need their own guards since they can require write access. + let real_create_device = match dev_state_read() { + Some((_lck, ds)) => match &ds.hook { + Some(HookDeviceState::D3D9(d3d9)) if d3d9.d3d9.is_some() => { + d3d9.d3d9.as_ref().unwrap().real_create_device + }, + None => return Err(HookError::Direct3D9InstanceNotFound), + _ => return Err(HookError::D3D9HookFailed), + }, + None => return Err(HookError::BadStateError("no device state pointer??".to_owned())), + }; + + write_log_file(&format!("calling real create device")); + if BehaviorFlags & D3DCREATE_MULTITHREADED == D3DCREATE_MULTITHREADED { + write_log_file(&format!( + "Notice: device being created with D3DCREATE_MULTITHREADED" + )); + } + let result = (real_create_device)( + THIS, + Adapter, + DeviceType, + hFocusWindow, + BehaviorFlags, + pPresentationParameters, + ppReturnedDeviceInterface, + ); + if result != S_OK { + write_log_file(&format!("create device FAILED: {}", result)); + return Err(HookError::CreateDeviceFailed(result)); + } + + let hook_result: Result<()> = (|| { + if let Some((_lck, ds)) = dev_state_write() { + ds.d3d_window = hFocusWindow; + } + let hook_d3d9device = hook_d3d9_device(*ppReturnedDeviceInterface, &lock)?; + if let Some((_lck, ds)) = dev_state_write() { + if let Some(HookDeviceState::D3D9(ref mut d3d9)) = ds.hook { + d3d9.device = Some(hook_d3d9device); } - Err(err) - }) + } + write_log_file(&format!( + "hooked device on thread {:?}", + std::thread::current().id() + )); + Ok(()) + })(); + + if let Err(err) = hook_result { + if ppReturnedDeviceInterface != null_mut() && *ppReturnedDeviceInterface != null_mut() { + (*(*ppReturnedDeviceInterface)).Release(); + } + return Err(err); + } + Ok(()) } pub unsafe extern "system" fn hook_create_device( @@ -618,33 +633,39 @@ pub extern "system" fn Direct3DCreate9(SDKVersion: u32) -> *mut u64 { /// out, as we would need to do in a new allocation, would lose /// the addresses of any "real" functions such as create device. pub fn init_device_state_once() -> bool { - unsafe { - // its possible to get in here more than once in same process - // (if it creates multiple devices). leak the previous - // pointer to avoided crashes; if the game is creating devices - // in a tight loop we've got bigger problems than a memory leak. - // note: in a single threaded env nothing else should be - // using the state right now so we could free it. - let was_init = DEVICE_STATE != null_mut(); - let has_hook = DEVICE_STATE != null_mut() && (*DEVICE_STATE).hook.is_some(); - - // allow it be created if it doesn't exist yet or if there is no hook yet - if !was_init || !has_hook { - DEVICE_STATE = Box::into_raw(Box::new(DeviceState { - hook: None, - d3d_window: null_mut(), - d3d_resource_count: 0, - })); - - write_log_file(&format!("initted new device state instance: {}; was initted: {}", DEVICE_STATE as usize, was_init)); - } - // but if there is a hook already don't replace it since we might lose the real hook fn addresses if we do that - else if has_hook { - write_log_file(&format!("not creating new device state because it already has a hook")); + // its possible to get in here more than once in same process + // (if it creates multiple devices). leak the previous + // pointer to avoided crashes; if the game is creating devices + // in a tight loop we've got bigger problems than a memory leak. + // note: in a single threaded env nothing else should be + // using the state right now so we could free it. + let mut guard = match DEVICE_STATE.write() { + Ok(g) => g, + Err(e) => { + write_log_file(&format!("init_device_state_once: lock poisoned: {}", e)); + return false; } - - was_init + }; + let was_init = !guard.0.is_null(); + let has_hook = !guard.0.is_null() && unsafe { (*guard.0).hook.is_some() }; + + // allow it be created if it doesn't exist yet or if there is no hook yet + if !was_init || !has_hook { + let new_ptr = Box::into_raw(Box::new(DeviceState { + hook: None, + d3d_window: null_mut(), + d3d_resource_count: 0, + })); + guard.0 = new_ptr; + + write_log_file(&format!("initted new device state instance: {}; was initted: {}", new_ptr as usize, was_init)); } + // but if there is a hook already don't replace it since we might lose the real hook fn addresses if we do that + else if has_hook { + write_log_file(&format!("not creating new device state because it already has a hook")); + } + + was_init } pub fn init_log(mm_root:&str) { @@ -751,10 +772,12 @@ pub fn late_hook_device(deviceptr: u64) -> i32 { let hook_d3d9device = hook_d3d9_device(device, &lock)?; //(*DEVICE_STATE).d3d_window = hFocusWindow; // TODO: need to get this in late hook API - (*DEVICE_STATE).hook = Some(HookDeviceState::D3D9(HookD3D9State { - d3d9: None, - device: Some(hook_d3d9device) - })); + if let Some((_lck, ds)) = dev_state_write() { + ds.hook = Some(HookDeviceState::D3D9(HookD3D9State { + d3d9: None, + device: Some(hook_d3d9device) + })); + } write_log_file(&format!( "hooked device on thread {:?}", std::thread::current().id() @@ -830,13 +853,13 @@ pub fn create_d3d9(sdk_ver: u32) -> Result<*mut IDirect3D9> { .lock() .map_err(|_err| HookError::D3D9HookFailed)?; - match (*DEVICE_STATE).hook { - Some(HookDeviceState::D3D9(HookD3D9State { d3d9: ref what, device: _ })) => { - let _ = what; - return Ok(direct3d9); - }, - _ => {} + let already_hooked = match dev_state_read() { + Some((_lck, ds)) => matches!(ds.hook, Some(HookDeviceState::D3D9(HookD3D9State { d3d9: Some(_), .. }))), + None => false, }; + if already_hooked { + return Ok(direct3d9); + } GLOBAL_STATE.mm_root = Some(mm_root); @@ -870,13 +893,19 @@ pub fn create_d3d9(sdk_ver: u32) -> Result<*mut IDirect3D9> { real_create_device: real_create_device, }; - (*DEVICE_STATE).hook = - Some(HookDeviceState::D3D9(HookD3D9State { + if let Some((_lck, ds)) = dev_state_write() { + ds.hook = Some(HookDeviceState::D3D9(HookD3D9State { d3d9: Some(hd3d9), device: None })); + } - write_log_file(&format!("device state set with hook on ds instance: {}", DEVICE_STATE as u64)); + // log the (possibly null) ds pointer for debugging purposes. + let ds_addr = match DEVICE_STATE.read() { + Ok(g) => g.0 as u64, + Err(_) => 0, + }; + write_log_file(&format!("device state set with hook on ds instance: {}", ds_addr)); Ok(direct3d9) } } diff --git a/Native/hook_core/src/hook_device_d3d11.rs b/Native/hook_core/src/hook_device_d3d11.rs index 41f91beb..3737a159 100644 --- a/Native/hook_core/src/hook_device_d3d11.rs +++ b/Native/hook_core/src/hook_device_d3d11.rs @@ -73,7 +73,9 @@ use shared_dx::{util::write_log_file, types::{HookDeviceState, HookD3D11State, DX11Metrics, DevicePointer}, error::*, dx11rs::{DX11RenderState, VertexFormat}}; use util::mm_verify_load; -use device_state::{DEVICE_STATE, dev_state_d3d11_nolock, dev_state_d3d11_write}; +use device_state::{dev_state_d3d11_read, dev_state_d3d11_write, dev_state_write}; +#[allow(unused_imports)] +use device_state::DEVICE_STATE; use crate::hook_render_d3d11::*; static mut DEVICE_REALFN: RwLock> = RwLock::new(None); @@ -389,7 +391,7 @@ pub unsafe fn apply_context_hooks(context:*mut ID3D11DeviceContext, first_hook:b rehook_start.unwrap_or(SystemTime::UNIX_EPOCH)); let _ = elapsed.map(|dur| { let nanos = dur.subsec_nanos() as u64 + dur.as_secs() * 1_000_000_000; - dev_state_d3d11_nolock().map(|state| { + dev_state_d3d11_write().map(|(_lck, state)| { state.metrics.rehook_time_nanos += nanos; state.metrics.rehook_calls += 1; }) @@ -756,18 +758,20 @@ fn init_d3d11(device:*mut ID3D11Device, swapchain:*mut IDXGISwapChain, context:* let multithreaded = ((*device).GetCreationFlags() & D3D11_CREATE_DEVICE_SINGLETHREADED) == 0; - (*DEVICE_STATE).hook = Some(HookDeviceState::D3D11(HookD3D11State { - hooks, - devptr: DevicePointer::D3D11(device), - metrics: DX11Metrics::new(), - rs: DX11RenderState::new(), - app_hwnds: Vec::new(), - last_timebased_update: SystemTime::now(), - app_foreground: false, - last_data_expire: SystemTime::now(), - last_data_expire_type_flip: false, - multithreaded, - })); + if let Some((_lck, ds)) = dev_state_write() { + ds.hook = Some(HookDeviceState::D3D11(HookD3D11State { + hooks, + devptr: DevicePointer::D3D11(device), + metrics: DX11Metrics::new(), + rs: DX11RenderState::new(), + app_hwnds: Vec::new(), + last_timebased_update: SystemTime::now(), + app_foreground: false, + last_data_expire: SystemTime::now(), + last_data_expire_type_flip: false, + multithreaded, + })); + } // TODO11: d3d9 also has: d3d_resource_count: 0, @@ -1063,8 +1067,8 @@ pub unsafe fn ensure_vb_checksum_dx11(vb_ptr: usize) { if already_resolved { return; } - let status = match dev_state_d3d11_nolock() { - Some(state) => match state.rs.device_vertex_buffer_data.get(&vb_ptr) { + let status = match dev_state_d3d11_read() { + Some((_lck, state)) => match state.rs.device_vertex_buffer_data.get(&vb_ptr) { Some(bytes) => { let crc = util::vb_checksum::compute(bytes); global_state::VBChecksumStatus::Checksum(crc) @@ -1253,8 +1257,12 @@ pub unsafe extern "system" fn hook_device_QueryInterface( /// Most of these tests can't be run simulataneously, however as they poke at the device globals, /// so they lock at the start, since cargo will normally run them threaded. pub mod tests { - use std::{sync::{Arc}, thread::JoinHandle}; - use device_state::DEVICE_STATE_LOCK; + use std::{sync::{Arc, Mutex}, thread::JoinHandle}; + // Tests serialize on this separate mutex rather than on DEVICE_STATE + // itself, because functions invoked inside the tests (such as + // `init_device_state_once`) take the DEVICE_STATE write lock internally; + // holding that lock across a test body would deadlock with them. + static TEST_DEV_STATE_LOCK: Mutex<()> = Mutex::new(()); use shared_dx::util::{LOG_EXCL_LOCK}; use util::{prep_log_file, prep_log_file_nolock}; use winapi::{um::{unknwnbase::{IUnknown, IUnknownVtbl}, @@ -1318,8 +1326,12 @@ pub mod tests { (*device).Release(); } - let _unbox = Box::from_raw(DEVICE_STATE); - DEVICE_STATE = null_mut(); + let mut lock = DEVICE_STATE.write().expect(&format!("{}: dev state lock failed", testcontext)); + if !lock.0.is_null() { + let _unbox = Box::from_raw(lock.0); + lock.0 = null_mut(); + } + drop(lock); DEVICE_REALFN.write().expect(&format!("{}: device hooks clear failed", testcontext)).take(); } } @@ -1342,14 +1354,16 @@ pub mod tests { let testlog = prep_log_file(&_loglock, "__testhd3d11__test_query_interface.txt").expect("doh"); - let _lock = unsafe { - let lock = DEVICE_STATE_LOCK.write().unwrap(); - if DEVICE_STATE != null_mut() { - panic!("DEVICE_STATE already initialized"); + let _lock = TEST_DEV_STATE_LOCK.lock().unwrap(); + unsafe { + { + let g = DEVICE_STATE.read().unwrap(); + if !g.0.is_null() { + panic!("DEVICE_STATE already initialized"); + } } init_device_state_once(); - lock - }; + } let iunkvtbl = IUnknownVtbl { QueryInterface: hook_device_QueryInterface, @@ -1458,13 +1472,13 @@ pub mod tests { let testlog = prep_log_file(&_loglock, "__testhd3d11__test_create_device.txt").expect("doh"); - let _lock = unsafe { - let lock = DEVICE_STATE_LOCK.write().unwrap(); - if DEVICE_STATE != null_mut() { + let _lock = TEST_DEV_STATE_LOCK.lock().unwrap(); + unsafe { + let g = DEVICE_STATE.read().unwrap(); + if !g.0.is_null() { panic!("DEVICE_STATE already initialized"); } - lock - }; + } let mut device = std::ptr::null_mut(); let mut context = std::ptr::null_mut(); @@ -1548,13 +1562,13 @@ pub mod tests { fn test_create_device_thread() { let _loglock = LOG_EXCL_LOCK.lock().unwrap(); - let _lock = unsafe { - let lock = DEVICE_STATE_LOCK.write().unwrap(); - if DEVICE_STATE != null_mut() { + let _lock = TEST_DEV_STATE_LOCK.lock().unwrap(); + unsafe { + let g = DEVICE_STATE.read().unwrap(); + if !g.0.is_null() { panic!("DEVICE_STATE already initialized"); } - lock - }; + } // we only support one log file so prep it first to remove/clear it, then // each thread will reprep without clearing to set its thread local status that it was @@ -1706,7 +1720,7 @@ pub mod tests { assert_eq!(ret, 0); assert!(!pLayout.is_null()); // don't deref this, its garbage - dev_state_d3d11_nolock().map(|ds| { + dev_state_d3d11_read().map(|(_lck, ds)| { assert_eq!(ds.rs.num_input_layouts.load(Ordering::Relaxed), 1); assert_eq!(ds.rs.device_input_layouts_by_ptr.len(), 1); assert!(ds.rs.device_input_layouts_by_ptr.contains_key(&(pLayout as usize))); @@ -1728,7 +1742,7 @@ pub mod tests { GLOBAL_STATE.metrics.dip_calls += HOOK_DRAW_PERIODIC_CALLS - 1; (*context).DrawIndexed(12, 0, 0); assert_eq!(GLOBAL_STATE.metrics.dip_calls, 2 * HOOK_DRAW_PERIODIC_CALLS); - dev_state_d3d11_nolock().map(|ds| { + dev_state_d3d11_write().map(|(_lck, ds)| { assert_eq!(ds.rs.num_input_layouts.load(Ordering::Relaxed), 0); assert_eq!(ds.rs.device_input_layouts_by_ptr.len(), 0); assert_eq!(ds.rs.context_input_layouts_by_ptr.len(), 1); diff --git a/Native/hook_core/src/hook_render.rs b/Native/hook_core/src/hook_render.rs index 6e1df4a4..80090871 100644 --- a/Native/hook_core/src/hook_render.rs +++ b/Native/hook_core/src/hook_render.rs @@ -1,4 +1,4 @@ -use device_state::dev_state_d3d11_nolock; +use device_state::dev_state_d3d11_write; use global_state::HookState; use global_state::MAX_STAGE; use types::d3ddata::ModD3DData9; @@ -26,7 +26,7 @@ use crate::input_commands; use crate::mod_render; use mod_stats::mod_stats; use global_state::{GLOBAL_STATE, GLOBAL_STATE_LOCK, LOADED_MODS}; -use device_state::dev_state; +use device_state::{dev_state_read, dev_state_write}; use hook_snapshot; use types::native_mod; @@ -120,7 +120,7 @@ pub fn process_metrics(preserve_prims:bool, interval:u32) { // process dx11 metrics (if any). do this first because if we are using dx11 we // want to note that since we might not log some stuff below (which is less useful // in dx11) - unsafe {dev_state_d3d11_nolock()}.map(|state| { + dev_state_d3d11_write().map(|(_lck, state)| { // don't log these in dx11 because dips fps is not set and dips not usually useful report_dips_fps = false; let metrics = &state.metrics; @@ -379,12 +379,18 @@ pub (crate) unsafe extern "system" fn hook_set_texture( track_set_texture(pTexture as usize, Stage, &mut GLOBAL_STATE); } - match (dev_state()).hook { - Some(HookDeviceState::D3D9(HookD3D9State { d3d9: _, device: Some(ref dev) })) => { - (dev.real_set_texture)(THIS, Stage, pTexture) + // Pull the real fn pointer out and drop the guard before calling, in case + // it re-enters our hooks on the same thread. + let real_set_texture = match dev_state_read() { + Some((_lck, ds)) => match &ds.hook { + Some(HookDeviceState::D3D9(HookD3D9State { d3d9: _, device: Some(dev) })) => { + dev.real_set_texture + }, + _ => return E_FAIL, }, - _ => E_FAIL - } + None => return E_FAIL, + }; + real_set_texture(THIS, Stage, pTexture) } /// Record the pointer of the vertex buffer bound to slot `slot`. We only @@ -405,12 +411,16 @@ pub (crate) unsafe extern "system" fn hook_set_stream_source( ) -> HRESULT { track_bound_vertex_buffer(pStreamData as usize, StreamNumber, &mut GLOBAL_STATE); - match (dev_state()).hook { - Some(HookDeviceState::D3D9(HookD3D9State { d3d9: _, device: Some(ref dev) })) => { - (dev.real_set_stream_source)(THIS, StreamNumber, pStreamData, OffsetInBytes, Stride) + let real_set_stream_source = match dev_state_read() { + Some((_lck, ds)) => match &ds.hook { + Some(HookDeviceState::D3D9(HookD3D9State { d3d9: _, device: Some(dev) })) => { + dev.real_set_stream_source + }, + _ => return E_FAIL, }, - _ => E_FAIL - } + None => return E_FAIL, + }; + real_set_stream_source(THIS, StreamNumber, pStreamData, OffsetInBytes, Stride) } /// Hook for IDirect3DDevice9::Reset. @@ -425,12 +435,16 @@ pub (crate) unsafe extern "system" fn hook_reset( map.clear(); } - match (dev_state()).hook { - Some(HookDeviceState::D3D9(HookD3D9State { d3d9: _, device: Some(ref dev) })) => { - (dev.real_reset)(THIS, pPresentationParameters) + let real_reset = match dev_state_read() { + Some((_lck, ds)) => match &ds.hook { + Some(HookDeviceState::D3D9(HookD3D9State { d3d9: _, device: Some(dev) })) => { + dev.real_reset + }, + _ => return E_FAIL, }, - _ => E_FAIL - } + None => return E_FAIL, + }; + real_reset(THIS, pPresentationParameters) } // TODO: hook this up to device release at the proper time @@ -447,7 +461,9 @@ unsafe fn purge_device_resources(device: DevicePointer) { .input .as_mut() .map(|input| input.clear_handlers()); - dev_state().d3d_resource_count = 0; + if let Some((_lck, ds)) = dev_state_write() { + ds.d3d_resource_count = 0; + } } pub unsafe extern "system" fn hook_present( @@ -460,18 +476,18 @@ pub unsafe extern "system" fn hook_present( //write_log_file("present"); let call_real_present = || { - match (dev_state()).hook { - Some(HookDeviceState::D3D9(HookD3D9State { d3d9: _, device: Some(ref dev) })) => { - (dev.real_present)( - THIS, - pSourceRect, - pDestRect, - hDestWindowOverride, - pDirtyRegion, - ) + // Pull the real fn pointer out under a read guard, drop the guard, + // then call it (the call may re-enter our hooks on this thread). + let real_present = match dev_state_read() { + Some((_lck, ds)) => match &ds.hook { + Some(HookDeviceState::D3D9(HookD3D9State { d3d9: _, device: Some(dev) })) => { + dev.real_present + }, + _ => return E_FAIL, }, - _ => E_FAIL - } + None => return E_FAIL, + }; + real_present(THIS, pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion) }; if GLOBAL_STATE.in_any_hook_fn() { return call_real_present(); @@ -505,10 +521,12 @@ pub unsafe extern "system" fn hook_present( .unwrap_or(0) as f64; let metrics = &mut GLOBAL_STATE.metrics; - let present_ret = dev_state() - .hook - .as_mut() - .map_or(S_OK, |_hdstate| { + let has_hook = match dev_state_read() { + Some((_lck, ds)) => ds.hook.is_some(), + None => false, + }; + let present_ret = if !has_hook { S_OK } else { + (|| { metrics.frames += 1; metrics.total_frames += 1; if metrics.frames % 90 == 0 { @@ -540,13 +558,18 @@ pub unsafe extern "system" fn hook_present( } } call_real_present() - }); + })() + }; if GLOBAL_STATE.selection_texture.is_none() { input_commands::create_selection_texture_d3d9(THIS); } - if util::appwnd_is_foreground(dev_state().d3d_window) { + let d3d_window = match dev_state_read() { + Some((_lck, ds)) => ds.d3d_window, + None => null_mut(), + }; + if util::appwnd_is_foreground(d3d_window) { GLOBAL_STATE.input.as_mut().map(|inp| { if inp.get_press_fn_count() == 0 { input_commands::setup_input(DevicePointer::D3D9(THIS), inp) @@ -576,12 +599,24 @@ pub unsafe extern "system" fn hook_release(THIS: *mut IUnknown) -> ULONG { write_log_file(&format!("OOPS hook_release returning {} due to bad state", failret)); }; - if GLOBAL_STATE.in_hook_release { - return match (dev_state()).hook { - Some(HookDeviceState::D3D9(HookD3D9State { d3d9: _, device: Some(ref dev) })) => { - (dev.real_release)(THIS) + // Helper: extract the real_release fn pointer (no guard held on return). + use shared_dx::defs_dx9::IUnknownReleaseFn as D3D9ReleaseFn; + let get_real_release = || -> Option { + match dev_state_read() { + Some((_lck, ds)) => match &ds.hook { + Some(HookDeviceState::D3D9(HookD3D9State { d3d9: _, device: Some(dev) })) => { + Some(dev.real_release) + }, + _ => None, }, - _ => { + None => None, + } + }; + + if GLOBAL_STATE.in_hook_release { + return match get_real_release() { + Some(real_release) => real_release(THIS), + None => { oops_log_release_fail(); failret } @@ -590,64 +625,83 @@ pub unsafe extern "system" fn hook_release(THIS: *mut IUnknown) -> ULONG { GLOBAL_STATE.in_hook_release = true; - // dev_state() used to return an Option, but doesn't now, - // so Some() it for compat with old combinator flow - let r = Some(dev_state()) - .as_mut() - .map_or(failret, |hookds| { - let hookdevice = match hookds.hook { - Some(HookDeviceState::D3D9(HookD3D9State { d3d9: _, device: Some(ref mut dev) })) => { - dev - }, - _ => { return failret; } // "should never happen" + let real_release = match get_real_release() { + Some(f) => f, + None => { + GLOBAL_STATE.in_hook_release = false; + oops_log_release_fail(); + return failret; + } + }; + + let r = (|| -> ULONG { + // First release. Call without holding the lock since real_release + // can re-enter our hooks on the same thread. + let mut new_ref_count = real_release(THIS); + + // Persist ref_count and read d3d_resource_count under a write guard. + let (destroying, mut do_final_release) = match dev_state_write() { + Some((_lck, ds)) => { + let drc = ds.d3d_resource_count; + if let Some(HookDeviceState::D3D9(HookD3D9State { d3d9: _, device: Some(dev) })) = ds.hook.as_mut() { + dev.ref_count = new_ref_count; + } + let destroying = drc > 0 && new_ref_count == (drc + 1); + let do_final = destroying || (drc == 0 && new_ref_count == 1); + (destroying, do_final) + }, + None => return failret, + }; + + // could just leak everything on device destroy. but I know that will + // come back to haunt me. so make an effort to purge my stuff when the + // resource count gets to the expected value, this way the device can be + // properly disposed. + if destroying { + let drc = match dev_state_read() { + Some((_lck, ds)) => ds.d3d_resource_count, + None => 0, }; - hookdevice.ref_count = (hookdevice.real_release)(THIS); - - // if hookdevice.ref_count < 100 { - // write_log_file(&format!( - // "device {:x} refcount now {}", - // THIS as usize, hookdevice.ref_count - // )); - // } - - // could just leak everything on device destroy. but I know that will - // come back to haunt me. so make an effort to purge my stuff when the - // resource count gets to the expected value, this way the device can be - // properly disposed. - - let destroying = dev_state().d3d_resource_count > 0 - && hookdevice.ref_count == (dev_state().d3d_resource_count + 1); - if destroying { - // purge my stuff - write_log_file(&format!( - "device {:x} refcount is same as internal resource count ({}), - it is being destroyed: purging resources", - THIS as usize, dev_state().d3d_resource_count - )); - purge_device_resources(DevicePointer::D3D9(THIS as *mut IDirect3DDevice9)); - // Note, hookdevice.ref_count is wrong now since we bypassed - // this function during unload (no re-entrancy). however the count on the - // device should be 1 if I did the math right, anyway the release below - // will fix the count. + // purge my stuff + write_log_file(&format!( + "device {:x} refcount is same as internal resource count ({}), + it is being destroyed: purging resources", + THIS as usize, drc + )); + purge_device_resources(DevicePointer::D3D9(THIS as *mut IDirect3DDevice9)); + // Note, ref_count is wrong now since we bypassed this function + // during unload (no re-entrancy). However the count on the device + // should be 1 if I did the math right; the release below will + // fix the count. Re-evaluate the final-release condition after + // purge since d3d_resource_count may have changed. + if let Some((_lck, ds)) = dev_state_read() { + let drc = ds.d3d_resource_count; + do_final_release = destroying || (drc == 0 && new_ref_count == 1); } + } - if destroying || (dev_state().d3d_resource_count == 0 && hookdevice.ref_count == 1) { - // release again to trigger destruction of the device - hookdevice.ref_count = (hookdevice.real_release)(THIS); + if do_final_release { + // release again to trigger destruction of the device + new_ref_count = real_release(THIS); + write_log_file(&format!( + "device released: {:x}, refcount: {}", + THIS as usize, new_ref_count + )); + if new_ref_count != 0 { write_log_file(&format!( - "device released: {:x}, refcount: {}", - THIS as usize, hookdevice.ref_count + "WARNING: unexpected ref count of {} after supposedly final + device release, device probably leaked", + new_ref_count )); - if hookdevice.ref_count != 0 { - write_log_file(&format!( - "WARNING: unexpected ref count of {} after supposedly final - device release, device probably leaked", - hookdevice.ref_count - )); + } + if let Some((_lck, ds)) = dev_state_write() { + if let Some(HookDeviceState::D3D9(HookD3D9State { d3d9: _, device: Some(dev) })) = ds.hook.as_mut() { + dev.ref_count = new_ref_count; } } - hookdevice.ref_count - }); + } + new_ref_count + })(); GLOBAL_STATE.in_hook_release = false; if r == failret { @@ -910,19 +964,27 @@ pub unsafe extern "system" fn hook_draw_indexed_primitive( profile_start!(hdip, state_begin); - let hookdevice = match dev_state().hook { - Some(HookDeviceState::D3D9(HookD3D9State { d3d9: _, device: Some(ref mut dev) })) => dev, - _ => { + let real_dip = match dev_state_read() { + Some((_lck, ds)) => match &ds.hook { + Some(HookDeviceState::D3D9(HookD3D9State { d3d9: _, device: Some(dev) })) => { + dev.real_draw_indexed_primitive + }, + _ => { + write_log_file(&format!("DIP: No d3d9 device found")); + return E_FAIL; + } + }, + None => { write_log_file(&format!("DIP: No d3d9 device found")); return E_FAIL; - }, + } }; profile_end!(hdip, state_begin); let metrics = &mut GLOBAL_STATE.metrics; if !GLOBAL_STATE.is_snapping && (metrics.low_framerate || !GLOBAL_STATE.show_mods || force_modding_off) { - return (hookdevice.real_draw_indexed_primitive)( + return (real_dip)( THIS, PrimitiveType, BaseVertexIndex, @@ -1027,7 +1089,7 @@ pub unsafe extern "system" fn hook_draw_indexed_primitive( None } }; - let r = (hookdevice.real_draw_indexed_primitive)( + let r = (real_dip)( THIS, PrimitiveType, BaseVertexIndex, diff --git a/Native/hook_core/src/hook_render_d3d11.rs b/Native/hook_core/src/hook_render_d3d11.rs index da6cddc9..bd725146 100644 --- a/Native/hook_core/src/hook_render_d3d11.rs +++ b/Native/hook_core/src/hook_render_d3d11.rs @@ -7,7 +7,7 @@ use std::time::{SystemTime, Duration}; use global_state::{GLOBAL_STATE, LOADED_MODS, METRICS_TRACK_MOD_PRIMS, HWND}; use mod_stats::mod_stats; use shared_dx::dx11rs::{DX11RenderState}; -use shared_dx::types::{HookDeviceState, DevicePointer, DX11Metrics, D3D11Tex}; +use shared_dx::types::{DevicePointer, DX11Metrics, D3D11Tex}; use shared_dx::types_dx11::{HookDirect3D11Context}; use shared_dx::util::{write_log_file, ReleaseOnDrop}; use types::TexPtr; @@ -29,7 +29,7 @@ use winapi::um::unknwnbase::IUnknown; use winapi::um::winuser::{EnumWindows, GetWindowThreadProcessId, GetParent, GetDesktopWindow, GetForegroundWindow}; use winapi::um::{d3d11::ID3D11DeviceContext, winnt::INT}; use winapi::shared::minwindef::UINT; -use device_state::{dev_state, dev_state_d3d11_nolock, dev_state_d3d11_write}; +use device_state::{dev_state_d3d11_read, dev_state_d3d11_write}; use shared_dx::error::{Result, HookError}; use crate::hook_device_d3d11::apply_context_hooks; use crate::hook_render::{process_metrics, frame_init_clr, frame_load_mods, check_and_render_mod, CheckRenderModResult, track_set_texture, get_override_tex_if_selected}; @@ -38,16 +38,18 @@ use winapi::um::d3d11::D3D11_BUFFER_DESC; use crate::debugmode::DebugModeCalledFns; use fnv::FnvHashMap; -/// Return the d3d11 context hooks. -fn get_hook_context<'a>() -> Result<&'a mut HookDirect3D11Context> { - let hooks = match dev_state().hook { - Some(HookDeviceState::D3D11(ref mut rs)) => &mut rs.hooks, - _ => { +/// Return the d3d11 context hooks. Returns a Copy of the hook fn pointers +/// so that callers do not need to hold the device-state lock while invoking +/// the real fns (which can re-enter our hooks on the same thread and would +/// otherwise deadlock). +fn get_hook_context() -> Result { + match dev_state_d3d11_read() { + Some((_lck, state)) => Ok(state.hooks.context), + None => { write_log_file("draw: No d3d11 context found"); - return Err(shared_dx::error::HookError::D3D11NoContext); - }, - }; - Ok(&mut hooks.context) + Err(shared_dx::error::HookError::D3D11NoContext) + } + } } pub fn u8_slice_to_hex_string(slice: &[u8]) -> String { @@ -203,12 +205,9 @@ pub unsafe extern "system" fn hook_IASetPrimitiveTopology ( // let _func_hooked = apply_context_hooks(THIS, false); // } - match dev_state_d3d11_nolock() { - Some(state) => { - state.rs.prim_topology = Topology; - }, - None => {} - }; + if let Some((_lck, state)) = dev_state_d3d11_write() { + state.rs.prim_topology = Topology; + } (hook_context.real_ia_set_primitive_topology)(THIS, Topology); } @@ -233,9 +232,8 @@ pub unsafe extern "system" fn hook_IASetVertexBuffers( // } // TODO11 use the lock function here or switch to thread local for RS - let state = dev_state_d3d11_nolock(); - match state { - Some(state) => { + match dev_state_d3d11_write() { + Some((_lck, state)) => { if NumBuffers > 0 && ppVertexBuffers != null_mut() { for idx in 0..NumBuffers { let pbuf = (*ppVertexBuffers).offset(idx as isize); @@ -311,13 +309,13 @@ pub unsafe extern "system" fn hook_IASetInputLayout( // } // TODO11 use the lock function here or switch to thread local for RS - dev_state_d3d11_nolock().map(|state| { + if let Some((_lck, state)) = dev_state_d3d11_write() { if pInputLayout != null_mut() { state.rs.current_input_layout = pInputLayout; } else { state.rs.current_input_layout = null_mut(); } - }); + } (hook_context.real_ia_set_input_layout)( THIS, @@ -502,7 +500,7 @@ pub unsafe extern "system" fn hook_draw_indexed( } // input needs faster processing but it won't update faster than 1 per 16ms - let fore = dev_state_d3d11_nolock().map(|state| state.app_foreground).unwrap_or(false); + let fore = dev_state_d3d11_read().map(|(_lck, state)| state.app_foreground).unwrap_or(false); if GLOBAL_STATE.metrics.dip_calls % 250 == 0 && fore { GLOBAL_STATE.input.as_mut().map(|inp| { if inp.get_press_fn_count() > 0 { @@ -576,14 +574,23 @@ pub unsafe extern "system" fn hook_draw_indexed( GLOBAL_STATE.bound_vertex_buffer, ); } - dev_state_d3d11_nolock().map(|state| { - let checkres = compute_prim_vert_count(IndexCount, &state.rs); - let (prim_count, vert_count) = checkres.unwrap_or_else(|| (0,0)); + // Snapshot scalar state needed for the snapshot under a brief read + // guard, then drop it before calling hook_snapshot::take which + // internally re-acquires the device-state lock. + let snap_inputs = match dev_state_d3d11_read() { + Some((_lck, state)) => { + let checkres = compute_prim_vert_count(IndexCount, &state.rs); + let (prim_count, vert_count) = checkres.unwrap_or_else(|| (0,0)); + Some((state.rs.prim_topology, prim_count, vert_count, state.devptr)) + }, + None => None, + }; + if let Some((prim_topology, prim_count, vert_count, mut devptr)) = snap_inputs { let mut sd = types::interop::SnapshotData { sd_size: std::mem::size_of::() as u32, was_reset: false, clear_sd_on_reset: false, - prim_type: state.rs.prim_topology as i32, + prim_type: prim_topology as i32, base_vertex_index: BaseVertexLocation, min_vertex_index: 0, num_vertices: vert_count, @@ -594,8 +601,8 @@ pub unsafe extern "system" fn hook_draw_indexed( d3d11: D3D11SnapshotRendData::new(), }, }; - hook_snapshot::take(&mut state.devptr, &mut sd, this_is_selected); - }); + hook_snapshot::take(&mut devptr, &mut sd, this_is_selected); + } } @@ -603,126 +610,151 @@ pub unsafe extern "system" fn hook_draw_indexed( profile_start!(hdi, geom_check); - // TODO11 use the lock function here or switch to thread local for RS - let state = dev_state_d3d11_nolock(); - let draw_input = state.map(|state| { - // this is the only prim type I support but don't log if it is something else since - // it would be spammy (maybe log if trying to take a snapshot) - if state.rs.prim_topology != D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST { + // Snapshot just enough state to drive the geom/mod check, then drop the + // device-state read guard before calling code that may itself re-acquire + // it (ensure_vb_checksum_dx11, hook_snapshot::take, etc). + let geom = match dev_state_d3d11_read() { + Some((_lck, state)) => { + if state.rs.prim_topology != D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST { + profile_end!(hdi, geom_check); + None + } else { + Some((compute_prim_vert_count(IndexCount, &state.rs), state.rs.vb_state.clone())) + } + }, + None => { profile_end!(hdi, geom_check); - return true; + // No state -- behave as if no triangle list available, so just + // draw the input. + let _: Option<()> = None; + // signal "no mod state" by returning early from the surrounding + // logic; we emulate the previous `state.map(...).unwrap_or(true)` + // behavior of returning draw_input=true. + None } - let checkres = compute_prim_vert_count(IndexCount, &state.rs); - profile_end!(hdi, geom_check); + }; + let draw_input = match geom { + None => { + // either prim_topology mismatch or no state -- just draw the input + true + }, + Some((checkres, vb_state_clone)) => { + profile_end!(hdi, geom_check); - match checkres { - Some((prim_count,vert_count)) if vert_count > 2 => { - // if primitive tracking is enabled, log just the primcount,vertcount if we were able - // to compute it, otherwise log whatever we have - if global_state::METRICS_TRACK_PRIMS && prim_count > 2 { // filter out some spammy useless stuff - if vert_count > 0 { - use global_state::RenderedPrimType::PrimVertCount; - GLOBAL_STATE.metrics.rendered_prims.push(PrimVertCount(prim_count, vert_count)) - } else { - use global_state::RenderedPrimType::PrimCountVertSizeAndVBs; - GLOBAL_STATE.metrics.rendered_prims.push( - PrimCountVertSizeAndVBs(prim_count, vert_count, state.rs.vb_state.clone())); + match checkres { + Some((prim_count,vert_count)) if vert_count > 2 => { + // if primitive tracking is enabled, log just the primcount,vertcount if we were able + // to compute it, otherwise log whatever we have + if global_state::METRICS_TRACK_PRIMS && prim_count > 2 { // filter out some spammy useless stuff + if vert_count > 0 { + use global_state::RenderedPrimType::PrimVertCount; + GLOBAL_STATE.metrics.rendered_prims.push(PrimVertCount(prim_count, vert_count)) + } else { + use global_state::RenderedPrimType::PrimCountVertSizeAndVBs; + GLOBAL_STATE.metrics.rendered_prims.push( + PrimCountVertSizeAndVBs(prim_count, vert_count, vb_state_clone)); + } } - } - - // Compute the VB Crc if snapping or if the bound vb matches this prim/vert count - if !GLOBAL_STATE.is_snapping - && GLOBAL_STATE.bound_vertex_buffer != 0 - && global_state::vb_checksum_target_matches(prim_count, vert_count) - { - crate::hook_device_d3d11::ensure_vb_checksum_dx11( - GLOBAL_STATE.bound_vertex_buffer, - ); - } - // if there is a matching mod, render it - profile_start!(hdi, mod_precheck); - let quickcheck = match LOADED_MODS.lock() { - Ok(mut g) => g.as_mut().map( - |mods| mod_render::preselect(mods, prim_count, vert_count)) - .unwrap_or(false), - Err(e) => { - write_log_file(&format!("preselect: LOADED_MODS lock poisoned: {}", e)); - false + // Compute the VB Crc if snapping or if the bound vb matches this prim/vert count. + // No dev_state guard is held here so this is safe to call. + if !GLOBAL_STATE.is_snapping + && GLOBAL_STATE.bound_vertex_buffer != 0 + && global_state::vb_checksum_target_matches(prim_count, vert_count) + { + crate::hook_device_d3d11::ensure_vb_checksum_dx11( + GLOBAL_STATE.bound_vertex_buffer, + ); } - }; - let mod_status = if !quickcheck { - profile_end!(hdi, mod_precheck); - // this write_log_file can be used to get information about misses. - // consider lowing the log LOG_LIMIT counts in util.rs before uncommenting this, as it will be incredibly - // spammy. wait until it stabilizes and then try to produce the event which makes the draw you are interested in. - //write_log_file(&format!("miss: {}p/{}v;s={};b={}", prim_count,vert_count, StartIndexLocation, BaseVertexLocation)); - CheckRenderModResult::NotRendered - } else { - profile_end!(hdi, mod_precheck); - profile_start!(hdi, mod_check); - let mod_status = check_and_render_mod(prim_count, vert_count, - |d3dd,nmod| { - profile_start!(hdi, mod_render); - let res = if let ModD3DData::D3D11(d3d11d) = d3dd { - render_mod_d3d11(THIS, hook_context, d3d11d, nmod, override_texture, sel_stage, (prim_count,vert_count)) - } else { - false - }; - profile_end!(hdi, mod_render); - res - }); - profile_end!(hdi, mod_check); - mod_status - }; - profile_start!(hdi, post_mod_check); - use types::interop::ModType::GPUAdditive; - let draw_input = match mod_status { - CheckRenderModResult::NotRendered => true, - CheckRenderModResult::Rendered(mtype) if GPUAdditive as i32 == mtype => true, - CheckRenderModResult::Rendered(_) => false, // non-additive mod was rendered - CheckRenderModResult::Deleted => false, - CheckRenderModResult::NotRenderedButLoadRequested(ref name) => { - // setup data to begin mod load - match LOADED_MODS.lock() { - Ok(mut loaded_mods_guard) => { - let nmod = mod_load::get_mod_by_name(name, &mut *loaded_mods_guard); - if let Some(nmod) = nmod { - // need to store current input layout in the d3d data - if let ModD3DState::Unloaded = nmod.d3d_data { - let il = state.rs.current_input_layout; - if !il.is_null() { - // we're officially keeping an extra reference to the input layout now - // so note that. - (*il).AddRef(); - nmod.d3d_data = ModD3DState::Partial( - ModD3DData::D3D11(ModD3DData11::with_layout(il))); - write_log_file(&format!("created partial mod load state for mod {}", nmod.name)); - //write_log_file(&format!("current in layout is: {}", il as usize)); + // if there is a matching mod, render it + profile_start!(hdi, mod_precheck); + let quickcheck = match LOADED_MODS.lock() { + Ok(mut g) => g.as_mut().map( + |mods| mod_render::preselect(mods, prim_count, vert_count)) + .unwrap_or(false), + Err(e) => { + write_log_file(&format!("preselect: LOADED_MODS lock poisoned: {}", e)); + false + } + }; + let mod_status = if !quickcheck { + profile_end!(hdi, mod_precheck); + CheckRenderModResult::NotRendered + } else { + profile_end!(hdi, mod_precheck); + profile_start!(hdi, mod_check); + let mod_status = check_and_render_mod(prim_count, vert_count, + |d3dd,nmod| { + profile_start!(hdi, mod_render); + let res = if let ModD3DData::D3D11(d3d11d) = d3dd { + render_mod_d3d11(THIS, &hook_context, d3d11d, nmod, override_texture, sel_stage, (prim_count,vert_count)) + } else { + false + }; + profile_end!(hdi, mod_render); + res + }); + profile_end!(hdi, mod_check); + mod_status + }; + + profile_start!(hdi, post_mod_check); + use types::interop::ModType::GPUAdditive; + let draw_input = match mod_status { + CheckRenderModResult::NotRendered => true, + CheckRenderModResult::Rendered(mtype) if GPUAdditive as i32 == mtype => true, + CheckRenderModResult::Rendered(_) => false, // non-additive mod was rendered + CheckRenderModResult::Deleted => false, + CheckRenderModResult::NotRenderedButLoadRequested(ref name) => { + // setup data to begin mod load. Read the + // current input layout pointer briefly under the + // dev_state lock; LOADED_MODS is a separate + // mutex so it is safe to take it inside the + // dev_state guard. + let il = match dev_state_d3d11_read() { + Some((_lck, state)) => state.rs.current_input_layout, + None => null_mut(), + }; + match LOADED_MODS.lock() { + Ok(mut loaded_mods_guard) => { + let nmod = mod_load::get_mod_by_name(name, &mut *loaded_mods_guard); + if let Some(nmod) = nmod { + // need to store current input layout in the d3d data + if let ModD3DState::Unloaded = nmod.d3d_data { + if !il.is_null() { + // we're officially keeping an extra reference to the input layout now + // so note that. + (*il).AddRef(); + nmod.d3d_data = ModD3DState::Partial( + ModD3DData::D3D11(ModD3DData11::with_layout(il))); + write_log_file(&format!("created partial mod load state for mod {}", nmod.name)); + } } } } + Err(e) => { + write_log_file(&format!("NotRenderedButLoadRequested: LOADED_MODS lock poisoned: {}", e)); + } } - Err(e) => { - write_log_file(&format!("NotRenderedButLoadRequested: LOADED_MODS lock poisoned: {}", e)); - } + true + }, + }; + + // update metrics + if METRICS_TRACK_MOD_PRIMS { + if let Some((_lck, state)) = dev_state_d3d11_write() { + update_drawn_recently(&mut state.metrics, prim_count, vert_count, &mod_status); } - true - }, - }; - - // update metrics - if METRICS_TRACK_MOD_PRIMS { - update_drawn_recently(&mut state.metrics, prim_count, vert_count, &mod_status); - } - profile_end!(hdi, post_mod_check); + } + profile_end!(hdi, post_mod_check); - draw_input - }, - _ => true + draw_input + }, + _ => true + } } - }).unwrap_or(true); + }; if draw_input { profile_start!(hdi, draw_ovtex_check); @@ -768,36 +800,36 @@ pub unsafe extern "system" fn hook_draw_indexed( } /// Call a function with the d3d11 device pointer if it's available. If pointer is a different, -/// type or is null, does nothing. +/// type or is null, does nothing. The dev_state guard is dropped before the +/// closure runs so it can re-acquire the lock without deadlocking. fn with_dev_ptr(f: F) where F: FnOnce(DevicePointer) { - match dev_state().hook { - Some(HookDeviceState::D3D11(ref dev)) => { - if !dev.devptr.is_null() { - f(dev.devptr); - } - } - _ => {}, + let devptr = match dev_state_d3d11_read() { + Some((_lck, state)) if !state.devptr.is_null() => Some(state.devptr), + _ => None, }; + if let Some(dp) = devptr { + f(dp); + } } use winapi::shared::minwindef::BOOL; use winapi::shared::minwindef::TRUE; unsafe extern "system" fn enum_windows_proc(hwnd:HWND, lparam:isize) -> BOOL { - dev_state_d3d11_nolock().map(|state| { + if let Some((_lck, state)) = dev_state_d3d11_write() { // get the process id that owns the window let mut pid = 0; GetWindowThreadProcessId(hwnd, &mut pid); if pid == lparam as u32 { state.app_hwnds.push(hwnd); } - }); + } TRUE } /// Enumerate application top level windows amd return their handles in a vector. unsafe fn find_app_windows() { - if let Some(state) = dev_state_d3d11_nolock() { state.app_hwnds.clear(); } + if let Some((_lck, state)) = dev_state_d3d11_write() { state.app_hwnds.clear(); } // get my process id let my_pid = GetCurrentProcessId(); @@ -864,8 +896,7 @@ fn expire_data(now:&SystemTime, last_data_expire:&SystemTime, clear_list:&mut Ve if elapsed > min { let start = SystemTime::now(); let mut cleartype = ""; - let (expired_els,total_els) = unsafe { - dev_state_d3d11_write()}.map(|(_lock,state)| { + let (expired_els,total_els) = dev_state_d3d11_write().map(|(_lock,state)| { state.last_data_expire = *now; checked = true; let expire_dur = Duration::from_secs(DEF_EXPIRE_DUR_SECS); @@ -954,7 +985,7 @@ fn expire_data(now:&SystemTime, last_data_expire:&SystemTime, clear_list:&mut Ve unsafe fn time_based_update(mselapsed:u128, now:SystemTime, context:*mut ID3D11DeviceContext) { if mselapsed > 500 { - if let Some(state) = dev_state_d3d11_nolock() { + if let Some((_lck, state)) = dev_state_d3d11_write() { state.last_timebased_update = now; } // keep the frame counter rolling even though we don't know when the frames end. some @@ -970,20 +1001,20 @@ unsafe fn time_based_update(mselapsed:u128, now:SystemTime, context:*mut ID3D11D let mut devptr = null_mut(); (*context).GetDevice(&mut devptr); if !devptr.is_null() { - dev_state_d3d11_nolock().map(|state| { + if let Some((_lck, state)) = dev_state_d3d11_write() { if state.devptr.maybe_update(devptr) { // a bit weird so log it write_log_file("Warning: device pointer changed"); } - }); + } (*devptr).Release(); } - let need_layout_copy = unsafe {dev_state_d3d11_nolock()}.map(|state| { + let need_layout_copy = dev_state_d3d11_read().map(|(_lck, state)| { state.rs.num_input_layouts.load(Ordering::Relaxed) > 0 }).unwrap_or(false); if need_layout_copy { - unsafe {dev_state_d3d11_write()}.map(|(_lock,state)| { + dev_state_d3d11_write().map(|(_lock,state)| { // copy new layouts. note currently we don't clear the context layouts ever. // maybe hook release on them at some point so we can dispose of those entries. state.rs.context_input_layouts_by_ptr.extend( @@ -1009,20 +1040,20 @@ unsafe fn time_based_update(mselapsed:u128, now:SystemTime, context:*mut ID3D11D frame_load_mods(deviceptr); }); - let wnd_count = dev_state_d3d11_nolock().map(|state| { + let wnd_count = dev_state_d3d11_read().map(|(_lck, state)| { state.app_hwnds.len() }).unwrap_or(0); if wnd_count == 0 { find_app_windows(); let dw = GetDesktopWindow(); - dev_state_d3d11_nolock().map(|state| { + if let Some((_lck, state)) = dev_state_d3d11_write() { //let ocount = state.app_hwnds.len(); let wnds:Vec = state.app_hwnds.iter().filter(|wnd| { **wnd != dw && GetParent(**wnd).is_null() }).copied().collect(); state.app_hwnds = wnds; //write_log_file(&format!("found {} app windows, filtered to: {:?}", ocount, state.app_hwnds)); - }); + } } if GLOBAL_STATE.input.is_none() { @@ -1041,7 +1072,7 @@ unsafe fn time_based_update(mselapsed:u128, now:SystemTime, context:*mut ID3D11D // find the app main foreground window let fwnd = GetForegroundWindow(); - let appfwnd = dev_state_d3d11_nolock().and_then(|state| { + let appfwnd = dev_state_d3d11_read().and_then(|(_lck, state)| { state.app_hwnds.iter().find(|wnd| { **wnd == fwnd }).copied() @@ -1052,7 +1083,7 @@ unsafe fn time_based_update(mselapsed:u128, now:SystemTime, context:*mut ID3D11D util::appwnd_is_foreground(hwnd) }).unwrap_or(false); - if let Some(state) = dev_state_d3d11_nolock() { state.app_foreground = app_foreground; } + if let Some((_lck, state)) = dev_state_d3d11_write() { state.app_foreground = app_foreground; } // finish input setup if needed and app is foreground if app_foreground { @@ -1080,7 +1111,7 @@ fn draw_periodic(context:*mut ID3D11DeviceContext) { unsafe { let now = SystemTime::now(); let (el_sec,el_ms) = - dev_state_d3d11_nolock().map(|state| { + dev_state_d3d11_read().map(|(_lck, state)| { let elapsed = now.duration_since(state.last_timebased_update); match elapsed { @@ -1095,7 +1126,7 @@ fn draw_periodic(context:*mut ID3D11DeviceContext) { } } -unsafe fn render_mod_d3d11(context:*mut ID3D11DeviceContext, hook_context: &mut HookDirect3D11Context, +unsafe fn render_mod_d3d11(context:*mut ID3D11DeviceContext, hook_context: &HookDirect3D11Context, d3dd:&ModD3DData11, _nmod:&NativeModData, override_texture: *mut ID3D11ShaderResourceView, override_stage:u32, _primVerts:(u32,u32)) -> bool { @@ -1264,11 +1295,17 @@ fn create_selection_texture_dx11() -> Result<()> { let mut srv: *mut ID3D11ShaderResourceView = null_mut(); unsafe { - let dp = dev_state_d3d11_nolock().map(|ds| &mut ds.devptr); - - let device = match dp { - Some(DevicePointer::D3D11(dev)) => *dev, - _ => return Err(HookError::D3D11NoContext), + // Pull the device pointer out and drop the dev_state guard before + // invoking CreateTexture2D, which is itself hooked. + let device = { + let dp = match dev_state_d3d11_read() { + Some((_lck, ds)) => Some(ds.devptr), + None => None, + }; + match dp { + Some(DevicePointer::D3D11(dev)) => dev, + _ => return Err(HookError::D3D11NoContext), + } }; let hr = (*device).CreateTexture2D( diff --git a/Native/hook_core/src/input_commands.rs b/Native/hook_core/src/input_commands.rs index f82af82c..b3353530 100644 --- a/Native/hook_core/src/input_commands.rs +++ b/Native/hook_core/src/input_commands.rs @@ -10,7 +10,7 @@ use fnv::FnvHashSet; use std::ptr::null_mut; use shared_dx::util::*; use global_state::{GLOBAL_STATE, LOADED_MODS}; -use device_state::dev_state; +use device_state::dev_state_write; use crate::hook_device_d3d11::apply_device_hook; use crate::hook_device_d3d11::query_and_set_runconf_in_globalstate; use crate::hook_render::hook_set_texture; @@ -513,7 +513,9 @@ pub (crate) fn create_selection_texture_d3d9(device: *mut IDirect3DDevice9) { let post_rc = (*device).Release(); let diff = post_rc - pre_rc; - dev_state().d3d_resource_count += diff; + if let Some((_lck, ds)) = dev_state_write() { + ds.d3d_resource_count += diff; + } GLOBAL_STATE.selection_texture = Some(TexPtr::D3D9(tex)); } diff --git a/Native/hook_snapshot/src/hook_snapshot.rs b/Native/hook_snapshot/src/hook_snapshot.rs index f233c3ea..e03dc1c5 100644 --- a/Native/hook_snapshot/src/hook_snapshot.rs +++ b/Native/hook_snapshot/src/hook_snapshot.rs @@ -1,4 +1,3 @@ -use device_state::dev_state_d3d11_nolock; use device_state::dev_state_d3d11_read; use global_state::get_global_state_ptr; use global_state::HookState; @@ -654,7 +653,10 @@ impl SnapDeviceBuffers for D3D11SnapDeviceBuffers{ } unsafe fn set_buffers_d3d11(device:*mut ID3D11Device, sd:&mut types::interop::SnapshotData) -> Result> { - if let Some(state) = dev_state_d3d11_nolock() { + // Hold a single read guard for the duration so that we don't acquire the + // device-state lock multiple times (which can deadlock on RwLock if a + // writer queues up between two reads on the same thread). + if let Some((_state_lck, state)) = dev_state_d3d11_read() { let vf = state.rs.get_current_vertex_format().ok_or_else(|| { HookError::SnapshotFailed("no current input layout, cannot snap".to_string()) })?; @@ -698,10 +700,9 @@ unsafe fn set_buffers_d3d11(device:*mut ID3D11Device, sd:&mut types::interop::Sn // determine if we have the data for the buffer since at this time we can't read it // directly via Map - let ib_copy = dev_state_d3d11_read() - .map(|(_lock,ds)| { - ds.rs.device_index_buffer_data.get(&(curr_ibuffer as usize)).map(|v| v.clone()) - }).flatten() + let ib_copy = state.rs.device_index_buffer_data + .get(&(curr_ibuffer as usize)) + .map(|v| v.clone()) .ok_or_else(|| { HookError::SnapshotFailed("failed to get index buffer data, was not previously saved".to_string()) })?; @@ -744,11 +745,10 @@ unsafe fn set_buffers_d3d11(device:*mut ID3D11Device, sd:&mut types::interop::Sn return Err(HookError::SnapshotFailed(format!("more than 1 vertex buffer not supported (got {})", curr_vbuffers.len()))); } // copy the data - let vb_copy = dev_state_d3d11_read() - .map(|(_lock,ds)| { - let vb_usize = *curr_vbuffers[0] as usize; - ds.rs.device_vertex_buffer_data.get(&vb_usize).map(|v| v.clone()) - }).flatten() + let vb_copy = { + let vb_usize = *curr_vbuffers[0] as usize; + state.rs.device_vertex_buffer_data.get(&vb_usize).map(|v| v.clone()) + } .ok_or_else(|| { HookError::SnapshotFailed("failed to get vertex buffer data, was not previously saved".to_string()) })?; diff --git a/Native/mod_load/src/mod_load.rs b/Native/mod_load/src/mod_load.rs index efb120f8..a7e6d72c 100644 --- a/Native/mod_load/src/mod_load.rs +++ b/Native/mod_load/src/mod_load.rs @@ -87,13 +87,15 @@ pub unsafe fn clear_loaded_mods(device: DevicePointer) { let post_rc = (device).get_ref_count(); let diff = pre_rc - post_rc; - if (dev_state().d3d_resource_count as i64 - diff as i64) < 0 { - write_log_file(&format!( - "DOH resource count would go below zero (curr: {}, removed {}),", - dev_state().d3d_resource_count, diff - )); - } else { - dev_state().d3d_resource_count -= diff; + if let Some((_lck, ds)) = dev_state_write() { + if (ds.d3d_resource_count as i64 - diff as i64) < 0 { + write_log_file(&format!( + "DOH resource count would go below zero (curr: {}, removed {}),", + ds.d3d_resource_count, diff + )); + } else { + ds.d3d_resource_count -= diff; + } } write_log_file(&format!("unloaded {} mods", cnt)); @@ -286,10 +288,12 @@ pub unsafe fn load_d3d_data11(device: *mut ID3D11Device, callbacks: interop::Man write_log_file(&format!("Error, d3d11 data for mod {} has not been partially loaded", nmd.name)); return false; }; - // lookup actual layout data in render state using the pointer + // lookup actual layout data in render state using the pointer. Clone out + // what we need so the read guard does not have to be held across the + // (potentially callback-heavy) work below. let vlayout = { - match dev_state_d3d11_nolock() { - Some(state) => { + match dev_state_d3d11_read() { + Some((_lck, state)) => { let layout_usize = vlayout as usize; let res = state.rs.context_input_layouts_by_ptr .get(&layout_usize); @@ -301,7 +305,7 @@ pub unsafe fn load_d3d_data11(device: *mut ID3D11Device, callbacks: interop::Man )); return false; }, - Some(vf) => vf, + Some(vf) => vf.shallow_copy(), } }, _ => { @@ -774,7 +778,7 @@ pub fn get_mod_by_index<'a>(midx: i32, loaded_mods: &'a mut Option u32 { - match dev_state_d3d11_write() { + match dev_state_d3d11_read() { Some((_lck, _state)) => { device.get_ref_count() }, @@ -788,10 +792,10 @@ pub unsafe fn update_ref_count(device:DevicePointer, pre_rc:u32) -> (u32,u32) { let post_rc = get_dev_ref_count(device); let diff = post_rc.saturating_sub(pre_rc); if diff > 0 { - match dev_state_d3d11_write() { - Some((_lck, _state)) => { - (*DEVICE_STATE).d3d_resource_count += diff; - (diff,(*DEVICE_STATE).d3d_resource_count) + match dev_state_write() { + Some((_lck, ds)) => { + ds.d3d_resource_count += diff; + (diff, ds.d3d_resource_count) }, None => { (0,0) @@ -840,7 +844,10 @@ pub unsafe fn load_deferred_mods(device: DevicePointer, callbacks: interop::Mana let pre_rc = device.get_ref_count(); let mut cnt = 0; - let can_load_in_thread = (*DEVICE_STATE).multithreaded(); + let can_load_in_thread = match dev_state_read() { + Some((_lck, ds)) => ds.multithreaded(), + None => false, + }; let mut loaded_mods_guard = match LOADED_MODS.lock() { Ok(g) => g, diff --git a/Native/shared_dx/src/types_dx11.rs b/Native/shared_dx/src/types_dx11.rs index 97911cc1..0e1f031b 100644 --- a/Native/shared_dx/src/types_dx11.rs +++ b/Native/shared_dx/src/types_dx11.rs @@ -1,11 +1,13 @@ use crate::defs_dx11::*; +#[derive(Clone, Copy)] pub struct HookDirect3D11Device { pub real_create_buffer: CreateBufferFn, pub real_create_texture_2d: CreateTexture2DFn, pub real_query_interface: QueryInterfaceFn, pub real_create_input_layout: CreateInputLayoutFn } +#[derive(Clone, Copy)] pub struct HookDirect3D11Context { pub real_query_interface: QueryInterfaceFn, pub real_release: IUnknownReleaseFn, @@ -22,6 +24,7 @@ pub struct HookDirect3D11Context { pub real_ia_set_primitive_topology: IASetPrimitiveTopologyFn, pub real_ps_set_shader_resources: PSSetShaderResourcesFn, } +#[derive(Clone, Copy)] pub struct HookDirect3D11 { pub context: HookDirect3D11Context, } \ No newline at end of file