veeenu / hudhook

A videogame overlay framework written in Rust, supporting DirectX and OpenGL
MIT License
207 stars 30 forks source link

Incompatibility Issue with Sea of Thieves (DX11) #142

Closed Thoxy67 closed 9 months ago

Thoxy67 commented 9 months ago

I am currently experiencing issues while trying to implement the HudHook library into the Sea of Thieves game. Despite following all the instructions provided in the documentation, I encountered several problems that are preventing me from using it smoothly.

Firstly, when utilizing the master branch of the HudHook library, I experience random freezing of the game and I get "Could not lock in WndProc" every ~10sec in the trace console. Occasionally, the game runs without any apparent issues; however, the in-game menu fails to show up. Moreover, I encounter rendering glitches similar to those demonstrated in this video: Rendering Glitches Video

Secondly, as an alternative approach, I attempted to use the unified-renderer branch instead. While this version resolves the previous rendering concerns, there is now another issue where neither the mouse nor the keyboard functions properly within the ImGUI window. Consequently, controlling or interacting with the elements inside the ImGUI window becomes impossible. To provide further clarification regarding these challenges, please refer to this additional video demonstration: Demonstration Video

Would you happen to have any suggestions for addressing this issue?I have endeavored to modify your lib to try to fix this issue but without success.

Despite these hurdles, I genuinely appreciate the efforts invested in developing such a fantastic lib.

Thank you in advance for your time and support.

veeenu commented 9 months ago

Hi, thank you for reporting this!

Can't say I have ever seen that behavior but it is interesting. And glad to know the unified-renderer branch solves the rendering issue, means we're on the right track as far as compatibility goes.

It is expected for now that that branch does nothing, though. It is a major refactor of the entire project and I've only managed to port a proof of concept dx11 renderer to the new technique, without any of the other features like input. The current renderers are going to be deprecated as a result of this refactor so contributing towards those would not be really useful at the moment.

I am a bit overwhelmed by other things but work on it is progressing at a reasonable pace, hopefully I can finish everything and run some real world tests in the near future.

In the meantime, could you try compiling against the released version on crates.io ? main at any time may contain experimental features or even regressions. In this particular case I don't expect that to change anything as the dx11 render loop has been stable in that state for a while, but it's always worth a shot to gather more usage data.

Thoxy67 commented 9 months ago

As it happens, I first started with the version available on crates.io but I'm having the same problem as in the first video (i.e. the in-game menus no longer display or freeze and the green glitch).

With the unified-render branch, I don't have this lock problem ("Could not lock in WndProc") and the menus work, but I've seen in the code that all the input parts are missing and I'm not quite set up with the DirectX api to be able to do it myself.

I'm going to do some tests today but I don't think I'll be able to solve this problem.

veeenu commented 9 months ago

As I mentioned above, that part is completely missing anyway. Any branch other than main is not supposed to be used as it is only ever going to be in a usable state right before being merged.

Nothing much you can do there at the moment, I just need to find the time to actually port stuff over myself which I can't do right this second.

Feel free to use main or the crates.io version for now and ignore the rendering bugs: they are going to go away by themselves once the new work is completed. The client API is not going to change in any impactful way; when the time comes, you should be able to just bump the version and change one line, and your application should work just fine.

Also, feel free to ignore the "could not lock" messages; it just means that you are going to drop one frame every time they show up (so once every 10s?) and don't have any other kind of impact. They are warning messages that just say something about synchronization.

veeenu commented 9 months ago

@Thoxy67 if you want to give it a shot, I just pushed input support to the unified-renderer branch.

Thoxy67 commented 9 months ago

I have conducted tests on the unified-renderer branch. The user interface (UI) functions flawlessly without any freezing or glitches.

Since you added input in this branch the mouse interacts smoothly with the UI, but unfortunately, the keyboard does not. Specifically, the default ImGui is_key_<...> function isn't working at all. Maybe you didn't added the redirection yet like it was in the master branch.

However, I did find a workaround to detect whether a key has been pressed once using the following code snippet :

static mut pressed: [bool; 652] = [false; 652];

impl ImguiRenderLoop for Dx11HookSoT {
    fn render(&mut self, ui: &mut imgui::Ui) {
        // ...
        let keydown = ui.io().keys_down;
        if keydown[0x20] && unsafe { pressed }[0x20] != keydown[0x20] {
            println!("Space Pressed!");
        }
        // ...
    }
}

Keep up the excellent work on this project!

veeenu commented 9 months ago

Thank you! 😁

Yeah, that's definitely a bug, thank you for reporting that!

veeenu commented 9 months ago

Should be fixed in the last commit.

