...but for some reason you might know me as John.
I enjoy systems development, graphics programming and reverse engineering.
If you get distracted for a second, I will rewrite your entire codebase in Rust.
A videogame overlay framework written in Rust, supporting DirectX and OpenGL
License: MIT License
...but for some reason you might know me as John.
I enjoy systems development, graphics programming and reverse engineering.
If you get distracted for a second, I will rewrite your entire codebase in Rust.
title says everything
Hi there!
Recently found this project and tested v0.2.0 on a few games, notably Grand Theft Auto series. Here is what I got:
GTA III, GTA VC - did not work. They are old and use DirectX 8 so this might be expected. Tried d3d8to9, but no luck. Unofficial re-implementations known as re3/reVC worked since they use DX9.
GTA SA - worked with DX9 hook
Bully: SE 1.200 worked with DX9 hook
GTA Trilogy: The Definitive Edition - worked (x64 build with DX11 hook)
GTA V - worked (x64 build with DX11 hook)
GTA IV (The Complete Edition, Steam) - did not work. Tried both DX9 and DX11, tried injecting the hook on game launch or after a few minutes of playing. The game seems to function normally, but the overlay window simply does not appear.
I understand the tool is in its early stages but maybe you can share some clues on where to look at to find a reason why it did not work on GTA IV?
Thanks in advance and good luck with the project.
As both detours-rs
and detours2
look unmaintained, we should switch back to using minhook
as hooking backend.
Unfortunately this is blocking the 0.3.0 release and will have to be completed before that can be released.
In general, when using the custom window procedure things get a bit hairy, resize stops working and focus acts weird.
I think a good enough solution would be to have the option to call the original WindowProc when input is not being used (i.e. the 'mod' is not capturing anything).
If that sounds ok to you I might try to do it.
Up until now, constructing a Hook
trait object is considered infallible and panics at will. Implementations are peppered with unwrap
s. It would be best to change its signature so that it returns Result
, in order to allow managing an orderly cleanup.
Same as #38.
In common.rs, we have a HookableBackend
trait that is implemented for all our current supported backends.
In the interest of extensibility, it would probably be easier to remove the trait and find a different solution. The objective is that implementors of external hooks can rely on a simpler contract.
Hi, thanks again for this awesome tool. I’ve been poking around with my game tool again and have ran into an issue. It seems that unhooking a dx12 hook is unreliable. Most of the time it works fine but occasionally it hangs the whole computer and the only way to resolve it is to hard reset the PC.
A simple way to reproduce this would be to just create a button that on click calls the unhook provided by this lib and return. Clicking it would work as expected about 95% of the time.
I can get some trace logs of this later if those will be helpful. I’d also be willing to help debug if you are unable to reproduce. Thanks a bunch!
wgpu
is a crate that supports multiple backends.
This would currently leave out DirectX 9, but I wonder if we could leverage it to merge the DirectX11, DirectX12, OpenGL renderers and also add instant support for Vulkan.
We should add proper integration tests. Those most likely won't be runnable in CI, but I think we could get good coverage by implementing per-renderer test harnesses that create a window with a rendering surface. Inside of this harness, directly invoke hook functions.
I think it would be better to put log statements under a feature flag. Otherwise logs could get filled with extra stuff, or difficult log filtering would need to be implemented. I think it could sometimes be nice to have logs from this crate, however I think dependents should have the option to disable them.
Hi,
Firstly, thanks for this project.
I have been working on getting unhooking working on my tool and have got it working with one exception. The WND proc is not resetting after disabling hooks so the game will crash.
I didn’t want to PR my (probably wrong) approach because it felt pretty nasty and hacky. But the gist is that you need to call SetWindowLongPtrA
with the original WND proc. You can get the original one by calling GetWindowLongPtrA
with the hwnd in the new
function of the ImguiRenderer
and just save it somewhere until it’s time to shutdown. I had to create some functions to expose a shutdown method on the imgui renderer in the hook module and that felt weird. I’m sure you’d find a better way to handle that.
Thanks again.
Have you considered using https://crates.io/crates/retour instead of MinHook? It's essentially a maintained version of "detours".
Should also be easier to use than MH.
Hello there,
Wanted to give this crate a shot but unfortunately hit a bump on the road right away, hehe.
Detour2 0.9.0 uses #![feature] which is not allowed without nightly.
error[E0554]:
#![feature] may not be used on the stable release channel
Not sure if the crate is meant to be ran on Rust nightly, and if that's the case, an update of the docs would be in place.
For the D3D12 hook implementation, windows-rs
will be used in place of winapi-rs
which has a more idiomatic API with less pointer manipulations and casts. We want to refactor the DirectX 11 bits to also use windows-rs
.
Ugh, so, recently, The Witcher 3 was updated and now provides two binaries, one that uses DX12 and the other one that uses DX11. Most of the other code is shared so the tool works by only changing the into_hook
type.
I can easily produce two DLLs for each one using some compile-time features, but I'm not to keen into that solution.
I'm also thinking what's an easy way of detecting this at runtime? A stupidly simple way I think is using GetModuleHandle
to check which DLL is loaded?
Not sure if it's an issue on my end or not, but I can't seem to find a way to actually interact with the UI. I can't move or resize the window, and I set up a little demo with a render function that looks something like this:
ctx.frame.text(format!("Hello, hello, {:?}!", name));
if ctx.frame.button(im_str!("click"), [30., 30.]) {
info!("CLICKED");
}
Which I can see both the text and the button, but when I click the button, I don't get anything in my logs. If it's any help, the game I'm trying to hook into is Escape the Backrooms.
Here are the other two functions too just in case:
fn is_visible(&self) -> bool {
true
}
fn is_capturing(&self) -> bool {
true
}
We should strive to minimize the unnecessary dependencies.
Of particular concern right now is winit
which carries with it some vulnerabilities.
The library is safe as it's only used in examples, but it's better to just do without it.
Hey, this is more of an enhancement request.
There are some use cases for Imgui (drawing custom overlays in game world space), that are most easily achieved if we have access to the D3D Device that was used when we created the renderer. Not sure about OpenGL at the moment sorry.
The idea being, let's say you want to do an Imgui drawList to draw some lines in the game's world space. Some games already have the projection and view matrices necessary to do this in memory, but in order to call the D3D functions to do the math, you need to pass in the actual device's viewport (device ->GetViewport(*tempViewport)). Which I'm pretty sure wouldn't work with a dummy device, unless you knew the correct creation parameters ahead of time.
So you'd need to hold onto a reference to the D3D device when creating the ImguiRenderer, and expose that to the hooks class that the user actually interacts with in their code.
The alternative is to try and pull the games camera position/orientation + FOV out of memory, and do the math with those but it seems more involved and can't be re-used between games as easily.
The cleanest implementation of this would likely be to expose a function to the user that takes in a 3D vector, projection matrix, and view matrix, and returns the resulting screenspace vector. So that the API is essentially: WorldToScreen(pos, projectionMatrix, viewMatrix) -> screenPos, which could be re-used for any game since the user could handle any position conversions before the function, and handle finding the correct matrices.
If this sounds like something that you'd find useful, once I get to the point I need it I can take a stab at setting it up in a fresh fork. Thanks!
Another modification I had to make to use this lib was reimplementing is_capturing
on the ImguiRenderLoop
trait. I wanted mouse clicks and text input to not pass through to the game when the imgui box is focused by the user.
My exact implementation:
Trait impl:
fn is_capturing(&self, io: &imgui_dx12::imgui::Io) -> bool {
io.want_capture_mouse
}
Default trait impl
fn is_capturing(&self, _: &imgui_dx12::imgui::Io) -> bool {
false
}
Then near the bottom of imgui_wnd_proc
:
if let Some(render_loop) = IMGUI_RENDER_LOOP.get() {
if render_loop.is_capturing(imgui_renderer.ctx.io()) {
return LRESULT(1);
}
}
Again, I don’t think this is the perfect implementation but it seems to work OK for my use case. If this could be implemented I believe I won’t have to vendor this lib anymore and can just use the one from git :)
To draw images using ImGui from what I can tell you need to load textures before you can do draw_bg.add_image(texture_id, p_min, p_max)
.
To accomplish this, from what I can understand, you would basically need to do what's happening in create_font_texture
but for an image file or array of bytes, is that correct?
Hello, I just recently found out about this project and it seems to suit all of my needs, one "small" issue though is that when I try and inject the DirectX 11 Example provided (built with x86_64-pc-windows-gnu
), it errors with the message: Force load module failed:failed finding address of d3d1 D3D12CreateDevice
, I'm using Cheat Engine to inject it.
I also tried to use the OpenGL 3 example and it gives the same output. I'm on Linux but I have also tried to build and run it on a Windows VM and got the exact same result.
As far as I can tell, D3D12CreateDevice should only be called for the DirectX 12 hook, so I'm completely lost as to why it's crashing on it for hooks that aren't related to it (?).
I tried to use another DirectX 11 Hook, which is this: https://github.com/ynuwenhof/imferris and it worked just fine, both on Linux and on Windows.
Are there any steps I can take to potentially solve this issue? Thanks!
I was looking for IMGUI hooks in general, and found kiero, which is more to hook to DirectX APIs, and a few libraries that use kiero to hook IMGUI, but none of them were as well capsulated as this one and one-for-all apis.
Since I like experimenting with languages (I still enjoy Rust tho!) I was thinking it may be possible to expose the IMGUI hook to FFI to be able to use it with other languages.
I think we only should need to expose very few functions (per Implementation) and it would be not so complicated.
I think the most troublesome could be expose imgui itself, but I wouldn't say it's impossible.
Thoughts?
The solution could be to save the previous size & check if it changes on each frame. Since we already call get_window_rect
, hopefully that is enough, but it could very well be so that the device's viewport needs to be checked as well like in the OpenGL solution.
Since this is a fairly isolated thing, I'm wondering - Is the overhead of checking the viewport basically non-existent or is it something that has to be considered?
There is also another game (don't remember which one) with a similar issue. Might even have been a DirectDraw game that I ran with dxwrapper, so I'm not too sure of the relevancy here since it may be so that there is no equivalent to Reset
in those games.
The only solution I could come up with was to read for calls to WM_ACTIVATEAPP
and subsequently reset the device manually from there.
WM_ACTIVATEAPP => {
if let Some(device) = D3D9DEVICE.get() {
let mut present_params = D3DPRESENT_PARAMETERS {
Windowed: BOOL(1),
SwapEffect: D3DSWAPEFFECT_DISCARD,
BackBufferFormat: D3DFMT_UNKNOWN,
..core::mem::zeroed()
};
device.Reset(&mut present_params).unwrap();
}
}
It's not the prettiest solution & would require us to expose the device somehow like talked about in #80
Hi,
would it be possible to add a function to initialize hudhook with an existing Direct9Device pointer? one of my projects is an overlay for a DirectX8 game which i'm running through d3d8to9 but initialize hudhook using the following code:
// imports, Overlay struct definition and empty ImguiRenderLoop impl left out for brevity
static OVERLAY_INITIALIZED: AtomicBool = AtomicBool::new(false);
pub fn init() -> Result<()> {
if OVERLAY_INITIALIZED.swap(true, Ordering::SeqCst) {
bail!("Tried to initialize overlay twice!");
}
// Passing a handle to the module around into the function would be a bit cumbersome, hence GetModuleHandleA()
let hmodule = unsafe {std::mem::transmute(windows::Win32::System::LibraryLoader::GetModuleHandleA(None)?)};
hudhook::lifecycle::global_state::set_module(hmodule);
let hooks: Box<dyn hooks::Hooks> = Overlay::new().into_hook::<ImguiDx9Hooks>();
unsafe {hooks.hook()};
hudhook::lifecycle::global_state::set_hooks(hooks);
println!("OK!");
Ok(())
}
fails with the following error message:
panicked at 'IDirect3DDevice9::CreateDevice: failed to create device: Error { code: 0x8876086C, message: }', C:\Users\Earthnuker\scoop\persist\rustup-msvc\.cargo\registry\src\index.crates.io-6f17d22bba15001f\hudhook-0.4.0\src\hooks\dx9.rs:324:6
Best regards,
Earthnuker
For writing customized hooks It would be nice If the module could be exposed somehow, I think It's also easily possible to gate this behind a feature flag.
I'm still not totally sure about this but it would be worth the discussion.
Currently, although this captures mouse input, it doesn't have any mechanism to 'force' the cursor to be displayed.
I'd like to be something like Reshade, when the Menu is open, show and capture mouse input (and blocks mouse input
from the game).
Right now I'm doing some very nasty hack for the tool I've been building for The Witcher and it kinda works,
it shows the mouse even if the game doesn't want to:
etra0/hudhook@master...temp_patches
This could work as a proof of concept I guess if we test it in more games.
Background: I'm attempting to hook PCSX2 which is a 32-bit process. Because hooking involves looking up the address of Kernel32::LoadLibraryA, it seems convenient if the hooking process is also 32-bit (see https://stackoverflow.com/questions/8776437/c-injecting-32-bit-targets-from-64-bit-process).
Unfortunately I cannot compile this crate with the "i686-pc-windows-msvc" target.
Compiling hudhook v0.1.5
error[E0308]: mismatched types
--> .cargo\registry\src\github.com-1ecc6299db9ec823\hudhook-0.1.5\src\hook.rs:101:9
|
101 | wnd_proc as WndProc as isize,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `i32`, found `isize`
|
help: you can convert an `isize` to an `i32` and panic if the converted value doesn't fit
|
101 | (wnd_proc as WndProc as isize).try_into().unwrap(),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I tried to investigate this and it would appear that under 32-bit windows, SetWindowLongPtrA
is redefined as SetWindowLongA
which takes a LONG
3rd argument. Rust is not happy about treating this as an isize
for reasons I do not completely understand. I'm unsure if the suggestion of (wnd_proc as WndProc as isize).try_into().unwrap()
is a sensible solution.
Some examples are broken, we should go back and review them.
As an added bonus, we should find a good strategy for end-to-end tests, i.e. creating dummy host applications and example overlays to be run over them, in order to exercise as much of the client API surface as possible.
```error[E0603]: struct ImguiRenderLoopFlags
is private --> src\lib.rs:6:45 | 6 | use hudhook::hooks::dx11::{ImguiRenderLoop, ImguiRenderLoopFlags}; | ^^^^^^^^^^^^^^^^^^^^ private struct |
I noticed that the ImguiRenderLoop initialize function will run several times if the DLL is injected early in certain games.
Examples: Mount & Blade: Warband (32bit, DX9), Niche (64bit, DX11), Oxygen Not Included (64bit, DX11).
Is this something you're aware of @veeenu or possibly have an idea of why that is happening?
The side effect of this is that the game input gets all whacked up. Sometimes a keypress or mouseclick gets 'locked' to keydown and sometimes you have to click several times before anything registers.
We should add a GitHub action to pull the repo, run cargo doc
and publish the output on GitHub pages.
See this issue on the Elden Ring practice tool.
The FPS of the ERPT drops significantly when in windowed borderless mode in at least the issue creator's setup.
We should investigate whether this is consistently reproducible both using in-game windowed borderless and using external tools like Borderless Gaming.
Example
if let Some(wnd_proc) = imgui_render_loop.as_ref().wnd_proc() {
unsafe {
wnd_proc(hwnd, umsg, WPARAM(wparam), LPARAM(lparam));
}
}
let wnd_proc = imgui_renderer.wnd_proc();
let should_block_messages = imgui_render_loop
.as_ref()
.should_block_messages(imgui_renderer.io());
pub trait ImguiRenderLoop {
fn wnd_proc(&self) -> Option<WndProcType> {
None
}
}
Example
imgui_render_loop.as_ref().wnd_proc(&hwnd, umsg, &WPARAM(wparam), &LPARAM(lparam));
let wnd_proc = imgui_renderer.wnd_proc();
let should_block_messages = imgui_render_loop
.as_ref()
.should_block_messages(imgui_renderer.io());
pub trait ImguiRenderLoop {
fn wnd_proc(
&self,
_hwnd: &HWND,
_umsg: u32,
WPARAM(_wparam): &WPARAM,
LPARAM(_lparam): &LPARAM,
) {
}
}
OnceCell
and OnceLock
are stable in Rust 1.70 🎉, so we should now drop the nightly feature flags.
I want to write a small mdBook with hudhook tutorials.
It would be a good idea to gate renderers and hooks behind feature flags, e.g. a cartesian product of ["imgui", "egui"] x ["dx9", "dx11", "dx12", "opengl3"]
.
To keep this from being a breaking change, it would be enough to enable all of those features by default, and allow (and encourage) users to set default-features = false
and then enable only the features they are interested in.
There's an issue with game input that pops up in some games, seemingly primarily DX9 games, although I have had limited possibilities to try lots of games with the other renderers.
Basically, input sometimes registers fine, and other times not.
The "WndProc called before hook was set" log from the imgui_wnd_proc function always show up in the games where this happens.
I'm not sure what the cause of this is yet. I'm going to investigate further and I'd be happy if anyone knows anything that'd help.
Hello,
I was wondering whether OpenGL support has been considered? Looking at the newly added DX9 renderer, there seems to be something similar here. In case I attempt to tackle it myself, would it possible to PR it here?
We should refactor the whole library to adhere to the API Guidelines. This checklist should guide the work.
Hi , I tried to compile the code from documentation but I get this error:
error[E0059]: type parameter to bare Fn
trait must be a tuple
--> C:\Users\Aref.cargo\registry\src\github.com-1ecc6299db9ec823\detour2-0.9.0\src\detours\statik.rs:106:8
|
106 | D: Fn<T::Arguments, Output = T::Output> + Send + 'static,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait Tuple
is not implemented for <T as Function>::Arguments
|
note: required by a bound in Fn
--> C:\Users\Aref.rustup\toolchains\nightly-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\core\src\ops\function.rs:159:20
|
159 | pub trait Fn<Args: Tuple>: FnMut {
| ^^^^^ required by this bound in Fn
help: consider further restricting the associated type
|
106 | D: Fn<T::Arguments, Output = T::Output> + Send + 'static, ::Arguments: Tuple
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error[E0059]: type parameter to bare Fn
trait must be a tuple
--> C:\Users\Aref.cargo\registry\src\github.com-1ecc6299db9ec823\detour2-0.9.0\src\detours\statik.rs:157:8
|
157 | C: Fn<T::Arguments, Output = T::Output> + Send + 'static,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait Tuple
is not implemented for <T as Function>::Arguments
|
note: required by a bound in Fn
--> C:\Users\Aref.rustup\toolchains\nightly-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\core\src\ops\function.rs:159:20
|
159 | pub trait Fn<Args: Tuple>: FnMut {
| ^^^^^ required by this bound in Fn
help: consider further restricting the associated type
|
157 | C: Fn<T::Arguments, Output = T::Output> + Send + 'static, ::Arguments: Tuple
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For more information about this error, try rustc --explain E0059
.
error: could not compile detour2
due to 2 previous errors
I followed the instructions in the documentation https://veeenu.github.io/hudhook/creating-library/project-setup.html
thanks.
When utilizing ImguiDx9Hooks, calling eject will cause a crash as the ImguiRenderer cleanup does nothing. This can be slightly remedied by implementing it as:
unsafe fn cleanup(&mut self) {
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
SetWindowLongPtrA(self.renderer.get_hwnd(), GWLP_WNDPROC, self.wnd_proc as usize as isize);
#[cfg(target_arch = "x86")]
SetWindowLongA(self.renderer.get_hwnd(), GWLP_WNDPROC, self.wnd_proc as usize as i32);
}
Note that even with this cleanup implementation sometimes ejecting (in a previously injected game process) will still cause a crash, but not every time. This happens even when utilizing the hudhook! macro and using a modified version of the README ImGui window that contains only a button that calls eject.
This is not a whole solution as DX9 will still crash when/after Reset gets called. I'm unsure if it's directly caused by Reset, but when resizing a game window (Aion MMO in this case), it will call Reset, which promptly crashes in the sequence of:
I have not made a PR for this fix due to the Reset issue, but implementing cleanup and waiting a bit before calling eject does improve the DX9 experience.
If any extra information is needed on these issues please let me know. Thank you
Hey, first off kudos for this project, I went down the rabbit hole of DX hooking and using this lets me focus on the more fun parts of what I want to create.
I noticed that when I unload the DLL it crashes my game, and looking at your source there is functionality to unhook, but there's no logic implemeted for DLL Reason = Detach.
Would it make sense to do it that way? Have the hudhook macro generate a reason == DLL_DETACH block that just calls unhook for whatever DX version we're using?
It would make iterating on my code a lot faster than restarting the game every time haha.
Thanks!
As mentioned briefly in #116, building with windows-gnu and injecting doesn't work.
Although I know it's only really officially supported for Windows via MSVC, it would still be nice to have it working by cross-compiling from a different platform.
Steps:
x86_64-unknown-linux-gnu
build target via rustup
cargo build --release --target x86_64-unknown-linux-gnu
Using MSVC would obviously be the most ideal solution, not just because of this issue, but because of smaller build-sizes and other misc things. Although because it's a true hassle to get it even working on Linux, it doesn't want to compile hudhook nor anything other than some basic println Hello World applications.
Injecting a working DLL works just fine on Linux, so no issues there.
Same as #38.
I've been looking at how this library could be tweaked to make it possible to "unhook" the injected code and eject the library. The motivation is so that during development you don't have to restart a game in order to inject new code.
I have a branch which I will push soon that seems to work most of the time, but I don't think it's robust enough to merge yet.
What I do is call minhook's MH_Uninitialize
function to reset the directX hook, and call SetWindowLongPtrA
again to put the original wnd_proc function back.
Unfortunately at the moment I invoke this behaviour from DllMain on DLL_PROCESS_DETACH. This seems wildly dangerous, because the injected code might be running when the library is freed. I suspect a better approach would be to use some sort of IPC mechanism to send a message to the library that it should detach itself, which it could do more cleanly on the next call to wnd_proc / directx11 present. But I'm still not sure if that would be safe.
I also had to get rid of the ThrowawayHwnd
because currently it tries to re-register the same window class on the host exe repeatedly. This could be fixed but I'm not familiar with the motivation for creating our own window rather than using an existing one like the code did previously.
We need to rename some structs to follow Rust conventions, i.e. ImguiDX12Hooks
should become ImguiDx12Hooks
.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.