mirror of
https://github.com/smartcmd/MinecraftConsoles.git
synced 2026-08-20 09:57:09 +00:00
feat: add DInput/WinMM controller support for Windows (DS4, generic gamepads)
Hooks XInputGetState and XInputGetCapabilities via IAT patching so non-XInput controllers (DS4, generic HID gamepads) are visible to 4J_Input.lib and the rest of the game. Uses the WinMM joystick API to read device state and translates it to XINPUT_STATE format, mapping DS4 axes (Z=RSX, Rz=RSY, Ry=R2, Rx=L2) and buttons to their Xbox equivalents. Unoccupied XInput slots are assigned toDInput devices and rescanned every ~3 seconds.
This commit is contained in:
parent
6e736ebff3
commit
bbc7e214ae
|
|
@ -16302,6 +16302,7 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CU</Comman
|
|||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ORBIS'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage_Vita|ORBIS'">true</ExcludedFromBuild>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Windows64\Windows64_DInputManager.h" />
|
||||
<ClInclude Include="Windows64\KeyboardMouseInput.h">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage|Durango'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='CONTENTPACKAGE_SYMBOLS|Durango'">true</ExcludedFromBuild>
|
||||
|
|
@ -29201,6 +29202,51 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CU</Comman
|
|||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ORBIS'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage_Vita|ORBIS'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Windows64\Windows64_DInputManager.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage|Durango'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='CONTENTPACKAGE_SYMBOLS|Durango'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ReleaseForArt|Durango'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage_NO_TU|Durango'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugContentPackage|Durango'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Durango'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage_Vita|Durango'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Durango'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage|Xbox 360'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='CONTENTPACKAGE_SYMBOLS|Xbox 360'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ReleaseForArt|Xbox 360'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage_NO_TU|Xbox 360'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Xbox 360'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage_Vita|Xbox 360'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Xbox 360'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage|x64'">false</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='CONTENTPACKAGE_SYMBOLS|x64'">false</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ReleaseForArt|x64'">false</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage_NO_TU|x64'">false</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage_Vita|x64'">false</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage|PS3'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage|PSVita'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='CONTENTPACKAGE_SYMBOLS|PS3'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='CONTENTPACKAGE_SYMBOLS|PSVita'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ReleaseForArt|PS3'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ReleaseForArt|PSVita'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage_NO_TU|PS3'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage_NO_TU|PSVita'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|PS3'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage_Vita|PS3'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|PSVita'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage_Vita|PSVita'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|PS3'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|PSVita'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage|ORBIS'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='CONTENTPACKAGE_SYMBOLS|ORBIS'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ReleaseForArt|ORBIS'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage_NO_TU|ORBIS'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ORBIS'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ORBIS'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage_Vita|ORBIS'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Windows64\Windows64_UIController.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ContentPackage|Durango'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='CONTENTPACKAGE_SYMBOLS|Durango'">true</ExcludedFromBuild>
|
||||
|
|
|
|||
|
|
@ -2344,6 +2344,9 @@
|
|||
<ClInclude Include="Windows64\KeyboardMouseInput.h">
|
||||
<Filter>Windows64\Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Windows64\Windows64_DInputManager.h">
|
||||
<Filter>Windows64\Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Windows64\Windows64_App.h">
|
||||
<Filter>Windows64</Filter>
|
||||
</ClInclude>
|
||||
|
|
@ -4776,6 +4779,9 @@
|
|||
<ClCompile Include="Windows64\KeyboardMouseInput.cpp">
|
||||
<Filter>Windows64\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Windows64\Windows64_DInputManager.cpp">
|
||||
<Filter>Windows64\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Windows64\Windows64_App.cpp">
|
||||
<Filter>Windows64</Filter>
|
||||
</ClCompile>
|
||||
|
|
|
|||
338
Minecraft.Client/Windows64/Windows64_DInputManager.cpp
Normal file
338
Minecraft.Client/Windows64/Windows64_DInputManager.cpp
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
#include "stdafx.h"
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
|
||||
#include "Windows64_DInputManager.h"
|
||||
#include <xinput.h>
|
||||
#include <mmsystem.h>
|
||||
#include <imagehlp.h> // for ImageDirectoryEntryToData / manual PE walk
|
||||
#pragma comment(lib, "winmm.lib")
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Internal types & state
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
typedef DWORD (WINAPI* PFN_XInputGetState )(DWORD, XINPUT_STATE*);
|
||||
typedef DWORD (WINAPI* PFN_XInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES*);
|
||||
|
||||
static PFN_XInputGetState s_origGetState = nullptr;
|
||||
static PFN_XInputGetCapabilities s_origGetCaps = nullptr;
|
||||
|
||||
// WinMM joystick ID assigned to each XInput slot as a DInput fallback.
|
||||
// -1 means "no DInput fallback for this slot".
|
||||
static int s_dInputForSlot[XUSER_MAX_COUNT] = { -1, -1, -1, -1 };
|
||||
|
||||
// Simple frame-rate throttle: only re-scan for new DInput devices every N ticks.
|
||||
static int s_rescanCountdown = 0;
|
||||
static const int RESCAN_INTERVAL = 90; // ~3 s at 30 fps
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// WinMM -> XInput axis conversion helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// WinMM range: 0-65535, centre ~32767
|
||||
// XInput range: -32768..32767
|
||||
static SHORT WinMMAxisToXInput(DWORD winmmAxis)
|
||||
{
|
||||
int v = (int)winmmAxis - 32768;
|
||||
if (v < -32768) v = -32768;
|
||||
if (v > 32767) v = 32767;
|
||||
return (SHORT)v;
|
||||
}
|
||||
|
||||
// WinMM trigger range: 0-65535 (0 = not pressed)
|
||||
// XInput trigger range: 0-255
|
||||
static BYTE WinMMTriggerToXInput(DWORD winmmAxis)
|
||||
{
|
||||
return (BYTE)(winmmAxis >> 8);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Build an XINPUT_STATE from a WinMM joyGetPosEx call.
|
||||
// Returns true if the joystick responded successfully.
|
||||
// ---------------------------------------------------------------------------
|
||||
static bool WinMMToXInputState(UINT wmId, XINPUT_STATE* out)
|
||||
{
|
||||
JOYINFOEX ji = {};
|
||||
ji.dwSize = sizeof(ji);
|
||||
ji.dwFlags = JOY_RETURNALL;
|
||||
|
||||
if (joyGetPosEx(wmId, &ji) != JOYERR_NOERROR)
|
||||
return false;
|
||||
|
||||
ZeroMemory(out, sizeof(*out));
|
||||
out->dwPacketNumber = GetTickCount();
|
||||
|
||||
WORD buttons = 0;
|
||||
|
||||
// ---- Face buttons (DS4 standard Windows HID driver mapping) ----
|
||||
if (ji.dwButtons & (1u << 0)) buttons |= XINPUT_GAMEPAD_X; // Square -> X
|
||||
if (ji.dwButtons & (1u << 1)) buttons |= XINPUT_GAMEPAD_A; // Cross -> A
|
||||
if (ji.dwButtons & (1u << 2)) buttons |= XINPUT_GAMEPAD_B; // Circle -> B
|
||||
if (ji.dwButtons & (1u << 3)) buttons |= XINPUT_GAMEPAD_Y; // Triangle -> Y
|
||||
if (ji.dwButtons & (1u << 4)) buttons |= XINPUT_GAMEPAD_LEFT_SHOULDER; // L1 -> LB
|
||||
if (ji.dwButtons & (1u << 5)) buttons |= XINPUT_GAMEPAD_RIGHT_SHOULDER; // R1 -> RB
|
||||
// buttons 6 & 7 are digital L2/R2 – we use the analog axes instead
|
||||
if (ji.dwButtons & (1u << 8)) buttons |= XINPUT_GAMEPAD_BACK; // Share -> Back
|
||||
if (ji.dwButtons & (1u << 9)) buttons |= XINPUT_GAMEPAD_START; // Options -> Start
|
||||
if (ji.dwButtons & (1u << 10)) buttons |= XINPUT_GAMEPAD_LEFT_THUMB; // L3
|
||||
if (ji.dwButtons & (1u << 11)) buttons |= XINPUT_GAMEPAD_RIGHT_THUMB; // R3
|
||||
|
||||
// ---- D-pad via POV hat ----
|
||||
// dwPOV is in 100ths of a degree. JOY_POVCENTERED (0xFFFF) = not pressed.
|
||||
if (ji.dwPOV != JOY_POVCENTERED)
|
||||
{
|
||||
DWORD pov = ji.dwPOV;
|
||||
if (pov <= 4500 || pov >= 31500) buttons |= XINPUT_GAMEPAD_DPAD_UP;
|
||||
if (pov >= 4500 && pov <= 13500) buttons |= XINPUT_GAMEPAD_DPAD_RIGHT;
|
||||
if (pov >= 13500 && pov <= 22500) buttons |= XINPUT_GAMEPAD_DPAD_DOWN;
|
||||
if (pov >= 22500 && pov <= 31500) buttons |= XINPUT_GAMEPAD_DPAD_LEFT;
|
||||
}
|
||||
|
||||
out->Gamepad.wButtons = buttons;
|
||||
|
||||
// ---- Sticks ----
|
||||
// Left stick: X / Y (WinMM Y: 0=top, XInput Y: positive=up -> invert)
|
||||
out->Gamepad.sThumbLX = WinMMAxisToXInput(ji.dwXpos);
|
||||
out->Gamepad.sThumbLY = (SHORT)(-WinMMAxisToXInput(ji.dwYpos) - 1);
|
||||
|
||||
// Right stick: Z (RSX) / Rz (RSY)
|
||||
// DS4 HID axis order: X, Y, Z(RSX), Rz(RSY), Ry(R2), Rx(L2)
|
||||
// WinMM maps these sequentially: dwZpos=RSX, dwRpos=RSY, dwUpos=R2, dwVpos=L2
|
||||
out->Gamepad.sThumbRX = WinMMAxisToXInput(ji.dwZpos);
|
||||
out->Gamepad.sThumbRY = (SHORT)(-WinMMAxisToXInput(ji.dwRpos) - 1);
|
||||
|
||||
// ---- Triggers ----
|
||||
// Ry (dwUpos) = R2, Rx (dwVpos) = L2
|
||||
out->Gamepad.bLeftTrigger = WinMMTriggerToXInput(ji.dwVpos);
|
||||
out->Gamepad.bRightTrigger = WinMMTriggerToXInput(ji.dwUpos);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Slot assignment: build / refresh the DInput -> XInput slot map.
|
||||
//
|
||||
// Heuristic: Windows places XInput controllers in the lowest WinMM joystick
|
||||
// slots. Joystick slots at index >= (number of connected XInput controllers)
|
||||
// are treated as DInput devices.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void RefreshDInputSlots()
|
||||
{
|
||||
if (!s_origGetState)
|
||||
return;
|
||||
|
||||
// Count connected XInput slots and record which slots are empty.
|
||||
bool xinputConnected[XUSER_MAX_COUNT] = {};
|
||||
int numXInputConnected = 0;
|
||||
for (DWORD xi = 0; xi < XUSER_MAX_COUNT; xi++)
|
||||
{
|
||||
XINPUT_STATE st;
|
||||
if (s_origGetState(xi, &st) == ERROR_SUCCESS)
|
||||
{
|
||||
xinputConnected[xi] = true;
|
||||
numXInputConnected++;
|
||||
}
|
||||
}
|
||||
|
||||
// Release any DInput slot whose WinMM device is no longer responding,
|
||||
// or whose XInput slot is now occupied by a real XInput controller.
|
||||
for (int xi = 0; xi < XUSER_MAX_COUNT; xi++)
|
||||
{
|
||||
if (s_dInputForSlot[xi] < 0)
|
||||
continue;
|
||||
|
||||
UINT wmId = (UINT)s_dInputForSlot[xi];
|
||||
JOYINFOEX ji = {};
|
||||
ji.dwSize = sizeof(ji);
|
||||
ji.dwFlags = JOY_RETURNBUTTONS;
|
||||
|
||||
bool devGone = (joyGetPosEx(wmId, &ji) != JOYERR_NOERROR);
|
||||
bool slotTaken = xinputConnected[xi];
|
||||
if (devGone || slotTaken)
|
||||
s_dInputForSlot[xi] = -1;
|
||||
}
|
||||
|
||||
// Build a set of WinMM IDs already in use as DInput slots.
|
||||
bool wmIdInUse[16] = {};
|
||||
for (int xi = 0; xi < XUSER_MAX_COUNT; xi++)
|
||||
if (s_dInputForSlot[xi] >= 0 && s_dInputForSlot[xi] < 16)
|
||||
wmIdInUse[s_dInputForSlot[xi]] = true;
|
||||
|
||||
// Enumerate WinMM joysticks. Those at index >= numXInputConnected are
|
||||
// treated as non-XInput (DInput) devices.
|
||||
UINT numWinMM = joyGetNumDevs();
|
||||
for (UINT wmId = (UINT)numXInputConnected; wmId < numWinMM; wmId++)
|
||||
{
|
||||
if (wmId >= 16) break;
|
||||
if (wmIdInUse[wmId]) continue;
|
||||
|
||||
// Test whether this device is actually connected.
|
||||
JOYINFOEX ji = {};
|
||||
ji.dwSize = sizeof(ji);
|
||||
ji.dwFlags = JOY_RETURNBUTTONS;
|
||||
if (joyGetPosEx(wmId, &ji) != JOYERR_NOERROR)
|
||||
continue;
|
||||
|
||||
// Find the first XInput slot without a real XInput controller or a
|
||||
// DInput assignment, and assign this device to it.
|
||||
for (int xi = 0; xi < XUSER_MAX_COUNT; xi++)
|
||||
{
|
||||
if (!xinputConnected[xi] && s_dInputForSlot[xi] < 0)
|
||||
{
|
||||
s_dInputForSlot[xi] = (int)wmId;
|
||||
wmIdInUse[wmId] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Hooked XInputGetState
|
||||
// ---------------------------------------------------------------------------
|
||||
static DWORD WINAPI Hooked_XInputGetState(DWORD dwUserIndex, XINPUT_STATE* pState)
|
||||
{
|
||||
// Always try the real XInput first.
|
||||
DWORD result = s_origGetState
|
||||
? s_origGetState(dwUserIndex, pState)
|
||||
: ERROR_DEVICE_NOT_CONNECTED;
|
||||
|
||||
if (result == ERROR_DEVICE_NOT_CONNECTED
|
||||
&& dwUserIndex < XUSER_MAX_COUNT
|
||||
&& s_dInputForSlot[dwUserIndex] >= 0)
|
||||
{
|
||||
if (WinMMToXInputState((UINT)s_dInputForSlot[dwUserIndex], pState))
|
||||
return ERROR_SUCCESS;
|
||||
|
||||
// Device disappeared – clear the slot.
|
||||
s_dInputForSlot[dwUserIndex] = -1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Hooked XInputGetCapabilities
|
||||
// ---------------------------------------------------------------------------
|
||||
static DWORD WINAPI Hooked_XInputGetCapabilities(DWORD dwUserIndex, DWORD dwFlags,
|
||||
XINPUT_CAPABILITIES* pCapabilities)
|
||||
{
|
||||
DWORD result = s_origGetCaps
|
||||
? s_origGetCaps(dwUserIndex, dwFlags, pCapabilities)
|
||||
: ERROR_DEVICE_NOT_CONNECTED;
|
||||
|
||||
if (result == ERROR_DEVICE_NOT_CONNECTED
|
||||
&& dwUserIndex < XUSER_MAX_COUNT
|
||||
&& s_dInputForSlot[dwUserIndex] >= 0)
|
||||
{
|
||||
ZeroMemory(pCapabilities, sizeof(*pCapabilities));
|
||||
pCapabilities->Type = XINPUT_DEVTYPE_GAMEPAD;
|
||||
pCapabilities->SubType = XINPUT_DEVSUBTYPE_GAMEPAD;
|
||||
pCapabilities->Gamepad.wButtons = 0xF3FF; // all mapped buttons
|
||||
pCapabilities->Gamepad.bLeftTrigger = 0xFF;
|
||||
pCapabilities->Gamepad.bRightTrigger = 0xFF;
|
||||
pCapabilities->Gamepad.sThumbLX = 32767;
|
||||
pCapabilities->Gamepad.sThumbLY = 32767;
|
||||
pCapabilities->Gamepad.sThumbRX = 32767;
|
||||
pCapabilities->Gamepad.sThumbRY = 32767;
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// IAT patching
|
||||
//
|
||||
// Walks the PE import table of the current process and replaces the entries
|
||||
// for XInputGetState and XInputGetCapabilities with our hooks. This affects
|
||||
// all callers in the process, including the statically-linked 4J_Input.lib.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Overwrite one function pointer in the IAT with page-permission handling.
|
||||
static void PatchIATEntry(void** entry, void* newFn, void** oldFn)
|
||||
{
|
||||
if (oldFn)
|
||||
*oldFn = *entry;
|
||||
|
||||
DWORD oldProtect = 0;
|
||||
VirtualProtect(entry, sizeof(void*), PAGE_READWRITE, &oldProtect);
|
||||
*entry = newFn;
|
||||
VirtualProtect(entry, sizeof(void*), oldProtect, &oldProtect);
|
||||
}
|
||||
|
||||
static void PatchXInputIAT()
|
||||
{
|
||||
HMODULE hMod = GetModuleHandle(NULL);
|
||||
if (!hMod) return;
|
||||
|
||||
auto* dosHdr = reinterpret_cast<IMAGE_DOS_HEADER*>(hMod);
|
||||
auto* ntHdrs = reinterpret_cast<IMAGE_NT_HEADERS*>(
|
||||
reinterpret_cast<BYTE*>(hMod) + dosHdr->e_lfanew);
|
||||
|
||||
DWORD impRVA = ntHdrs->OptionalHeader
|
||||
.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
|
||||
if (!impRVA) return;
|
||||
|
||||
auto* impDesc = reinterpret_cast<IMAGE_IMPORT_DESCRIPTOR*>(
|
||||
reinterpret_cast<BYTE*>(hMod) + impRVA);
|
||||
|
||||
for (; impDesc->Name; impDesc++)
|
||||
{
|
||||
const char* dllName = reinterpret_cast<char*>(
|
||||
reinterpret_cast<BYTE*>(hMod) + impDesc->Name);
|
||||
|
||||
// Match any XInput DLL version (xinput1_4.dll, xinput1_3.dll, etc.)
|
||||
if (_strnicmp(dllName, "xinput", 6) != 0)
|
||||
continue;
|
||||
|
||||
auto* thunk = reinterpret_cast<IMAGE_THUNK_DATA*>(
|
||||
reinterpret_cast<BYTE*>(hMod) + impDesc->FirstThunk);
|
||||
auto* origThunk = reinterpret_cast<IMAGE_THUNK_DATA*>(
|
||||
reinterpret_cast<BYTE*>(hMod) + impDesc->OriginalFirstThunk);
|
||||
|
||||
for (; thunk->u1.AddressOfData; thunk++, origThunk++)
|
||||
{
|
||||
if (origThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG)
|
||||
continue; // ordinal import, skip
|
||||
|
||||
auto* byName = reinterpret_cast<IMAGE_IMPORT_BY_NAME*>(
|
||||
reinterpret_cast<BYTE*>(hMod) + origThunk->u1.AddressOfData);
|
||||
|
||||
if (strcmp(byName->Name, "XInputGetState") == 0)
|
||||
{
|
||||
PatchIATEntry(reinterpret_cast<void**>(&thunk->u1.Function),
|
||||
reinterpret_cast<void*>(Hooked_XInputGetState),
|
||||
reinterpret_cast<void**>(&s_origGetState));
|
||||
}
|
||||
else if (strcmp(byName->Name, "XInputGetCapabilities") == 0)
|
||||
{
|
||||
PatchIATEntry(reinterpret_cast<void**>(&thunk->u1.Function),
|
||||
reinterpret_cast<void*>(Hooked_XInputGetCapabilities),
|
||||
reinterpret_cast<void**>(&s_origGetCaps));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public API
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void DInputManager_Init()
|
||||
{
|
||||
PatchXInputIAT();
|
||||
RefreshDInputSlots();
|
||||
}
|
||||
|
||||
void DInputManager_Tick()
|
||||
{
|
||||
if (--s_rescanCountdown <= 0)
|
||||
{
|
||||
s_rescanCountdown = RESCAN_INTERVAL;
|
||||
RefreshDInputSlots();
|
||||
}
|
||||
}
|
||||
|
||||
#endif // _WINDOWS64
|
||||
56
Minecraft.Client/Windows64/Windows64_DInputManager.h
Normal file
56
Minecraft.Client/Windows64/Windows64_DInputManager.h
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
|
||||
// DInput / WinMM controller support for Windows.
|
||||
//
|
||||
// The 4J_Input.lib uses XInput internally, which only sees XInput-compatible
|
||||
// controllers (Xbox controllers, XInput-mode adapters, etc.). Non-XInput
|
||||
// devices – DualShock 4, generic HID gamepads, most non-Xbox controllers –
|
||||
// are invisible to XInput and therefore to the game.
|
||||
//
|
||||
// This module fixes that by:
|
||||
// 1. Hooking XInputGetState / XInputGetCapabilities via IAT patching so
|
||||
// that every call in the process (including from 4J_Input.lib) passes
|
||||
// through our wrapper.
|
||||
// 2. Using the WinMM joystick API (joyGetPosEx) to enumerate non-XInput
|
||||
// devices and feed their state back through the XInput interface as if
|
||||
// they were standard XInput gamepads.
|
||||
//
|
||||
// The DS4 / DualShock 4 button layout used here matches the standard Windows
|
||||
// HID driver (no DS4Windows required):
|
||||
//
|
||||
// WinMM button index -> Xbox equivalent
|
||||
// 0 Square -> X
|
||||
// 1 Cross -> A
|
||||
// 2 Circle -> B
|
||||
// 3 Triangle -> Y
|
||||
// 4 L1 -> Left Shoulder
|
||||
// 5 R1 -> Right Shoulder
|
||||
// 6 L2 (digital) -> (analog axis used instead)
|
||||
// 7 R2 (digital) -> (analog axis used instead)
|
||||
// 8 Share -> Back
|
||||
// 9 Options -> Start
|
||||
// 10 L3 -> Left Thumb
|
||||
// 11 R3 -> Right Thumb
|
||||
//
|
||||
// Axis mapping (joyGetPosEx) - DS4 standard Windows HID driver:
|
||||
// DS4 HID axis order: X, Y, Z(RSX), Rz(RSY), Ry(R2), Rx(L2)
|
||||
// WinMM maps HID axes sequentially to dwXpos/Ypos/Zpos/Rpos/Upos/Vpos:
|
||||
// dwXpos -> Left stick X
|
||||
// dwYpos -> Left stick Y (WinMM: 0=up; inverted for XInput)
|
||||
// dwZpos -> Right stick X (HID Z)
|
||||
// dwRpos -> Right stick Y (HID Rz, inverted for XInput)
|
||||
// dwUpos -> R2 trigger (HID Ry)
|
||||
// dwVpos -> L2 trigger (HID Rx)
|
||||
// dwPOV -> D-pad (centidegrees, JOY_POVCENTERED = not pressed)
|
||||
|
||||
// Call once after InputManager.Initialise() and before the first Tick().
|
||||
// Patches the IAT so both game code and 4J_Input.lib see the DInput fallback.
|
||||
void DInputManager_Init();
|
||||
|
||||
// Call once per frame (alongside InputManager.Tick()) to refresh the list of
|
||||
// connected DInput controllers.
|
||||
void DInputManager_Tick();
|
||||
|
||||
#endif // _WINDOWS64
|
||||
|
|
@ -38,6 +38,7 @@
|
|||
#include "..\..\Minecraft.World\OldChunkStorage.h"
|
||||
|
||||
#include "Xbox/resource.h"
|
||||
#include "Windows64_DInputManager.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma comment(lib, "legacy_stdio_definitions.lib")
|
||||
|
|
@ -395,9 +396,6 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|||
case WM_MOUSEWHEEL:
|
||||
KMInput.OnMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam));
|
||||
break;
|
||||
case WM_MOUSEMOVE:
|
||||
KMInput.OnMouseMove(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
||||
break;
|
||||
case WM_ACTIVATE:
|
||||
if (LOWORD(wParam) == WA_INACTIVE)
|
||||
KMInput.SetCapture(false);
|
||||
|
|
@ -408,8 +406,9 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|||
break;
|
||||
|
||||
case WM_SETCURSOR:
|
||||
// Hide the OS cursor when an Iggy/Flash menu is displayed (it has its own Flash cursor)
|
||||
if (LOWORD(lParam) == HTCLIENT && !KMInput.IsCaptured() && ui.GetMenuDisplayed(0))
|
||||
// Hide the OS cursor in inventory/container menus - the game renders its own in-game pointer.
|
||||
// Main menus show the OS cursor so the player can see exactly where they are clicking.
|
||||
if (LOWORD(lParam) == HTCLIENT && !KMInput.IsCaptured() && ui.IsContainerMenuDisplayed(0))
|
||||
{
|
||||
SetCursor(NULL);
|
||||
return TRUE;
|
||||
|
|
@ -856,6 +855,10 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
|
|||
// Set the number of possible joypad layouts that the user can switch between, and the number of actions
|
||||
InputManager.Initialise(1,3,MINECRAFT_ACTION_MAX, ACTION_MAX_MENU);
|
||||
|
||||
// Hook XInputGetState/XInputGetCapabilities so non-XInput controllers
|
||||
// (DS4, generic DInput gamepads) are visible to InputManager and the game.
|
||||
DInputManager_Init();
|
||||
|
||||
// Initialize keyboard/mouse input
|
||||
KMInput.Init(g_hWnd);
|
||||
|
||||
|
|
@ -1083,6 +1086,7 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
|
|||
|
||||
app.UpdateTime();
|
||||
PIXBeginNamedEvent(0,"Input manager tick");
|
||||
DInputManager_Tick(); // refresh DInput slot assignments
|
||||
InputManager.Tick();
|
||||
KMInput.Tick();
|
||||
PIXEndNamedEvent();
|
||||
|
|
|
|||
Loading…
Reference in a new issue