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 @@ + + + + + + + + DVD + DISPLAY + + REVERSE + PLAY + FORWARD + + + + SKIP- + STOP + PAUSE + SKIP+ + + + + + TITLE + INFO + + + + + SELECT + + + + + + MENU + BACK + + + + + + + + + + + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 + X + XBOX + + + + + + + + + + + My TV + My Music + My Pictures + My Videos + + + + STOP + REC + PAUSE + + + REW + FWD + + + + REPLAY + + SKIP + + + BACK + MORE + + + + + + OK + START + + + + + + + VOL + MUTE + CH/PG + + + + + - + - + RECORDED + TV + GUIDE + LIVE TV + DVD + MENU + + + + + + + + + + + + + + + + + ABC + DEF + 1 + 2 + 3 + GHI + JKL + MNO + 4 + 5 + 6 + PQRS + TUV + WXYZ + 7 + 8 + 9 + + * + 0 + # + CLEAR + ENTER + + + XBOX + + + 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");