Skip to content

Commit 6807541

Browse files
committed
Input: SDL3 backend implementation
1 parent a1e755b commit 6807541

1,542 files changed

Lines changed: 728189 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

C2ModLoader/CMakeLists.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ set(SOURCES
5858
FileOverrides.cpp
5959
GameHooks.cpp
6060
Input/AnalogInput.cpp
61+
Input/Backends/SDL3.cpp
6162
Input/Backends/Xinput.cpp
6263
Input/DpadMovement.cpp
6364
Input/Input.cpp
@@ -128,6 +129,20 @@ else()
128129
list(APPEND MINHOOK_SOURCES libs/minhook/src/hde/hde32.c)
129130
endif()
130131

132+
# SDL3
133+
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libs/sdl3/CMakeLists.txt")
134+
set(SDL_SHARED ON CACHE BOOL "Build shared SDL library" FORCE)
135+
set(SDL_STATIC OFF CACHE BOOL "Build static SDL library" FORCE)
136+
137+
set(SDL_TEST_LIBRARY OFF CACHE BOOL "" FORCE)
138+
set(SDL_CAMERA OFF CACHE BOOL "" FORCE)
139+
set(SDL_RENDER OFF CACHE BOOL "" FORCE)
140+
set(SDL_AUDIO OFF CACHE BOOL "" FORCE)
141+
set(SDL_VIDEO OFF CACHE BOOL "" FORCE)
142+
143+
add_subdirectory(libs/sdl3 EXCLUDE_FROM_ALL)
144+
endif()
145+
131146
# Create DLL
132147
add_library(C2ModLoader SHARED
133148
${SOURCES}
@@ -149,6 +164,7 @@ target_include_directories(C2ModLoader PRIVATE
149164
${CMAKE_CURRENT_SOURCE_DIR}/libs/minhook/include
150165
${CMAKE_CURRENT_SOURCE_DIR}/libs/minhook/src
151166
${CMAKE_CURRENT_SOURCE_DIR}/libs/minhook/src/hde
167+
${CMAKE_CURRENT_SOURCE_DIR}/libs/sdl3/include
152168
)
153169

154170
# Windows resource compiler
@@ -223,6 +239,17 @@ target_link_libraries(C2ModLoader PRIVATE
223239
ws2_32
224240
)
225241

242+
# SDL3 configuration and packaging
243+
target_compile_definitions(C2ModLoader PRIVATE SDL_MAIN_HANDLED)
244+
target_link_libraries(C2ModLoader PRIVATE SDL3::SDL3)
245+
246+
add_custom_command(TARGET C2ModLoader POST_BUILD
247+
COMMAND ${CMAKE_COMMAND} -E copy_if_different
248+
$<TARGET_FILE:SDL3::SDL3>
249+
${CMAKE_BINARY_DIR}/Release/$<TARGET_FILE_NAME:SDL3::SDL3>
250+
COMMENT "Copying SDL3 runtime to Release folder"
251+
)
252+
226253
# Copy to Release folder
227254
add_custom_command(TARGET C2ModLoader POST_BUILD
228255
COMMAND ${CMAKE_COMMAND} -E copy
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
#include "SDL3.h"
2+
3+
#include "Input/Input.h"
4+
5+
#include <SDL3/SDL.h>
6+
#include <algorithm>
7+
#include <cmath>
8+
#include <string>
9+
10+
#include "ModApi.h"
11+
12+
extern ModApi *api;
13+
14+
namespace {
15+
16+
SDL_Gamepad *activeGamepad = nullptr;
17+
int deviceIndex;
18+
19+
float stickDeadzone;
20+
float stickOuterDeadzone;
21+
float triggerDeadzone;
22+
float triggerOuterDeadzone;
23+
24+
const float stickScale = 128.0f;
25+
const float triggerScale = 128.0f;
26+
27+
using std::min, std::max;
28+
29+
template <typename T>
30+
int sign(T val) {
31+
return (T(0) < val) - (val < T(0));
32+
}
33+
34+
float applyStickDeadzone(float rawInput, float inner, float outer) {
35+
float absVal = std::abs(rawInput);
36+
if (absVal <= inner)
37+
return 0.0f;
38+
if (absVal >= outer)
39+
return sign(rawInput) * 1.0f;
40+
float normalized = (absVal - inner) / (outer - inner);
41+
return sign(rawInput) * normalized;
42+
}
43+
44+
float applyTriggerDeadzone(float rawInput, float inner, float outer) {
45+
if (rawInput <= inner)
46+
return 0.0f;
47+
if (rawInput >= outer)
48+
return 1.0f;
49+
float normalized = (rawInput - inner) / (outer - inner);
50+
return normalized;
51+
}
52+
53+
void RefreshGamepadConnection() {
54+
if (activeGamepad)
55+
return;
56+
57+
int count = 0;
58+
SDL_JoystickID *joysticks = SDL_GetGamepads(&count);
59+
if (joysticks && count > 0) {
60+
int targetIndex = min(max(deviceIndex, 0), count - 1);
61+
activeGamepad = SDL_OpenGamepad(joysticks[targetIndex]);
62+
// api->LogDebug((std::string("Found ") + std::to_string(count) + " gamepad(s), opening index " + std::to_string(targetIndex)).c_str());
63+
} else {
64+
// api->LogDebug((std::string("No gamepads found. Count: ") + std::to_string(count)).c_str());
65+
}
66+
SDL_free(joysticks);
67+
68+
if (activeGamepad) {
69+
if (SDL_GamepadHasSensor(activeGamepad, SDL_SENSOR_GYRO)) {
70+
SDL_SetGamepadSensorEnabled(activeGamepad, SDL_SENSOR_GYRO, true);
71+
}
72+
}
73+
}
74+
75+
void vibrateImpl(int strength, int durationMs) {
76+
if (activeGamepad) {
77+
if (strength < 0)
78+
strength = 0;
79+
if (strength > 65535)
80+
strength = 65535;
81+
uint16_t vibrationStrength = static_cast<uint16_t>(strength);
82+
SDL_RumbleGamepad(activeGamepad, vibrationStrength, vibrationStrength, durationMs);
83+
}
84+
}
85+
86+
} // namespace
87+
88+
namespace Input::Backends::SDL3 {
89+
90+
bool enabled;
91+
92+
void Backend::PollInput() {
93+
SDL_UpdateGamepads();
94+
RefreshGamepadConnection();
95+
96+
input = {};
97+
98+
if (activeGamepad && SDL_GamepadConnected(activeGamepad)) {
99+
float leftX = SDL_GetGamepadAxis(activeGamepad, SDL_GAMEPAD_AXIS_LEFTX) / 32768.0f;
100+
float leftY = SDL_GetGamepadAxis(activeGamepad, SDL_GAMEPAD_AXIS_LEFTY) / -32768.0f;
101+
float rightX = SDL_GetGamepadAxis(activeGamepad, SDL_GAMEPAD_AXIS_RIGHTX) / 32768.0f;
102+
float rightY = SDL_GetGamepadAxis(activeGamepad, SDL_GAMEPAD_AXIS_RIGHTY) / -32768.0f;
103+
104+
float leftTrigger = SDL_GetGamepadAxis(activeGamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) / 32767.0f;
105+
float rightTrigger = SDL_GetGamepadAxis(activeGamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) / 32767.0f;
106+
107+
input.leftStick.x = -applyStickDeadzone(leftX, stickDeadzone, stickOuterDeadzone) * stickScale;
108+
input.leftStick.y = applyStickDeadzone(leftY, stickDeadzone, stickOuterDeadzone) * stickScale;
109+
input.leftStick.click = SDL_GetGamepadButton(activeGamepad, SDL_GAMEPAD_BUTTON_LEFT_STICK);
110+
111+
input.rightStick.x = applyStickDeadzone(rightX, stickDeadzone, stickOuterDeadzone) * stickScale;
112+
input.rightStick.y = -applyStickDeadzone(rightY, stickDeadzone, stickOuterDeadzone) * stickScale;
113+
input.rightStick.click = SDL_GetGamepadButton(activeGamepad, SDL_GAMEPAD_BUTTON_RIGHT_STICK);
114+
115+
input.leftTrigger = applyTriggerDeadzone(leftTrigger, triggerDeadzone, triggerOuterDeadzone) * triggerScale;
116+
input.rightTrigger = applyTriggerDeadzone(rightTrigger, triggerDeadzone, triggerOuterDeadzone) * triggerScale;
117+
118+
input.dpad.up = SDL_GetGamepadButton(activeGamepad, SDL_GAMEPAD_BUTTON_DPAD_UP);
119+
input.dpad.down = SDL_GetGamepadButton(activeGamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
120+
input.dpad.left = SDL_GetGamepadButton(activeGamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
121+
input.dpad.right = SDL_GetGamepadButton(activeGamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
122+
123+
input.leftShoulder = SDL_GetGamepadButton(activeGamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER);
124+
input.rightShoulder = SDL_GetGamepadButton(activeGamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER);
125+
126+
input.aButton = SDL_GetGamepadButton(activeGamepad, SDL_GAMEPAD_BUTTON_SOUTH);
127+
input.bButton = SDL_GetGamepadButton(activeGamepad, SDL_GAMEPAD_BUTTON_EAST);
128+
input.xButton = SDL_GetGamepadButton(activeGamepad, SDL_GAMEPAD_BUTTON_WEST);
129+
input.yButton = SDL_GetGamepadButton(activeGamepad, SDL_GAMEPAD_BUTTON_NORTH);
130+
131+
input.startButton = SDL_GetGamepadButton(activeGamepad, SDL_GAMEPAD_BUTTON_START);
132+
input.backButton = SDL_GetGamepadButton(activeGamepad, SDL_GAMEPAD_BUTTON_BACK);
133+
} else {
134+
if (activeGamepad) {
135+
SDL_CloseGamepad(activeGamepad);
136+
activeGamepad = nullptr;
137+
}
138+
input = {};
139+
}
140+
input.config.enabled = Input::enabled;
141+
input.config.stickScale = stickScale;
142+
input.config.triggerScale = triggerScale;
143+
}
144+
145+
ModernInput Backend::GetState() {
146+
return input;
147+
}
148+
149+
void Backend::Setup() {
150+
input = {};
151+
152+
enabled = api->SetupIniBool(L"Input", L"Enabled", true);
153+
deviceIndex = api->SetupIniInt(L"Input", L"DeviceIndex", 0);
154+
155+
api->LogInfo((std::string("SDL platform: ") + SDL_GetPlatform()).c_str());
156+
157+
stickDeadzone = api->SetupIniInt(L"Input", L"StickDeadzone", 25) / 100.0f;
158+
stickDeadzone = min(max(stickDeadzone, 0.0f), 1.0f);
159+
stickOuterDeadzone = api->SetupIniInt(L"Input", L"StickOuterDeadzone", 75) / 100.0f;
160+
stickOuterDeadzone = min(max(stickOuterDeadzone, 0.0f), 1.0f);
161+
triggerDeadzone = api->SetupIniInt(L"Input", L"TriggerDeadzone", 10) / 100.0f;
162+
triggerDeadzone = min(max(triggerDeadzone, 0.0f), 1.0f);
163+
triggerOuterDeadzone = api->SetupIniInt(L"Input", L"TriggerOuterDeadzone", 90) / 100.0f;
164+
triggerOuterDeadzone = min(max(triggerOuterDeadzone, 0.0f), 1.0f);
165+
166+
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5, "1");
167+
SDL_SetHint(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, "1");
168+
169+
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
170+
SDL_SetHint(SDL_HINT_JOYSTICK_THREAD, "1");
171+
SDL_InitSubSystem(SDL_INIT_GAMEPAD | SDL_INIT_EVENTS);
172+
}
173+
174+
void Backend::vibrate(int strength, int durationMs) {
175+
vibrateImpl(strength, durationMs);
176+
}
177+
178+
} // namespace Input::Backends::SDL3

C2ModLoader/Input/Backends/SDL3.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#pragma once
2+
3+
#include "Input/Input.h"
4+
5+
namespace Input::Backends::SDL3 {
6+
7+
class Backend : public IInputBackend {
8+
public:
9+
void PollInput() override;
10+
ModernInput GetState() override;
11+
void Setup() override;
12+
void vibrate(int strength, int durationMs) override;
13+
};
14+
15+
} // namespace Input::Backends::SDL3

C2ModLoader/Input/Backends/Xinput.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ static DWORD WINAPI vibrationThread(LPVOID param) {
8282
}
8383

8484
void vibrateImpl(int strength, int durationMs) {
85+
if (strength < 0)
86+
strength = 0;
87+
if (strength > 65535)
88+
strength = 65535;
8589
VibrationParams *params = new VibrationParams{strength, durationMs};
8690
HANDLE threadHandle = CreateThread(nullptr, 0, vibrationThread, params, 0, nullptr);
8791
if (threadHandle != nullptr) {

C2ModLoader/Input/Input.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "Input/TypeSwitching.h"
77
#include "Input/Vibration.h"
88

9+
#include "Input/Backends/SDL3.h"
910
#include "Input/Backends/Xinput.h"
1011

1112
#include "ModApi.h"
@@ -40,6 +41,10 @@ void Setup() {
4041
inputBackend = new Input::Backends::Xinput::Backend();
4142
api->LogInfo("Using XInput backend for input.");
4243
break;
44+
case 1: // SDL3
45+
inputBackend = new Input::Backends::SDL3::Backend();
46+
api->LogInfo("Using SDL3 backend for input.");
47+
break;
4348
default:
4449
inputBackend = new Input::Backends::Xinput::Backend();
4550
api->LogInfo("Using XInput backend for input as fallback.");

C2ModLoader/Resource.rc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ BEGIN
7676
"Cheats/MusicSelect[Music Select|Enable music selection cheat]:bool=0;"
7777
"@Input[Modern Input|Enable and configure input enhancements];"
7878
"Input/Enabled[Enabled|Enable input enhancements]:bool=1;"
79-
"Input/Backends[Backend|Select the input backend to use]:enum[XInput]=0;"
79+
"Input/Backend[Backend|The input backend to use]:enum[XInput|SDL3]=0;"
8080
"Input/DeviceIndex[Device Index|Index of the input device to use]:int=0;"
8181
"Input/StickDeadzone[Stick Deadzone|Deadzone for analog sticks]:int=25;"
8282
"Input/StickOuterDeadzone[Stick Outer Deadzone|Outer deadzone for analog sticks]:int=75;"
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
---
2+
AlignConsecutiveMacros: Consecutive
3+
AlignConsecutiveAssignments: None
4+
AlignConsecutiveBitFields: None
5+
AlignConsecutiveDeclarations: None
6+
AlignEscapedNewlines: Right
7+
AlignOperands: Align
8+
AlignTrailingComments: true
9+
10+
AllowAllArgumentsOnNextLine: true
11+
AllowAllParametersOfDeclarationOnNextLine: true
12+
AllowShortEnumsOnASingleLine: true
13+
AllowShortBlocksOnASingleLine: Never
14+
AllowShortCaseLabelsOnASingleLine: false
15+
AllowShortFunctionsOnASingleLine: All
16+
AllowShortIfStatementsOnASingleLine: Never
17+
AllowShortLoopsOnASingleLine: false
18+
19+
AlwaysBreakAfterDefinitionReturnType: None
20+
AlwaysBreakAfterReturnType: None
21+
AlwaysBreakBeforeMultilineStrings: false
22+
AlwaysBreakTemplateDeclarations: MultiLine
23+
24+
# Custom brace breaking
25+
BreakBeforeBraces: Custom
26+
BraceWrapping:
27+
AfterCaseLabel: true
28+
AfterClass: true
29+
AfterControlStatement: Never
30+
AfterEnum: true
31+
AfterFunction: true
32+
AfterNamespace: true
33+
AfterObjCDeclaration: true
34+
AfterStruct: true
35+
AfterUnion: true
36+
AfterExternBlock: false
37+
BeforeElse: false
38+
BeforeWhile: false
39+
IndentBraces: false
40+
SplitEmptyFunction: true
41+
SplitEmptyRecord: true
42+
43+
# Make the closing brace of container literals go to a new line
44+
Cpp11BracedListStyle: false
45+
46+
# Never format includes
47+
IncludeBlocks: Preserve
48+
# clang-format version 4.0 through 12.0:
49+
#SortIncludes: false
50+
# clang-format version 13.0+:
51+
#SortIncludes: Never
52+
53+
# No length limit, in case it breaks macros, you can
54+
# disable it with /* clang-format off/on */ comments
55+
ColumnLimit: 0
56+
57+
IndentWidth: 4
58+
ContinuationIndentWidth: 4
59+
IndentCaseLabels: false
60+
IndentCaseBlocks: false
61+
IndentGotoLabels: true
62+
IndentPPDirectives: None
63+
IndentExternBlock: NoIndent
64+
65+
PointerAlignment: Right
66+
SpaceAfterCStyleCast: false
67+
SpacesInCStyleCastParentheses: false
68+
SpacesInConditionalStatement: false
69+
SpacesInContainerLiterals: true
70+
SpaceBeforeAssignmentOperators: true
71+
SpaceBeforeCaseColon: false
72+
SpaceBeforeParens: ControlStatements
73+
SpaceAroundPointerQualifiers: Default
74+
SpaceInEmptyBlock: false
75+
SpaceInEmptyParentheses: false
76+
77+
UseCRLF: false
78+
UseTab: Never
79+
80+
ForEachMacros:
81+
[
82+
"spa_list_for_each",
83+
"spa_list_for_each_safe",
84+
"wl_list_for_each",
85+
"wl_list_for_each_safe",
86+
"wl_array_for_each",
87+
"udev_list_entry_foreach",
88+
]
89+
90+
---
91+

0 commit comments

Comments
 (0)