Thoxy67 commented 9 months ago

I see that it works, but I also noticed that not every key is functioning correctly. Did you have to manually map each key like this?

        io[Key::Tab] = VK_TAB.0 as _;
        io[Key::LeftArrow] = VK_LEFT.0 as _;
        // ...

The keys you added to the list are now working, but not all the others... Isn't there a better way to map io keys to vk keys by utilizing the keycodes on both sides? I will conduct some tests to find a more elegant way to implement this feature.

veeenu commented 9 months ago

Those are the ones that are available in the Key enum which doesn't cover the entire 652 entry array. I'm not sure why that is the case. The others should default to the virtual key codes.

I tested text boxes in the practice tools and those work fine with all keys. Is the input behavior different compared to hudhook main/v0.5?

I think you could do something like that on your end from the initialize method, which still exposes all imgui internals:

let mut io = ctx.io_mut();
io[some_index] = VK_SOMETHING.0 as _;
// ...keep going

I'm not aware of what the intended mappings are in the original imgui but I think it should be quite different from ASCII.

Can you share your code, or a minimal reproducible example? If you get this working, I'd be interested in including the changes.

Thoxy67 commented 9 months ago

I have seen that the imgui not using the same keycode as VK_<...> i don't know why but i have integrated the rest of the key that imgui support :

use imgui::Key;
use windows::Win32::UI::Input::KeyboardAndMouse::{
    VIRTUAL_KEY, VK_0, VK_1, VK_2, VK_3, VK_4, VK_5, VK_6, VK_7, VK_8, VK_9, VK_A, VK_ADD, VK_B,
    VK_BACK, VK_C, VK_CAPITAL, VK_CONTROL, VK_D, VK_DECIMAL, VK_DELETE, VK_DIVIDE, VK_DOWN, VK_E,
    VK_END, VK_ESCAPE, VK_EXECUTE, VK_EXSEL, VK_F, VK_F1, VK_F10, VK_F11, VK_F12, VK_F2, VK_F3,
    VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_G, VK_GAMEPAD_A, VK_GAMEPAD_B,
    VK_GAMEPAD_DPAD_DOWN, VK_GAMEPAD_DPAD_LEFT, VK_GAMEPAD_DPAD_RIGHT, VK_GAMEPAD_DPAD_UP,
    VK_GAMEPAD_LEFT_SHOULDER, VK_GAMEPAD_LEFT_THUMBSTICK_DOWN, VK_GAMEPAD_LEFT_THUMBSTICK_LEFT,
    VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT, VK_GAMEPAD_LEFT_THUMBSTICK_UP, VK_GAMEPAD_LEFT_TRIGGER,
    VK_GAMEPAD_MENU, VK_GAMEPAD_RIGHT_SHOULDER, VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN,
    VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT, VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT,
    VK_GAMEPAD_RIGHT_THUMBSTICK_UP, VK_GAMEPAD_RIGHT_TRIGGER, VK_GAMEPAD_VIEW, VK_GAMEPAD_X,
    VK_GAMEPAD_Y, VK_H, VK_HOME, VK_I, VK_INSERT, VK_J, VK_K, VK_L, VK_LBUTTON, VK_LCONTROL,
    VK_LEFT, VK_LMENU, VK_LSHIFT, VK_LWIN, VK_M, VK_MBUTTON, VK_MENU, VK_MULTIPLY, VK_N, VK_NEXT,
    VK_NUMLOCK, VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6,
    VK_NUMPAD7, VK_NUMPAD8, VK_NUMPAD9, VK_O, VK_OEM_1, VK_OEM_2, VK_OEM_3, VK_OEM_4, VK_OEM_5,
    VK_OEM_6, VK_OEM_7, VK_OEM_COMMA, VK_OEM_MINUS, VK_OEM_PERIOD, VK_OEM_PLUS, VK_P, VK_PAUSE,
    VK_PRIOR, VK_Q, VK_R, VK_RBUTTON, VK_RCONTROL, VK_RETURN, VK_RIGHT, VK_RMENU, VK_RSHIFT,
    VK_RWIN, VK_S, VK_SCROLL, VK_SNAPSHOT, VK_SPACE, VK_SUBTRACT, VK_T, VK_TAB, VK_U, VK_UP, VK_V,
    VK_W, VK_X, VK_XBUTTON1, VK_XBUTTON2, VK_Y, VK_Z,
};

