mod data;
use core::{ffi::c_void, mem};
use std::ffi::CString;
use anyhow::Context;
use asdf_overlay_hook::DetourHook;
use dashmap::Entry;
use once_cell::sync::{Lazy, OnceCell};
use tracing::{debug, error, trace};
use windows::{
Win32::{
Foundation::{HMODULE, HWND, LUID},
Graphics::{
Dxgi::{CreateDXGIFactory1, IDXGIFactory1},
Gdi::{HDC, WindowFromDC},
OpenGL::{
HGLRC, wglGetCurrentContext, wglGetCurrentDC, wglGetProcAddress, wglMakeCurrent,
},
},
System::LibraryLoader::{GetModuleHandleA, GetProcAddress},
},
core::{BOOL, PCSTR, s},
};
use crate::{
backend::{Backends, WindowBackend, render::Renderer},
event_sink::OverlayEventSink,
gl,
hook::opengl::data::with_renderer_gl_data,
renderer::opengl::OpenglRenderer,
types::IntDashMap,
util::find_adapter_by_luid,
wgl,
};
struct Hook {
wgl_delete_context: DetourHook<WglDeleteContextFn>,
wgl_swap_buffers: DetourHook<WglSwapBuffersFn>,
}
static HOOK: OnceCell<Hook> = OnceCell::new();
struct GlData {
hglrc: u32,
hwnd: u32,
renderer: Option<OpenglRenderer>,
}
static MAP: Lazy<IntDashMap<u32, GlData>> = Lazy::new(IntDashMap::default);
#[tracing::instrument]
pub fn hook(dummy_hwnd: HWND) {
fn inner() -> anyhow::Result<()> {
let addrs = get_wgl_addrs().context("failed to load opengl addrs")?;
HOOK.get_or_try_init(|| unsafe {
debug!("hooking WglDeleteContext");
let wgl_delete_context =
DetourHook::attach(addrs.delete_context, hooked_wgl_delete_context as _)?;
debug!("hooking WglSwapBuffers");
let wgl_swap_buffers =
DetourHook::attach(addrs.swap_buffers, hooked_wgl_swap_buffers as _)?;
Ok::<_, anyhow::Error>(Hook {
wgl_delete_context,
wgl_swap_buffers,
})
})?;
Ok(())
}
if let Err(err) = inner() {
error!("failed to hook opengl. err: {err:?}");
}
}
#[tracing::instrument]
extern "system" fn hooked_wgl_delete_context(hglrc: HGLRC) -> BOOL {
trace!("wglDeleteContext called");
let current_hdc = unsafe { wglGetCurrentDC() };
let current_hglrc = unsafe { wglGetCurrentContext() };
let mut renderer_cleanup = false;
MAP.retain(|&hdc, gl_data| {
if gl_data.hglrc != hglrc.0 as u32 {
return true;
}
if !renderer_cleanup {
renderer_cleanup = true;
}
debug!(
"gl renderer cleanup hdc: {hdc:x} hwnd: {:x} hglrc: {:x}",
gl_data.hwnd, gl_data.hglrc
);
unsafe {
_ = wglMakeCurrent(HDC(hdc as _), HGLRC(gl_data.hglrc as _));
}
false
});
if renderer_cleanup {
unsafe {
_ = wglMakeCurrent(current_hdc, current_hglrc);
}
}
unsafe { HOOK.wait().wgl_delete_context.original_fn()(hglrc) }
}
fn draw_overlay(hdc: HDC) {
#[inline]
fn inner(backend: &WindowBackend, renderer: &mut Option<OpenglRenderer>) {
let mut render = backend.render.lock();
match render.renderer {
Some(Renderer::Opengl) => {}
Some(_) => {
trace!("ignoring opengl rendering");
return;
}
None => {
debug!("Found opengl window");
render.renderer = Some(Renderer::Opengl);
return;
}
}
trace!("using opengl renderer");
with_renderer_gl_data(|| {
let renderer = match renderer {
Some(renderer) => renderer,
None => {
debug!("initializing opengl renderer");
renderer.insert(match OpenglRenderer::new() {
Ok(renderer) => renderer,
Err(err) => {
error!("renderer setup failed. err: {:?}", err);
return;
}
})
}
};
let Some(surface) = render.surface.get() else {
return;
};
let size = surface.size();
let position = render.position;
let screen = render.window_size;
if render.surface.invalidate_update()
&& let Err(err) =
renderer.update_texture(render.surface.get().map(|surface| surface.texture()))
{
error!("failed to update opengl texture. err: {err:?}");
return;
}
let _res = renderer.draw(position, size, screen);
trace!("opengl render: {:?}", _res);
})
}
if !OverlayEventSink::connected() {
return;
}
if !gl::GetIntegerv::is_loaded() {
debug!("setting up opengl");
if let Err(err) = setup_gl() {
error!("opengl setup failed. err: {:?}", err);
return;
}
}
let data = &mut *match MAP.entry(hdc.0 as u32) {
Entry::Occupied(entry) => entry.into_ref(),
Entry::Vacant(entry) => {
let hglrc = unsafe { wglGetCurrentContext() };
if hglrc.is_invalid() {
return;
}
let hwnd = unsafe { WindowFromDC(hdc) };
if hwnd.is_invalid() {
return;
}
entry.insert(GlData {
hglrc: hglrc.0 as _,
hwnd: hwnd.0 as _,
renderer: None,
})
}
};
let res = Backends::with_or_init_backend(
data.hwnd,
|| {
let mut luid = LUID::default();
unsafe {
_ = gl::GetError();
gl::GetUnsignedBytevEXT(gl::DEVICE_LUID_EXT, &mut luid as *mut _ as _);
if gl::GetError() != gl::NO_ERROR {
return None;
}
}
let factory = unsafe { CreateDXGIFactory1::<IDXGIFactory1>().ok()? };
find_adapter_by_luid(&factory, luid)
},
|backend| inner(backend, &mut data.renderer),
);
match res {
Ok(_) => {}
Err(_err) => {
error!("Backends::with_or_init_backend failed. err: {:?}", _err);
}
}
}
#[tracing::instrument]
extern "system" fn hooked_wgl_swap_buffers(hdc: HDC) -> BOOL {
trace!("WglSwapBuffers called");
draw_overlay(hdc);
unsafe { HOOK.wait().wgl_swap_buffers.original_fn()(hdc) }
}
type WglSwapBuffersFn = unsafe extern "system" fn(HDC) -> BOOL;
type WglDeleteContextFn = unsafe extern "system" fn(HGLRC) -> BOOL;
struct WglAddrs {
delete_context: WglDeleteContextFn,
swap_buffers: WglSwapBuffersFn,
}
#[tracing::instrument]
fn get_wgl_addrs() -> anyhow::Result<WglAddrs> {
let opengl32module = unsafe { GetModuleHandleA(s!("opengl32.dll"))? };
let func = unsafe {
GetProcAddress(opengl32module, s!("wglDeleteContext"))
.context("wglDeleteContext not found")?
};
debug!("wglDeleteContext found: {:p}", func);
let delete_context =
unsafe { mem::transmute::<unsafe extern "system" fn() -> isize, WglDeleteContextFn>(func) };
let func = unsafe {
GetProcAddress(opengl32module, s!("wglSwapBuffers")).context("wglSwapBuffers not found")?
};
debug!("WglSwapBuffers found: {:p}", func);
let swap_buffers =
unsafe { mem::transmute::<unsafe extern "system" fn() -> isize, WglSwapBuffersFn>(func) };
Ok(WglAddrs {
delete_context,
swap_buffers,
})
}
#[tracing::instrument]
fn setup_gl() -> anyhow::Result<()> {
let opengl32module = unsafe { GetModuleHandleA(s!("opengl32.dll"))? };
#[tracing::instrument]
fn loader(module: HMODULE, s: &str) -> *const c_void {
let name = CString::new(s).unwrap();
let addr = unsafe {
let addr = PCSTR(name.as_ptr() as _);
let fn_ptr = wglGetProcAddress(addr);
if let Some(ptr) = fn_ptr {
ptr as _
} else {
GetProcAddress(module, addr).map_or(std::ptr::null(), |fn_ptr| fn_ptr as *const _)
}
};
trace!("found: {:p}", addr);
addr
}
wgl::load_with(|s| loader(opengl32module, s));
gl::load_with(|s| loader(opengl32module, s));
Ok(())
}