Skip to content

Commit ea963ff

Browse files
Florin9doilightningterror
authored andcommitted
USB: Train Mascon and Master Controller emulation
1 parent bd9dcbe commit ea963ff

File tree

2 files changed

+375
-26
lines changed

2 files changed

+375
-26
lines changed

pcsx2/USB/usb-pad/usb-train.cpp

Lines changed: 217 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ namespace usb_pad
3737
TRANSLATE_NOOP("USB", "Type 2"),
3838
TRANSLATE_NOOP("USB", "Shinkansen"),
3939
TRANSLATE_NOOP("USB", "Ryojōhen"),
40+
TRANSLATE_NOOP("USB", "Train Mascon"),
41+
TRANSLATE_NOOP("USB", "Master Controller"),
4042
};
4143
return subtypes;
4244
}
@@ -63,6 +65,14 @@ namespace usb_pad
6365
CID_TC_L = CID_TC_C,
6466
CID_TC_R = CID_TC_D,
6567

68+
// Train Mascon
69+
CID_TC_ATS = CID_TC_D,
70+
CID_TC_CLOSE = CID_TC_CAMERA,
71+
CID_TC_POWER_UP,
72+
CID_TC_POWER_DOWN,
73+
CID_TC_REVERSER_UP,
74+
CID_TC_REVERSER_DOWN,
75+
6676
BUTTONS_OFFSET = CID_TC_B,
6777
};
6878

@@ -110,6 +120,43 @@ namespace usb_pad
110120