const KEYS: [(Key, VIRTUAL_KEY); 132] = [
    (Key::Tab, VK_TAB),
    (Key::LeftArrow, VK_LEFT),
    (Key::RightArrow, VK_RIGHT),
    (Key::UpArrow, VK_UP),
    (Key::DownArrow, VK_DOWN),
    (Key::PageUp, VK_PRIOR),
    (Key::PageDown, VK_NEXT),
    (Key::Home, VK_HOME),
    (Key::End, VK_END),
    (Key::Insert, VK_INSERT),
    (Key::Delete, VK_DELETE),
    (Key::Backspace, VK_BACK),
    (Key::Space, VK_SPACE),
    (Key::Enter, VK_RETURN),
    (Key::Escape, VK_ESCAPE),
    (Key::LeftCtrl, VK_LCONTROL),
    (Key::LeftShift, VK_LSHIFT),
    (Key::LeftAlt, VK_LMENU),
    (Key::LeftSuper, VK_LWIN),
    (Key::RightCtrl, VK_RCONTROL),
    (Key::RightShift, VK_RSHIFT),
    (Key::RightAlt, VK_RMENU),
    (Key::RightSuper, VK_RWIN),
    (Key::Menu, VK_MENU),
    (Key::Alpha0, VK_0),
    (Key::Alpha1, VK_1),
    (Key::Alpha2, VK_2),
    (Key::Alpha3, VK_3),
    (Key::Alpha4, VK_4),
    (Key::Alpha5, VK_5),
    (Key::Alpha6, VK_6),
    (Key::Alpha7, VK_7),
    (Key::Alpha8, VK_8),
    (Key::Alpha9, VK_9),
    (Key::A, VK_A),
    (Key::B, VK_B),
    (Key::C, VK_C),
    (Key::D, VK_D),
    (Key::E, VK_E),
    (Key::F, VK_F),
    (Key::G, VK_G),
    (Key::H, VK_H),
    (Key::I, VK_I),
    (Key::J, VK_J),
    (Key::K, VK_K),
    (Key::L, VK_L),
    (Key::M, VK_M),
    (Key::N, VK_N),
    (Key::O, VK_O),
    (Key::P, VK_P),
    (Key::Q, VK_Q),
    (Key::R, VK_R),
    (Key::S, VK_S),
    (Key::T, VK_T),
    (Key::U, VK_U),
    (Key::V, VK_V),
    (Key::W, VK_W),
    (Key::X, VK_X),
    (Key::Y, VK_Y),
    (Key::Z, VK_Z),
    (Key::F1, VK_F1),
    (Key::F2, VK_F2),
    (Key::F3, VK_F3),
    (Key::F4, VK_F4),
    (Key::F5, VK_F5),
    (Key::F6, VK_F6),
    (Key::F7, VK_F7),
    (Key::F8, VK_F8),
    (Key::F9, VK_F9),
    (Key::F10, VK_F10),
    (Key::F11, VK_F11),
    (Key::F12, VK_F12),
    (Key::Apostrophe, VK_OEM_7),
    (Key::Comma, VK_OEM_COMMA),
    (Key::Minus, VK_OEM_MINUS),
    (Key::Period, VK_OEM_PERIOD),
    (Key::Slash, VK_OEM_2),
    (Key::Semicolon, VK_OEM_1),
    (Key::Equal, VK_OEM_PLUS),
    (Key::LeftBracket, VK_OEM_4),
    (Key::Backslash, VK_OEM_5),
    (Key::RightBracket, VK_OEM_6),
    (Key::GraveAccent, VK_OEM_3),
    (Key::CapsLock, VK_CAPITAL),
    (Key::ScrollLock, VK_SCROLL),
    (Key::NumLock, VK_NUMLOCK),
    (Key::PrintScreen, VK_SNAPSHOT),
    (Key::Pause, VK_PAUSE),
    (Key::Keypad0, VK_NUMPAD0),
    (Key::Keypad1, VK_NUMPAD1),
    (Key::Keypad2, VK_NUMPAD2),
    (Key::Keypad3, VK_NUMPAD3),
    (Key::Keypad4, VK_NUMPAD4),
    (Key::Keypad5, VK_NUMPAD5),
    (Key::Keypad6, VK_NUMPAD6),
    (Key::Keypad7, VK_NUMPAD7),
    (Key::Keypad8, VK_NUMPAD8),
    (Key::Keypad9, VK_NUMPAD9),
    (Key::KeypadDecimal, VK_DECIMAL),
    (Key::KeypadDivide, VK_DIVIDE),
    (Key::KeypadMultiply, VK_MULTIPLY),
    (Key::KeypadSubtract, VK_SUBTRACT),
    (Key::KeypadAdd, VK_ADD),
    (Key::KeypadEnter, VK_EXECUTE),
    (Key::KeypadEqual, VK_EXSEL),
    (Key::GamepadStart, VK_GAMEPAD_MENU),
    (Key::GamepadBack, VK_GAMEPAD_VIEW),
    (Key::GamepadFaceLeft, VK_GAMEPAD_X),
    (Key::GamepadFaceRight, VK_GAMEPAD_B),
    (Key::GamepadFaceUp, VK_GAMEPAD_Y),
    (Key::GamepadFaceDown, VK_GAMEPAD_A),
    (Key::GamepadDpadLeft, VK_GAMEPAD_DPAD_LEFT),
    (Key::GamepadDpadRight, VK_GAMEPAD_DPAD_RIGHT),
    (Key::GamepadDpadUp, VK_GAMEPAD_DPAD_UP),
    (Key::GamepadDpadDown, VK_GAMEPAD_DPAD_DOWN),
    (Key::GamepadL1, VK_GAMEPAD_LEFT_SHOULDER),
    (Key::GamepadR1, VK_GAMEPAD_RIGHT_SHOULDER),
    (Key::GamepadL2, VK_GAMEPAD_LEFT_TRIGGER),
    (Key::GamepadR2, VK_GAMEPAD_RIGHT_TRIGGER),
    (Key::GamepadLStickLeft, VK_GAMEPAD_LEFT_THUMBSTICK_LEFT),
    (Key::GamepadLStickRight, VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT),
    (Key::GamepadLStickUp, VK_GAMEPAD_LEFT_THUMBSTICK_UP),
    (Key::GamepadLStickDown, VK_GAMEPAD_LEFT_THUMBSTICK_DOWN),
    (Key::GamepadRStickLeft, VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT),
    (Key::GamepadRStickRight, VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT),
    (Key::GamepadRStickUp, VK_GAMEPAD_RIGHT_THUMBSTICK_UP),
    (Key::GamepadRStickDown, VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN),
    (Key::MouseLeft, VK_LBUTTON),
    (Key::MouseRight, VK_RBUTTON),
    (Key::MouseMiddle, VK_MBUTTON),
    (Key::MouseX1, VK_XBUTTON1),
    (Key::MouseX2, VK_XBUTTON2),
    // (Key::MouseWheelX),
    // (Key::MouseWheelY),
    // (Key::ReservedForModCtrl),
    // (Key::ReservedForModShift),
    // (Key::ReservedForModAlt),
    // (Key::ReservedForModSuper),
    // (Key::ModCtrl),
    // (Key::ModShift),
    // (Key::ModAlt),
    // (Key::ModSuper),
    // (Key::ModShortcut),
    // (Key::GamepadL3),
    // (Key::GamepadR3),
];

