Add wine_patches

This commit is contained in:
brunodev85 2024-01-23 17:11:51 -03:00
parent 9b1757a397
commit 440956199e
3 changed files with 2726 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,746 @@
/* DirectInput Gamepad device
*
* Copyright 2024 BrunoSX
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <stdarg.h>
#include <string.h>
#include <math.h>
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winternl.h"
#include "winuser.h"
#include "winerror.h"
#include "winreg.h"
#include "dinput.h"
#include "winsock2.h"
#include "devguid.h"
#include "hidusage.h"
#include "dinput_private.h"
#include "device_private.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(dinput);
#define SERVER_PORT 7948
#define CLIENT_PORT 7947
#define BUFFER_SIZE 64
#define REQUEST_CODE_GET_GAMEPAD 8
#define REQUEST_CODE_GET_GAMEPAD_STATE 9
#define REQUEST_CODE_RELEASE_GAMEPAD 10
#define MAPPER_TYPE_STANDARD 0
#define MAPPER_TYPE_XINPUT 1
#define IDX_BUTTON_A 0
#define IDX_BUTTON_B 1
#define IDX_BUTTON_X 2
#define IDX_BUTTON_Y 3
#define IDX_BUTTON_L1 4
#define IDX_BUTTON_R1 5
#define IDX_BUTTON_L2 10
#define IDX_BUTTON_R2 11
#define IDX_BUTTON_SELECT 6
#define IDX_BUTTON_START 7
#define IDX_BUTTON_L3 8
#define IDX_BUTTON_R3 9
struct gamepad_state
{
short buttons;
char dpad;
short thumb_lx;
short thumb_ly;
short thumb_rx;
short thumb_ry;
};
struct gamepad
{
struct dinput_device base;
struct gamepad_state state;
};
static const struct dinput_device_vtbl gamepad_vtbl;
static SOCKET server_sock = INVALID_SOCKET;
static BOOL winsock_loaded = FALSE;
static int connected_gamepad_id = 0;
static char mapper_type = MAPPER_TYPE_XINPUT;
static inline struct gamepad *impl_from_IDirectInputDevice8W( IDirectInputDevice8W *iface )
{
return CONTAINING_RECORD( CONTAINING_RECORD( iface, struct dinput_device, IDirectInputDevice8W_iface ), struct gamepad, base );
}
static void close_server_socket( void )
{
if (server_sock != INVALID_SOCKET)
{
closesocket( server_sock );
server_sock = INVALID_SOCKET;
}
if (winsock_loaded)
{
WSACleanup();
winsock_loaded = FALSE;
}
}
static BOOL create_server_socket( void )
{
WSADATA wsa_data;
struct sockaddr_in server_addr;
int res;
close_server_socket();
winsock_loaded = WSAStartup( MAKEWORD(2,2), &wsa_data ) == NO_ERROR;
if (!winsock_loaded) return FALSE;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr( "127.0.0.1" );
server_addr.sin_port = htons( SERVER_PORT );
server_sock = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
if (server_sock == INVALID_SOCKET) return FALSE;
res = bind( server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr) );
if (res == SOCKET_ERROR) return FALSE;
return TRUE;
}
static BOOL get_gamepad_request( DIDEVICEINSTANCEW *instance, DWORD version )
{
int res, client_addr_len, gamepad_id, name_len;
char buffer[BUFFER_SIZE];
struct sockaddr_in client_addr;
DWORD size;
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = inet_addr( "127.0.0.1" );
client_addr.sin_port = htons( CLIENT_PORT );
client_addr_len = sizeof(client_addr);
buffer[0] = REQUEST_CODE_GET_GAMEPAD;
buffer[1] = 0;
res = sendto( server_sock, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&client_addr, client_addr_len );
if (res == SOCKET_ERROR) return FALSE;
res = recvfrom( server_sock, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&client_addr, &client_addr_len );
if (res == SOCKET_ERROR || buffer[0] != REQUEST_CODE_GET_GAMEPAD) return FALSE;
memcpy( &gamepad_id, buffer + 1, 4 );
if (gamepad_id == 0)
{
connected_gamepad_id = 0;
return FALSE;
}
connected_gamepad_id = gamepad_id;
mapper_type = buffer[5];
memcpy( &name_len, buffer + 6, 4 );
char gamepad_name[name_len + 1];
gamepad_name[name_len] = '\0';
memcpy( gamepad_name, buffer + 10, name_len );
size = instance->dwSize;
memset( instance, 0, size );
instance->dwSize = size;
instance->guidInstance = GUID_Joystick;
instance->guidProduct = GUID_Joystick;
instance->guidProduct.Data1 = MAKELONG( 0x045e, 0x028e );
if (version >= 0x0800) instance->dwDevType = DIDEVTYPE_HID | DI8DEVTYPE_GAMEPAD | (DI8DEVTYPEGAMEPAD_STANDARD << 8);
else instance->dwDevType = DIDEVTYPE_HID | DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_GAMEPAD << 8);
instance->wUsagePage = HID_USAGE_PAGE_GENERIC;
instance->wUsage = HID_USAGE_GENERIC_GAMEPAD;
MultiByteToWideChar( CP_ACP, 0, gamepad_name, -1, instance->tszInstanceName, MAX_PATH );
MultiByteToWideChar( CP_ACP, 0, gamepad_name, -1, instance->tszProductName, MAX_PATH );
return TRUE;
}
static LONG scale_value( LONG value, struct object_properties *properties )
{
LONG log_min, log_max, phy_min, phy_max;
log_min = properties->logical_min;
log_max = properties->logical_max;
phy_min = properties->range_min;
phy_max = properties->range_max;
return phy_min + MulDiv( value - log_min, phy_max - phy_min, log_max - log_min );
}
static LONG scale_axis_value( LONG value, struct object_properties *properties )
{
LONG log_ctr, log_min, log_max, phy_ctr, phy_min, phy_max;
log_min = properties->logical_min;
log_max = properties->logical_max;
phy_min = properties->range_min;
phy_max = properties->range_max;
if (phy_min == 0) phy_ctr = phy_max >> 1;
else phy_ctr = round( (phy_min + phy_max) / 2.0 );
if (log_min == 0) log_ctr = log_max >> 1;
else log_ctr = round( (log_min + log_max) / 2.0 );
value -= log_ctr;
if (value <= 0)
{
log_max = MulDiv( log_min - log_ctr, properties->deadzone, 10000 );
log_min = MulDiv( log_min - log_ctr, properties->saturation, 10000 );
phy_max = phy_ctr;
}
else
{
log_min = MulDiv( log_max - log_ctr, properties->deadzone, 10000 );
log_max = MulDiv( log_max - log_ctr, properties->saturation, 10000 );
phy_min = phy_ctr;
}
if (value <= log_min) return phy_min;
if (value >= log_max) return phy_max;
return phy_min + MulDiv( value - log_min, phy_max - phy_min, log_max - log_min );
}
static void handle_gamepad_input( IDirectInputDevice8W *iface, short thumb_lx, short thumb_ly, short thumb_rx, short thumb_ry, short buttons, char dpad )
{
int i, j;
DWORD time, seq;
BOOL notify = FALSE;
struct gamepad *impl = impl_from_IDirectInputDevice8W( iface );
DIJOYSTATE *state = (DIJOYSTATE *)impl->base.device_state;
time = GetCurrentTime();
seq = impl->base.dinput->evsequence++;
if (mapper_type == MAPPER_TYPE_STANDARD)
{
if (thumb_lx != impl->state.thumb_lx)
{
impl->state.thumb_lx = thumb_lx;
state->lX = scale_axis_value(thumb_lx, &impl->base.object_properties[0]);
queue_event( iface, DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 0 ), state->lX, time, seq );
notify = TRUE;
}
if (thumb_ly != impl->state.thumb_ly)
{
impl->state.thumb_ly = thumb_ly;
state->lY = scale_axis_value(thumb_ly, &impl->base.object_properties[1]);
queue_event( iface, DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 1 ), state->lY, time, seq );
notify = TRUE;
}
if (thumb_rx != impl->state.thumb_rx)
{
impl->state.thumb_rx = thumb_rx;
state->lZ = scale_axis_value(thumb_rx, &impl->base.object_properties[2]);
queue_event( iface, DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 2 ), state->lZ, time, seq );
notify = TRUE;
}
if (thumb_ry != impl->state.thumb_ry)
{
impl->state.thumb_ry = thumb_ry;
state->lRz = scale_axis_value(thumb_ry, &impl->base.object_properties[5]);
queue_event( iface, DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 3 ), state->lRz, time, seq );
notify = TRUE;
}
if (buttons != impl->state.buttons)
{
impl->state.buttons = buttons;
for (i = 0, j = 0; i < 12; i++)
{
switch (i)
{
case IDX_BUTTON_A: j = 1; break;
case IDX_BUTTON_B: j = 2; break;
case IDX_BUTTON_X: j = 0; break;
case IDX_BUTTON_Y: j = 3; break;
case IDX_BUTTON_L1: j = 4; break;
case IDX_BUTTON_R1: j = 5; break;
case IDX_BUTTON_L2: j = 6; break;
case IDX_BUTTON_R2: j = 7; break;
case IDX_BUTTON_SELECT: j = 8; break;
case IDX_BUTTON_START: j = 9; break;
case IDX_BUTTON_L3: j = 10; break;
case IDX_BUTTON_R3: j = 11; break;
}
state->rgbButtons[j] = (buttons & (1<<i)) ? 0x80 : 0x00;
queue_event( iface, DIDFT_BUTTON | DIDFT_MAKEINSTANCE( j ), state->rgbButtons[j], time, seq );
}
notify = TRUE;
}
if (dpad != impl->state.dpad)
{
impl->state.dpad = dpad;
state->rgdwPOV[0] = dpad != -1 ? dpad * 4500 : -1;
queue_event( iface, DIDFT_POV | DIDFT_MAKEINSTANCE( 0 ), state->rgdwPOV[0], time, seq );
notify = TRUE;
}
}
else if (mapper_type == MAPPER_TYPE_XINPUT)
{
if (thumb_lx != impl->state.thumb_lx)
{
impl->state.thumb_lx = thumb_lx;
state->lX = scale_axis_value(thumb_lx, &impl->base.object_properties[0]);
queue_event( iface, DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 0 ), state->lX, time, seq );
notify = TRUE;
}
if (thumb_ly != impl->state.thumb_ly)
{
impl->state.thumb_ly = thumb_ly;
state->lY = scale_axis_value(thumb_ly, &impl->base.object_properties[1]);
queue_event( iface, DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 1 ), state->lY, time, seq );
notify = TRUE;
}
if (thumb_rx != impl->state.thumb_rx)
{
impl->state.thumb_rx = thumb_rx;
state->lRx = scale_axis_value(thumb_rx, &impl->base.object_properties[3]);
queue_event( iface, DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 2 ), state->lRx, time, seq );
notify = TRUE;
}
if (thumb_ry != impl->state.thumb_ry)
{
impl->state.thumb_ry = thumb_ry;
state->lRy = scale_axis_value(thumb_ry, &impl->base.object_properties[4]);
queue_event( iface, DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 4 ), state->lRy, time, seq );
notify = TRUE;
}
if (buttons != impl->state.buttons)
{
impl->state.buttons = buttons;
for (i = 0; i < 10; i++)
{
state->rgbButtons[i] = (buttons & (1<<i)) ? 0x80 : 0x00;
queue_event( iface, DIDFT_BUTTON | DIDFT_MAKEINSTANCE( i ), state->rgbButtons[i], time, seq );
}
state->lZ = scale_value((buttons & (1<<10)) ? 32767 : ((buttons & (1<<11)) ? -32768 : 0), &impl->base.object_properties[2]);
queue_event( iface, DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 2 ), state->lZ, time, seq );
notify = TRUE;
}
if (dpad != impl->state.dpad)
{
impl->state.dpad = dpad;
state->rgdwPOV[0] = dpad != -1 ? dpad * 4500 : -1;
queue_event( iface, DIDFT_POV | DIDFT_MAKEINSTANCE( 0 ), state->rgdwPOV[0], time, seq );
notify = TRUE;
}
}
if (notify && impl->base.hEvent) SetEvent( impl->base.hEvent );
}
static BOOL get_gamepad_state_request( IDirectInputDevice8W *iface )
{
char buffer[BUFFER_SIZE];
int res, client_addr_len, gamepad_id;
char dpad;
short buttons, thumb_lx, thumb_ly, thumb_rx, thumb_ry;
struct sockaddr_in client_addr;
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = inet_addr( "127.0.0.1" );
client_addr.sin_port = htons( CLIENT_PORT );
client_addr_len = sizeof(client_addr);
buffer[0] = REQUEST_CODE_GET_GAMEPAD_STATE;
memcpy( buffer + 1, &connected_gamepad_id, 4 );
res = sendto( server_sock, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&client_addr, client_addr_len );
if (res == SOCKET_ERROR) return FALSE;
res = recvfrom( server_sock, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&client_addr, &client_addr_len );
if (res == SOCKET_ERROR || buffer[0] != REQUEST_CODE_GET_GAMEPAD_STATE || buffer[1] != 1) return FALSE;
memcpy( &gamepad_id, buffer + 2, 4 );
if (gamepad_id != connected_gamepad_id) return FALSE;
memcpy( &buttons, buffer + 6, 2 );
dpad = buffer[8];
memcpy( &thumb_lx, buffer + 9, 2 );
memcpy( &thumb_ly, buffer + 11, 2 );
memcpy( &thumb_rx, buffer + 13, 2 );
memcpy( &thumb_ry, buffer + 15, 2 );
handle_gamepad_input( iface, thumb_lx, thumb_ly, thumb_rx, thumb_ry, buttons, dpad );
return TRUE;
}
static void release_gamepad_request( void )
{
char buffer[BUFFER_SIZE];
struct sockaddr_in client_addr;
int client_addr_len;
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = inet_addr( "127.0.0.1" );
client_addr.sin_port = htons( CLIENT_PORT );
client_addr_len = sizeof(client_addr);
buffer[0] = REQUEST_CODE_RELEASE_GAMEPAD;
sendto( server_sock, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&client_addr, client_addr_len );
}
HRESULT gamepad_enum_device( DWORD type, DWORD flags, DIDEVICEINSTANCEW *instance, DWORD version )
{
if (!create_server_socket()) return DIERR_INPUTLOST;
return get_gamepad_request( instance, version ) ? DI_OK : DIERR_INPUTLOST;
}
static BOOL CALLBACK init_object_properties( const DIDEVICEOBJECTINSTANCEW *instance, void *data )
{
struct gamepad *impl = (struct gamepad *)data;
struct object_properties *properties = impl->base.object_properties + instance->dwOfs / sizeof(LONG);
properties->logical_min = -32768;
properties->logical_max = 32767;
properties->range_min = 0;
properties->range_max = 65535;
properties->saturation = 10000;
properties->granularity = 1;
return DIENUM_CONTINUE;
}
HRESULT gamepad_create_device( struct dinput *dinput, const GUID *guid, IDirectInputDevice8W **out )
{
struct gamepad *impl;
*out = NULL;
if (!IsEqualGUID( &GUID_Joystick, guid )) return DIERR_DEVICENOTREG;
if (!(impl = calloc( 1, sizeof(*impl) ))) return E_OUTOFMEMORY;
dinput_device_init( &impl->base, &gamepad_vtbl, guid, dinput );
impl->base.crit.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": struct gamepad*->base.crit");
impl->base.read_event = CreateEventW( NULL, TRUE, FALSE, NULL );
gamepad_enum_device( 0, 0, &impl->base.instance, dinput->dwVersion );
impl->base.caps.dwDevType = impl->base.instance.dwDevType;
impl->base.caps.dwFirmwareRevision = 100;
impl->base.caps.dwHardwareRevision = 100;
impl->base.dwCoopLevel = DISCL_NONEXCLUSIVE | DISCL_BACKGROUND;
impl->base.object_properties = calloc( 6, sizeof(struct object_properties) );
if (!impl->base.object_properties)
{
IDirectInputDevice_Release( &impl->base.IDirectInputDevice8W_iface );
return E_OUTOFMEMORY;
}
IDirectInputDevice8_EnumObjects( &impl->base.IDirectInputDevice8W_iface, init_object_properties, impl, DIDFT_ABSAXIS );
if (dinput->dwVersion >= 0x0800)
{
impl->base.raw_device.usUsagePage = HID_USAGE_PAGE_GENERIC;
impl->base.raw_device.usUsage = HID_USAGE_GENERIC_GAMEPAD;
}
*out = &impl->base.IDirectInputDevice8W_iface;
return DI_OK;
}
static void gamepad_release( IDirectInputDevice8W *iface )
{
struct gamepad *impl = impl_from_IDirectInputDevice8W( iface );
CloseHandle( impl->base.read_event );
}
static HRESULT gamepad_read( IDirectInputDevice8W *iface )
{
get_gamepad_state_request( iface );
Sleep( 16 );
return DI_OK;
}
static HRESULT gamepad_acquire( IDirectInputDevice8W *iface )
{
struct gamepad *impl = impl_from_IDirectInputDevice8W( iface );
SetEvent( impl->base.read_event );
return DI_OK;
}
static HRESULT gamepad_unacquire( IDirectInputDevice8W *iface )
{
struct gamepad *impl = impl_from_IDirectInputDevice8W( iface );
WaitForSingleObject( impl->base.read_event, INFINITE );
connected_gamepad_id = 0;
release_gamepad_request();
close_server_socket();
return DI_OK;
}
static BOOL try_enum_object( const DIPROPHEADER *filter, DWORD flags, LPDIENUMDEVICEOBJECTSCALLBACKW callback,
DIDEVICEOBJECTINSTANCEW *instance, void *data )
{
if (flags != DIDFT_ALL && !(flags & DIDFT_GETTYPE( instance->dwType ))) return DIENUM_CONTINUE;
switch (filter->dwHow)
{
case DIPH_DEVICE:
return callback( instance, data );
case DIPH_BYOFFSET:
if (filter->dwObj != instance->dwOfs) return DIENUM_CONTINUE;
return callback( instance, data );
case DIPH_BYID:
if ((filter->dwObj & 0x00ffffff) != (instance->dwType & 0x00ffffff)) return DIENUM_CONTINUE;
return callback( instance, data );
}
return DIENUM_CONTINUE;
}
static void get_device_objects( int *instance_count, DIDEVICEOBJECTINSTANCEW **out )
{
int i, index = 0;
*instance_count = 0;
*out = NULL;
if (mapper_type == MAPPER_TYPE_STANDARD)
{
*instance_count = 17;
DIDEVICEOBJECTINSTANCEW instances[*instance_count];
instances[index].guidType = GUID_XAxis;
instances[index].dwOfs = DIJOFS_X;
instances[index].dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 0 );
instances[index].dwFlags = DIDOI_ASPECTPOSITION;
swprintf( instances[index].tszName, MAX_PATH, L"X Axis" );
instances[index].wUsagePage = HID_USAGE_PAGE_GENERIC;
instances[index].wUsage = HID_USAGE_GENERIC_X;
index++;
instances[index].guidType = GUID_YAxis;
instances[index].dwOfs = DIJOFS_Y;
instances[index].dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 1 );
instances[index].dwFlags = DIDOI_ASPECTPOSITION;
swprintf( instances[index].tszName, MAX_PATH, L"Y Axis" );
instances[index].wUsagePage = HID_USAGE_PAGE_GENERIC;
instances[index].wUsage = HID_USAGE_GENERIC_Y;
index++;
instances[index].guidType = GUID_ZAxis;
instances[index].dwOfs = DIJOFS_Z;
instances[index].dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 2 );
instances[index].dwFlags = DIDOI_ASPECTPOSITION;
swprintf( instances[index].tszName, MAX_PATH, L"Z Axis" );
instances[index].wUsagePage = HID_USAGE_PAGE_GENERIC;
instances[index].wUsage = HID_USAGE_GENERIC_Z;
index++;
instances[index].guidType = GUID_RzAxis;
instances[index].dwOfs = DIJOFS_RZ;
instances[index].dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 3 );
instances[index].dwFlags = DIDOI_ASPECTPOSITION;
swprintf( instances[index].tszName, MAX_PATH, L"Rz Axis" );
instances[index].wUsagePage = HID_USAGE_PAGE_GENERIC;
instances[index].wUsage = HID_USAGE_GENERIC_RZ;
index++;
for (i = 0; i < 12; i++)
{
instances[index].guidType = GUID_Button,
instances[index].dwOfs = DIJOFS_BUTTON( i ),
instances[index].dwType = DIDFT_BUTTON | DIDFT_MAKEINSTANCE( i ),
swprintf( instances[index].tszName, MAX_PATH, L"Button %d", i );
instances[index].wUsagePage = HID_USAGE_PAGE_BUTTON;
instances[index].wUsage = i + 1;
index++;
}
instances[index].guidType = GUID_POV;
instances[index].dwOfs = DIJOFS_POV( 0 );
instances[index].dwType = DIDFT_POV | DIDFT_MAKEINSTANCE( 0 );
swprintf( instances[index].tszName, MAX_PATH, L"POV" );
instances[index].wUsagePage = HID_USAGE_PAGE_GENERIC;
instances[index].wUsage = HID_USAGE_GENERIC_HATSWITCH;
*out = instances;
}
else if (mapper_type == MAPPER_TYPE_XINPUT)
{
*instance_count = 16;
DIDEVICEOBJECTINSTANCEW instances[*instance_count];
instances[index].guidType = GUID_XAxis;
instances[index].dwOfs = DIJOFS_X;
instances[index].dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 0 );
instances[index].dwFlags = DIDOI_ASPECTPOSITION;
swprintf( instances[index].tszName, MAX_PATH, L"X Axis" );
instances[index].wUsagePage = HID_USAGE_PAGE_GENERIC;
instances[index].wUsage = HID_USAGE_GENERIC_X;
index++;
instances[index].guidType = GUID_YAxis;
instances[index].dwOfs = DIJOFS_Y;
instances[index].dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 1 );
instances[index].dwFlags = DIDOI_ASPECTPOSITION;
swprintf( instances[index].tszName, MAX_PATH, L"Y Axis" );
instances[index].wUsagePage = HID_USAGE_PAGE_GENERIC;
instances[index].wUsage = HID_USAGE_GENERIC_Y;
index++;
instances[index].guidType = GUID_ZAxis;
instances[index].dwOfs = DIJOFS_Z;
instances[index].dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 2 );
instances[index].dwFlags = DIDOI_ASPECTPOSITION;
swprintf( instances[index].tszName, MAX_PATH, L"Z Axis" );
instances[index].wUsagePage = HID_USAGE_PAGE_GENERIC;
instances[index].wUsage = HID_USAGE_GENERIC_Z;
index++;
instances[index].guidType = GUID_RxAxis;
instances[index].dwOfs = DIJOFS_RX;
instances[index].dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 3 );
instances[index].dwFlags = DIDOI_ASPECTPOSITION;
swprintf( instances[index].tszName, MAX_PATH, L"Rx Axis" );
instances[index].wUsagePage = HID_USAGE_PAGE_GENERIC;
instances[index].wUsage = HID_USAGE_GENERIC_RX;
index++;
instances[index].guidType = GUID_RyAxis;
instances[index].dwOfs = DIJOFS_RY;
instances[index].dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 4 );
instances[index].dwFlags = DIDOI_ASPECTPOSITION;
swprintf( instances[index].tszName, MAX_PATH, L"Ry Axis" );
instances[index].wUsagePage = HID_USAGE_PAGE_GENERIC;
instances[index].wUsage = HID_USAGE_GENERIC_RY;
index++;
for (i = 0; i < 10; i++)
{
instances[index].guidType = GUID_Button,
instances[index].dwOfs = DIJOFS_BUTTON( i ),
instances[index].dwType = DIDFT_BUTTON | DIDFT_MAKEINSTANCE( i ),
swprintf( instances[index].tszName, MAX_PATH, L"Button %d", i );
instances[index].wUsagePage = HID_USAGE_PAGE_BUTTON;
instances[index].wUsage = i + 1;
index++;
}
instances[index].guidType = GUID_POV;
instances[index].dwOfs = DIJOFS_POV( 0 );
instances[index].dwType = DIDFT_POV | DIDFT_MAKEINSTANCE( 0 );
swprintf( instances[index].tszName, MAX_PATH, L"POV" );
instances[index].wUsagePage = HID_USAGE_PAGE_GENERIC;
instances[index].wUsage = HID_USAGE_GENERIC_HATSWITCH;
*out = instances;
}
}
static HRESULT gamepad_enum_objects( IDirectInputDevice8W *iface, const DIPROPHEADER *filter,
DWORD flags, LPDIENUMDEVICEOBJECTSCALLBACKW callback, void *context )
{
int instance_count;
DIDEVICEOBJECTINSTANCEW* instances;
BOOL ret;
DWORD i;
get_device_objects( &instance_count, &instances );
for (i = 0; i < instance_count; i++)
{
instances[i].dwSize = sizeof(DIDEVICEOBJECTINSTANCEW);
instances[i].wReportId = 1;
ret = try_enum_object( filter, flags, callback, instances + i, context );
if (ret != DIENUM_CONTINUE) return DIENUM_STOP;
}
return DIENUM_CONTINUE;
}
static HRESULT gamepad_get_property(IDirectInputDevice8W *iface, DWORD property,
DIPROPHEADER *header, const DIDEVICEOBJECTINSTANCEW *instance)
{
struct gamepad *impl = impl_from_IDirectInputDevice8W( iface );
switch (property)
{
case (DWORD_PTR)DIPROP_PRODUCTNAME:
{
DIPROPSTRING *value = (DIPROPSTRING *)header;
lstrcpynW( value->wsz, impl->base.instance.tszProductName, MAX_PATH );
return DI_OK;
}
case (DWORD_PTR)DIPROP_INSTANCENAME:
{
DIPROPSTRING *value = (DIPROPSTRING *)header;
lstrcpynW( value->wsz, impl->base.instance.tszInstanceName, MAX_PATH );
return DI_OK;
}
case (DWORD_PTR)DIPROP_VIDPID:
{
DIPROPDWORD *value = (DIPROPDWORD *)header;
value->dwData = MAKELONG( 0x045e, 0x028e );
return DI_OK;
}
case (DWORD_PTR)DIPROP_JOYSTICKID:
{
DIPROPDWORD *value = (DIPROPDWORD *)header;
value->dwData = connected_gamepad_id;
return DI_OK;
}
case (DWORD_PTR)DIPROP_GUIDANDPATH:
{
DIPROPGUIDANDPATH *value = (DIPROPGUIDANDPATH *)header;
value->guidClass = GUID_DEVCLASS_HIDCLASS;
return DI_OK;
}
}
return DIERR_UNSUPPORTED;
}
static const struct dinput_device_vtbl gamepad_vtbl =
{
gamepad_release,
NULL,
gamepad_read,
gamepad_acquire,
gamepad_unacquire,
gamepad_enum_objects,
gamepad_get_property,
NULL,
NULL,
NULL,
NULL,
NULL,
};

View File

@ -0,0 +1,523 @@
/*
* The Wine project - Xinput Joystick Library
* Copyright 2008 Andrew Fenn
* Copyright 2018 Aric Stewart
* Copyright 2021 Rémi Bernon for CodeWeavers
* Copyright 2024 BrunoSX
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <assert.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include "windef.h"
#include "winbase.h"
#include "winerror.h"
#include "winuser.h"
#include "winreg.h"
#include "wingdi.h"
#include "winnls.h"
#include "winternl.h"
#include "winsock2.h"
#include "dbt.h"
#include "setupapi.h"
#include "initguid.h"
#include "devguid.h"
#include "xinput.h"
#include "wine/debug.h"
/* Not defined in the headers, used only by XInputGetStateEx */
#define XINPUT_GAMEPAD_GUIDE 0x0400
#define SERVER_PORT 7949
#define CLIENT_PORT 7947
#define BUFFER_SIZE 64
#define REQUEST_CODE_GET_GAMEPAD 8
#define REQUEST_CODE_GET_GAMEPAD_STATE 9
#define REQUEST_CODE_RELEASE_GAMEPAD 10
#define IDX_BUTTON_A 0
#define IDX_BUTTON_B 1
#define IDX_BUTTON_X 2
#define IDX_BUTTON_Y 3
#define IDX_BUTTON_L1 4
#define IDX_BUTTON_R1 5
#define IDX_BUTTON_L2 10
#define IDX_BUTTON_R2 11
#define IDX_BUTTON_SELECT 6
#define IDX_BUTTON_START 7
#define IDX_BUTTON_L3 8
#define IDX_BUTTON_R3 9
WINE_DEFAULT_DEBUG_CHANNEL(xinput);
struct xinput_controller
{
XINPUT_CAPABILITIES caps;
XINPUT_STATE state;
BOOL enabled;
BOOL connected;
int id;
};
static struct xinput_controller controller =
{
.enabled = FALSE,
.connected = FALSE,
.id = 0
};
static HMODULE xinput_instance;
static HANDLE start_event;
static HANDLE stop_event;
static HANDLE done_event;
static HANDLE update_event;
static SOCKET server_sock = INVALID_SOCKET;
static BOOL winsock_loaded = FALSE;
static void close_server_socket(void)
{
if (server_sock != INVALID_SOCKET)
{
closesocket(server_sock);
server_sock = INVALID_SOCKET;
}
if (winsock_loaded)
{
WSACleanup();
winsock_loaded = FALSE;
}
}
static BOOL create_server_socket(void)
{
WSADATA wsa_data;
struct sockaddr_in server_addr;
int res;
close_server_socket();
winsock_loaded = WSAStartup(MAKEWORD(2,2), &wsa_data) == NO_ERROR;
if (!winsock_loaded) return FALSE;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
server_addr.sin_port = htons(SERVER_PORT);
server_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (server_sock == INVALID_SOCKET) return FALSE;
res = bind(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (res == SOCKET_ERROR) return FALSE;
return TRUE;
}
static int get_gamepad_request(void)
{
int res, client_addr_len, gamepad_id;
char buffer[BUFFER_SIZE];
struct sockaddr_in client_addr;
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
client_addr.sin_port = htons(CLIENT_PORT);
client_addr_len = sizeof(client_addr);
buffer[0] = REQUEST_CODE_GET_GAMEPAD;
buffer[1] = 1;
res = sendto(server_sock, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&client_addr, client_addr_len);
if (res == SOCKET_ERROR) return 0;
res = recvfrom(server_sock, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&client_addr, &client_addr_len);
if (res == SOCKET_ERROR || buffer[0] != REQUEST_CODE_GET_GAMEPAD) return 0;
memcpy(&gamepad_id, buffer + 1, 4);
return gamepad_id;
}
static void release_gamepad_request(void)
{
char buffer[BUFFER_SIZE];
struct sockaddr_in client_addr;
int client_addr_len;
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
client_addr.sin_port = htons(CLIENT_PORT);
client_addr_len = sizeof(client_addr);
buffer[0] = REQUEST_CODE_RELEASE_GAMEPAD;
sendto(server_sock, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&client_addr, client_addr_len);
}
static BOOL controller_check_caps(void)
{
XINPUT_CAPABILITIES *caps = &controller.caps;
memset(caps, 0, sizeof(XINPUT_CAPABILITIES));
caps->Gamepad.wButtons = 0xffff;
caps->Gamepad.bLeftTrigger = (1u << (sizeof(caps->Gamepad.bLeftTrigger) + 1)) - 1;
caps->Gamepad.bRightTrigger = (1u << (sizeof(caps->Gamepad.bRightTrigger) + 1)) - 1;
caps->Gamepad.sThumbLX = (1u << (sizeof(caps->Gamepad.sThumbLX) + 1)) - 1;
caps->Gamepad.sThumbLY = (1u << (sizeof(caps->Gamepad.sThumbLY) + 1)) - 1;
caps->Gamepad.sThumbRX = (1u << (sizeof(caps->Gamepad.sThumbRX) + 1)) - 1;
caps->Gamepad.sThumbRY = (1u << (sizeof(caps->Gamepad.sThumbRY) + 1)) - 1;
caps->Type = XINPUT_DEVTYPE_GAMEPAD;
caps->SubType = XINPUT_DEVSUBTYPE_GAMEPAD;
return TRUE;
}
static void controller_disable(void)
{
if (!controller.enabled) return;
controller.enabled = FALSE;
SetEvent(update_event);
}
static void controller_destroy(void)
{
release_gamepad_request();
if (controller.connected)
{
controller_disable();
controller.connected = FALSE;
}
close_server_socket();
}
static void controller_enable(void)
{
if (controller.enabled) return;
controller.enabled = TRUE;
SetEvent(update_event);
}
static void controller_init(void)
{
memset(&controller.state, 0, sizeof(controller.state));
controller.enabled = FALSE;
controller.connected = TRUE;
controller_check_caps();
controller_enable();
}
static void controller_check_connection(void)
{
controller.id = 0;
if (server_sock == INVALID_SOCKET) create_server_socket();
int gamepad_id = get_gamepad_request();
if (gamepad_id > 0)
{
controller.id = gamepad_id;
if (!controller.connected)
{
controller.id = gamepad_id;
controller_init();
}
}
}
static void stop_update_thread(void)
{
ResetEvent(update_event);
SetEvent(stop_event);
WaitForSingleObject(done_event, INFINITE);
CloseHandle(start_event);
CloseHandle(stop_event);
CloseHandle(done_event);
CloseHandle(update_event);
controller_destroy();
}
static void read_controller_state(void)
{
char buffer[BUFFER_SIZE];
int i, res, client_addr_len, gamepad_id;
char dpad;
short buttons, thumb_lx, thumb_ly, thumb_rx, thumb_ry;
struct sockaddr_in client_addr;
XINPUT_STATE state;
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
client_addr.sin_port = htons(CLIENT_PORT);
client_addr_len = sizeof(client_addr);
buffer[0] = REQUEST_CODE_GET_GAMEPAD_STATE;
memcpy(buffer + 1, &controller.id, 4);
res = sendto(server_sock, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&client_addr, client_addr_len);
if (res == SOCKET_ERROR) return;
res = recvfrom(server_sock, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&client_addr, &client_addr_len);
if (res == SOCKET_ERROR || buffer[0] != REQUEST_CODE_GET_GAMEPAD_STATE || buffer[1] != 1) return;
memcpy(&gamepad_id, buffer + 2, 4);
if (gamepad_id != controller.id) return;
memcpy(&buttons, buffer + 6, 2);
dpad = buffer[8];
memcpy(&thumb_lx, buffer + 9, 2);
memcpy(&thumb_ly, buffer + 11, 2);
memcpy(&thumb_rx, buffer + 13, 2);
memcpy(&thumb_ry, buffer + 15, 2);
state.Gamepad.wButtons = 0;
for (i = 0; i < 10; i++)
{
if ((buttons & (1<<i))) {
switch (i)
{
case IDX_BUTTON_A: state.Gamepad.wButtons |= XINPUT_GAMEPAD_A; break;
case IDX_BUTTON_B: state.Gamepad.wButtons |= XINPUT_GAMEPAD_B; break;
case IDX_BUTTON_X: state.Gamepad.wButtons |= XINPUT_GAMEPAD_X; break;
case IDX_BUTTON_Y: state.Gamepad.wButtons |= XINPUT_GAMEPAD_Y; break;
case IDX_BUTTON_L1: state.Gamepad.wButtons |= XINPUT_GAMEPAD_LEFT_SHOULDER; break;
case IDX_BUTTON_R1: state.Gamepad.wButtons |= XINPUT_GAMEPAD_RIGHT_SHOULDER; break;
case IDX_BUTTON_SELECT: state.Gamepad.wButtons |= XINPUT_GAMEPAD_BACK; break;
case IDX_BUTTON_START: state.Gamepad.wButtons |= XINPUT_GAMEPAD_START; break;
case IDX_BUTTON_L3: state.Gamepad.wButtons |= XINPUT_GAMEPAD_LEFT_THUMB; break;
case IDX_BUTTON_R3: state.Gamepad.wButtons |= XINPUT_GAMEPAD_RIGHT_THUMB; break;
}
}
}
state.Gamepad.bLeftTrigger = (buttons & (1<<10)) ? 255 : 0;
state.Gamepad.bRightTrigger = (buttons & (1<<11)) ? 255 : 0;
switch (dpad)
{
case 0: state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_UP; break;
case 1: state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_UP | XINPUT_GAMEPAD_DPAD_RIGHT; break;
case 2: state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_RIGHT; break;
case 3: state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_RIGHT | XINPUT_GAMEPAD_DPAD_DOWN; break;
case 4: state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_DOWN; break;
case 5: state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_DOWN | XINPUT_GAMEPAD_DPAD_LEFT; break;
case 6: state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_LEFT; break;
case 7: state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_LEFT | XINPUT_GAMEPAD_DPAD_UP; break;
}
state.Gamepad.sThumbLX = thumb_lx;
state.Gamepad.sThumbLY = -thumb_ly;
state.Gamepad.sThumbRX = thumb_rx;
state.Gamepad.sThumbRY = -thumb_ry;
state.dwPacketNumber = controller.state.dwPacketNumber + 1;
controller.state = state;
}
static DWORD WINAPI controller_update_thread_proc(void *param)
{
DWORD last_time, curr_time;
const int num_events = 2;
HANDLE events[num_events];
DWORD ret = WAIT_TIMEOUT;
SetThreadDescription(GetCurrentThread(), L"wine_xinput_controller_update");
SetEvent(start_event);
last_time = GetCurrentTime();
do
{
curr_time = GetCurrentTime();
if (ret == WAIT_TIMEOUT || (curr_time - last_time) >= 2000) {
controller_check_connection();
last_time = curr_time;
}
if (controller.connected && controller.enabled)
{
read_controller_state();
Sleep(16);
}
events[0] = update_event;
events[1] = stop_event;
}
while ((ret = MsgWaitForMultipleObjectsEx(num_events, events, 2000, QS_ALLINPUT, MWMO_ALERTABLE)) < num_events - 1 ||
ret == num_events || ret == WAIT_TIMEOUT);
if (ret != num_events - 1) ERR("update thread exited unexpectedly, ret %lu\n", ret);
SetEvent(done_event);
return ret;
}
static BOOL WINAPI start_update_thread_once(INIT_ONCE *once, void *param, void **context)
{
HANDLE thread;
start_event = CreateEventA(NULL, FALSE, FALSE, NULL);
if (!start_event) ERR("failed to create start event, error %lu\n", GetLastError());
stop_event = CreateEventA(NULL, FALSE, FALSE, NULL);
if (!stop_event) ERR("failed to create stop event, error %lu\n", GetLastError());
done_event = CreateEventA(NULL, FALSE, FALSE, NULL);
if (!done_event) ERR("failed to create done event, error %lu\n", GetLastError());
update_event = CreateEventA(NULL, TRUE, FALSE, NULL);
if (!update_event) ERR("failed to create update event, error %lu\n", GetLastError());
thread = CreateThread(NULL, 0, controller_update_thread_proc, NULL, 0, NULL);
if (!thread) ERR("failed to create update thread, error %lu\n", GetLastError());
CloseHandle(thread);
WaitForSingleObject(start_event, INFINITE);
return TRUE;
}
static void start_update_thread(void)
{
static INIT_ONCE init_once = INIT_ONCE_STATIC_INIT;
InitOnceExecuteOnce(&init_once, start_update_thread_once, NULL, NULL);
}
BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved)
{
TRACE("inst %p, reason %lu, reserved %p.\n", inst, reason, reserved);
switch (reason)
{
case DLL_PROCESS_ATTACH:
xinput_instance = inst;
DisableThreadLibraryCalls(inst);
break;
case DLL_PROCESS_DETACH:
if (reserved) break;
stop_update_thread();
break;
}
return TRUE;
}
void WINAPI DECLSPEC_HOTPATCH XInputEnable(BOOL enable)
{
TRACE("enable %d.\n", enable);
/* Setting to false will stop messages from XInputSetState being sent
to the controllers. Setting to true will send the last vibration
value (sent to XInputSetState) to the controller and allow messages to
be sent */
start_update_thread();
if (!controller.connected) return;
if (enable) controller_enable();
else controller_disable();
}
DWORD WINAPI DECLSPEC_HOTPATCH XInputSetState(DWORD index, XINPUT_VIBRATION *vibration)
{
TRACE("index %lu, vibration %p.\n", index, vibration);
start_update_thread();
if (index >= XUSER_MAX_COUNT) return ERROR_BAD_ARGUMENTS;
return ERROR_SUCCESS;
}
/* Some versions of SteamOverlayRenderer hot-patch XInputGetStateEx() and call
* XInputGetState() in the hook, so we need a wrapper. */
static DWORD xinput_get_state(DWORD index, XINPUT_STATE *state)
{
if (!state) return ERROR_BAD_ARGUMENTS;
start_update_thread();
if (index >= XUSER_MAX_COUNT) return ERROR_BAD_ARGUMENTS;
if (index != 0 || !controller.connected) return ERROR_DEVICE_NOT_CONNECTED;
*state = controller.state;
return ERROR_SUCCESS;
}
DWORD WINAPI DECLSPEC_HOTPATCH XInputGetState(DWORD index, XINPUT_STATE *state)
{
DWORD ret;
TRACE("index %lu, state %p.\n", index, state);
ret = xinput_get_state(index, state);
if (ret != ERROR_SUCCESS) return ret;
/* The main difference between this and the Ex version is the media guide button */
state->Gamepad.wButtons &= ~XINPUT_GAMEPAD_GUIDE;
return ERROR_SUCCESS;
}
DWORD WINAPI DECLSPEC_HOTPATCH XInputGetStateEx(DWORD index, XINPUT_STATE *state)
{
TRACE("index %lu, state %p.\n", index, state);
return xinput_get_state(index, state);
}
DWORD WINAPI DECLSPEC_HOTPATCH XInputGetKeystroke(DWORD index, DWORD reserved, PXINPUT_KEYSTROKE keystroke)
{
TRACE("index %lu, reserved %lu, keystroke %p.\n", index, reserved, keystroke);
if (index >= XUSER_MAX_COUNT && index != XUSER_INDEX_ANY) return ERROR_BAD_ARGUMENTS;
if (!controller.connected) return ERROR_DEVICE_NOT_CONNECTED;
return ERROR_SUCCESS;
}
DWORD WINAPI DECLSPEC_HOTPATCH XInputGetCapabilities(DWORD index, DWORD flags, XINPUT_CAPABILITIES *capabilities)
{
TRACE("index %lu, flags %#lx, capabilities %p.\n", index, flags, capabilities);
start_update_thread();
if (index >= XUSER_MAX_COUNT) return ERROR_BAD_ARGUMENTS;
if (index != 0 || !controller.connected) return ERROR_DEVICE_NOT_CONNECTED;
if (flags & XINPUT_FLAG_GAMEPAD && controller.caps.SubType != XINPUT_DEVSUBTYPE_GAMEPAD) return ERROR_DEVICE_NOT_CONNECTED;
memcpy(capabilities, &controller.caps, sizeof(*capabilities));
return ERROR_SUCCESS;
}
DWORD WINAPI DECLSPEC_HOTPATCH XInputGetDSoundAudioDeviceGuids(DWORD index, GUID *render_guid, GUID *capture_guid)
{
if (index >= XUSER_MAX_COUNT) return ERROR_BAD_ARGUMENTS;
if (index != 0 || !controller.connected) return ERROR_DEVICE_NOT_CONNECTED;
return ERROR_NOT_SUPPORTED;
}
DWORD WINAPI DECLSPEC_HOTPATCH XInputGetBatteryInformation(DWORD index, BYTE type, XINPUT_BATTERY_INFORMATION* battery)
{
if (index >= XUSER_MAX_COUNT) return ERROR_BAD_ARGUMENTS;
if (index != 0 || !controller.connected) return ERROR_DEVICE_NOT_CONNECTED;
return ERROR_NOT_SUPPORTED;
}