Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/core/ddgo_controller.cpp
4212 views
1
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "ddgo_controller.h"
5
#include "host.h"
6
#include "system.h"
7
8
#include "util/state_wrapper.h"
9
10
#include "IconsPromptFont.h"
11
12
#include "common/assert.h"
13
#include "common/bitutils.h"
14
#include "common/settings_interface.h"
15
16
DDGoController::DDGoController(u32 index) : Controller(index)
17
{
18
}
19
20
DDGoController::~DDGoController() = default;
21
22
ControllerType DDGoController::GetType() const
23
{
24
return ControllerType::DDGoController;
25
}
26
27
void DDGoController::Reset()
28
{
29
m_transfer_state = TransferState::Idle;
30
m_power_transition_frames_remaining = 0;
31
UpdatePowerBits();
32
m_brake_transition_frames_remaining = 0;
33
UpdateBrakeBits();
34
}
35
36
bool DDGoController::DoState(StateWrapper& sw, bool apply_input_state)
37
{
38
if (!Controller::DoState(sw, apply_input_state))
39
return false;
40
41
u16 button_state = m_button_state;
42
u8 power_level = m_power_level;
43
u8 power_transition_frames_remaining = m_power_transition_frames_remaining;
44
u8 brake_level = m_brake_level;
45
u8 brake_transition_frames_remaining = m_brake_transition_frames_remaining;
46
sw.Do(&button_state);
47
sw.Do(&power_level);
48
sw.Do(&power_transition_frames_remaining);
49
sw.Do(&brake_level);
50
sw.Do(&brake_transition_frames_remaining);
51
52
if (apply_input_state)
53
{
54
m_button_state = button_state;
55
m_power_level = power_level;
56
m_power_transition_frames_remaining = power_transition_frames_remaining;
57
m_brake_level = brake_level;
58
m_brake_transition_frames_remaining = brake_transition_frames_remaining;
59
UpdatePowerBits();
60
UpdateBrakeBits();
61
}
62
63
sw.Do(&m_transfer_state);
64
return true;
65
}
66
67
float DDGoController::GetBindState(u32 index) const
68
{
69
if (index >= static_cast<u32>(Bind::VirtualButtonStart))
70
{
71
if (index < static_cast<u32>(Bind::VirtualBrakeReleased))
72
return BoolToFloat(m_power_level == (index - static_cast<u32>(Bind::VirtualPowerOff)));
73
else
74
return BoolToFloat(m_brake_level == (index - static_cast<u32>(Bind::VirtualBrakeReleased)));
75
}
76
77
// don't show the buttons set by the level
78
static constexpr u16 REPORT_MASK = (1u << static_cast<u16>(Bind::Start)) | (1u << static_cast<u16>(Bind::Select)) |
79
(1u << static_cast<u16>(Bind::A)) | (1u << static_cast<u16>(Bind::B)) |
80
(1u << static_cast<u16>(Bind::C));
81
return static_cast<float>((((m_button_state ^ 0xFFFFu) & REPORT_MASK) >> index) & 1u);
82
}
83
84
void DDGoController::SetBindState(u32 index, float value)
85
{
86
if (index == static_cast<u32>(Bind::Power))
87
{
88
value = (value < m_analog_deadzone) ? 0.0f : (value * m_analog_sensitivity);
89
SetPowerLevel(std::min(static_cast<u32>(value * MAX_POWER_LEVEL), MAX_POWER_LEVEL));
90
return;
91
}
92
else if (index == static_cast<u32>(Bind::Brake))
93
{
94
value = (value < m_analog_deadzone) ? 0.0f : (value * m_analog_sensitivity);
95
SetBrakeLevel(std::min(static_cast<u32>(value * MAX_BRAKE_LEVEL), MAX_BRAKE_LEVEL));
96
return;
97
}
98
else if (index >= static_cast<u32>(Bind::VirtualButtonStart))
99
{
100
// vbutton, only handle press
101
if (value < 0.5f)
102
return;
103
104
if (index < static_cast<u32>(Bind::VirtualBrakeReleased))
105
SetPowerLevel(index - static_cast<u32>(Bind::VirtualPowerOff));
106
else
107
SetBrakeLevel(index - static_cast<u32>(Bind::VirtualBrakeReleased));
108
109
return;
110
}
111
112
const bool pressed = (value >= 0.5f);
113
const u16 bit = u16(1) << static_cast<u8>(index);
114
if (pressed)
115
{
116
if (m_button_state & bit)
117
System::SetRunaheadReplayFlag();
118
119
m_button_state &= ~bit;
120
}
121
else
122
{
123
if (!(m_button_state & bit))
124
System::SetRunaheadReplayFlag();
125
126
m_button_state |= bit;
127
}
128
}
129
130
u32 DDGoController::GetButtonStateBits() const
131
{
132
return m_button_state ^ 0xFFFF;
133
}
134
135
void DDGoController::ResetTransferState()
136
{
137
m_transfer_state = TransferState::Idle;
138
}
139
140
bool DDGoController::Transfer(const u8 data_in, u8* data_out)
141
{
142
static constexpr u16 ID = 0x5A41;
143
144
switch (m_transfer_state)
145
{
146
case TransferState::Idle:
147
{
148
*data_out = 0xFF;
149
150
if (data_in == 0x01)
151
{
152
m_transfer_state = TransferState::Ready;
153
154
// handle transition time
155
if (m_power_transition_frames_remaining > 0)
156
{
157
m_power_transition_frames_remaining--;
158
UpdatePowerBits();
159
}
160
161
if (m_brake_transition_frames_remaining > 0)
162
{
163
m_brake_transition_frames_remaining--;
164
UpdateBrakeBits();
165
}
166
167
return true;
168
}
169
return false;
170
}
171
172
case TransferState::Ready:
173
{
174
if (data_in == 0x42)
175
{
176
*data_out = Truncate8(ID);
177
m_transfer_state = TransferState::IDMSB;
178
return true;
179
}
180
181
*data_out = 0xFF;
182
return false;
183
}
184
185
case TransferState::IDMSB:
186
{
187
*data_out = Truncate8(ID >> 8);
188
m_transfer_state = TransferState::ButtonsLSB;
189
return true;
190
}
191
192
case TransferState::ButtonsLSB:
193
{
194
*data_out = Truncate8(m_button_state & BUTTON_MASK);
195
m_transfer_state = TransferState::ButtonsMSB;
196
return true;
197
}
198
199
case TransferState::ButtonsMSB:
200
*data_out = Truncate8((m_button_state & BUTTON_MASK) >> 8);
201
m_transfer_state = TransferState::Idle;
202
return false;
203
204
default:
205
UnreachableCode();
206
}
207
}
208
209
void DDGoController::SetPowerLevel(u32 level)
210
{
211
DebugAssert(level <= MAX_POWER_LEVEL);
212
if (m_power_level == level)
213
return;
214
215
m_power_level = Truncate8(level);
216
m_power_transition_frames_remaining = m_power_transition_frames;
217
UpdatePowerBits();
218
System::SetRunaheadReplayFlag();
219
}
220
221
void DDGoController::UpdatePowerBits()
222
{
223
#define POWER_BITS(b0, b1, b2) \
224
static_cast<u16>(~(((b0) ? (1u << static_cast<u32>(Bind::PowerBit0)) : 0u) | \
225
((b1) ? (1u << static_cast<u32>(Bind::PowerBit1)) : 0u) | \
226
((b2) ? (1u << static_cast<u32>(Bind::PowerBit2)) : 0u)) & \
227
POWER_MASK)
228
229
static constexpr std::array<u16, MAX_POWER_LEVEL + 2> POWER_TABLE = {{
230
POWER_BITS(0, 1, 1), // N
231
POWER_BITS(1, 0, 1), // P1
232
POWER_BITS(0, 0, 1), // P2
233
POWER_BITS(1, 1, 0), // P3
234
POWER_BITS(0, 1, 0), // P4
235
POWER_BITS(1, 0, 0), // P5
236
POWER_BITS(0, 0, 0), // Transition
237
}};
238
239
#undef POWER_BITS
240
241
const u32 idx = (m_power_transition_frames_remaining > 0) ? (MAX_POWER_LEVEL + 1) : m_power_level;
242
m_button_state = (m_button_state & ~POWER_MASK) | POWER_TABLE[idx];
243
}
244
245
void DDGoController::SetBrakeLevel(u32 level)
246
{
247
DebugAssert(level <= MAX_BRAKE_LEVEL);
248
if (m_brake_level == level)
249
return;
250
251
m_brake_level = Truncate8(level);
252
m_brake_transition_frames_remaining = m_brake_transition_frames;
253
UpdateBrakeBits();
254
System::SetRunaheadReplayFlag();
255
}
256
257
void DDGoController::UpdateBrakeBits()
258
{
259
#define BRAKE_BITS(b0, b1, b2, b3) \
260
static_cast<u16>(~(((b0) ? (1u << static_cast<u32>(Bind::BrakeBit0)) : 0u) | \
261
((b1) ? (1u << static_cast<u32>(Bind::BrakeBit1)) : 0u) | \
262
((b2) ? (1u << static_cast<u32>(Bind::BrakeBit2)) : 0u) | \
263
((b3) ? (1u << static_cast<u32>(Bind::BrakeBit3)) : 0u)) & \
264
BRAKE_MASK)
265
266
static constexpr std::array<u16, MAX_BRAKE_LEVEL + 2> BRAKE_TABLE = {{
267
BRAKE_BITS(0, 1, 1, 1), // Released
268
BRAKE_BITS(1, 0, 1, 1), // B1
269
BRAKE_BITS(0, 0, 1, 1), // B2
270
BRAKE_BITS(1, 1, 0, 1), // B3
271
BRAKE_BITS(0, 1, 0, 1), // B4
272
BRAKE_BITS(1, 0, 0, 1), // B5
273
BRAKE_BITS(0, 0, 0, 1), // B6
274
BRAKE_BITS(1, 1, 1, 0), // B7
275
BRAKE_BITS(0, 1, 1, 0), // B8
276
BRAKE_BITS(0, 0, 0, 0), // Emergency
277
BRAKE_BITS(1, 1, 1, 1), // Transition
278
}};
279
280
#undef BRAKE_BITS
281
282
const u32 idx = (m_brake_transition_frames_remaining > 0) ? (MAX_BRAKE_LEVEL + 1) : m_brake_level;
283
m_button_state = (m_button_state & ~BRAKE_MASK) | BRAKE_TABLE[idx];
284
}
285
286
void DDGoController::LoadSettings(const SettingsInterface& si, const char* section, bool initial)
287
{
288
Controller::LoadSettings(si, section, initial);
289
m_analog_deadzone = std::clamp(si.GetFloatValue(section, "AnalogDeadzone", DEFAULT_STICK_DEADZONE), 0.0f, 1.0f);
290
m_analog_sensitivity =
291
std::clamp(si.GetFloatValue(section, "AnalogSensitivity", DEFAULT_STICK_SENSITIVITY), 0.01f, 3.0f);
292
m_power_transition_frames = static_cast<u8>(si.GetIntValue(section, "PowerTransitionFrames", 0));
293
m_brake_transition_frames = static_cast<u8>(si.GetIntValue(section, "BrakeTransitionFrames", 0));
294
}
295
296
std::unique_ptr<DDGoController> DDGoController::Create(u32 index)
297
{
298
return std::make_unique<DDGoController>(index);
299
}
300
301
static const Controller::ControllerBindingInfo s_binding_info[] = {
302
// clang-format off
303
#define BUTTON(name, display_name, icon_name, button, genb) \
304
{name, display_name, icon_name, static_cast<u32>(button), InputBindingInfo::Type::Button, genb}
305
#define AXIS(name, display_name, icon_name, axis, genb) \
306
{name, display_name, icon_name, static_cast<u32>(axis), InputBindingInfo::Type::HalfAxis, genb}
307
308
BUTTON("Select", TRANSLATE_NOOP("DDGoController", "Select"), ICON_PF_SELECT_SHARE, DDGoController::Bind::Select, GenericInputBinding::Select),
309
BUTTON("Start", TRANSLATE_NOOP("DDGoController", "Start"), ICON_PF_START, DDGoController::Bind::Start, GenericInputBinding::Start),
310
BUTTON("A", TRANSLATE_NOOP("DDGoController", "A"), ICON_PF_BUTTON_A, DDGoController::Bind::A, GenericInputBinding::Square),
311
BUTTON("B", TRANSLATE_NOOP("DDGoController", "B"), ICON_PF_BUTTON_B, DDGoController::Bind::B, GenericInputBinding::Cross),
312
BUTTON("C", TRANSLATE_NOOP("DDGoController", "C"), ICON_PF_BUTTON_C, DDGoController::Bind::C, GenericInputBinding::Circle),
313
BUTTON("VirtualPowerOff", TRANSLATE_NOOP("DDGoController", "Power Off"), ICON_PF_KEY_N, DDGoController::Bind::VirtualPowerOff, GenericInputBinding::Unknown),
314
BUTTON("VirtualPower1", TRANSLATE_NOOP("DDGoController", "Power 1"), ICON_PF_1, DDGoController::Bind::VirtualPower1, GenericInputBinding::Unknown),
315
BUTTON("VirtualPower2", TRANSLATE_NOOP("DDGoController", "Power 2"), ICON_PF_2, DDGoController::Bind::VirtualPower2, GenericInputBinding::Unknown),
316
BUTTON("VirtualPower3", TRANSLATE_NOOP("DDGoController", "Power 3"), ICON_PF_3, DDGoController::Bind::VirtualPower3, GenericInputBinding::Unknown),
317
BUTTON("VirtualPower4", TRANSLATE_NOOP("DDGoController", "Power 4"), ICON_PF_4, DDGoController::Bind::VirtualPower4, GenericInputBinding::Unknown),
318
BUTTON("VirtualPower5", TRANSLATE_NOOP("DDGoController", "Power 5"), ICON_PF_5, DDGoController::Bind::VirtualPower5, GenericInputBinding::Unknown),
319
BUTTON("VirtualBrakeReleased", TRANSLATE_NOOP("DDGoController", "Brake Released"), ICON_PF_KEY_R, DDGoController::Bind::VirtualBrakeReleased, GenericInputBinding::Unknown),
320
BUTTON("VirtualBrake1", TRANSLATE_NOOP("DDGoController", "Brake 1"), ICON_PF_1, DDGoController::Bind::VirtualBrake1, GenericInputBinding::Unknown),
321
BUTTON("VirtualBrake2", TRANSLATE_NOOP("DDGoController", "Brake 2"), ICON_PF_2, DDGoController::Bind::VirtualBrake2, GenericInputBinding::Unknown),
322
BUTTON("VirtualBrake3", TRANSLATE_NOOP("DDGoController", "Brake 3"), ICON_PF_3, DDGoController::Bind::VirtualBrake3, GenericInputBinding::Unknown),
323
BUTTON("VirtualBrake4", TRANSLATE_NOOP("DDGoController", "Brake 4"), ICON_PF_4, DDGoController::Bind::VirtualBrake4, GenericInputBinding::Unknown),
324
BUTTON("VirtualBrake5", TRANSLATE_NOOP("DDGoController", "Brake 5"), ICON_PF_5, DDGoController::Bind::VirtualBrake5, GenericInputBinding::Unknown),
325
BUTTON("VirtualBrake6", TRANSLATE_NOOP("DDGoController", "Brake 6"), ICON_PF_6, DDGoController::Bind::VirtualBrake6, GenericInputBinding::Unknown),
326
BUTTON("VirtualBrake7", TRANSLATE_NOOP("DDGoController", "Brake 7"), ICON_PF_7, DDGoController::Bind::VirtualBrake7, GenericInputBinding::Unknown),
327
BUTTON("VirtualBrake8", TRANSLATE_NOOP("DDGoController", "Brake 8"), ICON_PF_8, DDGoController::Bind::VirtualBrake8, GenericInputBinding::Unknown),
328
BUTTON("VirtualBrakeEmergency", TRANSLATE_NOOP("DDGoController", "Brake Emergency"), ICON_PF_KEY_E, DDGoController::Bind::VirtualBrakeEmergency, GenericInputBinding::Unknown),
329
330
AXIS("Power", TRANSLATE_NOOP("DDGoController", "Power"), ICON_PF_KEY_P, DDGoController::Bind::Power, GenericInputBinding::L2),
331
AXIS("Brake", TRANSLATE_NOOP("DDGoController", "Brake"), ICON_PF_KEY_B, DDGoController::Bind::Brake, GenericInputBinding::R2),
332
// clang-format on
333
334
#undef AXIS
335
#undef BUTTON
336
};
337
338
static const SettingInfo s_settings[] = {
339
{SettingInfo::Type::Float, "AnalogDeadzone", TRANSLATE_NOOP("DDGoController", "Analog Deadzone"),
340
TRANSLATE_NOOP("DDGoController",
341
"Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored."),
342
"0", "0", "1", "0.01", "%.0f%%", nullptr, 100.0f},
343
{SettingInfo::Type::Float, "AnalogSensitivity", TRANSLATE_NOOP("DDGoController", "Analog Sensitivity"),
344
TRANSLATE_NOOP(
345
"DDGoController",
346
"Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent "
347
"controllers, e.g. DualShock 4, Xbox One Controller."),
348
"1.33", "0.01", "2", "0.01", "%.0f%%", nullptr, 100.0f},
349
{SettingInfo::Type::Integer, "PowerTransitionFrames", TRANSLATE_NOOP("DDGoController", "Power Transition Frames"),
350
TRANSLATE_NOOP("DDGoController", "Sets the number of frames that the controller will report the "
351
"transitioning/inbetween state when changing power level."),
352
"10", "0", "255", "1", "%d", nullptr, 1.0f},
353
{SettingInfo::Type::Integer, "BrakeTransitionFrames", TRANSLATE_NOOP("DDGoController", "Brake Transition Frames"),
354
TRANSLATE_NOOP("DDGoController", "Sets the number of frames that the controller will report the "
355
"transitioning/inbetween state when changing brake level."),
356
"10", "0", "255", "1", "%d", nullptr, 1.0f}};
357
358
const Controller::ControllerInfo DDGoController::INFO = {
359
ControllerType::DDGoController, "DDGoController", TRANSLATE_NOOP("ControllerType", "Densha de Go! Controller"),
360
ICON_PF_FIGHT_STICK_JOYSTICK, s_binding_info, s_settings};
361
362