and after simply calling it like this inside the setup_io in your dx12.rs :

        // Map key indices to the virtual key codes
        for i in crate::renderer::keys::KEYS {
            io[i.0] = i.1 .0 as _;
        }

anyway you can still use this workaround to use the rest of the raw keycodes from the gui:

static mut pressed: [bool; 652] = [false; 652];

impl ImguiRenderLoop for Dx11HookSoT {
    fn render(&mut self, ui: &mut imgui::Ui) {
        // ...
        let keydown = ui.io().keys_down;
        if keydown[0x20] && unsafe { pressed }[0x20] != keydown[0x20] {
            println!("Space Pressed!");
        }
        // ...
    }
}

I think it would be nice to separate this large constant from the dx12.rs file since the file is already quite large and could become unwieldy with an additional large constant like this one. Separating them would also make the code cleaner and easier to navigate.

Thoxy67 commented 9 months ago

actually my first priority is to find a way to show the cursor and block mouse integration directly to the ui...

I don't know how to explain this really but when you make the ui pop ingame the mouse is not visible and you can't interact with the ui.

maybe using :

windows::Win32::UI::Input::KeyboardAndMouse::SetCapture(hwnd);

windows::Win32::UI::Input::KeyboardAndMouse::ReleaseCapture()

and some :

windows::Win32::UI::WindowsAndMessaging::ShowCursor(true);

windows::Win32::UI::WindowsAndMessaging:ShowCursor(false);

Edit:

after some research i have found those 4 :

ui.io().mouse_draw_cursor; //bool
// Request imgui-rs to draw a mouse cursor for you

ui.io().want_capture_mouse; // bool
// When true, imgui-rs will use the mouse inputs, so do not dispatch them to your main game/application

ui.io().want_capture_keyboard; // bool
// When true, imgui-rs will use the keyboard inputs, so do not dispatch them to your main game/application

ui.io().want_capture_mouse_unless_popup_close; // bool

but i cannot assign value to those i get this error : "error: cannot assign to data in a & reference"

