diff --git a/config_spec.yml b/config_spec.yml
index d013c806ac..e552e5b8ee 100644
--- a/config_spec.yml
+++ b/config_spec.yml
@@ -132,6 +132,154 @@ input:
rtrigger:
type: integer
default: 18 # w
+ keyboard_sbc_scancode_map:
+ eject:
+ type: integer
+ default: 41 # esc
+ cockpit_hatch:
+ type: integer
+ default: 62 # F5
+ ignition:
+ type: integer
+ default: 63 # F6
+ start:
+ type: integer
+ default: 64 # F7
+ open_close:
+ type: integer
+ default: 74 # home
+ map_zoom_in_out:
+ type: integer
+ default: 77 # end
+ mode_select:
+ type: integer
+ default: 73 # insert
+ sub_monitor_mode_select:
+ type: integer
+ default: 76 # delete
+ zoom_in:
+ type: integer
+ default: 75 # Page Up
+ zoom_out:
+ type: integer
+ default: 78 # Page Down
+ fss:
+ type: integer
+ default: 97 # numpad 9
+ manipulator:
+ type: integer
+ default: 94 # numpad 6
+ line_color_change:
+ type: integer
+ default: 91 # numpad 3
+ washing:
+ type: integer
+ default: 45 # -
+ extinguisher:
+ type: integer
+ default: 8 # e
+ chaff:
+ type: integer
+ default: 6 # c
+ tank_detach:
+ type: integer
+ default: 96 # numpad 8
+ override:
+ type: integer
+ default: 93 # numpad 5
+ night_scope:
+ type: integer
+ default: 90 # numpad 2
+ func1:
+ type: integer
+ default: 95 # numpad 7
+ func2:
+ type: integer
+ default: 92 # numpad 4
+ func3:
+ type: integer
+ default: 89 # numpad 1
+ main_weapon_control:
+ type: integer
+ default: 35 # 6
+ sub_weapon_control:
+ type: integer
+ default: 36 # 7
+ magazine_change:
+ type: integer
+ default: 21 # r
+ com1:
+ type: integer
+ default: 30 # 1
+ com2:
+ type: integer
+ default: 31 # 2
+ com3:
+ type: integer
+ default: 32 # 3
+ com4:
+ type: integer
+ default: 33 # 4
+ com5:
+ type: integer
+ default: 34 # 5
+ sight_change:
+ type: integer
+ default: 20 # q
+ filt_control_system:
+ type: integer
+ default: 65 # F8
+ oxygen_supply_system:
+ type: integer
+ default: 66 # F9
+ fuel_flow_rate:
+ type: integer
+ default: 67 # F10
+ buffer_material:
+ type: integer
+ default: 68 # F11
+ vt_location_measurement:
+ type: integer
+ default: 69 # F12
+ gear_up:
+ type: integer
+ default: 225 # lshift
+ gear_down:
+ type: integer
+ default: 224 # lctrl
+ tuner_left:
+ type: integer
+ default: 54 # <
+ tuner_right:
+ type: integer
+ default: 55 # >
+ sight_change_up:
+ type: integer
+ default: 82 # up
+ sight_change_down:
+ type: integer
+ default: 81 # down
+ sight_change_left:
+ type: integer
+ default: 80 # left
+ sight_change_right:
+ type: integer
+ default: 79 # right
+ rotation_left:
+ type: integer
+ default: 4 # a
+ rotation_right:
+ type: integer
+ default: 7 # d
+ left_pedal:
+ type: integer
+ default: 44 #space
+ right_pedal:
+ type: integer
+ default: 26 # w
+ middle_pedal:
+ type: integer
+ default: 22 # s
display:
renderer:
diff --git a/data/meson.build b/data/meson.build
index 4a82350d44..86f4b07f55 100644
--- a/data/meson.build
+++ b/data/meson.build
@@ -1,4 +1,5 @@
pfiles = [
+ 'sb_controller_mask.png',
'controller_mask.png',
'controller_mask_s.png',
'xmu_mask.png',
diff --git a/data/sb_controller_mask.png b/data/sb_controller_mask.png
new file mode 100644
index 0000000000..bfa39c775a
Binary files /dev/null and b/data/sb_controller_mask.png differ
diff --git a/hw/xbox/meson.build b/hw/xbox/meson.build
index dce2d3729e..b3b18e4c64 100644
--- a/hw/xbox/meson.build
+++ b/hw/xbox/meson.build
@@ -17,6 +17,7 @@ specific_ss.add(files(
'xid.c',
'xblc.c',
'xid-gamepad.c',
+ 'xid-steel-battalion.c',
))
subdir('nv2a')
subdir('mcpx')
diff --git a/hw/xbox/xid-steel-battalion.c b/hw/xbox/xid-steel-battalion.c
new file mode 100644
index 0000000000..29b2f6479d
--- /dev/null
+++ b/hw/xbox/xid-steel-battalion.c
@@ -0,0 +1,438 @@
+/*
+ * QEMU USB XID Devices
+ *
+ * Copyright (c) 2013 espes
+ * Copyright (c) 2017 Jannik Vogel
+ * Copyright (c) 2018-2021 Matt Borgerson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see .
+ */
+
+#include "xid.h"
+
+// #define DEBUG_XID
+#ifdef DEBUG_XID
+#define DPRINTF printf
+#else
+#define DPRINTF(...)
+#endif
+
+#define USB_VENDOR_CAPCOM 0x0a7b
+
+#define STEEL_BATTALION_IN_ENDPOINT_ID 0x02
+#define STEEL_BATTALION_OUT_ENDPOINT_ID 0x01
+
+typedef struct XIDSteelBattalionReport {
+ uint8_t bReportId;
+ uint8_t bLength;
+ uint32_t dwButtons;
+ uint8_t bMoreButtons;
+ uint16_t wPadding;
+ uint8_t bAimingX;
+ uint8_t bPadding;
+ uint8_t bAimingY;
+ int16_t sRotationLever; // only high byte is used
+ int16_t sSightChangeX; // only high byte is used
+ int16_t sSightChangeY; // only high byte is used
+ uint16_t wLeftPedal; // only high byte is used
+ uint16_t wMiddlePedal; // only high byte is used
+ uint16_t wRightPedal; // only high byte is used
+ uint8_t ucTunerDial; // low nibble, The 9 o'clock postion is 0, and the 6
+ // o'clock position is 12
+ uint8_t ucGearLever; // gear lever 1~5 for gear 1~5, 7~13 for gear R,N,1~5,
+ // 15 for gear R
+} QEMU_PACKED XIDSteelBattalionReport;
+
+// Based on:
+// https://github.com/Ryzee119/ogx360/blob/master/Firmware/src/usbd/usbd_xid.h:195
+typedef struct XIDSteelBattalionOutputReport {
+ uint8_t report_id;
+ uint8_t length;
+ uint8_t EmergencyEject : 4;
+ uint8_t CockpitHatch : 4;
+ uint8_t Ignition : 4;
+ uint8_t Start : 4;
+ uint8_t OpenClose : 4;
+ uint8_t MapZoomInOut : 4;
+ uint8_t ModeSelect : 4;
+ uint8_t SubMonitorModeSelect : 4;
+ uint8_t MainMonitorZoomIn : 4;
+ uint8_t MainMonitorZoomOut : 4;
+ uint8_t ForecastShootingSystem : 4;
+ uint8_t Manipulator : 4;
+ uint8_t LineColorChange : 4;
+ uint8_t Washing : 4;
+ uint8_t Extinguisher : 4;
+ uint8_t Chaff : 4;
+ uint8_t TankDetach : 4;
+ uint8_t Override : 4;
+ uint8_t NightScope : 4;
+ uint8_t F1 : 4;
+ uint8_t F2 : 4;
+ uint8_t F3 : 4;
+ uint8_t MainWeaponControl : 4;
+ uint8_t SubWeaponControl : 4;
+ uint8_t MagazineChange : 4;
+ uint8_t Comm1 : 4;
+ uint8_t Comm2 : 4;
+ uint8_t Comm3 : 4;
+ uint8_t Comm4 : 4;
+ uint8_t Comm5 : 4;
+ uint8_t : 4;
+ uint8_t GearR : 4;
+ uint8_t GearN : 4;
+ uint8_t Gear1 : 4;
+ uint8_t Gear2 : 4;
+ uint8_t Gear3 : 4;
+ uint8_t Gear4 : 4;
+ uint8_t Gear5 : 4;
+ uint8_t not_used;
+} QEMU_PACKED XIDSteelBattalionOutputReport;
+
+typedef struct USBXIDSteelBattalionState {
+ USBDevice dev;
+ USBEndpoint *intr;
+ const XIDDesc *xid_desc;
+ XIDSteelBattalionReport in_state;
+ XIDSteelBattalionReport in_state_capabilities;
+ XIDSteelBattalionOutputReport out_state;
+ XIDSteelBattalionOutputReport out_state_capabilities;
+ uint8_t device_index;
+} USBXIDSteelBattalionState;
+
+#define USB_XID_SB(obj) \
+ OBJECT_CHECK(USBXIDSteelBattalionState, (obj), TYPE_USB_XID_STEEL_BATTALION)
+
+static const USBDescIface desc_iface_steel_battalion = {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_XID,
+ .bInterfaceSubClass = 0x42,
+ .bInterfaceProtocol = 0x00,
+ .eps =
+ (USBDescEndpoint[]){
+ {
+ .bEndpointAddress = USB_DIR_IN | STEEL_BATTALION_IN_ENDPOINT_ID,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = 0x20,
+ .bInterval = 4,
+ },
+ {
+ .bEndpointAddress =
+ USB_DIR_OUT | STEEL_BATTALION_OUT_ENDPOINT_ID,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = 0x20,
+ .bInterval = 4,
+ },
+ },
+};
+
+static const USBDescDevice desc_device_steel_battalion = {
+ .bcdUSB = 0x0110,
+ .bMaxPacketSize0 = 0x40,
+ .bNumConfigurations = 1,
+ .confs =
+ (USBDescConfig[]){
+ {
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .bmAttributes = USB_CFG_ATT_ONE,
+ .bMaxPower = 50,
+ .nif = 1,
+ .ifs = &desc_iface_steel_battalion,
+ },
+ },
+};
+
+static const USBDesc desc_xbox_steel_battalion = {
+ .id = {
+ .idVendor = USB_VENDOR_CAPCOM,
+ .idProduct = 0xd000,
+ .bcdDevice = 0x0100,
+ .iManufacturer = STR_MANUFACTURER,
+ .iProduct = STR_PRODUCT,
+ .iSerialNumber = STR_SERIALNUMBER,
+ },
+ .full = &desc_device_steel_battalion,
+ .str = desc_strings,
+};
+
+static const XIDDesc desc_xid_steel_battalion = {
+ .bLength = 0x10,
+ .bDescriptorType = USB_DT_XID,
+ .bcdXid = 0x100,
+ .bType = XID_DEVICETYPE_STEEL_BATTALION,
+ .bSubType = XID_DEVICESUBTYPE_GAMEPAD,
+ .bMaxInputReportSize = 26,
+ .bMaxOutputReportSize = 32,
+ .wAlternateProductIds = { 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF },
+};
+
+static void update_sbc_input(USBXIDSteelBattalionState *s)
+{
+ if (xemu_input_get_test_mode()) {
+ // Don't report changes if we are testing the controller while running
+ return;
+ }
+
+ ControllerState *state = xemu_input_get_bound(s->device_index);
+ assert(state);
+ xemu_input_update_controller(state);
+
+ s->in_state.dwButtons = (uint32_t)(state->sbc.buttons & 0xFFFFFFFF);
+ s->in_state.bMoreButtons = (uint8_t)((state->sbc.buttons >> 32) & 0x7F);
+ s->in_state.bMoreButtons |= state->sbc.toggleSwitches;
+
+ s->in_state.sSightChangeX = state->sbc.axis[SBC_AXIS_SIGHT_CHANGE_X];
+ s->in_state.sSightChangeY = state->sbc.axis[SBC_AXIS_SIGHT_CHANGE_Y];
+ s->in_state.bAimingX =
+ (uint8_t)(128 + (state->sbc.axis[SBC_AXIS_AIMING_X] /
+ 256)); // Convert from int16_t to uint8_t
+ s->in_state.bAimingY =
+ (uint8_t)(128 + (state->sbc.axis[SBC_AXIS_AIMING_Y] /
+ 256)); // Convert from int16_t to uint8_t
+ s->in_state.sRotationLever = state->sbc.axis[SBC_AXIS_ROTATION_LEVER];
+ s->in_state.wLeftPedal = (uint16_t)state->sbc.axis[SBC_AXIS_LEFT_PEDAL];
+ s->in_state.wMiddlePedal = (uint16_t)state->sbc.axis[SBC_AXIS_MIDDLE_PEDAL];
+ s->in_state.wRightPedal = (uint16_t)state->sbc.axis[SBC_AXIS_RIGHT_PEDAL];
+
+ s->in_state.ucGearLever = state->sbc.gearLever;
+ s->in_state.ucTunerDial = state->sbc.tunerDial;
+}
+
+static void usb_xid_steel_battalion_handle_control(USBDevice *dev, USBPacket *p,
+ int request, int value,
+ int index, int length,
+ uint8_t *data)
+{
+ USBXIDSteelBattalionState *s =
+ DO_UPCAST(USBXIDSteelBattalionState, dev, dev);
+
+ DPRINTF("xid handle_control 0x%x 0x%x\n", request, value);
+
+ int ret =
+ usb_desc_handle_control(dev, p, request, value, index, length, data);
+ if (ret >= 0) {
+ DPRINTF("xid handled by usb_desc_handle_control: %d\n", ret);
+ return;
+ }
+
+ switch (request) {
+ /* HID requests */
+ case ClassInterfaceRequest | HID_GET_REPORT:
+ DPRINTF("xid GET_REPORT 0x%x\n", value);
+ update_sbc_input(s);
+ if (value == 0x0100) { /* input */
+ if (length <= s->in_state.bLength) {
+ memcpy(data, &s->in_state, s->in_state.bLength);
+ p->actual_length = length;
+ } else {
+ p->status = USB_RET_STALL;
+ }
+ } else {
+ p->status = USB_RET_STALL;
+ assert(false);
+ }
+ break;
+ case ClassInterfaceOutRequest | HID_SET_REPORT:
+ DPRINTF("xid SET_REPORT 0x%x\n", value);
+ if (value == 0x0200) { /* output */
+ /* Read length, then the entire packet */
+ if (length == s->out_state.length) {
+ memcpy(&s->out_state, data, sizeof(s->out_state));
+
+ /* FIXME: This should also be a STALL */
+ assert(s->out_state.length == sizeof(s->out_state));
+
+ p->actual_length = length;
+ } else {
+ p->status = USB_RET_STALL;
+ }
+ // update_output(s);
+ } else {
+ p->status = USB_RET_STALL;
+ assert(false);
+ }
+ break;
+ /* XID requests */
+ case VendorInterfaceRequest | USB_REQ_GET_DESCRIPTOR:
+ DPRINTF("xid GET_DESCRIPTOR 0x%x\n", value);
+ if (value == 0x4200) {
+ assert(s->xid_desc->bLength <= length);
+ memcpy(data, s->xid_desc, s->xid_desc->bLength);
+ p->actual_length = s->xid_desc->bLength;
+ } else {
+ p->status = USB_RET_STALL;
+ assert(false);
+ }
+ break;
+ case VendorInterfaceRequest | XID_GET_CAPABILITIES:
+ DPRINTF("xid XID_GET_CAPABILITIES 0x%x\n", value);
+ if (value == 0x0100) {
+ if (length > s->in_state_capabilities.bLength) {
+ length = s->in_state_capabilities.bLength;
+ }
+ memcpy(data, &s->in_state_capabilities, length);
+ p->actual_length = length;
+ } else if (value == 0x0200) {
+ if (length > s->out_state_capabilities.length) {
+ length = s->out_state_capabilities.length;
+ }
+ memcpy(data, &s->out_state_capabilities, length);
+ p->actual_length = length;
+ } else {
+ p->status = USB_RET_STALL;
+ assert(false);
+ }
+ break;
+ case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE) << 8) |
+ USB_REQ_GET_DESCRIPTOR:
+ /* FIXME: ! */
+ DPRINTF("xid unknown xpad request 0x%x: value = 0x%x\n", request,
+ value);
+ memset(data, 0x00, length);
+ // FIXME: Intended for the hub: usbd_get_hub_descriptor, UT_READ_CLASS?!
+ p->status = USB_RET_STALL;
+ // assert(false);
+ break;
+ case ((USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_ENDPOINT) << 8) |
+ USB_REQ_CLEAR_FEATURE:
+ /* FIXME: ! */
+ DPRINTF("xid unknown xpad request 0x%x: value = 0x%x\n", request,
+ value);
+ memset(data, 0x00, length);
+ p->status = USB_RET_STALL;
+ break;
+ default:
+ DPRINTF("xid USB stalled on request 0x%x value 0x%x\n", request, value);
+ p->status = USB_RET_STALL;
+ assert(false);
+ break;
+ }
+}
+
+
+static void usb_xid_steel_battalion_handle_data(USBDevice *dev, USBPacket *p)
+{
+ USBXIDSteelBattalionState *s =
+ DO_UPCAST(USBXIDSteelBattalionState, dev, dev);
+
+ DPRINTF("xid handle_steel_battalion_data 0x%x %d 0x%zx\n", p->pid,
+ p->ep->nr, p->iov.size);
+
+ switch (p->pid) {
+ case USB_TOKEN_IN:
+ if (p->ep->nr == STEEL_BATTALION_IN_ENDPOINT_ID) {
+ update_sbc_input(s);
+ usb_packet_copy(p, &s->in_state, s->in_state.bLength);
+ } else {
+ assert(false);
+ }
+ break;
+ case USB_TOKEN_OUT:
+ if (p->ep->nr == STEEL_BATTALION_OUT_ENDPOINT_ID) {
+ usb_packet_copy(p, &s->out_state, s->out_state.length);
+ // TODO: Update output for Steel Battalion Controller here, if we
+ // want to. It's LED data, so, maybe use it for RGB integration with
+ // RGB Keyboards?
+ } else {
+ assert(false);
+ }
+ break;
+ default:
+ p->status = USB_RET_STALL;
+ assert(false);
+ break;
+ }
+}
+
+static void usb_xid_steel_battalion_class_initfn(ObjectClass *klass, void *data)
+{
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->handle_reset = usb_xid_handle_reset;
+ uc->handle_control = usb_xid_steel_battalion_handle_control;
+ uc->handle_data = usb_xid_steel_battalion_handle_data;
+ // uc->handle_destroy = usb_xid_handle_destroy;
+ uc->handle_attach = usb_desc_attach;
+}
+
+static void usb_steel_battalion_realize(USBDevice *dev, Error **errp)
+{
+ USBXIDSteelBattalionState *s = USB_XID_SB(dev);
+ usb_desc_create_serial(dev);
+ usb_desc_init(dev);
+ s->intr = usb_ep_get(dev, USB_TOKEN_IN, 2);
+
+ s->in_state.bLength = sizeof(s->in_state);
+ s->in_state.bReportId = 0;
+
+ s->out_state.length = sizeof(s->out_state);
+ s->out_state.report_id = 0;
+
+ s->xid_desc = &desc_xid_steel_battalion;
+
+ memset(&s->in_state_capabilities, 0xFF, sizeof(s->in_state_capabilities));
+ s->in_state_capabilities.bLength = sizeof(s->in_state_capabilities);
+ s->in_state_capabilities.bReportId = 0;
+
+ memset(&s->out_state_capabilities, 0xFF, sizeof(s->out_state_capabilities));
+ s->out_state_capabilities.length = sizeof(s->out_state_capabilities);
+ s->out_state_capabilities.report_id = 0;
+}
+
+static Property xid_properties[] = {
+ DEFINE_PROP_UINT8("index", USBXIDSteelBattalionState, device_index, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_usb_sb = {
+ .name = TYPE_USB_XID_STEEL_BATTALION,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields =
+ (VMStateField[]){ VMSTATE_USB_DEVICE(dev, USBXIDSteelBattalionState),
+ // FIXME
+ VMSTATE_END_OF_LIST() },
+};
+
+static void usb_steel_battalion_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->product_desc = "Steel Battalion Controller";
+ uc->usb_desc = &desc_xbox_steel_battalion;
+ uc->realize = usb_steel_battalion_realize;
+ uc->unrealize = usb_xbox_gamepad_unrealize;
+ usb_xid_steel_battalion_class_initfn(klass, data);
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+ dc->vmsd = &vmstate_usb_sb;
+ device_class_set_props(dc, xid_properties);
+ dc->desc = "Steel Battalion Controller";
+}
+
+static const TypeInfo usb_steel_battalion_info = {
+ .name = TYPE_USB_XID_STEEL_BATTALION,
+ .parent = TYPE_USB_DEVICE,
+ .instance_size = sizeof(USBXIDSteelBattalionState),
+ .class_init = usb_steel_battalion_class_initfn,
+};
+
+static void usb_xid_register_types(void)
+{
+ type_register_static(&usb_steel_battalion_info);
+}
+
+type_init(usb_xid_register_types)
\ No newline at end of file
diff --git a/hw/xbox/xid.c b/hw/xbox/xid.c
index c37142bc0d..6580f29236 100644
--- a/hw/xbox/xid.c
+++ b/hw/xbox/xid.c
@@ -47,8 +47,8 @@ void update_output(USBXIDGamepadState *s)
ControllerState *state = xemu_input_get_bound(s->device_index);
assert(state);
- state->rumble_l = s->out_state.left_actuator_strength;
- state->rumble_r = s->out_state.right_actuator_strength;
+ state->gp.rumble_l = s->out_state.left_actuator_strength;
+ state->gp.rumble_r = s->out_state.right_actuator_strength;
xemu_input_update_rumble(state);
}
@@ -84,23 +84,23 @@ void update_input(USBXIDGamepadState *s)
};
for (int i = 0; i < 6; i++) {
- int pressed = state->buttons & button_map_analog[i][1];
+ int pressed = state->gp.buttons & button_map_analog[i][1];
s->in_state.bAnalogButtons[button_map_analog[i][0]] = pressed ? 0xff : 0;
}
s->in_state.wButtons = 0;
for (int i = 0; i < 8; i++) {
- if (state->buttons & button_map_binary[i][1]) {
+ if (state->gp.buttons & button_map_binary[i][1]) {
s->in_state.wButtons |= BUTTON_MASK(button_map_binary[i][0]);
}
}
- s->in_state.bAnalogButtons[GAMEPAD_LEFT_TRIGGER] = state->axis[CONTROLLER_AXIS_LTRIG] >> 7;
- s->in_state.bAnalogButtons[GAMEPAD_RIGHT_TRIGGER] = state->axis[CONTROLLER_AXIS_RTRIG] >> 7;
- s->in_state.sThumbLX = state->axis[CONTROLLER_AXIS_LSTICK_X];
- s->in_state.sThumbLY = state->axis[CONTROLLER_AXIS_LSTICK_Y];
- s->in_state.sThumbRX = state->axis[CONTROLLER_AXIS_RSTICK_X];
- s->in_state.sThumbRY = state->axis[CONTROLLER_AXIS_RSTICK_Y];
+ s->in_state.bAnalogButtons[GAMEPAD_LEFT_TRIGGER] = state->gp.axis[CONTROLLER_AXIS_LTRIG] >> 7;
+ s->in_state.bAnalogButtons[GAMEPAD_RIGHT_TRIGGER] = state->gp.axis[CONTROLLER_AXIS_RTRIG] >> 7;
+ s->in_state.sThumbLX = state->gp.axis[CONTROLLER_AXIS_LSTICK_X];
+ s->in_state.sThumbLY = state->gp.axis[CONTROLLER_AXIS_LSTICK_Y];
+ s->in_state.sThumbRX = state->gp.axis[CONTROLLER_AXIS_RSTICK_X];
+ s->in_state.sThumbRY = state->gp.axis[CONTROLLER_AXIS_RSTICK_Y];
}
void usb_xid_handle_reset(USBDevice *dev)
diff --git a/hw/xbox/xid.h b/hw/xbox/xid.h
index 44406eaf91..5acbed0d82 100644
--- a/hw/xbox/xid.h
+++ b/hw/xbox/xid.h
@@ -48,12 +48,14 @@
#define XID_GET_CAPABILITIES 0x01
#define XID_DEVICETYPE_GAMEPAD 0x01
+#define XID_DEVICETYPE_STEEL_BATTALION 0x80
#define XID_DEVICESUBTYPE_GAMEPAD 0x01
#define XID_DEVICESUBTYPE_GAMEPAD_S 0x02
#define TYPE_USB_XID_GAMEPAD "usb-xbox-gamepad"
#define TYPE_USB_XID_GAMEPAD_S "usb-xbox-gamepad-s"
+#define TYPE_USB_XID_STEEL_BATTALION "usb-steel-battalion"
#define GAMEPAD_A 0
#define GAMEPAD_B 1
diff --git a/ui/xemu-input.c b/ui/xemu-input.c
index 31a51eda9d..a76eacc92f 100644
--- a/ui/xemu-input.c
+++ b/ui/xemu-input.c
@@ -34,6 +34,9 @@
#include "sysemu/blockdev.h"
+extern SDL_Window *m_window;
+extern int viewport_coords[4];
+
// #define DEBUG_INPUT
#ifdef DEBUG_INPUT
@@ -58,33 +61,34 @@ static void xemu_input_print_controller_state(ControllerState *state)
"LTrig = %.3f, RTrig = %.3f\n"
"LStickX = %.3f, RStickX = %.3f\n"
"LStickY = %.3f, RStickY = %.3f\n\n",
- !!(state->buttons & CONTROLLER_BUTTON_A),
- !!(state->buttons & CONTROLLER_BUTTON_B),
- !!(state->buttons & CONTROLLER_BUTTON_X),
- !!(state->buttons & CONTROLLER_BUTTON_Y),
- !!(state->buttons & CONTROLLER_BUTTON_DPAD_LEFT),
- !!(state->buttons & CONTROLLER_BUTTON_DPAD_UP),
- !!(state->buttons & CONTROLLER_BUTTON_DPAD_RIGHT),
- !!(state->buttons & CONTROLLER_BUTTON_DPAD_DOWN),
- !!(state->buttons & CONTROLLER_BUTTON_BACK),
- !!(state->buttons & CONTROLLER_BUTTON_START),
- !!(state->buttons & CONTROLLER_BUTTON_WHITE),
- !!(state->buttons & CONTROLLER_BUTTON_BLACK),
- !!(state->buttons & CONTROLLER_BUTTON_LSTICK),
- !!(state->buttons & CONTROLLER_BUTTON_RSTICK),
- !!(state->buttons & CONTROLLER_BUTTON_GUIDE),
- state->axis[CONTROLLER_AXIS_LTRIG],
- state->axis[CONTROLLER_AXIS_RTRIG],
- state->axis[CONTROLLER_AXIS_LSTICK_X],
- state->axis[CONTROLLER_AXIS_RSTICK_X],
- state->axis[CONTROLLER_AXIS_LSTICK_Y],
- state->axis[CONTROLLER_AXIS_RSTICK_Y]
+ !!(state->gp.buttons & CONTROLLER_BUTTON_A),
+ !!(state->gp.buttons & CONTROLLER_BUTTON_B),
+ !!(state->gp.buttons & CONTROLLER_BUTTON_X),
+ !!(state->gp.buttons & CONTROLLER_BUTTON_Y),
+ !!(state->gp.buttons & CONTROLLER_BUTTON_DPAD_LEFT),
+ !!(state->gp.buttons & CONTROLLER_BUTTON_DPAD_UP),
+ !!(state->gp.buttons & CONTROLLER_BUTTON_DPAD_RIGHT),
+ !!(state->gp.buttons & CONTROLLER_BUTTON_DPAD_DOWN),
+ !!(state->gp.buttons & CONTROLLER_BUTTON_BACK),
+ !!(state->gp.buttons & CONTROLLER_BUTTON_START),
+ !!(state->gp.buttons & CONTROLLER_BUTTON_WHITE),
+ !!(state->gp.buttons & CONTROLLER_BUTTON_BLACK),
+ !!(state->gp.buttons & CONTROLLER_BUTTON_LSTICK),
+ !!(state->gp.buttons & CONTROLLER_BUTTON_RSTICK),
+ !!(state->gp.buttons & CONTROLLER_BUTTON_GUIDE),
+ state->gp.axis[CONTROLLER_AXIS_LTRIG],
+ state->gp.axis[CONTROLLER_AXIS_RTRIG],
+ state->gp.axis[CONTROLLER_AXIS_LSTICK_X],
+ state->gp.axis[CONTROLLER_AXIS_RSTICK_X],
+ state->gp.axis[CONTROLLER_AXIS_LSTICK_Y],
+ state->gp.axis[CONTROLLER_AXIS_RSTICK_Y]
);
}
#endif
ControllerStateList available_controllers =
QTAILQ_HEAD_INITIALIZER(available_controllers);
+
ControllerState *bound_controllers[4] = { NULL, NULL, NULL, NULL };
const char *bound_drivers[4] = { DRIVER_DUKE, DRIVER_DUKE, DRIVER_DUKE,
DRIVER_DUKE };
@@ -127,6 +131,7 @@ static const char **peripheral_params_settings_map[4][2] = {
};
static int sdl_kbd_scancode_map[25];
+static int sdl_sbc_kbd_scancode_map[52];
static const char *get_bound_driver(int port)
{
@@ -136,13 +141,15 @@ static const char *get_bound_driver(int port)
// If the driver in the config is NULL, empty, or unrecognized
// then default to DRIVER_DUKE
if (driver == NULL)
- return DRIVER_DUKE;
+ return DRIVER_DUKE;
if (strlen(driver) == 0)
return DRIVER_DUKE;
if (strcmp(driver, DRIVER_DUKE) == 0)
return DRIVER_DUKE;
if (strcmp(driver, DRIVER_S) == 0)
return DRIVER_S;
+ if (strcmp(driver, DRIVER_STEEL_BATTALION) == 0)
+ return DRIVER_STEEL_BATTALION;
return DRIVER_DUKE;
}
@@ -204,6 +211,115 @@ void xemu_input_init(void)
sdl_kbd_scancode_map[i] = SDL_SCANCODE_UNKNOWN;
}
}
+
+ sdl_sbc_kbd_scancode_map[3] =
+ g_config.input.keyboard_sbc_scancode_map.eject;
+ sdl_sbc_kbd_scancode_map[4] =
+ g_config.input.keyboard_sbc_scancode_map.cockpit_hatch;
+ sdl_sbc_kbd_scancode_map[5] =
+ g_config.input.keyboard_sbc_scancode_map.ignition;
+ sdl_sbc_kbd_scancode_map[6] =
+ g_config.input.keyboard_sbc_scancode_map.start;
+ sdl_sbc_kbd_scancode_map[7] =
+ g_config.input.keyboard_sbc_scancode_map.open_close;
+ sdl_sbc_kbd_scancode_map[8] =
+ g_config.input.keyboard_sbc_scancode_map.map_zoom_in_out;
+ sdl_sbc_kbd_scancode_map[9] =
+ g_config.input.keyboard_sbc_scancode_map.mode_select;
+ sdl_sbc_kbd_scancode_map[10] =
+ g_config.input.keyboard_sbc_scancode_map.sub_monitor_mode_select;
+ sdl_sbc_kbd_scancode_map[11] =
+ g_config.input.keyboard_sbc_scancode_map.zoom_in;
+ sdl_sbc_kbd_scancode_map[12] =
+ g_config.input.keyboard_sbc_scancode_map.zoom_out;
+ sdl_sbc_kbd_scancode_map[13] = g_config.input.keyboard_sbc_scancode_map.fss;
+ sdl_sbc_kbd_scancode_map[14] =
+ g_config.input.keyboard_sbc_scancode_map.manipulator;
+ sdl_sbc_kbd_scancode_map[15] =
+ g_config.input.keyboard_sbc_scancode_map.line_color_change;
+ sdl_sbc_kbd_scancode_map[16] =
+ g_config.input.keyboard_sbc_scancode_map.washing;
+ sdl_sbc_kbd_scancode_map[17] =
+ g_config.input.keyboard_sbc_scancode_map.extinguisher;
+ sdl_sbc_kbd_scancode_map[18] =
+ g_config.input.keyboard_sbc_scancode_map.chaff;
+ sdl_sbc_kbd_scancode_map[19] =
+ g_config.input.keyboard_sbc_scancode_map.tank_detach;
+ sdl_sbc_kbd_scancode_map[20] =
+ g_config.input.keyboard_sbc_scancode_map.override;
+ sdl_sbc_kbd_scancode_map[21] =
+ g_config.input.keyboard_sbc_scancode_map.night_scope;
+ sdl_sbc_kbd_scancode_map[22] =
+ g_config.input.keyboard_sbc_scancode_map.func1;
+ sdl_sbc_kbd_scancode_map[23] =
+ g_config.input.keyboard_sbc_scancode_map.func2;
+ sdl_sbc_kbd_scancode_map[24] =
+ g_config.input.keyboard_sbc_scancode_map.func3;
+ sdl_sbc_kbd_scancode_map[25] =
+ g_config.input.keyboard_sbc_scancode_map.main_weapon_control;
+ sdl_sbc_kbd_scancode_map[26] =
+ g_config.input.keyboard_sbc_scancode_map.sub_weapon_control;
+ sdl_sbc_kbd_scancode_map[27] =
+ g_config.input.keyboard_sbc_scancode_map.magazine_change;
+ sdl_sbc_kbd_scancode_map[28] =
+ g_config.input.keyboard_sbc_scancode_map.com1;
+ sdl_sbc_kbd_scancode_map[29] =
+ g_config.input.keyboard_sbc_scancode_map.com2;
+ sdl_sbc_kbd_scancode_map[30] =
+ g_config.input.keyboard_sbc_scancode_map.com3;
+ sdl_sbc_kbd_scancode_map[31] =
+ g_config.input.keyboard_sbc_scancode_map.com4;
+ sdl_sbc_kbd_scancode_map[32] =
+ g_config.input.keyboard_sbc_scancode_map.com5;
+ sdl_sbc_kbd_scancode_map[33] =
+ g_config.input.keyboard_sbc_scancode_map.sight_change;
+ sdl_sbc_kbd_scancode_map[34] =
+ g_config.input.keyboard_sbc_scancode_map.filt_control_system;
+ sdl_sbc_kbd_scancode_map[35] =
+ g_config.input.keyboard_sbc_scancode_map.oxygen_supply_system;
+ sdl_sbc_kbd_scancode_map[36] =
+ g_config.input.keyboard_sbc_scancode_map.fuel_flow_rate;
+ sdl_sbc_kbd_scancode_map[37] =
+ g_config.input.keyboard_sbc_scancode_map.buffer_material;
+ sdl_sbc_kbd_scancode_map[38] =
+ g_config.input.keyboard_sbc_scancode_map.vt_location_measurement;
+ sdl_sbc_kbd_scancode_map[39] =
+ g_config.input.keyboard_sbc_scancode_map.gear_up;
+ sdl_sbc_kbd_scancode_map[40] =
+ g_config.input.keyboard_sbc_scancode_map.gear_down;
+ sdl_sbc_kbd_scancode_map[41] =
+ g_config.input.keyboard_sbc_scancode_map.tuner_left;
+ sdl_sbc_kbd_scancode_map[42] =
+ g_config.input.keyboard_sbc_scancode_map.tuner_right;
+ sdl_sbc_kbd_scancode_map[43] =
+ g_config.input.keyboard_sbc_scancode_map.sight_change_up;
+ sdl_sbc_kbd_scancode_map[44] =
+ g_config.input.keyboard_sbc_scancode_map.sight_change_down;
+ sdl_sbc_kbd_scancode_map[45] =
+ g_config.input.keyboard_sbc_scancode_map.sight_change_left;
+ sdl_sbc_kbd_scancode_map[46] =
+ g_config.input.keyboard_sbc_scancode_map.sight_change_right;
+ sdl_sbc_kbd_scancode_map[47] =
+ g_config.input.keyboard_sbc_scancode_map.rotation_left;
+ sdl_sbc_kbd_scancode_map[48] =
+ g_config.input.keyboard_sbc_scancode_map.rotation_right;
+ sdl_sbc_kbd_scancode_map[49] =
+ g_config.input.keyboard_sbc_scancode_map.left_pedal;
+ sdl_sbc_kbd_scancode_map[50] =
+ g_config.input.keyboard_sbc_scancode_map.right_pedal;
+ sdl_sbc_kbd_scancode_map[51] =
+ g_config.input.keyboard_sbc_scancode_map.middle_pedal;
+
+ for (int i = 0; i < 49; i++) {
+ if ((sdl_sbc_kbd_scancode_map[i] < SDL_SCANCODE_UNKNOWN) ||
+ (sdl_sbc_kbd_scancode_map[i] >= SDL_NUM_SCANCODES)) {
+ fprintf(stderr,
+ "WARNING: Keyboard steel battalion controller map scancode "
+ "out of range (%d) : Disabled\n",
+ sdl_sbc_kbd_scancode_map[i]);
+ sdl_sbc_kbd_scancode_map[i] = SDL_SCANCODE_UNKNOWN;
+ }
+ }
bound_drivers[0] = get_bound_driver(0);
bound_drivers[1] = get_bound_driver(1);
@@ -277,7 +393,7 @@ void xemu_input_process_sdl_events(const SDL_Event *event)
memset(new_con, 0, sizeof(ControllerState));
new_con->type = INPUT_DEVICE_SDL_GAMECONTROLLER;
new_con->name = SDL_GameControllerName(sdl_con);
- new_con->rumble_enabled = true;
+ new_con->gp.rumble_enabled = true;
new_con->sdl_gamecontroller = sdl_con;
new_con->sdl_joystick = SDL_GameControllerGetJoystick(new_con->sdl_gamecontroller);
new_con->sdl_joystick_id = SDL_JoystickInstanceID(new_con->sdl_joystick);
@@ -422,75 +538,339 @@ void xemu_input_update_controllers(void)
void xemu_input_update_sdl_kbd_controller_state(ControllerState *state)
{
- state->buttons = 0;
- memset(state->axis, 0, sizeof(state->axis));
+ state->gp.buttons = 0;
+ state->sbc.buttons = 0;
+ memset(state->gp.axis, 0, sizeof(state->gp.axis));
+ memset(state->sbc.axis, 0, sizeof(state->sbc.axis));
const uint8_t *kbd = SDL_GetKeyboardState(NULL);
- for (int i = 0; i < 15; i++) {
- state->buttons |= kbd[sdl_kbd_scancode_map[i]] << i;
+ if (state->bound < 0)
+ return;
+
+ const char *bound_driver = get_bound_driver(state->bound);
+ if (strcmp(bound_driver, DRIVER_STEEL_BATTALION) == 0) {
+
+ if (state->sbc.gearLever == 0)
+ state->sbc.gearLever = 255;
+
+ // Update SBC Buttons
+ for (int i = 3; i < 43; i++) {
+ if (kbd[sdl_sbc_kbd_scancode_map[i]])
+ state->sbc.buttons |= (1ULL << i);
+ }
+
+ const uint64_t toggles[5] = { SBC_BUTTON_FILT_CONTROL_SYSTEM,
+ SBC_BUTTON_OXYGEN_SUPPLY_SYSTEM,
+ SBC_BUTTON_FUEL_FLOW_RATE,
+ SBC_BUTTON_BUFFER_MATERIAL,
+ SBC_BUTTON_VT_LOCATION_MEASUREMENT };
+
+ for (int i = 0; i < 5; i++) {
+ if ((state->sbc.buttons & toggles[i]) &&
+ !(state->sbc.previousButtons &
+ toggles[i])) { // When the for the toggle is pressed
+ uint8_t byteMask = (uint8_t)(toggles[i] >> 32);
+ // Toggle the toggle switch
+ state->sbc.toggleSwitches ^= byteMask;
+ }
+ }
+
+ // Tuner Dial Left
+ if ((state->sbc.buttons & SBC_BUTTON_TUNER_LEFT) &&
+ !(state->sbc.previousButtons & SBC_BUTTON_TUNER_LEFT)) {
+ if (state->sbc.tunerDial == 0)
+ state->sbc.tunerDial = 15;
+ else
+ state->sbc.tunerDial--;
+ }
+
+ // Tuner Dial Right
+ if ((state->sbc.buttons & SBC_BUTTON_TUNER_RIGHT) &&
+ !(state->sbc.previousButtons & SBC_BUTTON_TUNER_RIGHT)) {
+ if (state->sbc.tunerDial == 15)
+ state->sbc.tunerDial = 0;
+ else
+ state->sbc.tunerDial++;
+ }
+
+ // Gear Lever Up
+ if ((state->sbc.buttons & SBC_BUTTON_GEAR_UP) &&
+ !(state->sbc.previousButtons & SBC_BUTTON_GEAR_UP)) {
+ if (state->sbc.gearLever != 5) {
+ if (state->sbc.gearLever == 255)
+ state->sbc.gearLever = 1;
+ else
+ state->sbc.gearLever++;
+ }
+ }
+
+ // Gear Lever Down
+ if ((state->sbc.buttons & SBC_BUTTON_GEAR_DOWN) &&
+ !(state->sbc.previousButtons & SBC_BUTTON_GEAR_DOWN)) {
+ if (state->sbc.gearLever != 254) {
+ if (state->sbc.gearLever == 1)
+ state->sbc.gearLever = 255;
+ else
+ state->sbc.gearLever--;
+ }
+ }
+
+ // Update SBC Axes
+ int mouseX, mouseY;
+ uint32_t mouseBtn = SDL_GetMouseState(&mouseX, &mouseY);
+
+ if (mouseBtn & SDL_BUTTON(SDL_BUTTON_LEFT)) {
+ state->sbc.buttons |= SBC_BUTTON_MAIN_WEAPON;
+ }
+ if (mouseBtn & SDL_BUTTON(SDL_BUTTON_RIGHT)) {
+ state->sbc.buttons |= SBC_BUTTON_LOCK_ON;
+ }
+ if(mouseBtn & SDL_BUTTON(SDL_BUTTON_X1) ||
+ mouseBtn & SDL_BUTTON(SDL_BUTTON_X2)) {
+ state->sbc.buttons |= SBC_BUTTON_SUB_WEAPON;
+ }
+
+ int32_t windowWidth, windowHeight;
+ SDL_GL_GetDrawableSize(
+ m_window, &windowWidth,
+ &windowHeight); // get the mouse location relative to the Viewport,
+ // not the Window
+
+ // Calculate the position of the mouse coordinates in [-32768,32768]
+ DPRINTF("[Steel Battalion] Window Coordinates: %d, %d\n", mouseX, mouseY);
+
+ // Check that the mouse position is within the window coordinates
+ if (mouseX >= 0 && mouseX <= windowWidth && mouseY >= 0 &&
+ mouseY <= windowHeight) {
+ if (viewport_coords[2] > 0 && viewport_coords[3] > 0) {
+ // Switch from Window coordinates to Viewport Coordinates
+ mouseX -= viewport_coords[0];
+ mouseY -= viewport_coords[1];
+ windowWidth = viewport_coords[2];
+ windowHeight = viewport_coords[3];
+ }
+
+ DPRINTF("[Steel Battalion] Viewport Coordinates: %d, %d\n", mouseX, mouseY);
+ int32_t x = (int32_t)((mouseX - (windowWidth / 2)) *
+ 65535 / windowWidth);
+ int32_t y = (int32_t)((mouseY - (windowHeight / 2)) *
+ 65535 / windowHeight);
+
+ state->sbc.axis[SBC_AXIS_AIMING_X] =
+ (int16_t)MIN(MAX(x, -32768), 32767);
+ state->sbc.axis[SBC_AXIS_AIMING_Y] =
+ (int16_t)MIN(MAX(y, -32768), 32767);
+
+ DPRINTF("[Steel Battalion] X: %d, Y: %d",
+ state->sbc.axis[SBC_AXIS_AIMING_X],
+ state->sbc.axis[SBC_AXIS_AIMING_Y]);
+ }
+
+ if (kbd[sdl_sbc_kbd_scancode_map[43]])
+ state->sbc.axis[SBC_AXIS_SIGHT_CHANGE_Y] = -32768;
+ if (kbd[sdl_sbc_kbd_scancode_map[44]])
+ state->sbc.axis[SBC_AXIS_SIGHT_CHANGE_Y] = 32767;
+ if (kbd[sdl_sbc_kbd_scancode_map[45]])
+ state->sbc.axis[SBC_AXIS_SIGHT_CHANGE_X] = -32768;
+ if (kbd[sdl_sbc_kbd_scancode_map[46]])
+ state->sbc.axis[SBC_AXIS_SIGHT_CHANGE_X] = 32767;
+
+ if (kbd[sdl_sbc_kbd_scancode_map[47]])
+ state->sbc.axis[SBC_AXIS_ROTATION_LEVER] = -32768;
+ if (kbd[sdl_sbc_kbd_scancode_map[48]])
+ state->sbc.axis[SBC_AXIS_ROTATION_LEVER] = 32767;
+
+ if (kbd[sdl_sbc_kbd_scancode_map[49]])
+ state->sbc.axis[SBC_AXIS_LEFT_PEDAL] = 32767;
+ if (kbd[sdl_sbc_kbd_scancode_map[50]])
+ state->sbc.axis[SBC_AXIS_RIGHT_PEDAL] = 32767;
+ if (kbd[sdl_sbc_kbd_scancode_map[51]])
+ state->sbc.axis[SBC_AXIS_MIDDLE_PEDAL] = 32767;
+
+ state->sbc.previousButtons = state->sbc.buttons;
+ } else {
+ // Update Gamepad Buttons
+ for (int i = 0; i < 15; i++) {
+ state->gp.buttons |= kbd[sdl_kbd_scancode_map[i]] << i;
+ }
+
+ // Update Gamepad Axes
+ if (kbd[sdl_kbd_scancode_map[15]])
+ state->gp.axis[CONTROLLER_AXIS_LSTICK_Y] = 32767;
+ if (kbd[sdl_kbd_scancode_map[16]])
+ state->gp.axis[CONTROLLER_AXIS_LSTICK_X] = -32768;
+ if (kbd[sdl_kbd_scancode_map[17]])
+ state->gp.axis[CONTROLLER_AXIS_LSTICK_X] = 32767;
+ if (kbd[sdl_kbd_scancode_map[18]])
+ state->gp.axis[CONTROLLER_AXIS_LSTICK_Y] = -32768;
+ if (kbd[sdl_kbd_scancode_map[19]])
+ state->gp.axis[CONTROLLER_AXIS_LTRIG] = 32767;
+ if (kbd[sdl_kbd_scancode_map[20]])
+ state->gp.axis[CONTROLLER_AXIS_RSTICK_Y] = 32767;
+ if (kbd[sdl_kbd_scancode_map[21]])
+ state->gp.axis[CONTROLLER_AXIS_RSTICK_X] = -32768;
+ if (kbd[sdl_kbd_scancode_map[22]])
+ state->gp.axis[CONTROLLER_AXIS_RSTICK_X] = 32767;
+ if (kbd[sdl_kbd_scancode_map[23]])
+ state->gp.axis[CONTROLLER_AXIS_RSTICK_Y] = -32768;
+ if (kbd[sdl_kbd_scancode_map[24]])
+ state->gp.axis[CONTROLLER_AXIS_RTRIG] = 32767;
}
-
- if (kbd[sdl_kbd_scancode_map[15]]) state->axis[CONTROLLER_AXIS_LSTICK_Y] = 32767;
- if (kbd[sdl_kbd_scancode_map[16]]) state->axis[CONTROLLER_AXIS_LSTICK_X] = -32768;
- if (kbd[sdl_kbd_scancode_map[17]]) state->axis[CONTROLLER_AXIS_LSTICK_X] = 32767;
- if (kbd[sdl_kbd_scancode_map[18]]) state->axis[CONTROLLER_AXIS_LSTICK_Y] = -32768;
- if (kbd[sdl_kbd_scancode_map[19]]) state->axis[CONTROLLER_AXIS_LTRIG] = 32767;
-
- if (kbd[sdl_kbd_scancode_map[20]]) state->axis[CONTROLLER_AXIS_RSTICK_Y] = 32767;
- if (kbd[sdl_kbd_scancode_map[21]]) state->axis[CONTROLLER_AXIS_RSTICK_X] = -32768;
- if (kbd[sdl_kbd_scancode_map[22]]) state->axis[CONTROLLER_AXIS_RSTICK_X] = 32767;
- if (kbd[sdl_kbd_scancode_map[23]]) state->axis[CONTROLLER_AXIS_RSTICK_Y] = -32768;
- if (kbd[sdl_kbd_scancode_map[24]]) state->axis[CONTROLLER_AXIS_RTRIG] = 32767;
}
void xemu_input_update_sdl_controller_state(ControllerState *state)
{
- state->buttons = 0;
- memset(state->axis, 0, sizeof(state->axis));
+ state->gp.buttons = 0;
+ state->sbc.buttons = 0;
+ memset(state->gp.axis, 0, sizeof(state->gp.axis));
+ memset(state->sbc.axis, 0, sizeof(state->sbc.axis));
- const SDL_GameControllerButton sdl_button_map[15] = {
- SDL_CONTROLLER_BUTTON_A,
- SDL_CONTROLLER_BUTTON_B,
- SDL_CONTROLLER_BUTTON_X,
- SDL_CONTROLLER_BUTTON_Y,
- SDL_CONTROLLER_BUTTON_DPAD_LEFT,
- SDL_CONTROLLER_BUTTON_DPAD_UP,
- SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
- SDL_CONTROLLER_BUTTON_DPAD_DOWN,
- SDL_CONTROLLER_BUTTON_BACK,
- SDL_CONTROLLER_BUTTON_START,
- SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
- SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
- SDL_CONTROLLER_BUTTON_LEFTSTICK,
- SDL_CONTROLLER_BUTTON_RIGHTSTICK,
- SDL_CONTROLLER_BUTTON_GUIDE
- };
+ if (state->bound < 0)
+ return;
- for (int i = 0; i < 15; i++) {
- state->buttons |= SDL_GameControllerGetButton(state->sdl_gamecontroller, sdl_button_map[i]) << i;
+ const char *bound_driver = get_bound_driver(state->bound);
+ if (strcmp(bound_driver, DRIVER_STEEL_BATTALION) == 0) {
+ state->sbc.buttons = 0;
+
+ const uint64_t sdl_button_map_sbc[8][2] = {
+ { SDL_CONTROLLER_BUTTON_A, SBC_BUTTON_MAIN_WEAPON },
+ { SDL_CONTROLLER_BUTTON_B, SBC_BUTTON_LOCK_ON },
+ { SDL_CONTROLLER_BUTTON_LEFTSHOULDER, SBC_BUTTON_FUNC1 },
+ { SDL_CONTROLLER_BUTTON_LEFTSTICK, SBC_BUTTON_SIGHT_CHANGE },
+ { SDL_CONTROLLER_BUTTON_DPAD_UP, SBC_BUTTON_GEAR_UP },
+ { SDL_CONTROLLER_BUTTON_DPAD_DOWN, SBC_BUTTON_GEAR_DOWN },
+ { SDL_CONTROLLER_BUTTON_DPAD_LEFT, SBC_BUTTON_TUNER_LEFT },
+ { SDL_CONTROLLER_BUTTON_DPAD_RIGHT, SBC_BUTTON_TUNER_RIGHT }
+ };
+
+ if (state->sbc.gearLever == 0)
+ state->sbc.gearLever = 255;
+
+ for (int i = 0; i < 8; i++) {
+ if (SDL_GameControllerGetButton(state->sdl_gamecontroller,
+ sdl_button_map_sbc[i][0]))
+ state->sbc.buttons |= sdl_button_map_sbc[i][1];
+ }
+
+ const uint64_t toggles[5] = { SBC_BUTTON_FILT_CONTROL_SYSTEM,
+ SBC_BUTTON_OXYGEN_SUPPLY_SYSTEM,
+ SBC_BUTTON_FUEL_FLOW_RATE,
+ SBC_BUTTON_BUFFER_MATERIAL,
+ SBC_BUTTON_VT_LOCATION_MEASUREMENT };
+
+ for (int i = 0; i < 5; i++) {
+ if ((state->sbc.buttons & toggles[i]) &&
+ !(state->sbc.previousButtons &
+ toggles[i])) { // When the for the toggle is pressed
+ uint8_t byteMask = (uint8_t)(toggles[i] >> 32);
+ // Toggle the toggle switch
+ state->sbc.toggleSwitches ^= byteMask;
+ }
+ }
+
+ // Tuner Dial Left
+ if ((state->sbc.buttons & SBC_BUTTON_TUNER_LEFT) &&
+ !(state->sbc.previousButtons & SBC_BUTTON_TUNER_LEFT)) {
+ if (state->sbc.tunerDial == 0)
+ state->sbc.tunerDial = 15;
+ else
+ state->sbc.tunerDial--;
+ }
+
+ // Tuner Dial Right
+ if ((state->sbc.buttons & SBC_BUTTON_TUNER_RIGHT) &&
+ !(state->sbc.previousButtons & SBC_BUTTON_TUNER_RIGHT)) {
+ if (state->sbc.tunerDial == 15)
+ state->sbc.tunerDial = 0;
+ else
+ state->sbc.tunerDial++;
+ }
+
+ // Gear Lever Up
+ if ((state->sbc.buttons & SBC_BUTTON_GEAR_UP) &&
+ !(state->sbc.previousButtons & SBC_BUTTON_GEAR_UP)) {
+ if (state->sbc.gearLever != 5) {
+ if (state->sbc.gearLever == 255)
+ state->sbc.gearLever = 1;
+ else
+ state->sbc.gearLever++;
+ }
+ }
+
+ // Gear Lever Down
+ if ((state->sbc.buttons & SBC_BUTTON_GEAR_DOWN) &&
+ !(state->sbc.previousButtons & SBC_BUTTON_GEAR_DOWN)) {
+ if (state->sbc.gearLever != 254) {
+ if (state->sbc.gearLever == 1)
+ state->sbc.gearLever = 255;
+ else
+ state->sbc.gearLever--;
+ }
+ }
+
+ state->sbc.axis[SBC_AXIS_SIGHT_CHANGE_X] = SDL_GameControllerGetAxis(
+ state->sdl_gamecontroller, SDL_CONTROLLER_AXIS_LEFTX);
+ state->sbc.axis[SBC_AXIS_SIGHT_CHANGE_Y] = SDL_GameControllerGetAxis(
+ state->sdl_gamecontroller, SDL_CONTROLLER_AXIS_LEFTY);
+ state->sbc.axis[SBC_AXIS_AIMING_X] = SDL_GameControllerGetAxis(
+ state->sdl_gamecontroller, SDL_CONTROLLER_AXIS_RIGHTX);
+ state->sbc.axis[SBC_AXIS_AIMING_Y] = SDL_GameControllerGetAxis(
+ state->sdl_gamecontroller, SDL_CONTROLLER_AXIS_RIGHTY);
+ state->sbc.axis[SBC_AXIS_MIDDLE_PEDAL] = SDL_GameControllerGetAxis(
+ state->sdl_gamecontroller, SDL_CONTROLLER_AXIS_TRIGGERLEFT);
+ state->sbc.axis[SBC_AXIS_RIGHT_PEDAL] = SDL_GameControllerGetAxis(
+ state->sdl_gamecontroller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
+
+ state->sbc.previousButtons = state->sbc.buttons;
+ } else {
+ const SDL_GameControllerButton sdl_button_map[15] = {
+ SDL_CONTROLLER_BUTTON_A,
+ SDL_CONTROLLER_BUTTON_B,
+ SDL_CONTROLLER_BUTTON_X,
+ SDL_CONTROLLER_BUTTON_Y,
+ SDL_CONTROLLER_BUTTON_DPAD_LEFT,
+ SDL_CONTROLLER_BUTTON_DPAD_UP,
+ SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
+ SDL_CONTROLLER_BUTTON_DPAD_DOWN,
+ SDL_CONTROLLER_BUTTON_BACK,
+ SDL_CONTROLLER_BUTTON_START,
+ SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
+ SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
+ SDL_CONTROLLER_BUTTON_LEFTSTICK,
+ SDL_CONTROLLER_BUTTON_RIGHTSTICK,
+ SDL_CONTROLLER_BUTTON_GUIDE
+ };
+
+ for (int i = 0; i < 15; i++) {
+ state->gp.buttons |=
+ SDL_GameControllerGetButton(state->sdl_gamecontroller, sdl_button_map[i]) << i;
+ }
+
+ const SDL_GameControllerAxis sdl_axis_map[6] = {
+ SDL_CONTROLLER_AXIS_TRIGGERLEFT, SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
+ SDL_CONTROLLER_AXIS_LEFTX, SDL_CONTROLLER_AXIS_LEFTY,
+ SDL_CONTROLLER_AXIS_RIGHTX, SDL_CONTROLLER_AXIS_RIGHTY,
+ };
+
+ for (int i = 0; i < 6; i++) {
+ state->gp.axis[i] =
+ SDL_GameControllerGetAxis(state->sdl_gamecontroller, sdl_axis_map[i]);
+ }
+
+ // FIXME: Check range
+ state->gp.axis[CONTROLLER_AXIS_LSTICK_Y] =
+ -1 - state->gp.axis[CONTROLLER_AXIS_LSTICK_Y];
+ state->gp.axis[CONTROLLER_AXIS_RSTICK_Y] =
+ -1 - state->gp.axis[CONTROLLER_AXIS_RSTICK_Y];
+
+ // xemu_input_print_controller_state(state);
}
-
- const SDL_GameControllerAxis sdl_axis_map[6] = {
- SDL_CONTROLLER_AXIS_TRIGGERLEFT, SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
- SDL_CONTROLLER_AXIS_LEFTX, SDL_CONTROLLER_AXIS_LEFTY,
- SDL_CONTROLLER_AXIS_RIGHTX, SDL_CONTROLLER_AXIS_RIGHTY,
- };
-
- for (int i = 0; i < 6; i++) {
- state->axis[i] = SDL_GameControllerGetAxis(state->sdl_gamecontroller, sdl_axis_map[i]);
- }
-
- // FIXME: Check range
- state->axis[CONTROLLER_AXIS_LSTICK_Y] = -1 - state->axis[CONTROLLER_AXIS_LSTICK_Y];
- state->axis[CONTROLLER_AXIS_RSTICK_Y] = -1 - state->axis[CONTROLLER_AXIS_RSTICK_Y];
-
- // xemu_input_print_controller_state(state);
}
void xemu_input_update_rumble(ControllerState *state)
{
- if (!state->rumble_enabled) {
+ if (!state->gp.rumble_enabled) {
return;
}
@@ -500,7 +880,7 @@ void xemu_input_update_rumble(ControllerState *state)
return;
}
- SDL_GameControllerRumble(state->sdl_gamecontroller, state->rumble_l, state->rumble_r, 250);
+ SDL_GameControllerRumble(state->sdl_gamecontroller, state->gp.rumble_l, state->gp.rumble_r, 250);
state->last_rumble_updated_ts = qemu_clock_get_us(QEMU_CLOCK_REALTIME);
}
@@ -568,16 +948,22 @@ void xemu_input_bind(int index, ControllerState *state, int save)
bound_controllers[index]->bound = index;
char *tmp;
+ QDict *usbhub_qdict = NULL;
+ DeviceState *usbhub_dev = NULL;
- // Create controller's internal USB hub.
- QDict *usbhub_qdict = qdict_new();
- qdict_put_str(usbhub_qdict, "driver", "usb-hub");
- tmp = g_strdup_printf("1.%d", port_map[index]);
- qdict_put_str(usbhub_qdict, "port", tmp);
- qdict_put_int(usbhub_qdict, "ports", 3);
- QemuOpts *usbhub_opts = qemu_opts_from_qdict(qemu_find_opts("device"), usbhub_qdict, &error_abort);
- DeviceState *usbhub_dev = qdev_device_add(usbhub_opts, &error_abort);
- g_free(tmp);
+ bool hasInternalHub = strcmp(bound_drivers[index], DRIVER_STEEL_BATTALION) != 0;
+
+ if (hasInternalHub) {
+ // Create controller's internal USB hub.
+ usbhub_qdict = qdict_new();
+ qdict_put_str(usbhub_qdict, "driver", "usb-hub");
+ tmp = g_strdup_printf("1.%d", port_map[index]);
+ qdict_put_str(usbhub_qdict, "port", tmp);
+ qdict_put_int(usbhub_qdict, "ports", 3);
+ QemuOpts *usbhub_opts = qemu_opts_from_qdict(qemu_find_opts("device"), usbhub_qdict, &error_abort);
+ usbhub_dev = qdev_device_add(usbhub_opts, &error_abort);
+ g_free(tmp);
+ }
// Create XID controller. This is connected to Port 1 of the controller's internal USB Hub
QDict *qdict = qdict_new();
@@ -593,7 +979,8 @@ void xemu_input_bind(int index, ControllerState *state, int save)
// Specify index/port
qdict_put_int(qdict, "index", index);
- tmp = g_strdup_printf("1.%d.1", port_map[index]);
+ const char *fmt = hasInternalHub ? "1.%d.1" : "1.%d";
+ tmp = g_strdup_printf(fmt, port_map[index]);
qdict_put_str(qdict, "port", tmp);
g_free(tmp);
@@ -603,12 +990,14 @@ void xemu_input_bind(int index, ControllerState *state, int save)
assert(dev);
// Unref for eventual cleanup
- qobject_unref(usbhub_qdict);
- object_unref(OBJECT(usbhub_dev));
+ if (usbhub_qdict != NULL)
+ qobject_unref(usbhub_qdict);
+ if (usbhub_dev != NULL)
+ object_unref(OBJECT(usbhub_dev));
qobject_unref(qdict);
object_unref(OBJECT(dev));
- state->device = usbhub_dev;
+ state->device = hasInternalHub ? usbhub_dev : dev;
}
}
@@ -618,6 +1007,10 @@ bool xemu_input_bind_xmu(int player_index, int expansion_slot_index,
assert(player_index >= 0 && player_index < 4);
assert(expansion_slot_index >= 0 && expansion_slot_index < 2);
+ bool hasInternalHub =
+ strcmp(bound_drivers[player_index], DRIVER_STEEL_BATTALION) != 0;
+ assert(hasInternalHub);
+
ControllerState *player = bound_controllers[player_index];
enum peripheral_type peripheral_type =
player->peripheral_types[expansion_slot_index];
@@ -740,6 +1133,11 @@ void xemu_input_unbind_xmu(int player_index, int expansion_slot_index)
void xemu_input_rebind_xmu(int port)
{
+ bool hasInternalHub =
+ strcmp(bound_drivers[port], DRIVER_STEEL_BATTALION) != 0;
+ if (!hasInternalHub)
+ return;
+
// Try to bind peripherals back to controller
for (int i = 0; i < 2; i++) {
enum peripheral_type peripheral_type =
diff --git a/ui/xemu-input.h b/ui/xemu-input.h
index 23c1a9f91b..3fecc7ec29 100644
--- a/ui/xemu-input.h
+++ b/ui/xemu-input.h
@@ -32,9 +32,11 @@
#define DRIVER_DUKE "usb-xbox-gamepad"
#define DRIVER_S "usb-xbox-gamepad-s"
+#define DRIVER_STEEL_BATTALION "usb-steel-battalion"
#define DRIVER_DUKE_DISPLAY_NAME "Xbox Controller"
#define DRIVER_S_DISPLAY_NAME "Xbox Controller S"
+#define DRIVER_STEEL_BATTALION_DISPLAY_NAME "Steel Battalion Controller"
enum controller_state_buttons_mask {
CONTROLLER_BUTTON_A = (1 << 0),
@@ -56,6 +58,54 @@ enum controller_state_buttons_mask {
#define CONTROLLER_STATE_BUTTON_ID_TO_MASK(x) (1<buttons & CONTROLLER_BUTTON_GUIDE) {
- state->animate_guide_button_end = now + animate_guide_button_duration;
+ if (state->gp.buttons & CONTROLLER_BUTTON_GUIDE) {
+ state->gp.animate_guide_button_end = now + animate_guide_button_duration;
}
- if (now < state->animate_guide_button_end) {
- t = 1.0f - (float)(state->animate_guide_button_end-now)/(float)animate_guide_button_duration;
+ if (now < state->gp.animate_guide_button_end) {
+ t = 1.0f - (float)(state->gp.animate_guide_button_end-now)/(float)animate_guide_button_duration;
float sin_wav = (1-sin(M_PI * t / 2.0f));
// Animate guide button by highlighting logo jewel and fading out over time
@@ -549,7 +579,7 @@ static void RenderDukeController(float frame_x, float frame_y, uint32_t primary_
// The controller has alpha cutouts where the buttons are. Draw a surface
// behind the buttons if they are activated
for (int i = 0; i < 12; i++) {
- if (state->buttons & (1 << i)) {
+ if (state->gp.buttons & (1 << i)) {
RenderDecal(g_decal_shader, frame_x + buttons[i].x,
frame_y + buttons[i].y, buttons[i].w, buttons[i].h, 0,
0, 1, 1, 0, 0, primary_color + 0xff);
@@ -563,14 +593,14 @@ static void RenderDukeController(float frame_x, float frame_y, uint32_t primary_
float h = tex_items[obj_lstick].h;
float c_x = frame_x+lstick_ctr.x;
float c_y = frame_y+lstick_ctr.y;
- float lstick_x = (float)state->axis[CONTROLLER_AXIS_LSTICK_X]/32768.0;
- float lstick_y = (float)state->axis[CONTROLLER_AXIS_LSTICK_Y]/32768.0;
+ float lstick_x = (float)state->gp.axis[CONTROLLER_AXIS_LSTICK_X]/32768.0;
+ float lstick_y = (float)state->gp.axis[CONTROLLER_AXIS_LSTICK_Y]/32768.0;
RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f + 10.0f * lstick_x),
(int)(c_y - h / 2.0f + 10.0f * lstick_y), w, h,
tex_items[obj_lstick].x, tex_items[obj_lstick].y, w, h,
- (state->buttons & CONTROLLER_BUTTON_LSTICK) ? secondary_color :
+ (state->gp.buttons & CONTROLLER_BUTTON_LSTICK) ? secondary_color :
primary_color,
- (state->buttons & CONTROLLER_BUTTON_LSTICK) ? primary_color :
+ (state->gp.buttons & CONTROLLER_BUTTON_LSTICK) ? primary_color :
secondary_color,
0);
@@ -579,33 +609,33 @@ static void RenderDukeController(float frame_x, float frame_y, uint32_t primary_
h = tex_items[obj_rstick].h;
c_x = frame_x+rstick_ctr.x;
c_y = frame_y+rstick_ctr.y;
- float rstick_x = (float)state->axis[CONTROLLER_AXIS_RSTICK_X]/32768.0;
- float rstick_y = (float)state->axis[CONTROLLER_AXIS_RSTICK_Y]/32768.0;
+ float rstick_x = (float)state->gp.axis[CONTROLLER_AXIS_RSTICK_X]/32768.0;
+ float rstick_y = (float)state->gp.axis[CONTROLLER_AXIS_RSTICK_Y]/32768.0;
RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f + 10.0f * rstick_x),
(int)(c_y - h / 2.0f + 10.0f * rstick_y), w, h,
tex_items[obj_rstick].x, tex_items[obj_rstick].y, w, h,
- (state->buttons & CONTROLLER_BUTTON_RSTICK) ? secondary_color :
+ (state->gp.buttons & CONTROLLER_BUTTON_RSTICK) ? secondary_color :
primary_color,
- (state->buttons & CONTROLLER_BUTTON_RSTICK) ? primary_color :
+ (state->gp.buttons & CONTROLLER_BUTTON_RSTICK) ? primary_color :
secondary_color,
0);
glBlendFunc(GL_ONE, GL_ZERO); // Don't blend, just overwrite values in buffer
// Render trigger bars
- float ltrig = state->axis[CONTROLLER_AXIS_LTRIG] / 32767.0;
- float rtrig = state->axis[CONTROLLER_AXIS_RTRIG] / 32767.0;
+ float ltrig = state->gp.axis[CONTROLLER_AXIS_LTRIG] / 32767.0;
+ float rtrig = state->gp.axis[CONTROLLER_AXIS_RTRIG] / 32767.0;
const uint32_t animate_trigger_duration = 1000;
if ((ltrig > 0) || (rtrig > 0)) {
- state->animate_trigger_end = now + animate_trigger_duration;
+ state->gp.animate_trigger_end = now + animate_trigger_duration;
rumble_l = fmax(rumble_l, ltrig);
rumble_r = fmax(rumble_r, rtrig);
}
// Animate trigger alpha down after a period of inactivity
alpha = 0x80;
- if (state->animate_trigger_end > now) {
- t = 1.0f - (float)(state->animate_trigger_end-now)/(float)animate_trigger_duration;
+ if (state->gp.animate_trigger_end > now) {
+ t = 1.0f - (float)(state->gp.animate_trigger_end-now)/(float)animate_trigger_duration;
float sin_wav = (1-sin(M_PI * t / 2.0f));
alpha += fmin(sin_wav * 0x40, 0x80);
}
@@ -619,8 +649,8 @@ static void RenderDukeController(float frame_x, float frame_y, uint32_t primary_
rtrig, primary_color + alpha, primary_color + 0xff);
// Apply rumble updates
- state->rumble_l = (int)(rumble_l * (float)0xffff);
- state->rumble_r = (int)(rumble_r * (float)0xffff);
+ state->gp.rumble_l = (int)(rumble_l * (float)0xffff);
+ state->gp.rumble_r = (int)(rumble_r * (float)0xffff);
glBindVertexArray(0);
glUseProgram(0);
@@ -676,13 +706,13 @@ static void RenderControllerS(float frame_x, float frame_y, uint32_t primary_col
// Check to see if the guide button is pressed
const uint32_t animate_guide_button_duration = 2000;
- if (state->buttons & CONTROLLER_BUTTON_GUIDE) {
- state->animate_guide_button_end =
+ if (state->gp.buttons & CONTROLLER_BUTTON_GUIDE) {
+ state->gp.animate_guide_button_end =
now + animate_guide_button_duration;
}
- if (now < state->animate_guide_button_end) {
- t = 1.0f - (float)(state->animate_guide_button_end - now) /
+ if (now < state->gp.animate_guide_button_end) {
+ t = 1.0f - (float)(state->gp.animate_guide_button_end - now) /
(float)animate_guide_button_duration;
float sin_wav = (1 - sin(M_PI * t / 2.0f));
@@ -712,7 +742,7 @@ static void RenderControllerS(float frame_x, float frame_y, uint32_t primary_col
// The controller has alpha cutouts where the buttons are. Draw a surface
// behind the buttons if they are activated
for (int i = 0; i < 12; i++) {
- if (state->buttons & (1 << i)) {
+ if (state->gp.buttons & (1 << i)) {
RenderDecal(g_decal_shader, frame_x + buttons[i].x,
frame_y + buttons[i].y, buttons[i].w, buttons[i].h, 0,
0, 1, 1, 0, 0, primary_color + 0xff);
@@ -726,15 +756,15 @@ static void RenderControllerS(float frame_x, float frame_y, uint32_t primary_col
float h = tex_items[obj_lstick].h;
float c_x = frame_x + lstick_ctr.x;
float c_y = frame_y + lstick_ctr.y;
- float lstick_x = (float)state->axis[CONTROLLER_AXIS_LSTICK_X] / 32768.0;
- float lstick_y = (float)state->axis[CONTROLLER_AXIS_LSTICK_Y] / 32768.0;
+ float lstick_x = (float)state->gp.axis[CONTROLLER_AXIS_LSTICK_X] / 32768.0;
+ float lstick_y = (float)state->gp.axis[CONTROLLER_AXIS_LSTICK_Y] / 32768.0;
RenderDecal(
g_decal_shader, (int)(c_x - w / 2.0f + 10.0f * lstick_x),
(int)(c_y - h / 2.0f + 10.0f * lstick_y), w, h, tex_items[obj_lstick].x,
tex_items[obj_lstick].y, w, h,
- (state->buttons & CONTROLLER_BUTTON_LSTICK) ? secondary_color :
+ (state->gp.buttons & CONTROLLER_BUTTON_LSTICK) ? secondary_color :
primary_color,
- (state->buttons & CONTROLLER_BUTTON_LSTICK) ? primary_color :
+ (state->gp.buttons & CONTROLLER_BUTTON_LSTICK) ? primary_color :
secondary_color,
0);
@@ -743,15 +773,15 @@ static void RenderControllerS(float frame_x, float frame_y, uint32_t primary_col
h = tex_items[obj_rstick].h;
c_x = frame_x + rstick_ctr.x;
c_y = frame_y + rstick_ctr.y;
- float rstick_x = (float)state->axis[CONTROLLER_AXIS_RSTICK_X] / 32768.0;
- float rstick_y = (float)state->axis[CONTROLLER_AXIS_RSTICK_Y] / 32768.0;
+ float rstick_x = (float)state->gp.axis[CONTROLLER_AXIS_RSTICK_X] / 32768.0;
+ float rstick_y = (float)state->gp.axis[CONTROLLER_AXIS_RSTICK_Y] / 32768.0;
RenderDecal(
g_decal_shader, (int)(c_x - w / 2.0f + 10.0f * rstick_x),
(int)(c_y - h / 2.0f + 10.0f * rstick_y), w, h, tex_items[obj_rstick].x,
tex_items[obj_rstick].y, w, h,
- (state->buttons & CONTROLLER_BUTTON_RSTICK) ? secondary_color :
+ (state->gp.buttons & CONTROLLER_BUTTON_RSTICK) ? secondary_color :
primary_color,
- (state->buttons & CONTROLLER_BUTTON_RSTICK) ? primary_color :
+ (state->gp.buttons & CONTROLLER_BUTTON_RSTICK) ? primary_color :
secondary_color,
0);
@@ -759,19 +789,19 @@ static void RenderControllerS(float frame_x, float frame_y, uint32_t primary_col
GL_ZERO); // Don't blend, just overwrite values in buffer
// Render trigger bars
- float ltrig = state->axis[CONTROLLER_AXIS_LTRIG] / 32767.0;
- float rtrig = state->axis[CONTROLLER_AXIS_RTRIG] / 32767.0;
+ float ltrig = state->gp.axis[CONTROLLER_AXIS_LTRIG] / 32767.0;
+ float rtrig = state->gp.axis[CONTROLLER_AXIS_RTRIG] / 32767.0;
const uint32_t animate_trigger_duration = 1000;
if ((ltrig > 0) || (rtrig > 0)) {
- state->animate_trigger_end = now + animate_trigger_duration;
+ state->gp.animate_trigger_end = now + animate_trigger_duration;
rumble_l = fmax(rumble_l, ltrig);
rumble_r = fmax(rumble_r, rtrig);
}
// Animate trigger alpha down after a period of inactivity
alpha = 0x80;
- if (state->animate_trigger_end > now) {
- t = 1.0f - (float)(state->animate_trigger_end - now) /
+ if (state->gp.animate_trigger_end > now) {
+ t = 1.0f - (float)(state->gp.animate_trigger_end - now) /
(float)animate_trigger_duration;
float sin_wav = (1 - sin(M_PI * t / 2.0f));
alpha += fmin(sin_wav * 0x40, 0x80);
@@ -786,8 +816,300 @@ static void RenderControllerS(float frame_x, float frame_y, uint32_t primary_col
rtrig, primary_color + alpha, primary_color + 0xff);
// Apply rumble updates
- state->rumble_l = (int)(rumble_l * (float)0xffff);
- state->rumble_r = (int)(rumble_r * (float)0xffff);
+ state->gp.rumble_l = (int)(rumble_l * (float)0xffff);
+ state->gp.rumble_r = (int)(rumble_r * (float)0xffff);
+
+ glBindVertexArray(0);
+ glUseProgram(0);
+}
+
+void RenderSteelBattalionController(float frame_x, float frame_y, uint32_t primary_color,
+ uint32_t secondary_color, ControllerState *state)
+{
+ // Location within the controller texture of masked button locations,
+ // relative to the origin of the controller
+ const struct rect lstick_ctr = { 122, 263, 0, 0 };
+ const struct rect rstick_ctr = { 349, 263, 0, 0 };
+ const struct rect accel_pedal = { 281, 92, 0, 0 };
+ const struct rect brake_pedal = { 216, 96, 0, 0 };
+ const struct rect slide_step_pedal = { 133, 92, 0, 0 };
+ const struct rect radio_dial_ctr = { 205, 243, 0, 0 };
+ const struct rect sight_change_ctr = { 123, 329, 0, 0 };
+ const struct rect transmission_lever_ctr_R = { 44, 210, 0, 0 };
+ const struct rect transmission_lever_ctr_N = { 44, 219, 0, 0 };
+ const struct rect transmission_lever_ctr_1 = { 44, 228, 0, 0 };
+ const struct rect transmission_lever_ctr_2 = { 44, 238, 0, 0 };
+ const struct rect transmission_lever_ctr_3 = { 44, 248, 0, 0 };
+ const struct rect transmission_lever_ctr_4 = { 44, 258, 0, 0 };
+ const struct rect transmission_lever_ctr_5 = { 44, 268, 0, 0 };
+ const struct rect filt_ctrl_sys_ctr = { 103, 194, 0, 0 };
+ const struct rect oxygen_supply_system_ctr = { 112, 205, 0, 0 };
+ const struct rect fuel_flow_rate_ctr = { 126, 188, 0, 0 };
+ const struct rect buffer_material_ctr = { 135, 200, 0, 0 };
+ const struct rect vt_location_measurement_ctr = { 145, 210, 0, 0 };
+ const struct rect buttons[33] = {
+ { 350, 309, 11, 29 }, // SBC_BUTTON_MAIN_WEAPON
+ { 380, 308, 9, 35 }, // SBC_BUTTON_SUB_WEAPON
+ { 336, 316, 12, 12 }, // SBC_BUTTON_LOCK_ON
+ { 418, 263, 16, 15 }, // SBC_BUTTON_EJECT
+ { 418, 228, 16, 15 }, // SBC_BUTTON_COCKPIT_HATCH
+ { 418, 206, 16, 15 }, // SBC_BUTTON_IGNITION
+ { 418, 184, 16, 15 }, // SBC_BUTTON_START
+ { 339, 209, 22, 6 }, // SBC_BUTTON_OPEN_CLOSE
+ { 375, 209, 22, 6 }, // SBC_BUTTON_MAP_ZOOM_IN_OUT
+ { 339, 198, 22, 6 }, // SBC_BUTTON_MODE_SELECT
+ { 375, 198, 22, 6 }, // SBC_BUTTON_SUB_MONITOR_MODE_SELECT
+ { 339, 186, 22, 6 }, // SBC_BUTTON_ZOOM_IN
+ { 375, 186, 22, 6 }, // SBC_BUTTON_ZOOM_OUT
+ { 279, 274, 7, 13 }, // SBC_BUTTON_FSS
+ { 279, 252, 7, 13 }, // SBC_BUTTON_MANIPULATOR
+ { 279, 230, 7, 13 }, // SBC_BUTTON_LINE_COLOR_CHANGE
+ { 190, 204, 22, 6 }, // SBC_BUTTON_WASHING
+ { 223, 204, 22, 6 }, // SBC_BUTTON_EXTINGUISHER
+ { 256, 204, 22, 6 }, // SBC_BUTTON_CHAFF
+ { 268, 274, 7, 13 }, // SBC_BUTTON_TANK_DETACH
+ { 268, 252, 7, 13 }, // SBC_BUTTON_OVERRIDE
+ { 268, 230, 7, 13 }, // SBC_BUTTON_NIGHT_SCOPE
+ { 257, 274, 7, 13 }, // SBC_BUTTON_FUNC1
+ { 257, 252, 7, 13 }, // SBC_BUTTON_FUNC2
+ { 257, 230, 7, 13 }, // SBC_BUTTON_FUNC3
+ { 190, 189, 22, 6 }, // SBC_BUTTON_MAIN_WEAPON_CONTROL
+ { 223, 189, 22, 6 }, // SBC_BUTTON_SUB_WEAPON_CONTROL
+ { 256, 189, 22, 6 }, // SBC_BUTTON_MAGAZINE_CHANGE
+ { 181, 272, 7, 13 }, // SBC_BUTTON_COM1
+ { 192, 272, 7, 13 }, // SBC_BUTTON_COM2
+ { 202, 272, 7, 13 }, // SBC_BUTTON_COM3
+ { 213, 272, 7, 13 }, // SBC_BUTTON_COM4
+ { 223, 272, 7, 13 } // SBC_BUTTON_COM5
+ };
+
+ glUseProgram(g_decal_shader->prog);
+ glBindVertexArray(g_decal_shader->vao);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, g_sb_controller_tex);
+
+ // Add a 5 pixel space around the controller so we can wiggle the controller
+ // around to visualize rumble in action
+ frame_x += 5;
+ frame_y += 5;
+
+ glBlendEquation(GL_FUNC_ADD);
+ glBlendFunc(GL_ONE, GL_ZERO);
+
+ // Render controller texture
+ RenderDecal(g_decal_shader, frame_x + 0, frame_y + 0,
+ sb_tex_items[obj_controller].w, sb_tex_items[obj_controller].h,
+ sb_tex_items[obj_controller].x, sb_tex_items[obj_controller].y,
+ sb_tex_items[obj_controller].w, sb_tex_items[obj_controller].h,
+ primary_color, secondary_color, 0);
+
+ glBlendFunc(GL_ONE_MINUS_DST_ALPHA,
+ GL_ONE); // Blend with controller cutouts
+
+ // The controller has alpha cutouts where the buttons are. Draw a surface
+ // behind the buttons if they are activated
+ for (int i = 0; i < 33; i++) {
+ if (state->sbc.buttons & (1ULL << i)) {
+ RenderDecal(g_decal_shader, frame_x + buttons[i].x,
+ frame_y + buttons[i].y, buttons[i].w, buttons[i].h, 0,
+ 0, 1, 1, 0, 0, primary_color + 0xff);
+ }
+ }
+
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Blend with controller
+
+ // Render sight change joystick
+ float w = sb_tex_items[obj_sight_change_stick].w;
+ float h = sb_tex_items[obj_sight_change_stick].h;
+ float c_x = frame_x + sight_change_ctr.x;
+ float c_y = frame_y + sight_change_ctr.y;
+ float scstick_x = (float)state->sbc.axis[SBC_AXIS_SIGHT_CHANGE_X] / 32768.0;
+ float scstick_y = (float)state->sbc.axis[SBC_AXIS_SIGHT_CHANGE_Y] / 32768.0;
+ RenderDecal(
+ g_decal_shader, (int)(c_x - w / 2.0f + 5.0f * scstick_x),
+ (int)(c_y - h / 2.0f - 5.0f * scstick_y), w, h,
+ sb_tex_items[obj_sight_change_stick].x,
+ sb_tex_items[obj_sight_change_stick].y, w, h,
+ (state->sbc.buttons & SBC_BUTTON_SIGHT_CHANGE) ? secondary_color :
+ primary_color,
+ (state->sbc.buttons & SBC_BUTTON_SIGHT_CHANGE) ? primary_color :
+ secondary_color,
+ 0);
+
+ // Render left joystick
+ w = sb_tex_items[obj_left_stick].w;
+ h = sb_tex_items[obj_left_stick].h;
+ c_x = frame_x + lstick_ctr.x;
+ c_y = frame_y + lstick_ctr.y;
+ float lstick_x = (float)state->sbc.axis[SBC_AXIS_ROTATION_LEVER] / 32768.0;
+ RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f + 23.0f * lstick_x),
+ (int)(c_y - h / 2.0f), w, h, sb_tex_items[obj_left_stick].x,
+ sb_tex_items[obj_left_stick].y, w, h, primary_color,
+ secondary_color, 0);
+
+ // Render right joystick
+ w = sb_tex_items[obj_right_stick].w;
+ h = sb_tex_items[obj_right_stick].h;
+ c_x = frame_x + rstick_ctr.x;
+ c_y = frame_y + rstick_ctr.y;
+ float rstick_x = (float)state->sbc.axis[SBC_AXIS_AIMING_X] / 32768.0;
+ float rstick_y = (float)state->sbc.axis[SBC_AXIS_AIMING_Y] / 32768.0;
+ RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f + 23.0f * rstick_x),
+ (int)(c_y - h / 2.0f - 20.0f * rstick_y), w, h,
+ sb_tex_items[obj_right_stick].x,
+ sb_tex_items[obj_right_stick].y, w, h, primary_color,
+ secondary_color, 0);
+
+ // Render accel pedal
+ w = sb_tex_items[obj_accel_pedal].w;
+ h = sb_tex_items[obj_accel_pedal].h;
+ c_x = frame_x + accel_pedal.x;
+ c_y = frame_y + accel_pedal.y;
+ RenderDecal(g_decal_shader, c_x,
+ c_y + 10.0f * state->sbc.axis[SBC_AXIS_RIGHT_PEDAL] / 32768.0f,
+ w, h, sb_tex_items[obj_accel_pedal].x,
+ sb_tex_items[obj_accel_pedal].y, w, h, primary_color,
+ secondary_color, 0);
+
+ // Brake accel pedal
+ w = sb_tex_items[obj_brake_pedal].w;
+ h = sb_tex_items[obj_brake_pedal].h;
+ c_x = frame_x + brake_pedal.x;
+ c_y = frame_y + brake_pedal.y;
+ RenderDecal(g_decal_shader, c_x,
+ c_y + 10.0f * state->sbc.axis[SBC_AXIS_MIDDLE_PEDAL] / 32768.0f,
+ w, h, sb_tex_items[obj_brake_pedal].x,
+ sb_tex_items[obj_brake_pedal].y, w, h, primary_color,
+ secondary_color, 0);
+
+ // Slide step pedal
+ w = sb_tex_items[obj_slide_step_pedal].w;
+ h = sb_tex_items[obj_slide_step_pedal].h;
+ c_x = frame_x + slide_step_pedal.x;
+ c_y = frame_y + slide_step_pedal.y;
+ RenderDecal(g_decal_shader, c_x,
+ c_y + 10.0f * state->sbc.axis[SBC_AXIS_LEFT_PEDAL] / 32768.0f,
+ w, h, sb_tex_items[obj_slide_step_pedal].x,
+ sb_tex_items[obj_slide_step_pedal].y, w, h, primary_color,
+ secondary_color, 0);
+
+ // Render the radio dial
+ w = sb_tex_items[obj_radio_dial].w;
+ h = sb_tex_items[obj_radio_dial].h;
+ c_x = frame_x + radio_dial_ctr.x;
+ c_y = frame_x + radio_dial_ctr.y;
+ float tunerStep = 0.125f * 3.14159f;
+ // TODO: Figure out a way to either rotate the decal or remove the dot and
+ // move the dot based on current radio channel
+ RenderDecal(
+ g_decal_shader,
+ (int)(c_x - w / 2.0f - 9 * cosf(tunerStep * state->sbc.tunerDial)),
+ (int)(c_y - h / 2.0f + 11 * sinf(tunerStep * state->sbc.tunerDial)), w,
+ h, sb_tex_items[obj_radio_dial].x, sb_tex_items[obj_radio_dial].y, w, h,
+ primary_color, secondary_color, 0);
+
+ // Render the transmission lever
+ w = sb_tex_items[obj_transmission_lever].w;
+ h = sb_tex_items[obj_transmission_lever].h;
+ c_x = frame_x + transmission_lever_ctr_1.x;
+ c_y = frame_x + transmission_lever_ctr_1.y;
+ switch (state->sbc.gearLever) {
+ case 254:
+ c_y = frame_y + transmission_lever_ctr_R.y;
+ break;
+ case 255:
+ c_y = frame_y + transmission_lever_ctr_N.y;
+ break;
+ case 1:
+ c_y = frame_y + transmission_lever_ctr_1.y;
+ break;
+ case 2:
+ c_y = frame_y + transmission_lever_ctr_2.y;
+ break;
+ case 3:
+ c_y = frame_y + transmission_lever_ctr_3.y;
+ break;
+ case 4:
+ c_y = frame_y + transmission_lever_ctr_4.y;
+ break;
+ case 5:
+ c_y = frame_y + transmission_lever_ctr_5.y;
+ break;
+ }
+ // Determine the correct value for c_y based on the currently selected gear
+ RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f), (int)(c_y - h / 2.0f), w,
+ h, sb_tex_items[obj_transmission_lever].x,
+ sb_tex_items[obj_transmission_lever].y, w, h, primary_color,
+ secondary_color, 0);
+
+ // Filter Control System
+ w = sb_tex_items[obj_toggle].w;
+ h = sb_tex_items[obj_toggle].h;
+ c_x = frame_x + filt_ctrl_sys_ctr.x;
+ c_y = frame_y + filt_ctrl_sys_ctr.y;
+ if (state->sbc.toggleSwitches & (SBC_BUTTON_FILT_CONTROL_SYSTEM >> 32)) {
+ c_x -= 3;
+ c_y += 4;
+ }
+ RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f), (int)(c_y - h / 2.0f), w,
+ h, sb_tex_items[obj_toggle].x, sb_tex_items[obj_toggle].y, w, h,
+ primary_color, secondary_color, 0);
+
+ // Oxygen Supply System
+ w = sb_tex_items[obj_toggle].w;
+ h = sb_tex_items[obj_toggle].h;
+ c_x = frame_x + oxygen_supply_system_ctr.x;
+ c_y = frame_y + oxygen_supply_system_ctr.y;
+ if (state->sbc.toggleSwitches & (SBC_BUTTON_OXYGEN_SUPPLY_SYSTEM >> 32)) {
+ c_x -= 3;
+ c_y += 4;
+ }
+ RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f), (int)(c_y - h / 2.0f), w,
+ h, sb_tex_items[obj_toggle].x, sb_tex_items[obj_toggle].y, w, h,
+ primary_color, secondary_color, 0);
+
+ // Fuel Flow Rate
+ w = sb_tex_items[obj_toggle].w;
+ h = sb_tex_items[obj_toggle].h;
+ c_x = frame_x + fuel_flow_rate_ctr.x;
+ c_y = frame_y + fuel_flow_rate_ctr.y;
+ if (state->sbc.toggleSwitches & (SBC_BUTTON_FUEL_FLOW_RATE >> 32)) {
+ c_x -= 3;
+ c_y += 4;
+ }
+ RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f), (int)(c_y - h / 2.0f), w,
+ h, sb_tex_items[obj_toggle].x, sb_tex_items[obj_toggle].y, w, h,
+ primary_color, secondary_color, 0);
+
+ // Buffer Material
+ w = sb_tex_items[obj_toggle].w;
+ h = sb_tex_items[obj_toggle].h;
+ c_x = frame_x + buffer_material_ctr.x;
+ c_y = frame_y + buffer_material_ctr.y;
+ if (state->sbc.toggleSwitches & (SBC_BUTTON_BUFFER_MATERIAL >> 32)) {
+ c_x -= 3;
+ c_y += 4;
+ }
+ RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f), (int)(c_y - h / 2.0f), w,
+ h, sb_tex_items[obj_toggle].x, sb_tex_items[obj_toggle].y, w, h,
+ primary_color, secondary_color, 0);
+
+ // VT Location Measurement
+ w = sb_tex_items[obj_toggle].w;
+ h = sb_tex_items[obj_toggle].h;
+ c_x = frame_x + vt_location_measurement_ctr.x;
+ c_y = frame_y + vt_location_measurement_ctr.y;
+ if (state->sbc.toggleSwitches &
+ (SBC_BUTTON_VT_LOCATION_MEASUREMENT >> 32)) {
+ c_x -= 3;
+ c_y += 4;
+ }
+ RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f), (int)(c_y - h / 2.0f), w,
+ h, sb_tex_items[obj_toggle].x, sb_tex_items[obj_toggle].y, w, h,
+ primary_color, secondary_color, 0);
+
+ glBlendFunc(GL_ONE,
+ GL_ZERO); // Don't blend, just overwrite values in buffer
glBindVertexArray(0);
glUseProgram(0);
@@ -799,6 +1121,9 @@ void RenderController(float frame_x, float frame_y, uint32_t primary_color,
if (strcmp(bound_drivers[state->bound], DRIVER_S) == 0)
RenderControllerS(frame_x, frame_y, primary_color, secondary_color,
state);
+ if (strcmp(bound_drivers[state->bound], DRIVER_STEEL_BATTALION) == 0)
+ RenderSteelBattalionController(frame_x, frame_y, primary_color, secondary_color,
+ state);
else if (strcmp(bound_drivers[state->bound], DRIVER_DUKE) == 0)
RenderDukeController(frame_x, frame_y, primary_color, secondary_color,
state);
@@ -933,6 +1258,7 @@ void RenderFramebuffer(GLint tex, int width, int height, bool flip)
{
int tw, th;
float scale[2];
+ int viewport_width, viewport_height;
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex);
@@ -961,6 +1287,14 @@ void RenderFramebuffer(GLint tex, int width, int height, bool flip)
}
}
+ viewport_width = (int)(width * scale[0]);
+ viewport_height = (int)(height * scale[1]);
+
+ viewport_coords[0] = (width - viewport_width) / 2;
+ viewport_coords[1] = (height - viewport_height) / 2;
+ viewport_coords[2] = viewport_width;
+ viewport_coords[3] = viewport_height;
+
RenderFramebuffer(tex, width, height, flip, scale);
}
diff --git a/ui/xui/input-manager.cc b/ui/xui/input-manager.cc
index 786b54b176..646b67c776 100644
--- a/ui/xui/input-manager.cc
+++ b/ui/xui/input-manager.cc
@@ -21,11 +21,11 @@ void InputManager::Update()
ControllerState *iter;
QTAILQ_FOREACH(iter, &available_controllers, entry) {
if (iter->type != INPUT_DEVICE_SDL_GAMECONTROLLER) continue;
- m_buttons |= iter->buttons;
+ m_buttons |= iter->gp.buttons;
// We simply take any axis that is >10 % activation
for (int i = 0; i < CONTROLLER_AXIS__COUNT; i++) {
- if ((iter->axis[i] > 3276) || (iter->axis[i] < -3276)) {
- axis[i] = iter->axis[i];
+ if ((iter->gp.axis[i] > 3276) || (iter->gp.axis[i] < -3276)) {
+ axis[i] = iter->gp.axis[i];
}
}
}
diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc
index 9bb5dcf33f..d642e18857 100644
--- a/ui/xui/main-menu.cc
+++ b/ui/xui/main-menu.cc
@@ -172,14 +172,22 @@ void MainMenuInputView::Draw()
driver = DRIVER_DUKE_DISPLAY_NAME;
else if (strcmp(driver, DRIVER_S) == 0)
driver = DRIVER_S_DISPLAY_NAME;
+ else if (strcmp(driver, DRIVER_STEEL_BATTALION) == 0)
+ driver = DRIVER_STEEL_BATTALION_DISPLAY_NAME;
ImGui::SetNextItemWidth(-FLT_MIN);
if (ImGui::BeginCombo("###InputDrivers", driver,
ImGuiComboFlags_NoArrowButton)) {
- const char *available_drivers[] = { DRIVER_DUKE, DRIVER_S };
+ const char *available_drivers[] = {
+ DRIVER_DUKE,
+ DRIVER_S,
+ DRIVER_STEEL_BATTALION
+ };
const char *driver_display_names[] = {
DRIVER_DUKE_DISPLAY_NAME,
- DRIVER_S_DISPLAY_NAME
+ DRIVER_S_DISPLAY_NAME,
+ DRIVER_STEEL_BATTALION_DISPLAY_NAME
+
};
bool is_selected = false;
int num_drivers = sizeof(driver_display_names) / sizeof(driver_display_names[0]);
@@ -324,162 +332,169 @@ void MainMenuInputView::Draw()
ImGui::SetCursorPos(pos);
if (bound_state) {
- SectionTitle("Expansion Slots");
- // Begin a 2-column layout to render the expansion slots
- ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,
- g_viewport_mgr.Scale(ImVec2(0, 12)));
- ImGui::Columns(2, "mixed", false);
+ bool hasInternalHub =
+ strcmp(bound_drivers[active], DRIVER_STEEL_BATTALION) != 0;
+ if (hasInternalHub) {
+ SectionTitle("Expansion Slots");
+ // Begin a 2-column layout to render the expansion slots
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,
+ g_viewport_mgr.Scale(ImVec2(0, 12)));
+ ImGui::Columns(2, "mixed", false);
- xmu_fbo->Target();
- id = (ImTextureID)(intptr_t)xmu_fbo->Texture();
+ xmu_fbo->Target();
+ id = (ImTextureID)(intptr_t)xmu_fbo->Texture();
- const char *img_file_filters = ".img Files\0*.img\0All Files\0*.*\0";
- const char *comboLabels[2] = { "###ExpansionSlotA",
- "###ExpansionSlotB" };
- for (int i = 0; i < 2; i++) {
- // Display a combo box to allow the user to choose the type of
- // peripheral they want to use
- enum peripheral_type selected_type =
- bound_state->peripheral_types[i];
- const char *peripheral_type_names[2] = { "None", "Memory Unit" };
- const char *selected_peripheral_type =
- peripheral_type_names[selected_type];
- ImGui::SetNextItemWidth(-FLT_MIN);
- if (ImGui::BeginCombo(comboLabels[i], selected_peripheral_type,
- ImGuiComboFlags_NoArrowButton)) {
- // Handle all available peripheral types
- for (int j = 0; j < 2; j++) {
- bool is_selected = selected_type == j;
- ImGui::PushID(j);
- const char *selectable_label = peripheral_type_names[j];
+ const char *img_file_filters = ".img Files\0*.img\0All Files\0*.*\0";
+ const char *comboLabels[2] = { "###ExpansionSlotA",
+ "###ExpansionSlotB" };
+ for (int i = 0; i < 2; i++) {
+ // Display a combo box to allow the user to choose the type of
+ // peripheral they want to use
+ enum peripheral_type selected_type =
+ bound_state->peripheral_types[i];
+ const char *peripheral_type_names[2] = { "None", "Memory Unit" };
+ const char *selected_peripheral_type =
+ peripheral_type_names[selected_type];
+ ImGui::SetNextItemWidth(-FLT_MIN);
+ if (ImGui::BeginCombo(comboLabels[i], selected_peripheral_type,
+ ImGuiComboFlags_NoArrowButton)) {
+ // Handle all available peripheral types
+ for (int j = 0; j < 2; j++) {
+ bool is_selected = selected_type == j;
+ ImGui::PushID(j);
+ const char *selectable_label = peripheral_type_names[j];
- if (ImGui::Selectable(selectable_label, is_selected)) {
- // Free any existing peripheral
- if (bound_state->peripherals[i] != NULL) {
- if (bound_state->peripheral_types[i] ==
- PERIPHERAL_XMU) {
- // Another peripheral was already bound.
- // Unplugging
- xemu_input_unbind_xmu(active, i);
+ if (ImGui::Selectable(selectable_label, is_selected)) {
+ // Free any existing peripheral
+ if (bound_state->peripherals[i] != NULL) {
+ if (bound_state->peripheral_types[i] ==
+ PERIPHERAL_XMU) {
+ // Another peripheral was already bound.
+ // Unplugging
+ xemu_input_unbind_xmu(active, i);
+ }
+
+ // Free the existing state
+ g_free((void *)bound_state->peripherals[i]);
+ bound_state->peripherals[i] = NULL;
}
- // Free the existing state
- g_free((void *)bound_state->peripherals[i]);
- bound_state->peripherals[i] = NULL;
+ // Change the peripheral type to the newly selected type
+ bound_state->peripheral_types[i] =
+ (enum peripheral_type)j;
+
+ // Allocate state for the new peripheral
+ if (j == PERIPHERAL_XMU) {
+ bound_state->peripherals[i] =
+ g_malloc(sizeof(XmuState));
+ memset(bound_state->peripherals[i], 0,
+ sizeof(XmuState));
+ }
+
+ xemu_save_peripheral_settings(
+ active, i, bound_state->peripheral_types[i], NULL);
}
- // Change the peripheral type to the newly selected type
- bound_state->peripheral_types[i] =
- (enum peripheral_type)j;
-
- // Allocate state for the new peripheral
- if (j == PERIPHERAL_XMU) {
- bound_state->peripherals[i] =
- g_malloc(sizeof(XmuState));
- memset(bound_state->peripherals[i], 0,
- sizeof(XmuState));
+ if (is_selected) {
+ ImGui::SetItemDefaultFocus();
}
- xemu_save_peripheral_settings(
- active, i, bound_state->peripheral_types[i], NULL);
+ ImGui::PopID();
}
- if (is_selected) {
- ImGui::SetItemDefaultFocus();
+ ImGui::EndCombo();
+ }
+ DrawComboChevron();
+
+ // Set an X offset to center the image button within the column
+ ImGui::SetCursorPosX(
+ ImGui::GetCursorPosX() +
+ (int)((ImGui::GetColumnWidth() -
+ xmu_w * g_viewport_mgr.m_scale -
+ 2 * port_padding * g_viewport_mgr.m_scale) /
+ 2));
+
+ selected_type = bound_state->peripheral_types[i];
+ if (selected_type == PERIPHERAL_XMU) {
+ float x = xmu_x + i * xmu_x_stride;
+ float y = xmu_y;
+
+ XmuState *xmu = (XmuState *)bound_state->peripherals[i];
+ if (xmu->filename != NULL && strlen(xmu->filename) > 0) {
+ RenderXmu(x, y, 0x81dc8a00, 0x0f0f0f00);
+
+ } else {
+ RenderXmu(x, y, 0x1f1f1f00, 0x0f0f0f00);
}
+ ImVec2 xmu_display_size;
+ if (ImGui::GetContentRegionMax().x <
+ xmu_h * g_viewport_mgr.m_scale) {
+ xmu_display_size.x = ImGui::GetContentRegionMax().x / 2;
+ xmu_display_size.y = xmu_display_size.x * xmu_h / xmu_w;
+ } else {
+ xmu_display_size = ImVec2(xmu_w * g_viewport_mgr.m_scale,
+ xmu_h * g_viewport_mgr.m_scale);
+ }
+
+ ImGui::SetCursorPosX(
+ ImGui::GetCursorPosX() +
+ (int)((ImGui::GetColumnWidth() - xmu_display_size.x) /
+ 2.0));
+
+ ImGui::Image(id, xmu_display_size, ImVec2(0.5f * i, 1),
+ ImVec2(0.5f * (i + 1), 0));
+ ImVec2 pos = ImGui::GetCursorPos();
+
+ ImGui::SetCursorPos(pos);
+
+ // Button to generate a new XMU
+ ImGui::PushID(i);
+ if (ImGui::Button("New Image", ImVec2(250, 0))) {
+ int flags = NOC_FILE_DIALOG_SAVE |
+ NOC_FILE_DIALOG_OVERWRITE_CONFIRMATION;
+ const char *new_path = PausedFileOpen(
+ flags, img_file_filters, NULL, "xmu.img");
+
+ if (new_path) {
+ if (create_fatx_image(new_path, DEFAULT_XMU_SIZE)) {
+ // XMU was created successfully. Bind it
+ xemu_input_bind_xmu(active, i, new_path, false);
+ } else {
+ // Show alert message
+ char *msg = g_strdup_printf(
+ "Unable to create XMU image at %s", new_path);
+ xemu_queue_error_message(msg);
+ g_free(msg);
+ }
+ }
+ }
+
+ const char *xmu_port_path = NULL;
+ if (xmu->filename == NULL)
+ xmu_port_path = g_strdup("");
+ else
+ xmu_port_path = g_strdup(xmu->filename);
+ if (FilePicker("Image", &xmu_port_path, img_file_filters)) {
+ if (strlen(xmu_port_path) == 0) {
+ xemu_input_unbind_xmu(active, i);
+ } else {
+ xemu_input_bind_xmu(active, i, xmu_port_path, false);
+ }
+ }
+ g_free((void *)xmu_port_path);
+
ImGui::PopID();
}
- ImGui::EndCombo();
- }
- DrawComboChevron();
-
- // Set an X offset to center the image button within the column
- ImGui::SetCursorPosX(
- ImGui::GetCursorPosX() +
- (int)((ImGui::GetColumnWidth() -
- xmu_w * g_viewport_mgr.m_scale -
- 2 * port_padding * g_viewport_mgr.m_scale) /
- 2));
-
- selected_type = bound_state->peripheral_types[i];
- if (selected_type == PERIPHERAL_XMU) {
- float x = xmu_x + i * xmu_x_stride;
- float y = xmu_y;
-
- XmuState *xmu = (XmuState *)bound_state->peripherals[i];
- if (xmu->filename != NULL && strlen(xmu->filename) > 0) {
- RenderXmu(x, y, 0x81dc8a00, 0x0f0f0f00);
-
- } else {
- RenderXmu(x, y, 0x1f1f1f00, 0x0f0f0f00);
- }
-
- ImVec2 xmu_display_size;
- if (ImGui::GetContentRegionMax().x <
- xmu_h * g_viewport_mgr.m_scale) {
- xmu_display_size.x = ImGui::GetContentRegionMax().x / 2;
- xmu_display_size.y = xmu_display_size.x * xmu_h / xmu_w;
- } else {
- xmu_display_size = ImVec2(xmu_w * g_viewport_mgr.m_scale,
- xmu_h * g_viewport_mgr.m_scale);
- }
-
- ImGui::SetCursorPosX(
- ImGui::GetCursorPosX() +
- (int)((ImGui::GetColumnWidth() - xmu_display_size.x) /
- 2.0));
-
- ImGui::Image(id, xmu_display_size, ImVec2(0.5f * i, 1),
- ImVec2(0.5f * (i + 1), 0));
-
- // Button to generate a new XMU
- ImGui::PushID(i);
- if (ImGui::Button("New Image", ImVec2(250, 0))) {
- int flags = NOC_FILE_DIALOG_SAVE |
- NOC_FILE_DIALOG_OVERWRITE_CONFIRMATION;
- const char *new_path = PausedFileOpen(
- flags, img_file_filters, NULL, "xmu.img");
-
- if (new_path) {
- if (create_fatx_image(new_path, DEFAULT_XMU_SIZE)) {
- // XMU was created successfully. Bind it
- xemu_input_bind_xmu(active, i, new_path, false);
- } else {
- // Show alert message
- char *msg = g_strdup_printf(
- "Unable to create XMU image at %s", new_path);
- xemu_queue_error_message(msg);
- g_free(msg);
- }
- }
- }
-
- const char *xmu_port_path = NULL;
- if (xmu->filename == NULL)
- xmu_port_path = g_strdup("");
- else
- xmu_port_path = g_strdup(xmu->filename);
- if (FilePicker("Image", &xmu_port_path, img_file_filters)) {
- if (strlen(xmu_port_path) == 0) {
- xemu_input_unbind_xmu(active, i);
- } else {
- xemu_input_bind_xmu(active, i, xmu_port_path, false);
- }
- }
- g_free((void *)xmu_port_path);
-
- ImGui::PopID();
+ ImGui::NextColumn();
}
- ImGui::NextColumn();
+ xmu_fbo->Restore();
+
+ ImGui::PopStyleVar(); // ItemSpacing
+ ImGui::Columns(1);
}
-
- xmu_fbo->Restore();
-
- ImGui::PopStyleVar(); // ItemSpacing
- ImGui::Columns(1);
}
SectionTitle("Options");
diff --git a/ui/xui/main.cc b/ui/xui/main.cc
index 699805f113..f295107872 100644
--- a/ui/xui/main.cc
+++ b/ui/xui/main.cc
@@ -286,9 +286,7 @@ void xemu_hud_render(void)
g_scene_mgr.PushScene(g_main_menu);
} else if (ImGui::IsKeyPressed(ImGuiKey_F2)) {
g_scene_mgr.PushScene(g_popup_menu);
- } else if (menu_button ||
- (ImGui::IsMouseClicked(ImGuiMouseButton_Right) &&
- !ImGui::IsAnyItemFocused() && !ImGui::IsAnyItemHovered())) {
+ } else if (menu_button) {
g_scene_mgr.PushScene(g_popup_menu);
}