diff --git a/CMakeLists.txt b/CMakeLists.txt index e168453..be6d59d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ project(ClickBetweenFrames VERSION 1.0.0) add_library(${PROJECT_NAME} SHARED "src/main.cpp" + "src/input.cpp" ) if (NOT DEFINED ENV{GEODE_SDK}) diff --git a/mod.json b/mod.json index 13cae2f..ac9c0d1 100644 --- a/mod.json +++ b/mod.json @@ -3,7 +3,7 @@ "gd": { "win": "2.206" }, - "version": "v1.1.21", + "version": "v1.1.22", "id": "syzzi.click_between_frames", "name": "Click Between Frames", "developer": "syzzi", diff --git a/src/includes.hpp b/src/includes.hpp new file mode 100644 index 0000000..0e7c7ad --- /dev/null +++ b/src/includes.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +using namespace geode::prelude; + +enum GameAction : int { + p1Jump = 0, + p1Left = 1, + p1Right = 2, + p2Jump = 3, + p2Left = 4, + p2Right = 5 +}; + +enum Player : bool { + Player1 = 0, + Player2 = 1 +}; + +enum State : bool { + Press = 0, + Release = 1 +}; + +struct InputEvent { + LARGE_INTEGER time; + PlayerButton inputType; + bool inputState; + bool player; +}; + +struct Step { + InputEvent input; + double deltaFactor; + bool endStep; +}; + +extern std::queue inputQueue; + +extern std::unordered_set inputBinds[6]; +extern std::unordered_set heldInputs; + +extern CRITICAL_SECTION inputQueueLock; +extern CRITICAL_SECTION keybindsLock; + +extern bool enableRightClick; +extern bool threadPriority; + +void inputThread(); \ No newline at end of file diff --git a/src/input.cpp b/src/input.cpp new file mode 100644 index 0000000..3517c8c --- /dev/null +++ b/src/input.cpp @@ -0,0 +1,146 @@ +#include "includes.hpp" + +std::queue inputQueue; + +std::unordered_set inputBinds[6]; +std::unordered_set heldInputs; + +CRITICAL_SECTION inputQueueLock; +CRITICAL_SECTION keybindsLock; + +bool enableRightClick; +bool threadPriority; + +LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + LARGE_INTEGER time; + PlayerButton inputType; + bool inputState; + bool player; + + QueryPerformanceCounter(&time); + + LPVOID pData; + switch (uMsg) { + case WM_INPUT: { + UINT dwSize; + GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER)); + + auto lpb = std::unique_ptr(new BYTE[dwSize]); + if (!lpb) { + return 0; + } + if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb.get(), &dwSize, sizeof(RAWINPUTHEADER)) != dwSize) { + log::debug("GetRawInputData does not return correct size"); + } + + RAWINPUT* raw = (RAWINPUT*)lpb.get(); + switch (raw->header.dwType) { + case RIM_TYPEKEYBOARD: { + USHORT vkey = raw->data.keyboard.VKey; + inputState = raw->data.keyboard.Flags & RI_KEY_BREAK; + + if (vkey >= VK_NUMPAD0 && vkey <= VK_NUMPAD9) vkey -= 0x30; // make numpad numbers work with customkeybinds + + // cocos2d::enumKeyCodes corresponds directly to vkeys + if (heldInputs.contains(vkey)) { + if (!inputState) return 0; + else heldInputs.erase(vkey); + } + + bool shouldEmplace = true; + player = Player1; + + EnterCriticalSection(&keybindsLock); + + if (inputBinds[p1Jump].contains(vkey)) inputType = PlayerButton::Jump; + else if (inputBinds[p1Left].contains(vkey)) inputType = PlayerButton::Left; + else if (inputBinds[p1Right].contains(vkey)) inputType = PlayerButton::Right; + else { + player = Player2; + if (inputBinds[p2Jump].contains(vkey)) inputType = PlayerButton::Jump; + else if (inputBinds[p2Left].contains(vkey)) inputType = PlayerButton::Left; + else if (inputBinds[p2Right].contains(vkey)) inputType = PlayerButton::Right; + else shouldEmplace = false; + } + if (!inputState) heldInputs.emplace(vkey); + + LeaveCriticalSection(&keybindsLock); + + if (!shouldEmplace) return 0; // has to be done outside of the critical section + break; + } + case RIM_TYPEMOUSE: { + USHORT flags = raw->data.mouse.usButtonFlags; + bool shouldEmplace = true; + player = Player1; + inputType = PlayerButton::Jump; + + EnterCriticalSection(&keybindsLock); + bool rc = enableRightClick; + LeaveCriticalSection(&keybindsLock); + + if (flags & RI_MOUSE_BUTTON_1_DOWN) inputState = Press; + else if (flags & RI_MOUSE_BUTTON_1_UP) inputState = Release; + else { + player = Player2; + if (!rc) return 0; + if (flags & RI_MOUSE_BUTTON_2_DOWN) inputState = Press; + else if (flags & RI_MOUSE_BUTTON_2_UP) inputState = Release; + else return 0; + } + break; + } + default: + return 0; + } + break; + } + default: + return DefWindowProcA(hwnd, uMsg, wParam, lParam); + } + + EnterCriticalSection(&inputQueueLock); + inputQueue.emplace(InputEvent{ time, inputType, inputState, player }); + LeaveCriticalSection(&inputQueueLock); + + return 0; +} + +void inputThread() { + + WNDCLASS wc = {}; + wc.lpfnWndProc = WindowProc; + wc.hInstance = GetModuleHandleA(NULL); + wc.lpszClassName = "CBF"; + + RegisterClass(&wc); + HWND hwnd = CreateWindow("CBF", "Raw Input Window", 0, 0, 0, 0, 0, HWND_MESSAGE, 0, wc.hInstance, 0); + if (!hwnd) { + const DWORD err = GetLastError(); + log::error("Failed to create raw input window: {}", err); + return; + } + + RAWINPUTDEVICE dev[2]; + dev[0].usUsagePage = 0x01; // generic desktop controls + dev[0].usUsage = 0x02; // mouse + dev[0].dwFlags = RIDEV_INPUTSINK; // allow inputs without being in the foreground + dev[0].hwndTarget = hwnd; // raw input window + + dev[1].usUsagePage = 0x01; + dev[1].usUsage = 0x06; // keyboard + dev[1].dwFlags = RIDEV_INPUTSINK; + dev[1].hwndTarget = hwnd; + + if (!RegisterRawInputDevices(dev, 2, sizeof(dev[0]))) { + log::error("Failed to register raw input devices"); + return; + } + + if (threadPriority) SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); + + MSG msg; + while (GetMessage(&msg, hwnd, 0, 0)) { + DispatchMessage(&msg); + } +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 18b7eb8..aa727f5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,5 @@ +#include "includes.hpp" + #include #include #include @@ -13,191 +15,11 @@ #include -using namespace geode::prelude; - -enum GameAction : int { - p1Jump = 0, - p1Left = 1, - p1Right = 2, - p2Jump = 3, - p2Left = 4, - p2Right = 5 -}; - -enum Player : bool { - Player1 = 0, - Player2 = 1 -}; - -enum State : bool { - Press = 0, - Release = 1 -}; - -struct InputEvent { - LARGE_INTEGER time; - PlayerButton inputType; - bool inputState; - bool player; -}; - -struct Step { - InputEvent input; - double deltaFactor; - bool endStep; -}; +constexpr double smallestFloat = std::numeric_limits::min(); const InputEvent emptyInput = InputEvent{ 0, 0, PlayerButton::Jump, 0, 0 }; const Step emptyStep = Step{ emptyInput, 1.0, true }; -std::queue inputQueue; - -std::unordered_set inputBinds[6]; -std::unordered_set heldInputs; - -CRITICAL_SECTION inputQueueLock; -CRITICAL_SECTION keybindsLock; - -bool enableRightClick; - -LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - LARGE_INTEGER time; - PlayerButton inputType; - bool inputState; - bool player; - - QueryPerformanceCounter(&time); - - LPVOID pData; - switch (uMsg) { - case WM_INPUT: { - UINT dwSize; - GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER)); - - auto lpb = std::unique_ptr(new BYTE[dwSize]); - if (!lpb) { - return 0; - } - if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb.get(), &dwSize, sizeof(RAWINPUTHEADER)) != dwSize) { - log::debug("GetRawInputData does not return correct size"); - } - - RAWINPUT* raw = (RAWINPUT*)lpb.get(); - switch (raw->header.dwType) { - case RIM_TYPEKEYBOARD: { - USHORT vkey = raw->data.keyboard.VKey; - inputState = raw->data.keyboard.Flags & RI_KEY_BREAK; - - if (vkey >= VK_NUMPAD0 && vkey <= VK_NUMPAD9) vkey -= 0x30; // make numpad numbers work with customkeybinds - - // cocos2d::enumKeyCodes corresponds directly to vkeys - if (heldInputs.contains(vkey)) { - if (!inputState) return 0; - else heldInputs.erase(vkey); - } - - bool shouldEmplace = true; - player = Player1; - - EnterCriticalSection(&keybindsLock); - - if (inputBinds[p1Jump].contains(vkey)) inputType = PlayerButton::Jump; - else if (inputBinds[p1Left].contains(vkey)) inputType = PlayerButton::Left; - else if (inputBinds[p1Right].contains(vkey)) inputType = PlayerButton::Right; - else { - player = Player2; - if (inputBinds[p2Jump].contains(vkey)) inputType = PlayerButton::Jump; - else if (inputBinds[p2Left].contains(vkey)) inputType = PlayerButton::Left; - else if (inputBinds[p2Right].contains(vkey)) inputType = PlayerButton::Right; - else shouldEmplace = false; - } - if (!inputState) heldInputs.emplace(vkey); - - LeaveCriticalSection(&keybindsLock); - - if (!shouldEmplace) return 0; // has to be done outside of the critical section - break; - } - case RIM_TYPEMOUSE: { - USHORT flags = raw->data.mouse.usButtonFlags; - bool shouldEmplace = true; - player = Player1; - inputType = PlayerButton::Jump; - - EnterCriticalSection(&keybindsLock); - bool rc = enableRightClick; - LeaveCriticalSection(&keybindsLock); - - if (flags & RI_MOUSE_BUTTON_1_DOWN) inputState = Press; - else if (flags & RI_MOUSE_BUTTON_1_UP) inputState = Release; - else { - player = Player2; - if (!rc) return 0; - if (flags & RI_MOUSE_BUTTON_2_DOWN) inputState = Press; - else if (flags & RI_MOUSE_BUTTON_2_UP) inputState = Release; - else return 0; - } - break; - } - default: - return 0; - } - break; - } - default: - return DefWindowProcA(hwnd, uMsg, wParam, lParam); - } - - EnterCriticalSection(&inputQueueLock); - inputQueue.emplace(InputEvent{ time, inputType, inputState, player }); - LeaveCriticalSection(&inputQueueLock); - - return 0; -} - -bool threadPriority; - -void inputThread() { - - WNDCLASS wc = {}; - wc.lpfnWndProc = WindowProc; - wc.hInstance = GetModuleHandleA(NULL); - wc.lpszClassName = "CBF"; - - RegisterClass(&wc); - HWND hwnd = CreateWindow("CBF", "Raw Input Window", 0, 0, 0, 0, 0, HWND_MESSAGE, 0, wc.hInstance, 0); - if (!hwnd) { - const DWORD err = GetLastError(); - log::error("Failed to create raw input window: {}", err); - return; - } - - RAWINPUTDEVICE dev[2]; - dev[0].usUsagePage = 0x01; // generic desktop controls - dev[0].usUsage = 0x02; // mouse - dev[0].dwFlags = RIDEV_INPUTSINK; // allow inputs without being in the foreground - dev[0].hwndTarget = hwnd; // raw input window - - dev[1].usUsagePage = 0x01; - dev[1].usUsage = 0x06; // keyboard - dev[1].dwFlags = RIDEV_INPUTSINK; - dev[1].hwndTarget = hwnd; - - if (!RegisterRawInputDevices(dev, 2, sizeof(dev[0]))) { - log::error("Failed to register raw input devices"); - return; - } - - if (threadPriority) SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); - - MSG msg; - while (GetMessage(&msg, hwnd, 0, 0)) { - DispatchMessage(&msg); - } -} - -constexpr double smallestFloat = std::numeric_limits::min(); - std::queue inputQueueCopy; std::queue stepQueue;