veeenu commented 9 months ago

I was not aware that the Key enum was filled in the latest release of imgui-rs -- good to know! I'll integrate your strategy, thank you!

The want_* are constants set by imgui and we shouldn't set those manually anyway, they are meant to be read by implementors afaik. Not sure about mouse_draw_cursor. IIRC I managed that part manually via Windows api somewhere in the practice tool (sorry no direct link to the code, I'm from mobile atm).

Thoxy67 commented 9 months ago

I have found a way to achieve what I mean, but I think you need to modify your setup_io with something like this:

    unsafe fn setup_io(&mut self) -> Result<()> {
        let sd = try_out_param(|sd| unsafe { self.swap_chain.GetDesc1(sd) })?;

        let mut ctx = self.ctx.borrow_mut();

        // Setup display size and cursor position.
        let io = ctx.io_mut();

        io.display_size = [sd.Width as f32, sd.Height as f32];

        let active_window = unsafe { GetForegroundWindow() };
        if !HANDLE(active_window.0).is_invalid()
            && (active_window == self.target_hwnd
                || unsafe { IsChild(active_window, self.target_hwnd) }.as_bool())
        {
            let mut pos = Default::default();
            let gcp = unsafe { GetCursorPos(&mut pos) };
            if gcp.is_ok()
                && unsafe { ScreenToClient(self.target_hwnd, &mut pos as *mut _) }.as_bool()
            {
                io.mouse_pos[0] = pos.x as _;
                io.mouse_pos[1] = pos.y as _;
            }
        }

        io.nav_active = true;
        io.nav_visible = true;

        if io.key_alt {
            // io.want_capture_mouse = true;  // not work it restrict to the main windows + ui i don't know why
            io.mouse_draw_cursor = true;
        } else {
            // io.want_capture_mouse = false; // not work it restrict to the main windows + ui i don't know why
            io.mouse_draw_cursor = false;
        }

        // Map key indices to the virtual key codes
        for i in keys::KEYS {
            io[i.0] = i.1 .0 as _;
        }

        Ok(())
    }

And I can interact with the UI so long as I press the Alt key, but it would be nice to pass some kind of keycode as an option, so we are not limited to using just the Alt key and can define it at hook start-up.

Edit :

This make the ui crash after some time

Edit (2) :

The reason for the crash is that the operation is repeated within the loop; making it a toggle would help address the issue. This means switching between two states depending on specific conditions instead of continuously repeating the action.

Thoxy67 commented 9 months ago

Now in my pull request I added this config variable :

pub static mut SHOW_CURSOR_KEY: Option<u16> = None;

now you can use it like this :

#[no_mangle]
pub unsafe extern "stdcall" fn DllMain(hmodule: HINSTANCE, reason: u32, _: *mut std::ffi::c_void) {
    if reason == DLL_PROCESS_ATTACH {
        trace!("DllMain()");
        std::thread::spawn(move || {

            hudhook::SHOW_CURSOR_KEY = Some(0x73); // Here append some magic things (0x73 = F4)

            if let Err(e) = Hudhook::builder()
                .with::<ImguiDx11Hooks>(hook::hook::Dx11HookSoT::new())
                .with_hmodule(hmodule)
                .build()
                .apply()
            {
                error!("Couldn't apply hooks: {e:?}");
                eject();
            }
        });
    }
}

Please consider testing, examining, and potentially implementing a similar solution within your project.

Regarding placement, placing the SHOW_CURSOR_KEY variable directly into the lib.rs might not be ideal. Creating a dedicated configuration file specifically designed for such settings could prove more beneficial. Additionally, moving the key.rs file to a shared location may improve organization, especially if its contents are applicable across multiple modules. A 'common' directory could serve as an appropriate destination for these shared resources.

Today, I am feeling slightly unmotivated and plan on resuming coding tomorrow. However, during my next session, I intend to explore the possibility of implementing a UI hiding feature utilizing a similar approach.

It seems quite appealing to me to have the ability to configure certain aspects at a lower level in the rendering loop by simply adjusting relevant variables.

Thoxy67 commented 9 months ago

I have updated the code now you can use :

hudhook::config::SHOW_UI_KEY = Some(0x70);      // F1 = Show / Hide UI
hudhook::config::SHOW_CURSOR_KEY = Some(0x71);  // F2 = Force Show / Hide Cursor
hudhook::config::SHOW_UI = false;               // Show / Hide UI directly after injected

Now I have finished my code session for today 😄

Let me know what you think about the code I have added.

veeenu commented 9 months ago

Thank you for that!

I reviewed your PR. While I can't accept it as-is, I think it is a step in generally the right direction.