111121
return bindings;
112122
}
123+
case TRAIN_MASCON:
124+
{
125+
static constexpr const InputBindingInfo bindings[] = {
126+
{"PowerUp", TRANSLATE_NOOP("USB", "Power Up"), nullptr, InputBindingInfo::Type::Button, CID_TC_POWER_UP, GenericInputBinding::R1},
127+
{"PowerDown", TRANSLATE_NOOP("USB", "Power Down"), nullptr, InputBindingInfo::Type::Button, CID_TC_POWER_DOWN, GenericInputBinding::L1},
128+
{"ReverserUp", TRANSLATE_NOOP("USB", "Reverser Up"), nullptr, InputBindingInfo::Type::Button, CID_TC_REVERSER_UP, GenericInputBinding::R2},
129+
{"ReverserDown", TRANSLATE_NOOP("USB", "Reverser Down"), nullptr, InputBindingInfo::Type::Button, CID_TC_REVERSER_DOWN, GenericInputBinding::L2},
130+
131+
{"Up", TRANSLATE_NOOP("USB", "D-Pad Up"), ICON_PF_DPAD_UP, InputBindingInfo::Type::Button, CID_TC_UP, GenericInputBinding::DPadUp},
132+
{"Down", TRANSLATE_NOOP("USB", "D-Pad Down"), ICON_PF_DPAD_DOWN, InputBindingInfo::Type::Button, CID_TC_DOWN, GenericInputBinding::DPadDown},
133+
{"Left", TRANSLATE_NOOP("USB", "D-Pad Left"), ICON_PF_DPAD_LEFT, InputBindingInfo::Type::Button, CID_TC_LEFT, GenericInputBinding::DPadLeft},
134+
{"Right", TRANSLATE_NOOP("USB", "D-Pad Right"), ICON_PF_DPAD_RIGHT, InputBindingInfo::Type::Button, CID_TC_RIGHT, GenericInputBinding::DPadRight},
135+
136+
{"ATS", TRANSLATE_NOOP("USB", "ATS"), nullptr, InputBindingInfo::Type::Button, CID_TC_ATS, GenericInputBinding::Triangle},
137+
{"Close", TRANSLATE_NOOP("USB", "Close"), nullptr, InputBindingInfo::Type::Button, CID_TC_CLOSE, GenericInputBinding::R3},
138+
{"A", TRANSLATE_NOOP("USB", "A Button"), ICON_PF_KEY_A, InputBindingInfo::Type::Button, CID_TC_A, GenericInputBinding::Square},
139+
{"B", TRANSLATE_NOOP("USB", "B Button"), ICON_PF_KEY_B, InputBindingInfo::Type::Button, CID_TC_B, GenericInputBinding::Cross},
140+
{"C", TRANSLATE_NOOP("USB", "C Button"), ICON_PF_KEY_C, InputBindingInfo::Type::Button, CID_TC_C, GenericInputBinding::Circle},
141+
{"Select", TRANSLATE_NOOP("USB", "Select"), ICON_PF_SELECT_SHARE, InputBindingInfo::Type::Button, CID_TC_SELECT, GenericInputBinding::Select},
142+
{"Start", TRANSLATE_NOOP("USB", "Start"), ICON_PF_START, InputBindingInfo::Type::Button, CID_TC_START, GenericInputBinding::Start},
143+
};
144+
return bindings;
145+
}
146+
case MASTER_CONTROLLER:
147+
{
148+
static constexpr const InputBindingInfo bindings[] = {
149+
{"PowerUp", TRANSLATE_NOOP("USB", "Power Up"), nullptr, InputBindingInfo::Type::Button, CID_TC_POWER_UP, GenericInputBinding::R1},
150+
{"PowerDown", TRANSLATE_NOOP("USB", "Power Down"), nullptr, InputBindingInfo::Type::Button, CID_TC_POWER_DOWN, GenericInputBinding::L1},
151+
{"ReverserUp", TRANSLATE_NOOP("USB", "Reverser Up"), nullptr, InputBindingInfo::Type::Button, CID_TC_REVERSER_UP, GenericInputBinding::R2},
152+
{"ReverserDown", TRANSLATE_NOOP("USB", "Reverser Down"), nullptr, InputBindingInfo::Type::Button, CID_TC_REVERSER_DOWN, GenericInputBinding::L2},
153+
{"S", TRANSLATE_NOOP("USB", "S"), ICON_PF_KEY_S, InputBindingInfo::Type::Button, CID_TC_D, GenericInputBinding::Cross},
154+
{"A", TRANSLATE_NOOP("USB", "A"), ICON_PF_KEY_A, InputBindingInfo::Type::Button, CID_TC_A, GenericInputBinding::Square},
155+
{"B", TRANSLATE_NOOP("USB", "B"), ICON_PF_KEY_B, InputBindingInfo::Type::Button, CID_TC_B, GenericInputBinding::Triangle},
156+
{"C", TRANSLATE_NOOP("USB", "C"), ICON_PF_KEY_C, InputBindingInfo::Type::Button, CID_TC_C, GenericInputBinding::Circle},
157+
};
158+
return bindings;
159+
}
113160
default:
114161
break;
115162
}
@@ -151,29 +198,74 @@ namespace usb_pad
151198
{
152199
TrainDeviceState* s = USB_CONTAINER_OF(dev, TrainDeviceState, dev);
153200

154-
s->passthrough = USB::GetConfigBool(si, s->port, TypeName(), "Passthrough", false);
201+
switch (s->type)
202+
{
203+
case TRAIN_TYPE2:
204+
case TRAIN_SHINKANSEN:
205+
case TRAIN_RYOJOUHEN:
206+
s->passthrough = USB::GetConfigBool(si, s->port, TypeName(), "Passthrough", false);
207+
break;
208+
case MASTER_CONTROLLER:
209+
s->power_notches = USB::GetConfigInt(si, s->port, TypeName(), "power_notches", 5);
210+
s->brake_notches = USB::GetConfigInt(si, s->port, TypeName(), "brake_notches", 8);
211+
break;
212+
}
155213
}
156214

