mirror of
https://github.com/sfall-team/sfall.git
synced 2026-07-27 16:52:34 -07:00
484 lines
13 KiB
C++
484 lines
13 KiB
C++
/*
|
|
* sfall
|
|
* Copyright (C) 2008-2023 The sfall team
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#define DIRECTINPUT_VERSION 0x0700
|
|
|
|
#include <math.h>
|
|
#include <dinput.h>
|
|
#include <queue>
|
|
|
|
#include "main.h"
|
|
#include "Logging.h"
|
|
#include "FalloutEngine\Fallout2.h"
|
|
#include "Modules\Graphics.h"
|
|
#include "Modules\HookScripts\Common.h"
|
|
|
|
#include "InputFuncs.h"
|
|
|
|
namespace sfall
|
|
{
|
|
|
|
IDirectInputDeviceA* MouseDevice;
|
|
|
|
bool useScrollWheel = true;
|
|
static DWORD wheelMod;
|
|
|
|
static bool reverseMouse;
|
|
|
|
bool middleMouseDown = false;
|
|
static DWORD middleMouseKey;
|
|
|
|
static bool backgroundKeyboard;
|
|
static bool backgroundMouse;
|
|
|
|
static bool adjustMouseSpeed = false;
|
|
static double mouseSpeedMod;
|
|
static double mousePartX = 0.0;
|
|
static double mousePartY = 0.0;
|
|
|
|
#define MAX_KEYS (264)
|
|
|
|
static DWORD keysDown[MAX_KEYS] = {0};
|
|
|
|
static int mouseX;
|
|
static int mouseY;
|
|
|
|
void GetMouse(int* x, int* y) {
|
|
*x = mouseX;
|
|
*y = mouseY;
|
|
mouseX = 0;
|
|
mouseY = 0;
|
|
}
|
|
|
|
void MouseDeviceUnacquire(bool mode) {
|
|
if (backgroundMouse || !MouseDevice) return;
|
|
if (mode) {
|
|
MouseDevice->Unacquire();
|
|
} else {
|
|
MouseDevice->Acquire();
|
|
}
|
|
}
|
|
|
|
static BYTE LMouse = 0;
|
|
static BYTE RMouse = 0;
|
|
static int MPMouseX;
|
|
static int MPMouseY;
|
|
|
|
#define kDeviceType_KEYBOARD 0
|
|
#define kDeviceType_MOUSE 1
|
|
|
|
static std::queue<DIDEVICEOBJECTDATA> bufferedPresses;
|
|
|
|
static HKL keyboardLayout;
|
|
|
|
KeyPressedDelegate onKeyPressed;
|
|
MouseClickedDelegate onMouseClick;
|
|
Delegate<> onInputLoop;
|
|
|
|
void SetMDown(bool down, bool right) {
|
|
if (right) RMouse = down ? 0x80 : 0;
|
|
else LMouse = down ? 0x80 : 0;
|
|
}
|
|
|
|
void SetMPos(int x, int y) {
|
|
MPMouseX = x;
|
|
MPMouseY = y;
|
|
}
|
|
|
|
KeyPressedDelegate& OnKeyPressed() {
|
|
return onKeyPressed;
|
|
}
|
|
|
|
MouseClickedDelegate& OnMouseClick() {
|
|
return onMouseClick;
|
|
}
|
|
|
|
Delegate<>& OnInputLoop() {
|
|
return onInputLoop;
|
|
}
|
|
|
|
void FlushInputBuffer() {
|
|
__asm call fo::funcoffs::kb_clear_;
|
|
}
|
|
|
|
DWORD __stdcall KeyDown(DWORD key) {
|
|
if ((key & 0x80000000) > 0) { // special flag to check by VK code directly
|
|
return GetAsyncKeyState(key & 0xFFFF) & 0x8000;
|
|
}
|
|
key &= 0xFFFF;
|
|
// combined use of DINPUT states + confirmation from GetAsyncKeyState()
|
|
if (key < MAX_KEYS) {
|
|
if (keysDown[key]) { // confirm pressed state
|
|
DWORD keyVK = MapVirtualKeyEx(key, MAPVK_VSC_TO_VK, keyboardLayout);
|
|
if (keyVK) keysDown[key] = (GetAsyncKeyState(keyVK) & 0x8000);
|
|
}
|
|
return (keysDown[key] > 0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void __stdcall TapKey(DWORD key) {
|
|
DIDEVICEOBJECTDATA data;
|
|
data.dwTimeStamp = 0;
|
|
data.dwSequence = 0;
|
|
data.dwOfs = key;
|
|
data.dwData = 0x80; // flag pressed
|
|
bufferedPresses.push(data);
|
|
data.dwData = 0x00; // flag released
|
|
bufferedPresses.push(data);
|
|
}
|
|
|
|
class FakeDirectInputDevice : public IDirectInputDeviceA {
|
|
private:
|
|
IDirectInputDeviceA* RealDevice;
|
|
DWORD DeviceType;
|
|
ULONG Refs;
|
|
bool formatLock;
|
|
DIDATAFORMAT oldFormat;
|
|
|
|
public:
|
|
/*** Constructor and misc functions ***/
|
|
FakeDirectInputDevice(IDirectInputDevice* device, DWORD type) {
|
|
RealDevice = device;
|
|
DeviceType = type;
|
|
Refs = 1;
|
|
formatLock = false;
|
|
oldFormat.dwDataSize = 0;
|
|
}
|
|
|
|
/*** IUnknown methods ***/
|
|
HRESULT __stdcall QueryInterface(REFIID riid, LPVOID * ppvObj) {
|
|
return RealDevice->QueryInterface(riid, ppvObj);
|
|
}
|
|
|
|
ULONG __stdcall AddRef(void) {
|
|
return ++Refs;
|
|
}
|
|
|
|
ULONG __stdcall Release(void) {
|
|
if (--Refs == 0) {
|
|
RealDevice->Release();
|
|
delete this;
|
|
return 0;
|
|
} else {
|
|
return Refs;
|
|
}
|
|
}
|
|
|
|
/*** IDirectInputDevice8A methods ***/
|
|
HRESULT __stdcall GetCapabilities(LPDIDEVCAPS a) {
|
|
return RealDevice->GetCapabilities(a);
|
|
}
|
|
|
|
HRESULT __stdcall EnumObjects(LPDIENUMDEVICEOBJECTSCALLBACKA a, LPVOID b, DWORD c) {
|
|
return RealDevice->EnumObjects(a, b, c);
|
|
}
|
|
|
|
HRESULT __stdcall GetProperty(REFGUID a, DIPROPHEADER* b) {
|
|
return RealDevice->GetProperty(a, b);
|
|
}
|
|
|
|
HRESULT __stdcall SetProperty(REFGUID a, const DIPROPHEADER* b) {
|
|
return RealDevice->SetProperty(a, b);
|
|
}
|
|
|
|
HRESULT __stdcall Acquire(void) {
|
|
return RealDevice->Acquire();
|
|
}
|
|
|
|
HRESULT __stdcall Unacquire(void) {
|
|
return RealDevice->Unacquire();
|
|
}
|
|
|
|
// Only called for the mouse
|
|
HRESULT __stdcall GetDeviceState(DWORD a, LPVOID b) {
|
|
Graphics::RefreshGraphics();
|
|
if (DeviceType != kDeviceType_MOUSE) {
|
|
return RealDevice->GetDeviceState(a, b);
|
|
}
|
|
|
|
DIMOUSESTATE2 MouseState;
|
|
HRESULT hr;
|
|
if (formatLock)
|
|
hr = RealDevice->GetDeviceState(sizeof(DIMOUSESTATE2), &MouseState);
|
|
else
|
|
hr = RealDevice->GetDeviceState(sizeof(DIMOUSESTATE), &MouseState);
|
|
if (FAILED(hr)) return hr;
|
|
|
|
if (reverseMouse) {
|
|
BYTE tmp = MouseState.rgbButtons[0];
|
|
MouseState.rgbButtons[0] = MouseState.rgbButtons[1];
|
|
MouseState.rgbButtons[1] = tmp;
|
|
}
|
|
|
|
if (adjustMouseSpeed) {
|
|
double d = ((double)MouseState.lX) * mouseSpeedMod + mousePartX;
|
|
mousePartX = std::modf(d, &d);
|
|
MouseState.lX = (long)d;
|
|
d = ((double)MouseState.lY) * mouseSpeedMod + mousePartY;
|
|
mousePartY = std::modf(d, &d);
|
|
MouseState.lY = (long)d;
|
|
}
|
|
|
|
if (useScrollWheel) {
|
|
int count = 1;
|
|
if (MouseState.lZ > 0) {
|
|
if (fo::var::gmouse_current_cursor == 2 || fo::var::gmouse_current_cursor == 3) {
|
|
__asm {
|
|
call fo::funcoffs::display_scroll_up_;
|
|
}
|
|
} else {
|
|
if (wheelMod) count = MouseState.lZ / wheelMod;
|
|
if (count < 1) count = 1;
|
|
while (count--) TapKey(DIK_UP);
|
|
}
|
|
} else if (MouseState.lZ < 0) {
|
|
if (fo::var::gmouse_current_cursor == 2 || fo::var::gmouse_current_cursor == 3) {
|
|
__asm {
|
|
call fo::funcoffs::display_scroll_down_;
|
|
}
|
|
} else {
|
|
if (wheelMod) count = (-MouseState.lZ) / wheelMod;
|
|
if (count < 1) count = 1;
|
|
while (count--) TapKey(DIK_DOWN);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (middleMouseKey && MouseState.rgbButtons[2]) {
|
|
if (!middleMouseDown) {
|
|
TapKey(middleMouseKey);
|
|
middleMouseDown = true;
|
|
}
|
|
} else middleMouseDown = false;
|
|
mouseX = MouseState.lX;
|
|
mouseY = MouseState.lY;
|
|
|
|
int numButtons = formatLock ? 8 : 4;
|
|
for (int i = 0; i < numButtons; i++) {
|
|
if ((MouseState.rgbButtons[i] & 0x80) != (keysDown[256 + i] & 0x80)) { // state changed
|
|
onMouseClick.invoke(i, (MouseState.rgbButtons[i] & 0x80) > 0);
|
|
}
|
|
keysDown[256 + i] = MouseState.rgbButtons[i];
|
|
}
|
|
std::memcpy(b, &MouseState, sizeof(DIMOUSESTATE));
|
|
return 0;
|
|
}
|
|
|
|
/* Only called for the keyboard
|
|
0x4E06AB dxinput_read_keyboard_buffer_
|
|
0x4E0631 dxinput_flush_keyboard_buffer_
|
|
*/
|
|
HRESULT __stdcall GetDeviceData(DWORD a, DIDEVICEOBJECTDATA* buf, DWORD* count, DWORD d) { // buf - DirectInputKeyboardBuffer (0x6B2560)
|
|
if (DeviceType != kDeviceType_KEYBOARD) {
|
|
return RealDevice->GetDeviceData(a, buf, count, d);
|
|
}
|
|
if (*count == INFINITE && !buf && !d) { // flush
|
|
while (!bufferedPresses.empty()) bufferedPresses.pop();
|
|
return RealDevice->GetDeviceData(a, buf, count, d);
|
|
}
|
|
|
|
onInputLoop.invoke();
|
|
|
|
if (!buf || bufferedPresses.empty() || (d & DIGDD_PEEK)) {
|
|
HRESULT hr = RealDevice->GetDeviceData(a, buf, count, d);
|
|
if (FAILED(hr) || !buf || !(*count)) return hr;
|
|
|
|
for (DWORD i = 0; i < *count; i++) {
|
|
DWORD dxKey = buf[i].dwOfs;
|
|
assert(dxKey >= 0 && dxKey < MAX_KEYS);
|
|
DWORD state = buf[i].dwData & 0x80;
|
|
DWORD oldState = keysDown[dxKey];
|
|
keysDown[dxKey] = state;
|
|
|
|
HookCommon::KeyPressHook(&dxKey, (state > 0), MapVirtualKeyEx(dxKey, MAPVK_VSC_TO_VK, keyboardLayout));
|
|
|
|
if ((signed)dxKey > 0 && dxKey != buf[i].dwOfs) {
|
|
keysDown[buf[i].dwOfs] = oldState;
|
|
buf[i].dwOfs = dxKey; // Override key
|
|
keysDown[buf[i].dwOfs] = state;
|
|
}
|
|
onKeyPressed.invoke(buf[i].dwOfs, (state > 0));
|
|
}
|
|
return hr;
|
|
}
|
|
//Despite passing an array of 32 data objects, fallout cant seem to cope with a key being pressed and released in the same frame...
|
|
//TODO: Fallouts behaviour when passing multiple keypresses makes it appear like it's expecting the DIDEVICEOBJECTDATA struct to be
|
|
// something other than 16 bytes. afaik, fallout uses DX3 for input, which is before the appData field was added, but it could
|
|
// be worth checking anyway.
|
|
*buf = bufferedPresses.front();
|
|
bufferedPresses.pop();
|
|
*count = 1;
|
|
return DI_OK;
|
|
}
|
|
|
|
HRESULT __stdcall SetDataFormat(const DIDATAFORMAT* a) {
|
|
if (formatLock && oldFormat.dwSize) return RealDevice->SetDataFormat(&oldFormat);
|
|
memcpy(&oldFormat, a, sizeof(DIDATAFORMAT));
|
|
return RealDevice->SetDataFormat(a);
|
|
}
|
|
|
|
HRESULT __stdcall SetEventNotification(HANDLE a) {
|
|
return RealDevice->SetEventNotification(a);
|
|
}
|
|
|
|
HRESULT __stdcall SetCooperativeLevel(HWND a, DWORD b) {
|
|
if (DeviceType == kDeviceType_KEYBOARD && backgroundKeyboard) b = DISCL_BACKGROUND | DISCL_NONEXCLUSIVE;
|
|
if (DeviceType == kDeviceType_MOUSE && backgroundMouse) b = DISCL_BACKGROUND | DISCL_NONEXCLUSIVE;
|
|
return RealDevice->SetCooperativeLevel(a, b);
|
|
}
|
|
|
|
HRESULT __stdcall GetObjectInfo(LPDIDEVICEOBJECTINSTANCEA a, DWORD b, DWORD c) {
|
|
return RealDevice->GetObjectInfo(a, b, c);
|
|
}
|
|
|
|
HRESULT __stdcall GetDeviceInfo(LPDIDEVICEINSTANCEA a) {
|
|
return RealDevice->GetDeviceInfo(a);
|
|
}
|
|
|
|
HRESULT __stdcall RunControlPanel(HWND a, DWORD b) {
|
|
return RealDevice->RunControlPanel(a, b);
|
|
}
|
|
|
|
HRESULT __stdcall Initialize(HINSTANCE a, DWORD b, REFGUID c) {
|
|
return RealDevice->Initialize(a, b, c);
|
|
}
|
|
|
|
void LockDataFormat(bool lock) {
|
|
formatLock = lock;
|
|
}
|
|
};
|
|
|
|
class FakeDirectInput : public IDirectInputA {
|
|
private:
|
|
IDirectInputA* RealInput;
|
|
ULONG Refs;
|
|
public:
|
|
/*** Constructor ***/
|
|
FakeDirectInput(IDirectInput* Real) {
|
|
RealInput = Real;
|
|
Refs = 1;
|
|
}
|
|
|
|
/*** IUnknown methods ***/
|
|
HRESULT __stdcall QueryInterface(REFIID riid, LPVOID* ppvObj) {
|
|
return RealInput->QueryInterface(riid, ppvObj);
|
|
}
|
|
|
|
ULONG __stdcall AddRef(void) {
|
|
return ++Refs;
|
|
}
|
|
|
|
ULONG __stdcall Release(void) {
|
|
if (--Refs == 0) {
|
|
RealInput->Release();
|
|
delete this;
|
|
return 0;
|
|
} else {
|
|
return Refs;
|
|
}
|
|
}
|
|
|
|
/*** IDirectInput8A methods ***/
|
|
HRESULT __stdcall CreateDevice(REFGUID r, IDirectInputDeviceA** device, IUnknown* unused) {
|
|
GUID GUID_SysMouse = { 0x6F1D2B60, 0xD5A0, 0x11CF, { 0xBF, 0xC7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00} };
|
|
GUID GUID_SysKeyboard = { 0x6F1D2B61, 0xD5A0, 0x11CF, { 0xBF, 0xC7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00} };
|
|
|
|
if (r != GUID_SysKeyboard && r != GUID_SysMouse) {
|
|
return RealInput->CreateDevice(r, device, unused);
|
|
} else {
|
|
DWORD type = (r == GUID_SysKeyboard) ? kDeviceType_KEYBOARD : kDeviceType_MOUSE;
|
|
|
|
IDirectInputDeviceA* realDevice;
|
|
HRESULT hr = RealInput->CreateDevice(r, &realDevice, unused);
|
|
if (hr != DI_OK) return hr;
|
|
|
|
FakeDirectInputDevice* fdevice = new FakeDirectInputDevice(realDevice, type);
|
|
*device = fdevice;
|
|
|
|
if (type == kDeviceType_MOUSE) {
|
|
MouseDevice = realDevice;
|
|
if (FAILED(fdevice->SetDataFormat(&c_dfDIMouse2))) {
|
|
dlogr("ERROR: Could not set mouse format to DIMOUSESTATE2. Only 4 buttons will be available!", DL_MAIN);
|
|
} else fdevice->LockDataFormat(true);
|
|
}
|
|
return DI_OK;
|
|
}
|
|
}
|
|
|
|
HRESULT __stdcall EnumDevices(DWORD a, LPDIENUMDEVICESCALLBACKA b, void* c, DWORD d) {
|
|
return RealInput->EnumDevices(a, b, c, d);
|
|
}
|
|
|
|
HRESULT __stdcall GetDeviceStatus(REFGUID r) {
|
|
return RealInput->GetDeviceStatus(r);
|
|
}
|
|
|
|
HRESULT __stdcall RunControlPanel(HWND a, DWORD b) {
|
|
return RealInput->RunControlPanel(a, b);
|
|
}
|
|
|
|
HRESULT __stdcall Initialize(HINSTANCE a, DWORD b) {
|
|
return RealInput->Initialize(a, b);
|
|
}
|
|
};
|
|
|
|
void InitInput() {
|
|
reverseMouse = IniReader::GetConfigInt("Input", "ReverseMouseButtons", 0) != 0;
|
|
useScrollWheel = IniReader::GetConfigInt("Input", "UseScrollWheel", 1) != 0;
|
|
wheelMod = IniReader::GetConfigInt("Input", "ScrollMod", 0);
|
|
|
|
long MouseSpeed = IniReader::GetConfigInt("Input", "MouseSensitivity", 100);
|
|
if (MouseSpeed != 100) {
|
|
adjustMouseSpeed = true;
|
|
mouseSpeedMod = ((double)MouseSpeed) / 100.0;
|
|
}
|
|
|
|
middleMouseKey = IniReader::GetConfigInt("Input", "MiddleMouse", DIK_B);
|
|
|
|
backgroundKeyboard = IniReader::GetConfigInt("Input", "BackgroundKeyboard", 0) != 0;
|
|
backgroundMouse = IniReader::GetConfigInt("Input", "BackgroundMouse", 0) != 0;
|
|
}
|
|
|
|
}
|
|
|
|
typedef HRESULT (__stdcall *DInputCreateProc)(HINSTANCE a,DWORD b,IDirectInputA** c,IUnknown* d);
|
|
|
|
// This should be in global namespace
|
|
HRESULT __stdcall FakeDirectInputCreate(HINSTANCE a, DWORD b, IDirectInputA** c, IUnknown* d) {
|
|
HMODULE dinput = LoadLibraryA("dinput.dll");
|
|
if (!dinput || dinput == INVALID_HANDLE_VALUE) return -1;
|
|
|
|
DInputCreateProc proc = (DInputCreateProc)GetProcAddress(dinput, "DirectInputCreateA");
|
|
if (!proc) return -1;
|
|
|
|
HRESULT hr = proc(a, b, c, d);
|
|
if (FAILED(hr)) return hr;
|
|
|
|
// Prevent "Failure initializing input devices" error
|
|
if (sfall::Graphics::IsWindowedMode && !sfall::backgroundMouse) {
|
|
SetForegroundWindow((HWND)fo::var::getInt(FO_VAR_GNW95_hwnd));
|
|
}
|
|
|
|
*c = (IDirectInputA*)new sfall::FakeDirectInput(*c);
|
|
|
|
sfall::keyboardLayout = GetKeyboardLayout(0);
|
|
|
|
return hr;
|
|
}
|