diff --git a/config_spec.yml b/config_spec.yml
index 5e8ad31151..efb141bb29 100644
--- a/config_spec.yml
+++ b/config_spec.yml
@@ -23,12 +23,16 @@ input:
bindings:
port1_driver: string
port1: string
+ port1_dvd_firmware: string
port2_driver: string
port2: string
+ port2_dvd_firmware: string
port3_driver: string
port3: string
+ port3_dvd_firmware: string
port4_driver: string
port4: string
+ port4_dvd_firmware: string
peripherals:
port1:
peripheral_type_0: integer
@@ -56,7 +60,7 @@ input:
default: true
background_input_capture: bool
keyboard_controller_scancode_map:
- # Scancode reference : https://github.com/libsdl-org/SDL/blob/main/include/SDL_scancode.h
+ # Scancode reference : https://github.com/libsdl-org/SDL/blob/main/include/SDL3/SDL_scancode.h
a:
type: integer
default: 4 # a
@@ -132,6 +136,139 @@ input:
rtrigger:
type: integer
default: 18 # w
+ keyboard_dvd_kit_scancode_map:
+ up:
+ type: integer
+ default: 26 # W
+ left:
+ type: integer
+ default: 4 # A
+ select:
+ type: integer
+ default: 40 # Return
+ right:
+ type: integer
+ default: 7 # D
+ down:
+ type: integer
+ default: 22 # S
+ display:
+ type: integer
+ default: 20 # Q
+ reverse:
+ type: integer
+ default: 29 # Z
+ play:
+ type: integer
+ default: 27 # X
+ forward:
+ type: integer
+ default: 25 # V
+ skip_down:
+ type: integer
+ default: 54 # Comma<
+ stop:
+ type: integer
+ default: 19 # P
+ pause:
+ type: integer
+ default: 6 # C
+ skip_up:
+ type: integer
+ default: 55 # Period>
+ title:
+ type: integer
+ default: 23 # T
+ info:
+ type: integer
+ default: 12 # I
+ menu:
+ type: integer
+ default: 16 # M
+ back:
+ type: integer
+ default: 42 # Backspace
+ button1:
+ type: integer
+ default: 30 # 1
+ button2:
+ type: integer
+ default: 31 # 2
+ button3:
+ type: integer
+ default: 32 # 3
+ button4:
+ type: integer
+ default: 33 # 4
+ button5:
+ type: integer
+ default: 34 # 5
+ button6:
+ type: integer
+ default: 35 # 6
+ button7:
+ type: integer
+ default: 36 # 7
+ button8:
+ type: integer
+ default: 37 # 8
+ button9:
+ type: integer
+ default: 38 # 9
+ button0:
+ type: integer
+ default: 39 # 0
+ power:
+ type: integer
+ default: 58 # F1
+ my_tv:
+ type: integer
+ default: 59 # F2
+ my_music:
+ type: integer
+ default: 60 # F3
+ my_pictures:
+ type: integer
+ default: 61 # F4
+ my_videos:
+ type: integer
+ default: 62 # F5
+ record:
+ type: integer
+ default: 63 # F6
+ start:
+ type: integer
+ default: 64 # F7
+ volume_up:
+ type: integer
+ default: 65 # F8
+ volume_down:
+ type: integer
+ default: 66 # F9
+ mute:
+ type: integer
+ default: 67 # F10
+ channel_up:
+ type: integer
+ default: 68 # F11
+ channel_down:
+ type: integer
+ default: 69 # F12
+ recorded_tv:
+ type: integer
+ default: 21 # R
+ live_tv:
+ type: integer
+ default: 15 # L
+ star:
+ type: integer
+ default: 45 # minus-
+ pound:
+ type: integer
+ default: 46 # equal=
+ clear:
+ type: integer
+ default: 49 # backslash\
keyboard_sbc_scancode_map:
eject:
type: integer
diff --git a/data/dvd_remote_mask.png b/data/dvd_remote_mask.png
new file mode 100644
index 0000000000..eba245385e
Binary files /dev/null and b/data/dvd_remote_mask.png differ
diff --git a/data/dvd_remote_mask.svg b/data/dvd_remote_mask.svg
new file mode 100644
index 0000000000..6f961f2737
--- /dev/null
+++ b/data/dvd_remote_mask.svg
@@ -0,0 +1,785 @@
+
diff --git a/data/meson.build b/data/meson.build
index 87ea47e260..859fcfd8b4 100644
--- a/data/meson.build
+++ b/data/meson.build
@@ -2,6 +2,7 @@ pfiles = [
'sb_controller_mask.png',
'controller_mask.png',
'controller_mask_s.png',
+ 'dvd_remote_mask.png',
'xmu_mask.png',
'logo_sdf.png',
'xemu_64x64.png',
diff --git a/hw/xbox/meson.build b/hw/xbox/meson.build
index b3b18e4c64..25dbf4d5ae 100644
--- a/hw/xbox/meson.build
+++ b/hw/xbox/meson.build
@@ -13,6 +13,7 @@ specific_ss.add(files(
'smbus_storage.c',
'smbus_xbox_smc.c',
'xbox.c',
+ 'xbox_dvd_playback_kit.c',
'xbox_pci.c',
'xid.c',
'xblc.c',
diff --git a/hw/xbox/xbox_dvd_playback_kit.c b/hw/xbox/xbox_dvd_playback_kit.c
new file mode 100644
index 0000000000..fd7b6ca79a
--- /dev/null
+++ b/hw/xbox/xbox_dvd_playback_kit.c
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2025 Florin9doi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see .
+ */
+
+#include "xid.h"
+
+typedef struct XboxDVDPlaybackKitReport {
+ uint8_t bReportId;
+ uint8_t bLength;
+ uint16_t wButton;
+ uint16_t wTimer;
+} QEMU_PACKED XboxDVDPlaybackKitReport;
+
+typedef struct XboxDVDPlaybackKitState {
+ USBDevice dev;
+ uint8_t device_index;
+ char *firmware_path;
+ uint32_t firmware_len;
+ uint8_t firmware[0x40000];
+ gint64 last_button;
+ gint64 last_packet;
+ XboxDVDPlaybackKitReport in_state;
+} XboxDVDPlaybackKitState;
+
+enum {
+ STR_EMPTY
+};
+
+static const USBDescIface desc_iface[] = {
+ {
+ .bInterfaceNumber = 0,
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = 0x58, // USB_CLASS_XID,
+ .bInterfaceSubClass = 0x42, // USB_DT_XID
+ .bInterfaceProtocol = 0,
+ .iInterface = STR_EMPTY,
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | 0x01,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = 8,
+ .bInterval = 16,
+ },
+ },
+ },
+ {
+ .bInterfaceNumber = 1,
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 0,
+ .bInterfaceClass = 0x59,
+ .bInterfaceSubClass = 0,
+ .bInterfaceProtocol = 0,
+ .iInterface = STR_EMPTY,
+ },
+};
+
+static const USBDescDevice desc_device = {
+ .bcdUSB = 0x0110,
+ .bDeviceClass = 0,
+ .bDeviceSubClass = 0,
+ .bDeviceProtocol = 0,
+ .bMaxPacketSize0 = 64,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 2,
+ .bConfigurationValue = 1,
+ .iConfiguration = STR_EMPTY,
+ .bmAttributes = 0x00,
+ .bMaxPower = 0x00,
+ .nif = ARRAY_SIZE(desc_iface),
+ .ifs = desc_iface,
+ },
+ },
+};
+
+static const USBDesc desc_xbox_dvd_playback_kit = {
+ .id = {
+ .idVendor = 0x045e,
+ .idProduct = 0x0284,
+ .bcdDevice = 0x0100,
+ .iManufacturer = STR_EMPTY,
+ .iProduct = STR_EMPTY,
+ .iSerialNumber = STR_EMPTY,
+ },
+ .full = &desc_device,
+};
+
+static const XIDDesc desc_xid_xbox_dvd_playback_kit = {
+ .bLength = 0x08,
+ .bDescriptorType = USB_DT_XID,
+ .bcdXid = 0x0100,
+ .bType = XID_DEVICETYPE_DVD_PLAYBACK_KIT,
+ .bSubType = XID_DEVICESUBTYPE_DVD_PLAYBACK_KIT,
+ .bMaxInputReportSize = 0x06,
+ .bMaxOutputReportSize = 0x00,
+};
+
+struct {
+ uint64_t btn;
+ uint16_t id;
+} static const dvd_button_ids[] = {
+ {DVD_BUTTON_UP, 0x0AA6},
+ {DVD_BUTTON_LEFT, 0x0AA9},
+ {DVD_BUTTON_SELECT, 0x0A0B},
+ {DVD_BUTTON_RIGHT, 0x0AA8},
+ {DVD_BUTTON_DOWN, 0x0AA7},
+ {DVD_BUTTON_DISPLAY, 0x0AD5},
+ {DVD_BUTTON_REVERSE, 0x0AE2},
+ {DVD_BUTTON_PLAY, 0x0AEA},
+ {DVD_BUTTON_FORWARD, 0x0AE3},
+ {DVD_BUTTON_SKIP_DOWN, 0x0ADD},
+ {DVD_BUTTON_STOP, 0x0AE0},
+ {DVD_BUTTON_PAUSE, 0x0AE6},
+ {DVD_BUTTON_SKIP_UP, 0x0ADF},
+ {DVD_BUTTON_TITLE, 0x0AE5},
+ {DVD_BUTTON_INFO, 0x0AC3},
+ {DVD_BUTTON_MENU, 0x0AF7},
+ {DVD_BUTTON_BACK, 0x0AD8},
+ {DVD_BUTTON_1, 0x0ACE},
+ {DVD_BUTTON_2, 0x0ACD},
+ {DVD_BUTTON_3, 0x0ACC},
+ {DVD_BUTTON_4, 0x0ACB},
+ {DVD_BUTTON_5, 0x0ACA},
+ {DVD_BUTTON_6, 0x0AC9},
+ {DVD_BUTTON_7, 0x0AC8},
+ {DVD_BUTTON_8, 0x0AC7},
+ {DVD_BUTTON_9, 0x0AC6},
+ {DVD_BUTTON_0, 0x0ACF},
+ // Media Center Extender Remote
+ {MCE_BUTTON_POWER, 0x0AC4},
+ {MCE_BUTTON_MY_TV, 0x0A31},
+ {MCE_BUTTON_MY_MUSIC, 0x0A09},
+ {MCE_BUTTON_MY_PICTURES, 0x0A06},
+ {MCE_BUTTON_MY_VIDEOS, 0x0A07},
+ {MCE_BUTTON_RECORD, 0x0AE8},
+ {MCE_BUTTON_START, 0x0A25},
+ {MCE_BUTTON_VOL_UP, 0x0AD0},
+ {MCE_BUTTON_VOL_DOWN, 0x0AD1},
+ {MCE_BUTTON_MUTE, 0x0AC0},
+ {MCE_BUTTON_CH_UP, 0x0AD2},
+ {MCE_BUTTON_CH_DOWN, 0x0AD3},
+ {MCE_BUTTON_RECORDED_TV, 0x0A65},
+ {MCE_BUTTON_LIVE_TV, 0x0A18},
+ {MCE_BUTTON_STAR, 0x0A28},
+ {MCE_BUTTON_POUND, 0x0A29},
+ {MCE_BUTTON_CLEAR, 0x0AF9},
+};
+
+static void xbox_dvd_playback_kit_realize(USBDevice *dev, Error **errp) {
+ XboxDVDPlaybackKitState *s = (XboxDVDPlaybackKitState *) dev;
+
+ usb_desc_init(dev);
+ if (!s->firmware_path) {
+ fprintf(stderr, "Firmware file is required\n");
+ s->firmware_len = 0;
+ return;
+ }
+ int fd = open(s->firmware_path, O_RDONLY | O_BINARY);
+ if (fd < 0) {
+ fprintf(stderr, "Unable to access \"%s\"\n", s->firmware_path);
+ s->firmware_len = 0;
+ return;
+ }
+ size_t size = lseek(fd, 0, SEEK_END);
+ lseek(fd, 0, SEEK_SET);
+ s->firmware_len = read(fd, s->firmware, size);
+ close(fd);
+}
+
+static void xbox_dvd_playback_kit_handle_control(USBDevice *dev, USBPacket *p,
+ int request, int value, int index, int length, uint8_t *data) {
+ XboxDVDPlaybackKitState *s = (XboxDVDPlaybackKitState *) dev;
+
+ int ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
+ if (ret >= 0) {
+ return;
+ }
+
+ switch (request) {
+ case 0xc101:
+ case 0xc102:
+ {
+ uint32_t offset = 0x400 * value;
+ if (offset + length <= s->firmware_len) {
+ memcpy(data, s->firmware + offset, length);
+ p->actual_length = length;
+ } else {
+ p->actual_length = 0;
+ }
+ break;
+ }
+ case 0xc106: // GET_DESCRIPTOR
+ memcpy(data, &desc_xid_xbox_dvd_playback_kit, desc_xid_xbox_dvd_playback_kit.bLength);
+ p->actual_length = desc_xid_xbox_dvd_playback_kit.bLength;
+ break;
+ case 0xa101: // GET_REPORT
+ default:
+ p->actual_length = 0;
+ p->status = USB_RET_STALL;
+ break;
+ }
+}
+
+static void update_dvd_kit_input(XboxDVDPlaybackKitState *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.bReportId = 0x00;
+ s->in_state.bLength = 0x06;
+ s->in_state.wButton = 0x0000;
+ s->in_state.wTimer = MIN(g_get_monotonic_time() / 1000 - s->last_button, 0xffff);
+ if (state->dvdKit.buttons) {
+ for (int i = 0; i < sizeof(dvd_button_ids) / sizeof(dvd_button_ids[0]); i++) {
+ if ((1ULL << i) & state->dvdKit.buttons) {
+ s->in_state.wButton = dvd_button_ids[i].id;
+ s->last_button = g_get_monotonic_time() / 1000;
+ return;
+ }
+ }
+ }
+}
+
+static void xbox_dvd_playback_kit_handle_data(USBDevice *dev, USBPacket *p) {
+ XboxDVDPlaybackKitState *s = DO_UPCAST(XboxDVDPlaybackKitState, dev, dev);
+
+ switch (p->pid) {
+ case USB_TOKEN_IN:
+ if ((g_get_monotonic_time() / 1000 - s->last_packet) < 60) {
+ p->status = USB_RET_NAK;
+ return;
+ }
+ s->last_packet = g_get_monotonic_time() / 1000;
+ update_dvd_kit_input(s);
+ usb_packet_copy(p, &s->in_state, s->in_state.bLength);
+ break;
+ case USB_TOKEN_OUT:
+ default:
+ break;
+ }
+}
+
+static Property xid_properties[] = {
+ DEFINE_PROP_UINT8("index", XboxDVDPlaybackKitState, device_index, 0),
+ DEFINE_PROP_STRING("firmware", XboxDVDPlaybackKitState, firmware_path),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void xbox_dvd_playback_kit_class_init(ObjectClass *klass, void *class_data) {
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->product_desc = "Microsoft Xbox DVD Playback Kit";
+ uc->usb_desc = &desc_xbox_dvd_playback_kit;
+ uc->realize = xbox_dvd_playback_kit_realize;
+ uc->handle_control = xbox_dvd_playback_kit_handle_control;
+ uc->handle_data = xbox_dvd_playback_kit_handle_data;
+
+ device_class_set_props(dc, xid_properties);
+ dc->desc = "Microsoft Xbox DVD Playback Kit";
+}
+
+static const TypeInfo xbox_dvd_playback_kit_info = {
+ .name = TYPE_USB_XBOX_DVD_PLAYBACK_KIT,
+ .parent = TYPE_USB_DEVICE,
+ .instance_size = sizeof(XboxDVDPlaybackKitState),
+ .class_init = xbox_dvd_playback_kit_class_init,
+};
+
+static void usb_xbox_dvd_playback_kit_register_types(void) {
+ type_register_static(&xbox_dvd_playback_kit_info);
+}
+
+type_init(usb_xbox_dvd_playback_kit_register_types)
diff --git a/hw/xbox/xid.h b/hw/xbox/xid.h
index 5acbed0d82..2ca9d4877e 100644
--- a/hw/xbox/xid.h
+++ b/hw/xbox/xid.h
@@ -48,14 +48,17 @@
#define XID_GET_CAPABILITIES 0x01
#define XID_DEVICETYPE_GAMEPAD 0x01
+#define XID_DEVICETYPE_DVD_PLAYBACK_KIT 0x03
#define XID_DEVICETYPE_STEEL_BATTALION 0x80
+#define XID_DEVICESUBTYPE_DVD_PLAYBACK_KIT 0x00
#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 TYPE_USB_XBOX_DVD_PLAYBACK_KIT "xbox-dvd-playback-kit"
#define GAMEPAD_A 0
#define GAMEPAD_B 1
diff --git a/ui/xemu-input.c b/ui/xemu-input.c
index e20bfcfcf8..bb3839a86d 100644
--- a/ui/xemu-input.c
+++ b/ui/xemu-input.c
@@ -104,10 +104,17 @@ static const char **port_index_to_settings_key_map[] = {
static const char **port_index_to_driver_settings_key_map[] = {
&g_config.input.bindings.port1_driver,
&g_config.input.bindings.port2_driver,
- &g_config.input.bindings.port3_driver,
+ &g_config.input.bindings.port3_driver,
&g_config.input.bindings.port4_driver
};
+static const char **port_index_to_dvd_firmware_key_map[] = {
+ &g_config.input.bindings.port1_dvd_firmware,
+ &g_config.input.bindings.port2_dvd_firmware,
+ &g_config.input.bindings.port3_dvd_firmware,
+ &g_config.input.bindings.port4_dvd_firmware,
+};
+
static int *peripheral_types_settings_map[4][2] = {
{ &g_config.input.peripherals.port1.peripheral_type_0,
&g_config.input.peripherals.port1.peripheral_type_1 },
@@ -132,6 +139,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 int sdl_dvd_kit_kbd_scancode_map[44];
static const char *get_bound_driver(int port)
{
@@ -150,6 +158,8 @@ static const char *get_bound_driver(int port)
return DRIVER_S;
if (strcmp(driver, DRIVER_STEEL_BATTALION) == 0)
return DRIVER_STEEL_BATTALION;
+ if (strcmp(driver, DRIVER_DVD_PLAYBACK_KIT) == 0)
+ return DRIVER_DVD_PLAYBACK_KIT;
return DRIVER_DUKE;
}
@@ -321,6 +331,54 @@ void xemu_input_init(void)
}
}
+ {
+ int i = 0;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.up;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.left;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.select;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.right;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.down;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.display;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.reverse;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.play;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.forward;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.skip_down;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.stop;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.pause;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.skip_up;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.title;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.info;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.menu;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.back;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.button1;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.button2;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.button3;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.button4;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.button5;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.button6;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.button7;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.button8;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.button9;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.button0;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.power;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.my_tv;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.my_music;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.my_pictures;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.my_videos;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.record;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.start;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.volume_up;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.volume_down;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.mute;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.channel_up;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.channel_down;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.recorded_tv;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.live_tv;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.star;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.pound;
+ sdl_dvd_kit_kbd_scancode_map[i++] = g_config.input.keyboard_dvd_kit_scancode_map.clear;
+ }
+
bound_drivers[0] = get_bound_driver(0);
bound_drivers[1] = get_bound_driver(1);
bound_drivers[2] = get_bound_driver(2);
@@ -375,6 +433,14 @@ void xemu_save_peripheral_settings(int player_index, int peripheral_index,
peripheral_parameter == NULL ? "" : peripheral_parameter);
}
+void xemu_save_dvd_firmware_settings(int player_index, const char *firmware)
+{
+ ControllerState *state = bound_controllers[player_index];
+ state->dvdKit.firmware = g_strdup(firmware);
+ xemu_settings_set_string(port_index_to_dvd_firmware_key_map[player_index],
+ firmware);
+}
+
void xemu_input_process_sdl_events(const SDL_Event *event)
{
if (event->type == SDL_CONTROLLERDEVICEADDED) {
@@ -403,6 +469,7 @@ void xemu_input_process_sdl_events(const SDL_Event *event)
new_con->peripheral_types[1] = PERIPHERAL_NONE;
new_con->peripherals[0] = NULL;
new_con->peripherals[1] = NULL;
+ new_con->dvdKit.firmware = NULL;
char guid_buf[35] = { 0 };
SDL_JoystickGetGUIDString(new_con->sdl_joystick_guid, guid_buf, sizeof(guid_buf));
@@ -540,6 +607,7 @@ void xemu_input_update_sdl_kbd_controller_state(ControllerState *state)
{
state->gp.buttons = 0;
state->sbc.buttons = 0;
+ state->dvdKit.buttons = 0;
memset(state->gp.axis, 0, sizeof(state->gp.axis));
memset(state->sbc.axis, 0, sizeof(state->sbc.axis));
@@ -690,6 +758,10 @@ void xemu_input_update_sdl_kbd_controller_state(ControllerState *state)
state->sbc.axis[SBC_AXIS_MIDDLE_PEDAL] = 32767;
state->sbc.previousButtons = state->sbc.buttons;
+ } else if (strcmp(bound_driver, DRIVER_DVD_PLAYBACK_KIT) == 0) {
+ for (int i = 0; i < 44; i++) {
+ state->dvdKit.buttons |= (unsigned long long)kbd[sdl_dvd_kit_kbd_scancode_map[i]] << i;
+ }
} else {
// Update Gamepad Buttons
for (int i = 0; i < 15; i++) {
@@ -952,7 +1024,9 @@ void xemu_input_bind(int index, ControllerState *state, int save)
QDict *usbhub_qdict = NULL;
DeviceState *usbhub_dev = NULL;
- bool hasInternalHub = strcmp(bound_drivers[index], DRIVER_STEEL_BATTALION) != 0;
+ bool hasInternalHub = strcmp(bound_drivers[index], DRIVER_STEEL_BATTALION) != 0
+ && strcmp(bound_drivers[index], DRIVER_DVD_PLAYBACK_KIT) != 0;
+ bool hasFirmware = strcmp(bound_drivers[index], DRIVER_DVD_PLAYBACK_KIT) == 0;
if (hasInternalHub) {
// Create controller's internal USB hub.
@@ -985,6 +1059,12 @@ void xemu_input_bind(int index, ControllerState *state, int save)
qdict_put_str(qdict, "port", tmp);
g_free(tmp);
+ // Specify DVD firmware
+ if (hasFirmware) {
+ qdict_put_str(qdict, "firmware", *port_index_to_dvd_firmware_key_map[index]);
+ state->dvdKit.firmware = (char*) *port_index_to_dvd_firmware_key_map[index];
+ }
+
// Create the device
QemuOpts *opts = qemu_opts_from_qdict(qemu_find_opts("device"), qdict, &error_abort);
DeviceState *dev = qdev_device_add(opts, &error_abort);
@@ -1008,8 +1088,8 @@ 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;
+ bool hasInternalHub = strcmp(bound_drivers[player_index], DRIVER_STEEL_BATTALION) != 0
+ && strcmp(bound_drivers[player_index], DRIVER_DVD_PLAYBACK_KIT) != 0;
assert(hasInternalHub);
ControllerState *player = bound_controllers[player_index];
@@ -1134,11 +1214,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;
+ bool hasInternalHub = strcmp(bound_drivers[port], DRIVER_STEEL_BATTALION) != 0
+ && strcmp(bound_drivers[port], DRIVER_DVD_PLAYBACK_KIT) != 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 3fecc7ec29..2e76eee2b3 100644
--- a/ui/xemu-input.h
+++ b/ui/xemu-input.h
@@ -33,10 +33,12 @@
#define DRIVER_DUKE "usb-xbox-gamepad"
#define DRIVER_S "usb-xbox-gamepad-s"
#define DRIVER_STEEL_BATTALION "usb-steel-battalion"
+#define DRIVER_DVD_PLAYBACK_KIT "xbox-dvd-playback-kit"
#define DRIVER_DUKE_DISPLAY_NAME "Xbox Controller"
#define DRIVER_S_DISPLAY_NAME "Xbox Controller S"
#define DRIVER_STEEL_BATTALION_DISPLAY_NAME "Steel Battalion Controller"
+#define DRIVER_DVD_PLAYBACK_KIT_DISPLAY_NAME "Xbox DVD Playback Kit"
enum controller_state_buttons_mask {
CONTROLLER_BUTTON_A = (1 << 0),
@@ -106,6 +108,54 @@ enum steel_battalion_controller_state_buttons_mask {
#define SBC_BUTTON_TUNER_LEFT 0x20000000000ULL
#define SBC_BUTTON_TUNER_RIGHT 0x40000000000ULL
+enum dvd_playback_kit_state_buttons_mask {
+ DVD_BUTTON_UP = (1 << 0),
+ DVD_BUTTON_LEFT = (1 << 1),
+ DVD_BUTTON_SELECT = (1 << 2),
+ DVD_BUTTON_RIGHT = (1 << 3),
+ DVD_BUTTON_DOWN = (1 << 4),
+ DVD_BUTTON_DISPLAY = (1 << 5),
+ DVD_BUTTON_REVERSE = (1 << 6),
+ DVD_BUTTON_PLAY = (1 << 7),
+ DVD_BUTTON_FORWARD = (1 << 8),
+ DVD_BUTTON_SKIP_DOWN = (1 << 9),
+ DVD_BUTTON_STOP = (1 << 10),
+ DVD_BUTTON_PAUSE = (1 << 11),
+ DVD_BUTTON_SKIP_UP = (1 << 12),
+ DVD_BUTTON_TITLE = (1 << 13),
+ DVD_BUTTON_INFO = (1 << 14),
+ DVD_BUTTON_MENU = (1 << 15),
+ DVD_BUTTON_BACK = (1 << 16),
+ DVD_BUTTON_1 = (1 << 17),
+ DVD_BUTTON_2 = (1 << 18),
+ DVD_BUTTON_3 = (1 << 19),
+ DVD_BUTTON_4 = (1 << 20),
+ DVD_BUTTON_5 = (1 << 21),
+ DVD_BUTTON_6 = (1 << 22),
+ DVD_BUTTON_7 = (1 << 23),
+ DVD_BUTTON_8 = (1 << 24),
+ DVD_BUTTON_9 = (1 << 25),
+ DVD_BUTTON_0 = (1 << 26),
+ // Media Center Extender Remote
+ MCE_BUTTON_POWER = (1 << 27),
+ MCE_BUTTON_MY_TV = (1 << 28),
+ MCE_BUTTON_MY_MUSIC = (1 << 29),
+ MCE_BUTTON_MY_PICTURES = (1 << 30),
+ MCE_BUTTON_MY_VIDEOS = (1 << 31),
+ MCE_BUTTON_RECORD = (1ULL << 32),
+ MCE_BUTTON_START = (1ULL << 33),
+ MCE_BUTTON_VOL_UP = (1ULL << 34),
+ MCE_BUTTON_VOL_DOWN = (1ULL << 35),
+ MCE_BUTTON_MUTE = (1ULL << 36),
+ MCE_BUTTON_CH_UP = (1ULL << 37),
+ MCE_BUTTON_CH_DOWN = (1ULL << 38),
+ MCE_BUTTON_RECORDED_TV = (1ULL << 39),
+ MCE_BUTTON_LIVE_TV = (1ULL << 40),
+ MCE_BUTTON_STAR = (1ULL << 41),
+ MCE_BUTTON_POUND = (1ULL << 42),
+ MCE_BUTTON_CLEAR = (1ULL << 43),
+};
+
enum controller_state_axis_index {
CONTROLLER_AXIS_LTRIG,
CONTROLLER_AXIS_RTRIG,
@@ -164,6 +214,11 @@ typedef struct SteelBattalionState {
uint8_t toggleSwitches;
} SteelBattalionState;
+typedef struct {
+ char *firmware;
+ uint64_t buttons;
+} DVDPlaybackKitState;
+
typedef struct ControllerState {
QTAILQ_ENTRY(ControllerState) entry;
@@ -172,6 +227,7 @@ typedef struct ControllerState {
GamepadState gp;
SteelBattalionState sbc;
+ DVDPlaybackKitState dvdKit;
enum controller_input_device_type type;
const char *name;
@@ -213,6 +269,7 @@ int xemu_input_get_controller_default_bind_port(ControllerState *state, int star
void xemu_save_peripheral_settings(int player_index, int peripheral_index,
int peripheral_type,
const char *peripheral_parameter);
+void xemu_save_dvd_firmware_settings(int player_index, const char *firmware);
void xemu_input_set_test_mode(int enabled);
int xemu_input_get_test_mode(void);
diff --git a/ui/xui/gl-helpers.cc b/ui/xui/gl-helpers.cc
index 10a9524ba7..6146758d6a 100644
--- a/ui/xui/gl-helpers.cc
+++ b/ui/xui/gl-helpers.cc
@@ -21,6 +21,7 @@
#include "common.hh"
#include "data/controller_mask.png.h"
#include "data/controller_mask_s.png.h"
+#include "data/dvd_remote_mask.png.h"
#include "data/sb_controller_mask.png.h"
#include "data/logo_sdf.png.h"
#include "data/xemu_64x64.png.h"
@@ -37,7 +38,7 @@
extern int viewport_coords[4];
Fbo *controller_fbo, *xmu_fbo, *logo_fbo;
-GLuint g_controller_duke_tex, g_controller_s_tex, g_sb_controller_tex, g_logo_tex, g_icon_tex, g_xmu_tex;
+GLuint g_controller_duke_tex, g_controller_s_tex, g_sb_controller_tex, g_dvd_remote_tex, g_logo_tex, g_icon_tex, g_xmu_tex;
enum class ShaderType {
Blit,
@@ -474,6 +475,8 @@ void InitCustomRendering(void)
LoadTextureFromMemory(controller_mask_s_data, controller_mask_s_size);
g_sb_controller_tex =
LoadTextureFromMemory(sb_controller_mask_data, sb_controller_mask_size);
+ g_dvd_remote_tex =
+ LoadTextureFromMemory(dvd_remote_mask_data, dvd_remote_mask_size);
g_decal_shader = NewDecalShader(ShaderType::Mask);
controller_fbo = new Fbo(512, 512);
@@ -1115,6 +1118,135 @@ void RenderSteelBattalionController(float frame_x, float frame_y, uint32_t prima
glUseProgram(0);
}
+void RenderDVDRemote(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 dvd_buttons[] = {
+ { 108, 248, 20, 10 }, // UP
+ { 78, 226, 20, 10 }, // LEFT
+ { 106, 225, 24, 11 }, // SELECT
+ { 139, 226, 19, 11 }, // RIGHT
+ { 109, 203, 19, 11 }, // DOWN
+ { 106, 326, 24, 11 }, // DISPLAY
+ { 74, 300, 24, 11 }, // REVERSE
+ { 106, 300, 24, 11 }, // PLAY
+ { 139, 300, 24, 11 }, // FORWARD
+ { 74, 274, 11, 11 }, // SKIP-
+ { 100, 274, 11, 11 }, // STOP
+ { 126, 274, 11, 11 }, // PAUSE
+ { 152, 274, 11, 11 }, // SKIP+
+ { 74, 248, 11, 11 }, // TITLE
+ { 152, 248, 11, 11 }, // INFO
+ { 74, 202, 11, 12 }, // MENU
+ { 152, 202, 11, 12 }, // BACK
+ { 80, 176, 11, 12 }, // 1
+ { 113, 176, 11, 12 }, // 2
+ { 145, 176, 11, 12 }, // 3
+ { 80, 154, 11, 11 }, // 4
+ { 113, 154, 11, 11 }, // 5
+ { 145, 154, 11, 11 }, // 6
+ { 80, 131, 11, 11 }, // 7
+ { 113, 131, 11, 11 }, // 8
+ { 145, 131, 11, 11 }, // 9
+ { 113, 108, 11, 11 }, // 0
+ };
+ const struct rect mce_buttons[] = {
+ { 339, 267, 16, 8 }, // UP
+ { 312, 251, 16, 8 }, // LEFT
+ { 337, 251, 21, 8 }, // SELECT -> OK
+ { 366, 251, 16, 8 }, // RIGHT
+ { 339, 235, 16, 8 }, // DOWN
+ { 340, 40, 14, 8 }, // DISPLAY -> XBOX
+ { 298, 306, 8, 8 }, // REVERSE -> REW
+ { 340, 290, 14,15 }, // PLAY
+ { 389, 306, 8, 8 }, // FORWARD -> FWD
+ { 320, 290, 8, 8 }, // SKIP- -> REPLAY
+ { 343, 313, 8, 8 }, // STOP
+ { 366, 306, 8, 8 }, // PAUSE
+ { 366, 290, 8, 8 }, // SKIP+ -> SKIP
+ { 327, 173, 11, 8 }, // TITLE -> GUIDE
+ { 385, 271, 8, 8 }, // INFO -> MORE
+ { 382, 176, 11, 8 }, // MENU -> DVD MENU
+ { 301, 271, 8, 8 }, // BACK
+ { 307, 154, 15, 8 }, // 1
+ { 340, 154, 15, 8 }, // 2
+ { 372, 154, 15, 8 }, // 3
+ { 307, 134, 15, 8 }, // 4
+ { 340, 134, 15, 8 }, // 5
+ { 372, 134, 15, 8 }, // 6
+ { 307, 115, 15, 8 }, // 7
+ { 340, 115, 15, 8 }, // 8
+ { 372, 115, 15, 8 }, // 9
+ { 340, 95, 15, 8 }, // 0
+ { 395, 342, 8, 8 }, // POWER
+ { 301, 323, 8, 8 }, // MY_TV
+ { 327, 332, 8, 8 }, // MY_MUSIC
+ { 359, 332, 8, 8 }, // MY_PICTURES
+ { 385, 323, 8, 8 }, // MY_VIDEOS
+ { 320, 306, 8, 8 }, // RECORD
+ { 337, 215, 21, 8 }, // START
+ { 298, 225, 21, 8 }, // VOL_UP
+ { 304, 206, 21, 8 }, // VOL_DOWN
+ { 337, 196, 21, 8 }, // MUTE
+ { 376, 225, 21, 8 }, // CH_UP
+ { 369, 206, 21, 8 }, // CH_DOWN
+ { 301, 176, 11, 8 }, // RECORDED_TV
+ { 356, 173, 11, 8 }, // LIVE_TV
+ { 307, 95, 15, 8 }, // STAR
+ { 372, 95, 15, 8 }, // POUND
+ { 327, 72, 11, 8 }, // CLEAR
+ };
+ const struct rect mce_enter_button =
+ { 356, 72, 11, 8 }; // ENTER
+
+ glUseProgram(g_decal_shader->prog);
+ glBindVertexArray(g_decal_shader->vao);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, g_dvd_remote_tex);
+
+ glBlendEquation(GL_FUNC_ADD);
+ glBlendFunc(GL_ONE, GL_ZERO);
+
+ // Render controller texture
+ RenderDecal(g_decal_shader, frame_x + 0, frame_y + 0,
+ tex_items[obj_controller].w, tex_items[obj_controller].h,
+ tex_items[obj_controller].x, tex_items[obj_controller].y,
+ tex_items[obj_controller].w, 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 < sizeof(dvd_buttons) / sizeof(dvd_buttons[0]); i++) {
+ if (state->dvdKit.buttons & (1ULL << i)) {
+ RenderDecal(g_decal_shader, frame_x + dvd_buttons[i].x,
+ frame_y + dvd_buttons[i].y, dvd_buttons[i].w, dvd_buttons[i].h, 0,
+ 0, 1, 1, 0, 0, primary_color + 0xff);
+ }
+ }
+ for (int i = 0; i < sizeof(mce_buttons) / sizeof(mce_buttons[0]); i++) {
+ if (state->dvdKit.buttons & (1ULL << i)) {
+ RenderDecal(g_decal_shader, frame_x + mce_buttons[i].x,
+ frame_y + mce_buttons[i].y, mce_buttons[i].w, mce_buttons[i].h, 0,
+ 0, 1, 1, 0, 0, primary_color + 0xff);
+ }
+ }
+ if (state->dvdKit.buttons & DVD_BUTTON_SELECT) {
+ RenderDecal(g_decal_shader, frame_x + mce_enter_button.x,
+ frame_y + mce_enter_button.y, mce_enter_button.w, mce_enter_button.h, 0,
+ 0, 1, 1, 0, 0, primary_color + 0xff);
+ }
+
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Blend with controller
+
+ glBindVertexArray(0);
+ glUseProgram(0);
+}
+
void RenderController(float frame_x, float frame_y, uint32_t primary_color,
uint32_t secondary_color, ControllerState *state)
{
@@ -1127,6 +1259,8 @@ void RenderController(float frame_x, float frame_y, uint32_t primary_color,
else if (strcmp(bound_drivers[state->bound], DRIVER_DUKE) == 0)
RenderDukeController(frame_x, frame_y, primary_color, secondary_color,
state);
+ else if (strcmp(bound_drivers[state->bound], DRIVER_DVD_PLAYBACK_KIT) == 0)
+ RenderDVDRemote(frame_x, frame_y, primary_color, secondary_color, state);
}
void RenderControllerPort(float frame_x, float frame_y, int i,
diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc
index ab1fd7fe6a..a6f635c264 100644
--- a/ui/xui/main-menu.cc
+++ b/ui/xui/main-menu.cc
@@ -171,20 +171,23 @@ void MainMenuInputView::Draw()
driver = DRIVER_S_DISPLAY_NAME;
else if (strcmp(driver, DRIVER_STEEL_BATTALION) == 0)
driver = DRIVER_STEEL_BATTALION_DISPLAY_NAME;
-
+ else if (strcmp(driver, DRIVER_DVD_PLAYBACK_KIT) == 0)
+ driver = DRIVER_DVD_PLAYBACK_KIT_DISPLAY_NAME;
+
ImGui::SetNextItemWidth(-FLT_MIN);
if (ImGui::BeginCombo("###InputDrivers", driver,
ImGuiComboFlags_NoArrowButton)) {
- const char *available_drivers[] = {
- DRIVER_DUKE,
+ const char *available_drivers[] = {
+ DRIVER_DUKE,
DRIVER_S,
- DRIVER_STEEL_BATTALION
+ DRIVER_STEEL_BATTALION,
+ DRIVER_DVD_PLAYBACK_KIT
};
- const char *driver_display_names[] = {
- DRIVER_DUKE_DISPLAY_NAME,
- DRIVER_S_DISPLAY_NAME,
- DRIVER_STEEL_BATTALION_DISPLAY_NAME
-
+ const char *driver_display_names[] = {
+ DRIVER_DUKE_DISPLAY_NAME,
+ DRIVER_S_DISPLAY_NAME,
+ DRIVER_STEEL_BATTALION_DISPLAY_NAME,
+ DRIVER_DVD_PLAYBACK_KIT_DISPLAY_NAME
};
bool is_selected = false;
int num_drivers = sizeof(driver_display_names) / sizeof(driver_display_names[0]);
@@ -329,8 +332,9 @@ void MainMenuInputView::Draw()
ImGui::SetCursorPos(pos);
if (bound_state) {
- bool hasInternalHub =
- strcmp(bound_drivers[active], DRIVER_STEEL_BATTALION) != 0;
+ bool hasInternalHub = strcmp(bound_drivers[active], DRIVER_STEEL_BATTALION) != 0
+ && strcmp(bound_drivers[active], DRIVER_DVD_PLAYBACK_KIT) != 0;
+ bool hasFirmware = strcmp(bound_drivers[active], DRIVER_DVD_PLAYBACK_KIT) == 0;
if (hasInternalHub) {
SectionTitle("Expansion Slots");
// Begin a 2-column layout to render the expansion slots
@@ -492,6 +496,19 @@ void MainMenuInputView::Draw()
ImGui::PopStyleVar(); // ItemSpacing
ImGui::Columns(1);
}
+ if (hasFirmware) {
+ SectionTitle("Firmware");
+ const char *firmware_filters = ".bin Files\0*.bin\0All Files\0*.*\0";
+ const char *firmware_path = NULL;
+ if (bound_state->dvdKit.firmware == NULL)
+ firmware_path = g_strdup("");
+ else
+ firmware_path = g_strdup(bound_state->dvdKit.firmware);
+ if (FilePicker("DVD Kit Firmware", &firmware_path, firmware_filters)) {
+ xemu_save_dvd_firmware_settings(active, firmware_path);
+ }
+ g_free((void *)firmware_path);
+ }
}
SectionTitle("Options");