157215
std::span<const SettingInfo> TrainDevice::Settings(u32 subtype) const
158216
{
159-
static constexpr const SettingInfo passthrough = {
160-
SettingInfo::Type::Boolean,
161-
"Passthrough",
162-
TRANSLATE_NOOP("USB", "Axes Passthrough"),
163-
TRANSLATE_NOOP("USB", "Passes through the unprocessed input axis to the game. Enable if you are using a compatible Densha De Go! controller. Disable if you are using any other joystick."),
164-
"false",
165-
};
166-
167-
static constexpr const SettingInfo info[] = {passthrough};
168-
return info;
217+
switch (subtype)
218+
{
219+
case TRAIN_TYPE2:
220+
case TRAIN_SHINKANSEN:
221+
case TRAIN_RYOJOUHEN:
222+
{
223+
static constexpr const SettingInfo info[] = {
224+
{
225+
.type = SettingInfo::Type::Boolean,
226+
.name = "Passthrough",
227+
.display_name = TRANSLATE_NOOP("USB", "Axes Passthrough"),
228+
.description = TRANSLATE_NOOP("USB", "Passes through the unprocessed input axis to the game. Enable if you are using a compatible Densha De Go! controller. Disable if you are using any other joystick."),
229+
.default_value = "false",
230+
}
231+
};
232+
return info;
233+
}
234+
case MASTER_CONTROLLER:
235+
{
236+
static constexpr const SettingInfo info[] = {
237+
{
238+
.type = SettingInfo::Type::Integer,
239+
.name = "power_notches",
240+
.display_name = TRANSLATE_NOOP("USB", "Power notches"),
241+
.description = TRANSLATE_NOOP("USB", "Selects the number of power notches (3-6)"),
242+
.default_value = "5",
243+
.min_value = "3",
244+
.max_value = "6",
245+
},
246+
{
247+
.type = SettingInfo::Type::Integer,
248+
.name = "brake_notches",
249+
.display_name = TRANSLATE_NOOP("USB", "Brake notches"),
250+
.description = TRANSLATE_NOOP("USB", "Selects the number of brake notches (5-8)"),
251+
.default_value = "8",
252+
.min_value = "5",
253+
.max_value = "8",
254+
}
255+
};
256+
return info;
257+
}
258+
default:
259+
return {};
260+
}
169261
}
170262

171263
static constexpr u32 button_mask(u32 bind_index)
172264
{
173265
return (1u << (bind_index - TrainControlID::BUTTONS_OFFSET));
174266
}
175267

176-
static constexpr u8 button_at(u8 value, u32 index)
268+
static constexpr u16 button_at(u16 value, u32 index)
177269
{
178270
return value & button_mask(index);
179271
}
@@ -205,6 +297,10 @@ namespace usb_pad
205297
case CID_TC_SELECT:
206298
case CID_TC_START:
207299
case CID_TC_CAMERA:
300+
case CID_TC_POWER_UP:
301+
case CID_TC_POWER_DOWN:
302+
case CID_TC_REVERSER_UP:
303+
case CID_TC_REVERSER_DOWN:
208304
{
209305
return (button_at(s->data.buttons, bind_index) != 0u) ? 1.0f : 0.0f;
210306
}
@@ -251,14 +347,18 @@ namespace usb_pad
251347
case CID_TC_SELECT:
252348
case CID_TC_START:
253349
case CID_TC_CAMERA:
350+
case CID_TC_POWER_UP:
351+
case CID_TC_POWER_DOWN:
352+
case CID_TC_REVERSER_UP:
353+
case CID_TC_REVERSER_DOWN:
254354
{
255355
const u32 mask = button_mask(bind_index);
256356
if (value >= 0.5f)
257357
s->data.buttons |= mask;
258358
else
259359
s->data.buttons &= ~mask;
360+
break;
260361
}
261-
break;
262362

263363
default:
264364
break;
@@ -452,11 +552,23 @@ namespace usb_pad
452552
return (get_ab(buttons) | (button_at(buttons, CID_TC_CAMERA) >> 4) | ((get_cd(buttons) | get_ss(buttons)) << 1));
453553
}
454554

