@@ -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