Files
sfall/sfall/InputFuncs.cpp
T
2023-10-05 09:10:44 +08:00

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;
}