555+
void TrainDeviceState::UpdateHandles(u8 max_power, u8 max_brake)
556+
{
557+
if (!button_at(prev_buttons, CID_TC_POWER_UP) && button_at(data.buttons, CID_TC_POWER_UP) && handle < max_brake + 1 + max_power)
558+
handle++;
559+
if (!button_at(prev_buttons, CID_TC_POWER_DOWN) && button_at(data.buttons, CID_TC_POWER_DOWN) && handle > 0)
560+
handle--;
561+
if (!button_at(prev_buttons, CID_TC_REVERSER_UP) && button_at(data.buttons, CID_TC_REVERSER_UP) && reverser < 2)
562+
reverser++;
563+
if (!button_at(prev_buttons, CID_TC_REVERSER_DOWN) && button_at(data.buttons, CID_TC_REVERSER_DOWN) && reverser > 0)
564+
reverser--;
565+
}
566+
455567
static void train_handle_data(USBDevice* dev, USBPacket* p)
456568
{
457569
TrainDeviceState* s = USB_CONTAINER_OF(dev, TrainDeviceState, dev);
458570

459-
if (p->pid != USB_TOKEN_IN || p->ep->nr != 1)
571+
if (s->type < MASTER_CONTROLLER && (p->pid != USB_TOKEN_IN || p->ep->nr != 1))
460572
{
461573
Console.Error("Unhandled TrainController request pid=%d ep=%u", p->pid, p->ep->nr);
462574
p->status = USB_RET_STALL;
@@ -501,6 +613,77 @@ namespace usb_pad
501613
usb_packet_copy(p, &out, sizeof(out));
502614
break;
503615
}
616+
case TRAIN_MASCON:
617+
{
618+
s->UpdateHandles(5, 6);
619+
s->prev_buttons = s->data.buttons;
620+
621+
TrainConData_TrainMascon out = {};
622+
out.one = 0x01;
623+
out.handle = 1 + s->handle;
624+
out.reverser = s->reverser < 2 ? !s->reverser : s->reverser;
625+
out.ats = !!button_at(s->data.buttons, CID_TC_ATS);
626+
out.close = !!button_at(s->data.buttons, CID_TC_CLOSE);
627+
out.button_a_soft = !!button_at(s->data.buttons, CID_TC_A);
628+
out.button_a_hard = !!button_at(s->data.buttons, CID_TC_A);
629+
out.button_b = !!button_at(s->data.buttons, CID_TC_B);
630+
out.button_c = !!button_at(s->data.buttons, CID_TC_C);
631+
out.start = !!button_at(s->data.buttons, CID_TC_START);
632+
out.select = !!button_at(s->data.buttons, CID_TC_SELECT);
633+
out.dpad_up = s->data.hat_up;
634+
out.dpad_down = s->data.hat_down;
635+
out.dpad_left = s->data.hat_left;
636+
out.dpad_right = s->data.hat_right;
637+
usb_packet_copy(p, &out, sizeof(out));
638+
break;
639+
}
640+
case MASTER_CONTROLLER:
641+
{
642+
if (p->ep->nr == 1) // interrupt in
643+
{
644+
p->status = USB_RET_STALL;
645+
break;
646+
}
647+
else if (p->ep->nr == 2) // bulk out
648+
{
649+
// The game sends a reset command after ~1500ms without updates. Resend the status during the next transfer
650+
s->last_handle = -1;
651+
s->last_reverser = -1;
652+
break;
653+
} // else bulk in
654+
655+
s->UpdateHandles(s->power_notches, s->brake_notches);
656+
657+
char data[100];
658+
std::memset(data, 0, sizeof(data));
659+
u8 pos = 0;
660+
661+
if (s->last_handle != s->handle)
662+
{
663+
pos += snprintf(data + pos, sizeof(data) - pos, "%s\x0d", s->mc_handle[s->handle + 8 - s->brake_notches]);
664+
s->last_handle = s->handle;
665+
}
666+
if (s->last_reverser != s->reverser)
667+
{
668+
pos += snprintf(data + pos, sizeof(data) - pos, "%s\x0d", s->mc_reverser[s->reverser]);
669+
s->last_reverser = s->reverser;
670+
}
671+
672+
for (int i = 0; i < 4; i++)
673+
{
674+
if (!button_at(s->prev_buttons, BUTTONS_OFFSET + i) && button_at(s->data.buttons, BUTTONS_OFFSET + i))
675+
{
676+
pos += snprintf(data + pos, sizeof(data) - pos, "%s\x0d", s->mc_button_pressed[i]);
677+
}
678+
if (button_at(s->prev_buttons, BUTTONS_OFFSET + i) && !button_at(s->data.buttons, BUTTONS_OFFSET + i))
679+
{
680+
pos += snprintf(data + pos, sizeof(data) - pos, "%s\x0d", s->mc_button_released[i]);
681+
}
682+
}
683+
s->prev_buttons = s->data.buttons;
684+
usb_packet_copy(p, data, std::min<u16>(p->buffer_size, pos));
685+
break;
686+
}
504687
default:
505688
Console.Error("Unhandled TrainController USB_TOKEN_IN pid=%d ep=%u type=%u", p->pid, p->ep->nr, s->type);
506689
p->status = USB_RET_IOERROR;
@@ -520,25 +703,42 @@ namespace usb_pad
520703
s->desc.str = dct01_desc_strings;
521704
if (usb_desc_parse_dev(dct01_dev_descriptor, sizeof(dct01_dev_descriptor), s->desc, s->desc_dev) < 0)
522705
goto fail;
706+
if (usb_desc_parse_config(taito_denshacon_config_descriptor, sizeof(taito_denshacon_config_descriptor), s->desc_dev) < 0)
707+
goto fail;
523708
break;
524709
case TRAIN_SHINKANSEN:
525710
s->desc.str = dct02_desc_strings;
526711
if (usb_desc_parse_dev(dct02_dev_descriptor, sizeof(dct02_dev_descriptor), s->desc, s->desc_dev) < 0)
527712
goto fail;
713+
if (usb_desc_parse_config(taito_denshacon_config_descriptor, sizeof(taito_denshacon_config_descriptor), s->desc_dev) < 0)
714+
goto fail;
528715
break;
529716
case TRAIN_RYOJOUHEN:
530717
s->desc.str = dct03_desc_strings;
531718
if (usb_desc_parse_dev(dct03_dev_descriptor, sizeof(dct03_dev_descriptor), s->desc, s->desc_dev) < 0)
532719
goto fail;
720+
if (usb_desc_parse_config(taito_denshacon_config_descriptor, sizeof(taito_denshacon_config_descriptor), s->desc_dev) < 0)
721+
goto fail;
722+
break;
723+
case TRAIN_MASCON:
724+
s->desc.str = dct03_desc_strings;
725+
if (usb_desc_parse_dev(train_mascon_dev_descriptor, sizeof(train_mascon_dev_descriptor), s->desc, s->desc_dev) < 0)
726+
goto fail;
727+
if (usb_desc_parse_config(train_mascon_config_descriptor, sizeof(train_mascon_config_descriptor), s->desc_dev) < 0)
728+
goto fail;
729+
break;
730+
case MASTER_CONTROLLER:
731+
s->desc.str = dct03_desc_strings;
732+
if (usb_desc_parse_dev(master_controller_dev_descriptor, sizeof(master_controller_dev_descriptor), s->desc, s->desc_dev) < 0)
733+
goto fail;
734+
if (usb_desc_parse_config(master_controller_config_descriptor, sizeof(master_controller_config_descriptor), s->desc_dev) < 0)
735+
goto fail;
533736
break;
534737

535738
default:
536739
goto fail;
537740
}
538741

539-
if (usb_desc_parse_config(taito_denshacon_config_descriptor, sizeof(taito_denshacon_config_descriptor), s->desc_dev) < 0)
540-
goto fail;
541-
542742
s->dev.speed = USB_SPEED_FULL;
543743
s->dev.klass.handle_attach = usb_desc_attach;
544744
s->dev.klass.handle_reset = train_handle_reset;

0 commit comments

Comments
 (0)