From 33d2b27b916da95c19d177ae364acfe7c1fe2acd Mon Sep 17 00:00:00 2001 From: Florin9doi Date: Tue, 2 Jul 2024 00:08:10 +0300 Subject: [PATCH] USB: Top Shot Fearmaster device emulation --- rpcs3/Emu/CMakeLists.txt | 1 + rpcs3/Emu/Cell/lv2/sys_usbd.cpp | 15 + rpcs3/Emu/Io/TopShotFearmaster.cpp | 425 ++++++++++++++++++ rpcs3/Emu/Io/TopShotFearmaster.h | 17 + rpcs3/Emu/Io/pad_types.h | 9 +- rpcs3/Emu/Io/topshotfearmaster_config.h | 56 +++ rpcs3/Input/product_info.cpp | 11 + rpcs3/Input/product_info.h | 2 + rpcs3/emucore.vcxproj | 3 + rpcs3/emucore.vcxproj.filters | 9 + .../rpcs3qt/emulated_pad_settings_dialog.cpp | 31 +- rpcs3/rpcs3qt/emulated_pad_settings_dialog.h | 1 + rpcs3/rpcs3qt/main_window.cpp | 6 + rpcs3/rpcs3qt/main_window.ui | 6 + rpcs3/rpcs3qt/pad_settings_dialog.cpp | 6 + 15 files changed, 593 insertions(+), 5 deletions(-) create mode 100644 rpcs3/Emu/Io/TopShotFearmaster.cpp create mode 100644 rpcs3/Emu/Io/TopShotFearmaster.h create mode 100644 rpcs3/Emu/Io/topshotfearmaster_config.h diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 91aa60e14d..3fafe5c247 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -418,6 +418,7 @@ target_sources(rpcs3_emu PRIVATE Io/recording_config.cpp Io/Skylander.cpp Io/TopShotElite.cpp + Io/TopShotFearmaster.cpp Io/Turntable.cpp Io/usb_device.cpp Io/usb_vfs.cpp diff --git a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp index a4f4be8485..8787fece48 100644 --- a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp @@ -22,11 +22,13 @@ #include "Emu/Io/ghltar_config.h" #include "Emu/Io/guncon3_config.h" #include "Emu/Io/topshotelite_config.h" +#include "Emu/Io/topshotfearmaster_config.h" #include "Emu/Io/Buzz.h" #include "Emu/Io/buzz_config.h" #include "Emu/Io/GameTablet.h" #include "Emu/Io/GunCon3.h" #include "Emu/Io/TopShotElite.h" +#include "Emu/Io/TopShotFearmaster.h" #include "Emu/Io/Turntable.h" #include "Emu/Io/turntable_config.h" #include "Emu/Io/RB3MidiKeyboard.h" @@ -47,6 +49,7 @@ cfg_turntables g_cfg_turntable; cfg_usios g_cfg_usio; cfg_guncon3 g_cfg_guncon3; cfg_topshotelite g_cfg_topshotelite; +cfg_topshotfearmaster g_cfg_topshotfearmaster; template <> void fmt_class_string::format(std::string& out, u64 arg) @@ -891,6 +894,18 @@ void connect_usb_controller(u8 index, input::product_type type) usbh->connect_usb_device(dev, true); usbh->pad_to_usb.emplace(index, std::pair(type, dev)); } + if (type == input::product_type::top_shot_fearmaster) + { + if (!g_cfg_topshotfearmaster.load()) + { + sys_usbd.notice("Could not load Top Shot Fearmaster config. Using defaults."); + } + + sys_usbd.success("Adding emulated Top Shot Fearmaster (controller %d)", index); + std::shared_ptr dev = std::make_shared(index, usbh->get_new_location()); + usbh->connect_usb_device(dev, true); + usbh->pad_to_usb.emplace(index, std::pair(type, dev)); + } if (type == input::product_type::udraw_gametablet) { sys_usbd.success("Adding emulated uDraw GameTablet (controller %d)", index); diff --git a/rpcs3/Emu/Io/TopShotFearmaster.cpp b/rpcs3/Emu/Io/TopShotFearmaster.cpp new file mode 100644 index 0000000000..bfae945365 --- /dev/null +++ b/rpcs3/Emu/Io/TopShotFearmaster.cpp @@ -0,0 +1,425 @@ +#include "stdafx.h" +#include "TopShotFearmaster.h" +#include "MouseHandler.h" +#include "Emu/IdManager.h" +#include "Emu/Io/topshotfearmaster_config.h" +#include "Emu/Cell/lv2/sys_usbd.h" +#include "Emu/system_config.h" +#include "Input/pad_thread.h" + +LOG_CHANNEL(topshotfearmaster_log); + +#define TSF_CALIB_LOG false +// 0 < Calib_Top < Calib_Bottom < 0x2ff +// 0 < Calib_Right < Calib_Left < 0x3ff +constexpr u16 TSF_CALIB_TOP = 20; +constexpr u16 TSF_CALIB_BOTTOM = 840; +constexpr u16 TSF_CALIB_LEFT = 900; +constexpr u16 TSF_CALIB_RIGHT = 120; +constexpr u16 TSF_CALIB_DIST = 95; + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](topshotfearmaster_btn value) + { + switch (value) + { + case topshotfearmaster_btn::trigger: return "Trigger"; + case topshotfearmaster_btn::heartrate: return "Heartrate"; + case topshotfearmaster_btn::square: return "Square"; + case topshotfearmaster_btn::cross: return "Cross"; + case topshotfearmaster_btn::circle: return "Circle"; + case topshotfearmaster_btn::triangle: return "Triangle"; + case topshotfearmaster_btn::select: return "Select"; + case topshotfearmaster_btn::start: return "Start"; + case topshotfearmaster_btn::l3: return "L3"; + case topshotfearmaster_btn::ps: return "PS"; + case topshotfearmaster_btn::dpad_up: return "D-Pad Up"; + case topshotfearmaster_btn::dpad_down: return "D-Pad Down"; + case topshotfearmaster_btn::dpad_left: return "D-Pad Left"; + case topshotfearmaster_btn::dpad_right: return "D-Pad Right"; + case topshotfearmaster_btn::ls_x: return "Left Stick X-Axis"; + case topshotfearmaster_btn::ls_y: return "Left Stick Y-Axis"; + case topshotfearmaster_btn::count: return "Count"; + } + + return unknown; + }); +} + +#pragma pack(push, 1) +struct TopShotFearmaster_data +{ + uint8_t btn_square : 1; + uint8_t btn_cross : 1; + uint8_t btn_circle : 1; + uint8_t btn_triangle : 1; + uint8_t : 1; + uint8_t btn_trigger: 1; + uint8_t : 2; + + uint8_t btn_select : 1; + uint8_t btn_start : 1; + uint8_t btn_l3 : 1; + uint8_t btn_heartrate : 1; + uint8_t btn_ps: 1; + uint8_t : 3; + + uint8_t dpad; + uint8_t stick_lx; + uint8_t stick_ly; + union + { + struct + { + uint8_t stick_rx; + uint8_t stick_ry; + }; + uint16_t heartrate; + }; + + uint8_t led_lx_hi : 8; + uint8_t led_ly_hi : 6; + uint8_t led_lx_lo : 2; + uint8_t detect_l : 4; + uint8_t led_ly_lo : 4; + + uint8_t led_rx_hi : 8; + uint8_t led_ry_hi : 6; + uint8_t led_rx_lo : 2; + uint8_t detect_r : 4; + uint8_t led_ry_lo : 4; + + uint8_t : 8; + uint8_t : 8; + uint8_t : 8; + + uint8_t trigger; + uint8_t : 8; + uint8_t : 8; + uint16_t unk[4]; +}; +#pragma pack(pop) + +enum +{ + Dpad_North, + Dpad_NE, + Dpad_East, + Dpad_SE, + Dpad_South, + Dpad_SW, + Dpad_West, + Dpad_NW, + Dpad_None = 0x0f +}; + +usb_device_topshotfearmaster::usb_device_topshotfearmaster(u32 controller_index, const std::array& location) + : usb_device_emulated(location) + , m_controller_index(controller_index) + , m_mode(0) +{ + device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, + UsbDeviceDescriptor { + .bcdUSB = 0x0100, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 0x20, + .idVendor = 0x12ba, + .idProduct = 0x04a1, + .bcdDevice = 0x0108, + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + .bNumConfigurations = 0x01}); + auto& config0 = device.add_node(UsbDescriptorNode(USB_DESCRIPTOR_CONFIG, + UsbDeviceConfiguration { + .wTotalLength = 0x0029, + .bNumInterfaces = 0x01, + .bConfigurationValue = 0x01, + .iConfiguration = 0x00, + .bmAttributes = 0x80, + .bMaxPower = 0x32})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, + UsbDeviceInterface { + .bInterfaceNumber = 0x00, + .bAlternateSetting = 0x00, + .bNumEndpoints = 0x02, + .bInterfaceClass = 0x03, + .bInterfaceSubClass = 0x00, + .bInterfaceProtocol = 0x00, + .iInterface = 0x00})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_HID, + UsbDeviceHID { + .bcdHID = 0x0110, + .bCountryCode = 0x00, + .bNumDescriptors = 0x01, + .bDescriptorType = 0x22, + .wDescriptorLength = 0x0089})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, + UsbDeviceEndpoint { + .bEndpointAddress = 0x81, + .bmAttributes = 0x03, + .wMaxPacketSize = 0x0040, + .bInterval = 0x0a})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, + UsbDeviceEndpoint { + .bEndpointAddress = 0x02, + .bmAttributes = 0x03, + .wMaxPacketSize = 0x0040, + .bInterval = 0x0a})); + + add_string("Dangerous Hunts for Playstation (R) 3"); + add_string("Dangerous Hunts for Playstation (R) 3"); +} + +usb_device_topshotfearmaster::~usb_device_topshotfearmaster() +{ +} + +void usb_device_topshotfearmaster::control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) +{ + transfer->fake = true; + transfer->expected_count = buf_size; + transfer->expected_result = HC_CC_NOERR; + transfer->expected_time = get_timestamp() + 100; + + switch (bmRequestType) + { + case 0U /*silences warning*/ | LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE: // 0x21 + switch (bRequest) + { + case 0x09: // SET_REPORT + ensure(buf_size >= 8); + switch (buf[0]) + { + case 0x01: + topshotfearmaster_log.trace("Leds: %s/%s/%s/%s", + buf[2] & 1 ? "ON" : "OFF", + buf[2] & 2 ? "ON" : "OFF", + buf[2] & 4 ? "ON" : "OFF", + buf[2] & 8 ? "ON" : "OFF"); + break; + case 0x82: + m_mode = buf[2]; + break; + default: + topshotfearmaster_log.error("Unhandled SET_REPORT packet : %x", buf[0]); + break; + } + break; + default: + topshotfearmaster_log.error("Unhandled Request: 0x%02X/0x%02X", bmRequestType, bRequest); + break; + } + break; + default: + usb_device_emulated::control_transfer(bmRequestType, bRequest, wValue, wIndex, wLength, buf_size, buf, transfer); + break; + } +} + +extern bool is_input_allowed(); + +static int get_heartrate_sensor_value(u8 heartrate) +{ + static const uint16_t sensor_data[] = { + 0x7af, 0x771, 0x737, 0x6ff, 0x6cb, 0x69a, 0x66c, 0x640, 0x616, 0x5ef, // 30-39 + 0x5ca, 0x5a6, 0x584, 0x563, 0x544, 0x527, 0x50a, 0x4ef, 0x4d5, 0x4bc, // 40-49 + 0x4a4, 0x48d, 0x477, 0x462, 0x44d, 0x439, 0x426, 0x413, 0x401, 0x3f0, // 50-59 + 0x3e0, 0x3cf, 0x3c0, 0x3b1, 0x3a2, 0x394, 0x386, 0x379, 0x36c, 0x35f, // 60-69 + 0x353, 0x347, 0x33b, 0x330, 0x325, 0x31b, 0x310, 0x306, 0x2fc, 0x2f3, // 70-79 + 0x2e9, 0x2e0, 0x2d7, 0x2ce, 0x2c6, 0x2bd, 0x2b5, 0x2ad, 0x2a6, 0x29e, // 80-89 + 0x297, 0x290, 0x289, 0x282, 0x27b, 0x274, 0x26e, 0x267, 0x261, 0x25b, // 90-99 + 0x255, 0x24f, 0x249, 0x243, 0x23e, 0x239, 0x233, 0x22e, 0x229, 0x224, // 100-109 + 0x21f, 0x21a, 0x215, 0x210, 0x20c, 0x207, 0x203, 0x1fe, 0x1fa, 0x1f6, // 110-119 + 0x1f2, 0x1ed, 0x1e9, 0x1e5, 0x1e2, 0x1de, 0x1da, 0x1d6, 0x1d3, 0x1cf, // 120-129 + 0x1cc, 0x1c8, 0x1c5, 0x1c1, 0x1be, 0x1bb, 0x1b7, 0x1b4, 0x1b1, 0x1ae // 130-139 + }; + + if (heartrate < 30 || heartrate > 139) + { + return 0; + } + + return sensor_data[heartrate - 30]; +} + +static void set_sensor_pos(struct TopShotFearmaster_data* ts, s32 led_lx, s32 led_ly, s32 led_rx, s32 led_ry, s32 detect_l, s32 detect_r) +{ + ts->led_lx_hi = led_lx >> 2; + ts->led_lx_lo = led_lx & 0x3; + ts->led_ly_hi = led_ly >> 4; + ts->led_ly_lo = led_ly & 0xf; + + ts->led_rx_hi = led_rx >> 2; + ts->led_rx_lo = led_rx & 0x3; + ts->led_ry_hi = led_ry >> 4; + ts->led_ry_lo = led_ry & 0xf; + + ts->detect_l = detect_l; + ts->detect_r = detect_r; +} + +static void prepare_data(const TopShotFearmaster_data* ts, u8* data) +{ + std::memcpy(data, ts, sizeof(TopShotFearmaster_data)); + topshotfearmaster_log.trace("interrupt_transfer: %s", fmt::buf_to_hexstring(data, sizeof(TopShotFearmaster_data))); +} + +void usb_device_topshotfearmaster::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpoint*/, UsbTransfer* transfer) +{ + ensure(buf_size >= sizeof(TopShotFearmaster_data)); + + transfer->fake = true; + transfer->expected_count = sizeof(TopShotFearmaster_data); + transfer->expected_result = HC_CC_NOERR; + transfer->expected_time = get_timestamp() + 4000; + + struct TopShotFearmaster_data ts{}; + ts.dpad = Dpad_None; + ts.stick_lx = ts.stick_ly = ts.stick_rx = ts.stick_ry = 0x7f; + if (m_mode) + { + set_sensor_pos(&ts, 0x3ff, 0x3ff, 0x3ff, 0x3ff, 0xf, 0xf); + } + ts.unk[0] = ts.unk[1] = ts.unk[2] = ts.unk[3] = 0x0200; + + if (!is_input_allowed()) + { + prepare_data(&ts, buf); + return; + } + + if (g_cfg.io.mouse == mouse_handler::null) + { + topshotfearmaster_log.warning("Top Shot Fearmaster requires a Mouse Handler enabled"); + prepare_data(&ts, buf); + return; + } + + if (m_controller_index >= g_cfg_topshotfearmaster.players.size()) + { + topshotfearmaster_log.warning("Top Shot Fearmaster controllers are only supported for Player1 to Player%d", g_cfg_topshotfearmaster.players.size()); + prepare_data(&ts, buf); + return; + } + + bool up = false, right = false, down = false, left = false; + const auto input_callback = [&ts, &up, &down, &left, &right](topshotfearmaster_btn btn, u16 value, bool pressed) + { + if (!pressed) + return; + + switch (btn) + { + case topshotfearmaster_btn::trigger: ts.btn_trigger |= 1; break; + case topshotfearmaster_btn::heartrate: ts.btn_heartrate |= 1; break; + case topshotfearmaster_btn::square: ts.btn_square |= 1; break; + case topshotfearmaster_btn::cross: ts.btn_cross |= 1; break; + case topshotfearmaster_btn::circle: ts.btn_circle |= 1; break; + case topshotfearmaster_btn::triangle: ts.btn_triangle |= 1; break; + case topshotfearmaster_btn::select: ts.btn_select |= 1; break; + case topshotfearmaster_btn::start: ts.btn_start |= 1; break; + case topshotfearmaster_btn::l3: ts.btn_l3 |= 1; break; + case topshotfearmaster_btn::ps: ts.btn_ps |= 1; break; + case topshotfearmaster_btn::dpad_up: up = true; break; + case topshotfearmaster_btn::dpad_down: down = true; break; + case topshotfearmaster_btn::dpad_left: left = true; break; + case topshotfearmaster_btn::dpad_right: right = true; break; + case topshotfearmaster_btn::ls_x: ts.stick_lx = static_cast(value); break; + case topshotfearmaster_btn::ls_y: ts.stick_ly = static_cast(value); break; + case topshotfearmaster_btn::count: break; + } + }; + + const auto& cfg = ::at32(g_cfg_topshotfearmaster.players, m_controller_index); + + { + std::lock_guard lock(pad::g_pad_mutex); + const auto gamepad_handler = pad::get_current_handler(); + const auto& pads = gamepad_handler->GetPads(); + const auto& pad = ::at32(pads, m_controller_index); + if (pad->m_port_status & CELL_PAD_STATUS_CONNECTED) + { + cfg->handle_input(pad, true, input_callback); + } + } + + if (!up && !right && !down && !left) + ts.dpad = Dpad_None; + else if (up && !left && !right) + ts.dpad = Dpad_North; + else if (up && right) + ts.dpad = Dpad_NE; + else if (right && !up && !down) + ts.dpad = Dpad_East; + else if (down && right) + ts.dpad = Dpad_SE; + else if (down && !left && !right) + ts.dpad = Dpad_South; + else if (down && left) + ts.dpad = Dpad_SW; + else if (left && !up && !down) + ts.dpad = Dpad_West; + else if (up && left) + ts.dpad = Dpad_NW; + + if (m_mode) + { + auto& mouse_handler = g_fxo->get(); + std::lock_guard mouse_lock(mouse_handler.mutex); + + mouse_handler.Init(4); + + const u32 mouse_index = g_cfg.io.mouse == mouse_handler::basic ? 0 : m_controller_index; + if (mouse_index >= mouse_handler.GetMice().size()) + { + prepare_data(&ts, buf); + return; + } + + const Mouse& mouse_data = ::at32(mouse_handler.GetMice(), mouse_index); + cfg->handle_input(mouse_data, input_callback); + ts.trigger = ts.btn_trigger ? 0xff : 0x00; + ts.heartrate = ts.btn_heartrate ? get_heartrate_sensor_value(60) : 0; + + if (mouse_data.x_max <= 0 || mouse_data.y_max <= 0) + { + prepare_data(&ts, buf); + return; + } + + s32 led_lx = 0x3ff - (TSF_CALIB_RIGHT + (mouse_data.x_pos * (TSF_CALIB_LEFT - TSF_CALIB_RIGHT) / mouse_data.x_max) + TSF_CALIB_DIST); + s32 led_rx = 0x3ff - (TSF_CALIB_RIGHT + (mouse_data.x_pos * (TSF_CALIB_LEFT - TSF_CALIB_RIGHT) / mouse_data.x_max) - TSF_CALIB_DIST); + + s32 led_ly = TSF_CALIB_TOP + (mouse_data.y_pos * (TSF_CALIB_BOTTOM - TSF_CALIB_TOP) / mouse_data.y_max); + s32 led_ry = TSF_CALIB_TOP + (mouse_data.y_pos * (TSF_CALIB_BOTTOM - TSF_CALIB_TOP) / mouse_data.y_max); + + u8 detect_l = 0x2, detect_r = 0x2; // 0x2 = led detected / 0xf = undetected + + if (led_lx < 0 || led_lx > 0x3ff || led_ly < 0 || led_ly > 0x3ff) + { + led_lx = 0x3ff; + led_ly = 0x3ff; + detect_l = 0xf; + } + + if (led_rx < 0 || led_rx > 0x3ff || led_ry < 0 || led_ry > 0x3ff) + { + led_rx = 0x3ff; + led_ry = 0x3ff; + detect_r = 0xf; + } + + set_sensor_pos(&ts, led_lx, led_ly, led_rx, led_ry, detect_l, detect_r); + + #if TSF_CALIB_LOG + topshotfearmaster_log.error("L: %d x %d, R: %d x %d", led_lx + TSF_CALIB_DIST, led_ly, led_rx - TSF_CALIB_DIST, led_ry); + #endif + } + + prepare_data(&ts, buf); +} diff --git a/rpcs3/Emu/Io/TopShotFearmaster.h b/rpcs3/Emu/Io/TopShotFearmaster.h new file mode 100644 index 0000000000..7243fb5ef0 --- /dev/null +++ b/rpcs3/Emu/Io/TopShotFearmaster.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Emu/Io/usb_device.h" + +class usb_device_topshotfearmaster: public usb_device_emulated +{ +public: + usb_device_topshotfearmaster(u32 controller_index, const std::array& location); + ~usb_device_topshotfearmaster(); + + void control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) override; + void interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer) override; + +private: + u32 m_controller_index; + u8 m_mode; +}; diff --git a/rpcs3/Emu/Io/pad_types.h b/rpcs3/Emu/Io/pad_types.h index 5e7200abf5..28d1b1eba6 100644 --- a/rpcs3/Emu/Io/pad_types.h +++ b/rpcs3/Emu/Io/pad_types.h @@ -146,10 +146,11 @@ enum CELL_PAD_PCLASS_TYPE_SKATEBOARD = 0x8001, // these are used together with pad->is_fake_pad to capture input events for usbd/gem/move without conflicting with cellPad - CELL_PAD_FAKE_TYPE_FIRST = 0xa000, - CELL_PAD_FAKE_TYPE_GUNCON3 = 0xa000, - CELL_PAD_FAKE_TYPE_TOP_SHOT_ELITE = 0xa001, - CELL_PAD_FAKE_TYPE_GAMETABLET = 0xa003, + CELL_PAD_FAKE_TYPE_FIRST = 0xa000, + CELL_PAD_FAKE_TYPE_GUNCON3 = 0xa000, + CELL_PAD_FAKE_TYPE_TOP_SHOT_ELITE = 0xa001, + CELL_PAD_FAKE_TYPE_TOP_SHOT_FEARMASTER = 0xa002, + CELL_PAD_FAKE_TYPE_GAMETABLET = 0xa003, CELL_PAD_FAKE_TYPE_LAST, CELL_PAD_PCLASS_TYPE_MAX // last item diff --git a/rpcs3/Emu/Io/topshotfearmaster_config.h b/rpcs3/Emu/Io/topshotfearmaster_config.h new file mode 100644 index 0000000000..ef372c9b68 --- /dev/null +++ b/rpcs3/Emu/Io/topshotfearmaster_config.h @@ -0,0 +1,56 @@ +#pragma once + +#include "emulated_pad_config.h" + +#include + +enum class topshotfearmaster_btn +{ + trigger, + heartrate, + square, + cross, + circle, + triangle, + select, + start, + l3, + ps, + dpad_up, + dpad_down, + dpad_left, + dpad_right, + ls_x, + ls_y, + + count +}; + +struct cfg_tsf final : public emulated_pad_config +{ + cfg_tsf(node* owner, const std::string& name) : emulated_pad_config(owner, name) {} + + cfg_pad_btn trigger{this, "Trigger", topshotfearmaster_btn::trigger, pad_button::mouse_button_1}; + cfg_pad_btn heartrate{this, "Heartrate", topshotfearmaster_btn::heartrate, pad_button::mouse_button_2}; + cfg_pad_btn square{this, "Square", topshotfearmaster_btn::square, pad_button::square}; + cfg_pad_btn cross{this, "Cross", topshotfearmaster_btn::cross, pad_button::cross}; + cfg_pad_btn circle{this, "Circle", topshotfearmaster_btn::circle, pad_button::circle}; + cfg_pad_btn triangle{this, "Triangle", topshotfearmaster_btn::triangle, pad_button::triangle}; + cfg_pad_btn select{this, "Select", topshotfearmaster_btn::select, pad_button::select}; + cfg_pad_btn start{this, "Start", topshotfearmaster_btn::start, pad_button::start}; + cfg_pad_btn l3{this, "L3", topshotfearmaster_btn::l3, pad_button::L3}; + cfg_pad_btn ps{this, "PS", topshotfearmaster_btn::ps, pad_button::ps}; + cfg_pad_btn dpad_up{this, "D-Pad Up", topshotfearmaster_btn::dpad_up, pad_button::dpad_up}; + cfg_pad_btn dpad_down{this, "D-Pad Down", topshotfearmaster_btn::dpad_down, pad_button::dpad_down}; + cfg_pad_btn dpad_left{this, "D-Pad Left", topshotfearmaster_btn::dpad_left, pad_button::dpad_left}; + cfg_pad_btn dpad_right{this, "D-Pad Right", topshotfearmaster_btn::dpad_right, pad_button::dpad_right}; + cfg_pad_btn ls_x{this, "Left Stick X-Axis", topshotfearmaster_btn::ls_x, pad_button::ls_x}; + cfg_pad_btn ls_y{this, "Left Stick Y-Axis", topshotfearmaster_btn::ls_y, pad_button::ls_y}; +}; + +struct cfg_topshotfearmaster final : public emulated_pads_config +{ + cfg_topshotfearmaster() : emulated_pads_config("topshotfearmaster") {}; +}; + +extern cfg_topshotfearmaster g_cfg_topshotfearmaster; diff --git a/rpcs3/Input/product_info.cpp b/rpcs3/Input/product_info.cpp index 9e5732a0f9..9ce26e7878 100644 --- a/rpcs3/Input/product_info.cpp +++ b/rpcs3/Input/product_info.cpp @@ -216,6 +216,17 @@ namespace input .capabilites = 0x0 } }, + { + product_type::top_shot_fearmaster, + { + .type = product_type::top_shot_fearmaster, + .class_id = CELL_PAD_FAKE_TYPE_TOP_SHOT_FEARMASTER, + .vendor_id = vendor_id::sony_cea, + .product_id = product_id::top_shot_fearmaster, + .pclass_profile = 0x0, + .capabilites = 0x0 + } + }, { product_type::udraw_gametablet, { diff --git a/rpcs3/Input/product_info.h b/rpcs3/Input/product_info.h index 3b4857a690..da95fb4c59 100644 --- a/rpcs3/Input/product_info.h +++ b/rpcs3/Input/product_info.h @@ -20,6 +20,7 @@ namespace input ride_skateboard, guncon_3, top_shot_elite, + top_shot_fearmaster, udraw_gametablet, }; @@ -46,6 +47,7 @@ namespace input dance_dance_revolution_mat = 0x1010, // Dance Dance Revolution Dance Mat Controller ride_skateboard = 0x0400, // Tony Hawk RIDE Skateboard Controller top_shot_elite = 0x04A0, // Top Shot Elite Controller + top_shot_fearmaster = 0x04A1, // Top Shot Fearmaster Controller guncon_3 = 0x0800, // GunCon 3 Controller udraw_gametablet = 0xCB17, // uDraw GameTablet Controller }; diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index db57211d75..7acb0e45a0 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -85,6 +85,7 @@ + @@ -564,6 +565,8 @@ + + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 47340301fb..a7b91ba672 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -1041,6 +1041,9 @@ Emu\Io + + Emu\Io + Emu\Io @@ -2187,6 +2190,9 @@ Emu\Io + + Emu\Io + Emu\Io @@ -2470,6 +2476,9 @@ Emu\Io + + Emu\Io + Emu\Io diff --git a/rpcs3/rpcs3qt/emulated_pad_settings_dialog.cpp b/rpcs3/rpcs3qt/emulated_pad_settings_dialog.cpp index e432c3d69a..d423dae792 100644 --- a/rpcs3/rpcs3qt/emulated_pad_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/emulated_pad_settings_dialog.cpp @@ -6,6 +6,7 @@ #include "Emu/Io/ghltar_config.h" #include "Emu/Io/guncon3_config.h" #include "Emu/Io/topshotelite_config.h" +#include "Emu/Io/topshotfearmaster_config.h" #include "Emu/Io/turntable_config.h" #include "Emu/Io/usio_config.h" #include "util/asm.hpp" @@ -95,6 +96,10 @@ emulated_pad_settings_dialog::emulated_pad_settings_dialog(pad_type type, QWidge setWindowTitle(tr("Configure Emulated Top Shot Elite")); add_tabs(tabs); break; + case emulated_pad_settings_dialog::pad_type::topshotfearmaster: + setWindowTitle(tr("Configure Emulated Top Shot Fearmaster")); + add_tabs(tabs); + break; } v_layout->addWidget(tabs); @@ -139,6 +144,9 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs) case pad_type::topshotelite: players = g_cfg_topshotelite.players.size(); break; + case pad_type::topshotfearmaster: + players = g_cfg_topshotfearmaster.players.size(); + break; } m_combos.resize(players); @@ -166,7 +174,7 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs) combo->setItemData(index, i, button_role::emulated_button); } - if constexpr (std::is_same_v || std::is_same_v) + if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { for (int p = static_cast(pad_button::mouse_button_1); p <= static_cast(pad_button::mouse_button_8); p++) { @@ -202,6 +210,9 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs) case pad_type::topshotelite: saved_btn_id = ::at32(g_cfg_topshotelite.players, player)->get_pad_button(static_cast(id)); break; + case pad_type::topshotfearmaster: + saved_btn_id = ::at32(g_cfg_topshotfearmaster.players, player)->get_pad_button(static_cast(id)); + break; } combo->setCurrentIndex(combo->findData(static_cast(saved_btn_id))); @@ -240,6 +251,9 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs) case pad_type::topshotelite: ::at32(g_cfg_topshotelite.players, player)->set_button(static_cast(id), btn_id); break; + case pad_type::topshotfearmaster: + ::at32(g_cfg_topshotfearmaster.players, player)->set_button(static_cast(id), btn_id); + break; } }); @@ -306,6 +320,12 @@ void emulated_pad_settings_dialog::load_config() cfg_log.notice("Could not load topshotelite config. Using defaults."); } break; + case emulated_pad_settings_dialog::pad_type::topshotfearmaster: + if (!g_cfg_topshotfearmaster.load()) + { + cfg_log.notice("Could not load topshotfearmaster config. Using defaults."); + } + break; } } @@ -334,6 +354,9 @@ void emulated_pad_settings_dialog::save_config() case emulated_pad_settings_dialog::pad_type::topshotelite: g_cfg_topshotelite.save(); break; + case emulated_pad_settings_dialog::pad_type::topshotfearmaster: + g_cfg_topshotfearmaster.save(); + break; } } @@ -362,6 +385,9 @@ void emulated_pad_settings_dialog::reset_config() case emulated_pad_settings_dialog::pad_type::topshotelite: g_cfg_topshotelite.from_default(); break; + case emulated_pad_settings_dialog::pad_type::topshotfearmaster: + g_cfg_topshotfearmaster.from_default(); + break; } for (usz player = 0; player < m_combos.size(); player++) @@ -399,6 +425,9 @@ void emulated_pad_settings_dialog::reset_config() case pad_type::topshotelite: def_btn_id = ::at32(g_cfg_topshotelite.players, player)->default_pad_button(static_cast(data.toInt())); break; + case pad_type::topshotfearmaster: + def_btn_id = ::at32(g_cfg_topshotfearmaster.players, player)->default_pad_button(static_cast(data.toInt())); + break; } combo->setCurrentIndex(combo->findData(static_cast(def_btn_id))); diff --git a/rpcs3/rpcs3qt/emulated_pad_settings_dialog.h b/rpcs3/rpcs3qt/emulated_pad_settings_dialog.h index e035980bff..f9784b3123 100644 --- a/rpcs3/rpcs3qt/emulated_pad_settings_dialog.h +++ b/rpcs3/rpcs3qt/emulated_pad_settings_dialog.h @@ -22,6 +22,7 @@ public: ds3gem, guncon3, topshotelite, + topshotfearmaster, }; emulated_pad_settings_dialog(pad_type type, QWidget* parent = nullptr); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 43fa426e82..8022c3eee0 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -2784,6 +2784,12 @@ void main_window::CreateConnects() dlg->show(); }); + connect(ui->confTopShotFearmasterAct, &QAction::triggered, this, [this] + { + emulated_pad_settings_dialog* dlg = new emulated_pad_settings_dialog(emulated_pad_settings_dialog::pad_type::topshotfearmaster, this); + dlg->show(); + }); + connect(ui->actionBasic_Mouse, &QAction::triggered, this, [this] { basic_mouse_settings_dialog* dlg = new basic_mouse_settings_dialog(this); diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index 3e88728090..c3ad462c49 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -242,6 +242,7 @@ + @@ -1350,6 +1351,11 @@ Top Shot Elite + + + Top Shot Fearmaster + + Basic Mouse diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.cpp b/rpcs3/rpcs3qt/pad_settings_dialog.cpp index 4c41000c57..76be3f9270 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/pad_settings_dialog.cpp @@ -183,6 +183,7 @@ pad_settings_dialog::pad_settings_dialog(std::shared_ptr gui_setti ui->chooseClass->addItem(tr("Skateboard"), u32{CELL_PAD_PCLASS_TYPE_SKATEBOARD}); ui->chooseClass->addItem(tr("GunCon 3"), u32{CELL_PAD_FAKE_TYPE_GUNCON3}); ui->chooseClass->addItem(tr("Top Shot Elite"), u32{CELL_PAD_FAKE_TYPE_TOP_SHOT_ELITE}); + ui->chooseClass->addItem(tr("Top Shot Fearmaster"),u32{CELL_PAD_FAKE_TYPE_TOP_SHOT_FEARMASTER}); ui->chooseClass->addItem(tr("uDraw GameTablet"), u32{CELL_PAD_FAKE_TYPE_GAMETABLET}); connect(ui->chooseClass, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) @@ -1731,6 +1732,11 @@ void pad_settings_dialog::HandleDeviceClassChange(u32 class_id) const ui->chooseProduct->addItem(tr("Top Shot Elite", "Top Shot Elite Controller"), static_cast(product.type)); break; } + case input::product_type::top_shot_fearmaster: + { + ui->chooseProduct->addItem(tr("Top Shot Fearmaster", "Top Shot Fearmaster Controller"), static_cast(product.type)); + break; + } case input::product_type::udraw_gametablet: { ui->chooseProduct->addItem(tr("uDraw GameTablet", "uDraw GameTablet Controller"), static_cast(product.type));