[PATCH v8 0/2] MR9789: winebus: Use smallest report size for joystick axis
Some games don't handle 32-bit sized joystick axes well. Assetto Corsa Rally, which was released a month ago as an early access and is still v0.1, is a recent example. The proof of concept 16-bit patch resolves the problem for many users and hardware on Linux, (https://github.com/ValveSoftware/Proton/issues/9220#issuecomment-3647591465) and users with open-source hardware confirmed the behavior of the game on Windows. Since it's the case that the game is non-compliant while the wine is perfectly compliant, we might be able to push and wait for the developer to release the fix, but I believe that the fix will also help to improve compatibility for some old, non-maintained, non-compliant games. -- v8: winebus: Use 16-bit relative axis range for bus_sdl winebus: Use smallest report size for joystick axis https://gitlab.winehq.org/wine/wine/-/merge_requests/9789
From: SeongChan Lee <foriequal@gmail.com> --- dlls/winebus.sys/hid.c | 68 ++++++++++++++++++++++++++------- dlls/winebus.sys/unix_private.h | 4 +- 2 files changed, 56 insertions(+), 16 deletions(-) diff --git a/dlls/winebus.sys/hid.c b/dlls/winebus.sys/hid.c index 46ad0c69065..66ea37f077c 100644 --- a/dlls/winebus.sys/hid.c +++ b/dlls/winebus.sys/hid.c @@ -256,7 +256,14 @@ BOOL hid_device_add_hatswitch(struct unix_device *iface, INT count) return TRUE; } -static BOOL hid_device_add_axis_count(struct unix_device *iface, BOOL rel, BYTE count, +static BYTE hid_device_determine_axis_size(LONG min, LONG max) +{ + if ((min >= 0 && max <= 255) || (min >= -128 && max <= 127)) return 8; + if ((min >= 0 && max <= 65535) || (min >= -32768 && max <= 32767)) return 16; + return 32; +} + +static BOOL hid_device_add_axis_count(struct unix_device *iface, BOOL rel, BYTE size, BYTE count, USAGE usage_page, const USAGE *usages) { struct hid_device_state *state = &iface->hid_device_state; @@ -268,13 +275,22 @@ static BOOL hid_device_add_axis_count(struct unix_device *iface, BOOL rel, BYTE ERR("axes should be added before buttons or hatswitches!\n"); else if ((state->bit_size % 8)) ERR("axes should be byte aligned, missing padding!\n"); - else if (state->bit_size + 32 * count > 0x80000) + else if (size != 8 && size != 16 && size != 32) + ERR("unsupported axis size, not one of 8, 16, 32!\n"); + else if (state->bit_size + size * count > 0x80000) ERR("report size overflow, too many elements!\n"); + else if (state->abs_axis_count + state->rel_axis_count + count > ARRAY_SIZE(state->axis_byte_offsets)) + ERR("axis usage overflow, too many elements!\n"); else if (rel) { - if (!state->rel_axis_count) state->rel_axis_start = offset; + for (i = 0; i < count; ++i) + { + int axis = state->abs_axis_count + state->rel_axis_count + i; + state->axis_byte_offsets[axis] = offset + i * size / 8; + state->axis_sizes[axis] = size; + } state->rel_axis_count += count; - state->bit_size += 32 * count; + state->bit_size += size * count; return TRUE; } else @@ -286,13 +302,15 @@ static BOOL hid_device_add_axis_count(struct unix_device *iface, BOOL rel, BYTE } for (i = 0; i < count; ++i) { + state->axis_byte_offsets[state->abs_axis_count + i] = offset + i * size / 8; + state->axis_sizes[state->abs_axis_count + i] = size; + state->abs_axis_usages[state->abs_axis_count + i].UsagePage = usage_page; state->abs_axis_usages[state->abs_axis_count + i].Usage = usages[i]; } - if (!state->abs_axis_count) state->abs_axis_start = offset; state->abs_axis_count += count; - state->bit_size += 32 * count; + state->bit_size += size * count; return TRUE; } @@ -312,17 +330,18 @@ BOOL hid_device_add_axes(struct unix_device *iface, BYTE count, USAGE usage_page { END_COLLECTION, }; + BYTE size = hid_device_determine_axis_size(min, max); const BYTE template[] = { LOGICAL_MINIMUM(4, min), LOGICAL_MAXIMUM(4, max), - REPORT_SIZE(1, 32), + REPORT_SIZE(1, size), REPORT_COUNT(1, count), INPUT(1, Data|Var|(rel ? Rel : Abs)), }; int i; - if (!hid_device_add_axis_count(iface, rel, count, usage_page, usages)) + if (!hid_device_add_axis_count(iface, rel, size, count, usage_page, usages)) return FALSE; if (!hid_report_descriptor_append(desc, template_begin, sizeof(template_begin))) @@ -1457,26 +1476,47 @@ void *hid_device_create(const struct hid_device_vtbl *vtbl, SIZE_T size) #ifdef WORDS_BIGENDIAN # define LE_ULONG(x) RtlUlongByteSwap((ULONG)(x)) +# define LE_USHORT(x) RtlUshortByteSwap((USHORT)(x)) #else # define LE_ULONG(x) ((ULONG)(x)) +# define LE_USHORT(x) ((USHORT)(x)) #endif +static BOOL hid_device_set_axis(struct hid_device_state* state, ULONG axis, LONG value) +{ + USHORT offset = state->axis_byte_offsets[axis]; + BYTE size = state->axis_sizes[axis]; + switch (size) + { + case 8: + *(state->report_buf + offset) = (BYTE)value; + break; + case 16: + *(USHORT *)(state->report_buf + offset) = LE_USHORT(value); + break; + case 32: + *(ULONG *)(state->report_buf + offset) = LE_ULONG(value); + break; + default: + return FALSE; + } + + return TRUE; +} + BOOL hid_device_set_abs_axis(struct unix_device *iface, ULONG index, LONG value) { struct hid_device_state *state = &iface->hid_device_state; - ULONG offset = state->abs_axis_start + index * 4; if (index >= state->abs_axis_count) return FALSE; - *(ULONG *)(state->report_buf + offset) = LE_ULONG(value); - return TRUE; + return hid_device_set_axis(state, index, value); } BOOL hid_device_set_rel_axis(struct unix_device *iface, ULONG index, LONG value) { struct hid_device_state *state = &iface->hid_device_state; - ULONG offset = state->rel_axis_start + index * 4; + ULONG axis = state->abs_axis_count + index; if (index >= state->rel_axis_count) return FALSE; - *(ULONG *)(state->report_buf + offset) = LE_ULONG(value); - return TRUE; + return hid_device_set_axis(state, axis, value); } BOOL hid_device_set_button(struct unix_device *iface, ULONG index, BOOL is_set) diff --git a/dlls/winebus.sys/unix_private.h b/dlls/winebus.sys/unix_private.h index 11328caee08..c39c8787247 100644 --- a/dlls/winebus.sys/unix_private.h +++ b/dlls/winebus.sys/unix_private.h @@ -190,10 +190,10 @@ struct hid_physical struct hid_device_state { ULONG bit_size; + USHORT axis_byte_offsets[64]; + BYTE axis_sizes[64]; USAGE_AND_PAGE abs_axis_usages[32]; - USHORT abs_axis_start; USHORT abs_axis_count; - USHORT rel_axis_start; USHORT rel_axis_count; USHORT hatswitch_start; USHORT hatswitch_count; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9789
From: SeongChan Lee <foriequal@gmail.com> --- dlls/winebus.sys/bus_sdl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dlls/winebus.sys/bus_sdl.c b/dlls/winebus.sys/bus_sdl.c index bc7904059ed..0048dea8712 100644 --- a/dlls/winebus.sys/bus_sdl.c +++ b/dlls/winebus.sys/bus_sdl.c @@ -373,7 +373,7 @@ static NTSTATUS build_joystick_report_descriptor(struct unix_device *iface, cons for (i = 0; i < ball_count; i++) { if (!hid_device_add_axes(iface, 2, relative_axis_usages[2 * i].UsagePage, - &relative_axis_usages[2 * i].Usage, TRUE, INT32_MIN, INT32_MAX)) + &relative_axis_usages[2 * i].Usage, TRUE, INT16_MIN, INT16_MAX)) return STATUS_NO_MEMORY; } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9789
Hi all, Are we confident this is still really needed? I tested today with proton experimental and the non-working game (ACR) was working without the fix. Do we any another game that would suffer from it? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9789#note_126486
On Thu Jan 8 08:27:02 2026 +0000, cazzoo wrote:
Hi all, Are we confident this is still really needed? I tested today with proton experimental and the non-working game (ACR) was working without the fix. Do we any another game that would suffer from it? It is not needed at this time. The bug in the game was fixed with its v0.2 release (2025-12-18).
I'm not aware of other games that suffer from this. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9789#note_126545
On Thu Jan 8 08:27:02 2026 +0000, SeongChan Lee wrote:
It is not needed at this time. The bug in the game was fixed with its v0.2 release (2025-12-18). I'm not aware of other games that suffer from this. Farming Sim 22 has this issue, not sure about FS25 because it used to have this as far as I know and based on the reports (I never tried to use a wheel with it because of that) but when I tried it yesterday it sort of worked. The brake pedal was not detected but that's something else (it's not detected with the patch as well), it does work with the hidraw workaround. FS22/25 proton github also mentions Merchwarrior 5: Mercenaries as an affected game but I don't play that so no idea about it's current state.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9789#note_126607
This merge request was closed by Rémi Bernon. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9789
This has been merged -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9789#note_127184
participants (5)
-
cazzoo (@cazzoo) -
rLy07 (@rLy07) -
Rémi Bernon (@rbernon) -
SeongChan Lee -
SeongChan Lee (@foriequal0)