USB: Top Shot Elite device emulation

This commit is contained in:
Florin9doi 2024-04-26 16:24:26 +03:00 committed by Megamouse
parent a1a38b7b39
commit 088b594fa8
15 changed files with 587 additions and 19 deletions

View File

@ -417,6 +417,7 @@ target_sources(rpcs3_emu PRIVATE
Io/RB3MidiKeyboard.cpp
Io/recording_config.cpp
Io/Skylander.cpp
Io/TopShotElite.cpp
Io/Turntable.cpp
Io/usb_device.cpp
Io/usb_vfs.cpp

View File

@ -21,10 +21,12 @@
#include "Emu/Io/GHLtar.h"
#include "Emu/Io/ghltar_config.h"
#include "Emu/Io/guncon3_config.h"
#include "Emu/Io/topshotelite_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/Turntable.h"
#include "Emu/Io/turntable_config.h"
#include "Emu/Io/RB3MidiKeyboard.h"
@ -44,6 +46,7 @@ cfg_ghltars g_cfg_ghltar;
cfg_turntables g_cfg_turntable;
cfg_usios g_cfg_usio;
cfg_guncon3 g_cfg_guncon3;
cfg_topshotelite g_cfg_topshotelite;
template <>
void fmt_class_string<libusb_transfer>::format(std::string& out, u64 arg)
@ -876,6 +879,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_elite)
{
if (!g_cfg_topshotelite.load())
{
sys_usbd.notice("Could not load Top Shot Elite config. Using defaults.");
}
sys_usbd.success("Adding emulated Top Shot Elite (controller %d)", index);
std::shared_ptr<usb_device> dev = std::make_shared<usb_device_topshotelite>(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);

View File

@ -0,0 +1,400 @@
#include "stdafx.h"
#include "TopShotElite.h"
#include "MouseHandler.h"
#include "Emu/IdManager.h"
#include "Emu/Io/topshotelite_config.h"
#include "Emu/Cell/lv2/sys_usbd.h"
#include "Emu/system_config.h"
#include "Input/pad_thread.h"
LOG_CHANNEL(topshotelite_log);
#define TSE_CALIB_LOG false
// 0 < Calib_Top < Calib_Bottom < 0x2ff
// 0 < Calib_Right < Calib_Left < 0x3ff
constexpr u16 TSE_CALIB_TOP = 20;
constexpr u16 TSE_CALIB_BOTTOM = 840;
constexpr u16 TSE_CALIB_LEFT = 930;
constexpr u16 TSE_CALIB_RIGHT = 95;
constexpr u16 TSE_CALIB_DIST = 95;
template <>
void fmt_class_string<topshotelite_btn>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](topshotelite_btn value)
{
switch (value)
{
case topshotelite_btn::trigger: return "Trigger";
case topshotelite_btn::reload: return "Reload";
case topshotelite_btn::square: return "Square";
case topshotelite_btn::cross: return "Cross";
case topshotelite_btn::circle: return "Circle";
case topshotelite_btn::triangle: return "Triangle";
case topshotelite_btn::select: return "Select";
case topshotelite_btn::start: return "Start";
case topshotelite_btn::l3: return "L3";
case topshotelite_btn::r3: return "R3";
case topshotelite_btn::ps: return "PS";
case topshotelite_btn::dpad_up: return "D-Pad Up";
case topshotelite_btn::dpad_down: return "D-Pad Down";
case topshotelite_btn::dpad_left: return "D-Pad Left";
case topshotelite_btn::dpad_right: return "D-Pad Right";
case topshotelite_btn::ls_x: return "Left Stick X-Axis";
case topshotelite_btn::ls_y: return "Left Stick Y-Axis";
case topshotelite_btn::rs_x: return "Right Stick X-Axis";
case topshotelite_btn::rs_y: return "Right Stick Y-Axis";
case topshotelite_btn::count: return "Count";
}
return unknown;
});
}
#pragma pack(push, 1)
struct TopShotElite_data
{
uint8_t btn_square : 1;
uint8_t btn_cross : 1;
uint8_t btn_circle : 1;
uint8_t btn_triangle : 1;
uint8_t btn_reload: 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_r3 : 1;
uint8_t btn_ps: 1;
uint8_t : 3;
uint8_t dpad;
uint8_t stick_lx;
uint8_t stick_ly;
uint8_t stick_rx;
uint8_t stick_ry;
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_topshotelite::usb_device_topshotelite(u32 controller_index, const std::array<u8, 7>& 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 = 0x04a0,
.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("GuitarHero for Playstation (R) 3");
add_string("GuitarHero for Playstation (R) 3");
}
usb_device_topshotelite::~usb_device_topshotelite()
{
}
void usb_device_topshotelite::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:
topshotelite_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:
topshotelite_log.error("Unhandled SET_REPORT packet : %x", buf[0]);
break;
}
break;
default:
topshotelite_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 void set_sensor_pos(struct TopShotElite_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 TopShotElite_data* ts, u8* data)
{
std::memcpy(data, ts, sizeof(TopShotElite_data));
topshotelite_log.trace("interrupt_transfer: %s", fmt::buf_to_hexstring(data, sizeof(TopShotElite_data)));
}
void usb_device_topshotelite::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpoint*/, UsbTransfer* transfer)
{
ensure(buf_size >= sizeof(TopShotElite_data));
transfer->fake = true;
transfer->expected_count = sizeof(TopShotElite_data);
transfer->expected_result = HC_CC_NOERR;
transfer->expected_time = get_timestamp() + 4000;
struct TopShotElite_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)
{
topshotelite_log.warning("Top Shot Elite requires a Mouse Handler enabled");
prepare_data(&ts, buf);
return;
}
if (m_controller_index >= g_cfg_topshotelite.players.size())
{
topshotelite_log.warning("Top Shot Fearmaster controllers are only supported for Player1 to Player%d", g_cfg_topshotelite.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](topshotelite_btn btn, u16 value, bool pressed)
{
if (!pressed)
return;
switch (btn)
{
case topshotelite_btn::trigger: ts.btn_trigger |= 1; break;
case topshotelite_btn::reload: ts.btn_reload |= 1; break;
case topshotelite_btn::square: ts.btn_square |= 1; break;
case topshotelite_btn::cross: ts.btn_cross |= 1; break;
case topshotelite_btn::circle: ts.btn_circle |= 1; break;
case topshotelite_btn::triangle: ts.btn_triangle |= 1; break;
case topshotelite_btn::select: ts.btn_select |= 1; break;
case topshotelite_btn::start: ts.btn_start |= 1; break;
case topshotelite_btn::l3: ts.btn_l3 |= 1; break;
case topshotelite_btn::r3: ts.btn_r3 |= 1; break;
case topshotelite_btn::ps: ts.btn_ps |= 1; break;
case topshotelite_btn::dpad_up: up = true; break;
case topshotelite_btn::dpad_down: down = true; break;
case topshotelite_btn::dpad_left: left = true; break;
case topshotelite_btn::dpad_right: right = true; break;
case topshotelite_btn::ls_x: ts.stick_lx = static_cast<uint8_t>(value); break;
// you know you have a «Top» controller when the games are programmed to ignore a perfect controller, so we have to simulate a drift
case topshotelite_btn::ls_y: ts.stick_ly = std::min(0xff, 1 + static_cast<uint8_t>(value)); break;
case topshotelite_btn::rs_x: ts.stick_rx = static_cast<uint8_t>(value); break;
case topshotelite_btn::rs_y: ts.stick_ry = static_cast<uint8_t>(value); break;
case topshotelite_btn::count: break;
}
};
const auto& cfg = ::at32(g_cfg_topshotelite.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<MouseHandlerBase>();
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;
if (mouse_data.x_max <= 0 || mouse_data.y_max <= 0)
{
prepare_data(&ts, buf);
return;
}
s32 led_lx = 0x3ff - (TSE_CALIB_RIGHT + (mouse_data.x_pos * (TSE_CALIB_LEFT - TSE_CALIB_RIGHT) / mouse_data.x_max) + TSE_CALIB_DIST);
s32 led_rx = 0x3ff - (TSE_CALIB_RIGHT + (mouse_data.x_pos * (TSE_CALIB_LEFT - TSE_CALIB_RIGHT) / mouse_data.x_max) - TSE_CALIB_DIST);
s32 led_ly = TSE_CALIB_TOP + (mouse_data.y_pos * (TSE_CALIB_BOTTOM - TSE_CALIB_TOP) / mouse_data.y_max);
s32 led_ry = TSE_CALIB_TOP + (mouse_data.y_pos * (TSE_CALIB_BOTTOM - TSE_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 TSE_CALIB_LOG
topshotelite_log.error("L: %d x %d, R: %d x %d", led_lx + TSE_CALIB_DIST, led_ly, led_rx - TSE_CALIB_DIST, led_ry);
#endif
}
prepare_data(&ts, buf);
}

View File

@ -0,0 +1,17 @@
#pragma once
#include "Emu/Io/usb_device.h"
class usb_device_topshotelite : public usb_device_emulated
{
public:
usb_device_topshotelite(u32 controller_index, const std::array<u8, 7>& location);
~usb_device_topshotelite();
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;
};

View File

@ -146,9 +146,10 @@ 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_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_GAMETABLET = 0xa003,
CELL_PAD_FAKE_TYPE_LAST,
CELL_PAD_PCLASS_TYPE_MAX // last item

View File

@ -0,0 +1,62 @@
#pragma once
#include "emulated_pad_config.h"
#include <array>
enum class topshotelite_btn
{
trigger,
reload,
square,
cross,
circle,
triangle,
select,
start,
l3,
r3,
ps,
dpad_up,
dpad_down,
dpad_left,
dpad_right,
ls_x,
ls_y,
rs_x,
rs_y,
count
};
struct cfg_tse final : public emulated_pad_config<topshotelite_btn>
{
cfg_tse(node* owner, const std::string& name) : emulated_pad_config(owner, name) {}
cfg_pad_btn<topshotelite_btn> trigger{this, "Trigger", topshotelite_btn::trigger, pad_button::mouse_button_1};
cfg_pad_btn<topshotelite_btn> reload{this, "Reload", topshotelite_btn::reload, pad_button::mouse_button_2};
cfg_pad_btn<topshotelite_btn> square{this, "Square", topshotelite_btn::square, pad_button::square};
cfg_pad_btn<topshotelite_btn> cross{this, "Cross", topshotelite_btn::cross, pad_button::cross};
cfg_pad_btn<topshotelite_btn> circle{this, "Circle", topshotelite_btn::circle, pad_button::circle};
cfg_pad_btn<topshotelite_btn> triangle{this, "Triangle", topshotelite_btn::triangle, pad_button::triangle};
cfg_pad_btn<topshotelite_btn> select{this, "Select", topshotelite_btn::select, pad_button::select};
cfg_pad_btn<topshotelite_btn> start{this, "Start", topshotelite_btn::start, pad_button::start};
cfg_pad_btn<topshotelite_btn> l3{this, "L3", topshotelite_btn::l3, pad_button::L3};
cfg_pad_btn<topshotelite_btn> r3{this, "R3", topshotelite_btn::r3, pad_button::R3};
cfg_pad_btn<topshotelite_btn> ps{this, "PS", topshotelite_btn::ps, pad_button::ps};
cfg_pad_btn<topshotelite_btn> dpad_up{this, "D-Pad Up", topshotelite_btn::dpad_up, pad_button::dpad_up};
cfg_pad_btn<topshotelite_btn> dpad_down{this, "D-Pad Down", topshotelite_btn::dpad_down, pad_button::dpad_down};
cfg_pad_btn<topshotelite_btn> dpad_left{this, "D-Pad Left", topshotelite_btn::dpad_left, pad_button::dpad_left};
cfg_pad_btn<topshotelite_btn> dpad_right{this, "D-Pad Right", topshotelite_btn::dpad_right, pad_button::dpad_right};
cfg_pad_btn<topshotelite_btn> ls_x{this, "Left Stick X-Axis", topshotelite_btn::ls_x, pad_button::ls_x};
cfg_pad_btn<topshotelite_btn> ls_y{this, "Left Stick Y-Axis", topshotelite_btn::ls_y, pad_button::ls_y};
cfg_pad_btn<topshotelite_btn> rs_x{this, "Right Stick X-Axis", topshotelite_btn::rs_x, pad_button::rs_x};
cfg_pad_btn<topshotelite_btn> rs_y{this, "Right Stick Y-Axis", topshotelite_btn::rs_y, pad_button::rs_y};
};
struct cfg_topshotelite final : public emulated_pads_config<cfg_tse, 4>
{
cfg_topshotelite() : emulated_pads_config<cfg_tse, 4>("topshotelite") {};
};
extern cfg_topshotelite g_cfg_topshotelite;

View File

@ -205,6 +205,17 @@ namespace input
.capabilites = 0x0
}
},
{
product_type::top_shot_elite,
{
.type = product_type::top_shot_elite,
.class_id = CELL_PAD_FAKE_TYPE_TOP_SHOT_ELITE,
.vendor_id = vendor_id::sony_cea,
.product_id = product_id::top_shot_elite,
.pclass_profile = 0x0,
.capabilites = 0x0
}
},
{
product_type::udraw_gametablet,
{

View File

@ -19,6 +19,7 @@ namespace input
ps_move_navigation,
ride_skateboard,
guncon_3,
top_shot_elite,
udraw_gametablet,
};
@ -44,6 +45,7 @@ namespace input
ps_move_navigation = 0x042F, // PlayStation Move navigation controller
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
guncon_3 = 0x0800, // GunCon 3 Controller
udraw_gametablet = 0xCB17, // uDraw GameTablet Controller
};

View File

@ -71,7 +71,11 @@
<ClCompile Include="Emu\Cell\Modules\sys_crashdump.cpp" />
<ClCompile Include="Emu\Cell\Modules\HLE_PATCHES.cpp" />
<ClCompile Include="Emu\games_config.cpp" />
<ClCompile Include="Emu\Io\Buzz.cpp" />
<ClCompile Include="Emu\Io\camera_config.cpp" />
<ClCompile Include="Emu\Io\GameTablet.cpp" />
<ClCompile Include="Emu\Io\GHLtar.cpp" />
<ClCompile Include="Emu\Io\GunCon3.cpp" />
<ClCompile Include="Emu\Io\midi_config_types.cpp" />
<ClCompile Include="Emu\Io\MouseHandler.cpp" />
<ClCompile Include="Emu\Io\pad_types.cpp" />
@ -80,11 +84,8 @@
<ClCompile Include="Emu\Io\RB3MidiGuitar.cpp" />
<ClCompile Include="Emu\Io\RB3MidiKeyboard.cpp" />
<ClCompile Include="Emu\Io\recording_config.cpp" />
<ClCompile Include="Emu\Io\TopShotElite.cpp" />
<ClCompile Include="Emu\Io\Turntable.cpp" />
<ClCompile Include="Emu\Io\GameTablet.cpp" />
<ClCompile Include="Emu\Io\GHLtar.cpp" />
<ClCompile Include="Emu\Io\GunCon3.cpp" />
<ClCompile Include="Emu\Io\Buzz.cpp" />
<ClCompile Include="Emu\Io\usio.cpp" />
<ClCompile Include="Emu\Audio\AudioBackend.cpp" />
<ClCompile Include="Emu\Io\interception.cpp" />
@ -536,35 +537,36 @@
<ClInclude Include="Emu\config_mode.h" />
<ClInclude Include="Emu\CPU\sse2neon.h" />
<ClInclude Include="Emu\games_config.h" />
<ClInclude Include="Emu\Io\Buzz.h" />
<ClInclude Include="Emu\Io\buzz_config.h" />
<ClInclude Include="Emu\Io\camera_config.h" />
<ClInclude Include="Emu\Io\camera_handler_base.h" />
<ClInclude Include="Emu\Io\emulated_pad_config.h" />
<ClInclude Include="Emu\Io\GameTablet.h" />
<ClInclude Include="Emu\Io\gem_config.h" />
<ClInclude Include="Emu\Io\GHLtar.h" />
<ClInclude Include="Emu\Io\ghltar_config.h" />
<ClInclude Include="Emu\Io\guncon3_config.h" />
<ClInclude Include="Emu\Io\GunCon3.h" />
<ClInclude Include="Emu\Io\interception.h" />
<ClInclude Include="Emu\Io\Keyboard.h" />
<ClInclude Include="Emu\Io\midi_config_types.h" />
<ClInclude Include="Emu\Io\music_handler_base.h" />
<ClInclude Include="Emu\Io\Null\null_camera_handler.h" />
<ClInclude Include="Emu\Io\Null\null_music_handler.h" />
<ClInclude Include="Emu\Io\pad_config.h" />
<ClInclude Include="Emu\Io\pad_config_types.h" />
<ClInclude Include="Emu\Io\pad_types.h" />
<ClInclude Include="Emu\Io\rb3drums_config.h" />
<ClInclude Include="Emu\Io\RB3MidiDrums.h" />
<ClInclude Include="Emu\Io\RB3MidiGuitar.h" />
<ClInclude Include="Emu\Io\RB3MidiKeyboard.h" />
<ClInclude Include="Emu\Io\recording_config.h" />
<ClInclude Include="Emu\Io\TopShotElite.h" />
<ClInclude Include="Emu\Io\topshotelite_config.h" />
<ClInclude Include="Emu\Io\Turntable.h" />
<ClInclude Include="Emu\Io\GameTablet.h" />
<ClInclude Include="Emu\Io\GHLtar.h" />
<ClInclude Include="Emu\Io\GunCon3.h" />
<ClInclude Include="Emu\Io\Buzz.h" />
<ClInclude Include="Emu\Io\turntable_config.h" />
<ClInclude Include="Emu\Io\usio.h" />
<ClInclude Include="Emu\Io\interception.h" />
<ClInclude Include="Emu\Io\mouse_config.h" />
<ClInclude Include="Emu\Io\pad_types.h" />
<ClInclude Include="Emu\Io\Keyboard.h" />
<ClInclude Include="Emu\Io\pad_config.h" />
<ClInclude Include="Emu\Io\pad_config_types.h" />
<ClInclude Include="Emu\Io\usio_config.h" />
<ClInclude Include="Emu\IPC_config.h" />
<ClInclude Include="Emu\IPC_socket.h" />

View File

@ -1038,6 +1038,9 @@
<ClCompile Include="Emu\Cell\Modules\sys_crashdump.cpp">
<Filter>Emu\Cell\Modules</Filter>
</ClCompile>
<ClCompile Include="Emu\Io\TopShotElite.cpp">
<Filter>Emu\Io</Filter>
</ClCompile>
<ClCompile Include="Emu\Io\Turntable.cpp">
<Filter>Emu\Io</Filter>
</ClCompile>
@ -2181,6 +2184,9 @@
<ClInclude Include="Emu\RSX\Common\texture_cache_types.h">
<Filter>Emu\GPU\RSX\Common</Filter>
</ClInclude>
<ClInclude Include="Emu\Io\TopShotElite.h">
<Filter>Emu\Io</Filter>
</ClInclude>
<ClInclude Include="Emu\Io\Turntable.h">
<Filter>Emu\Io</Filter>
</ClInclude>
@ -2461,6 +2467,9 @@
<ClInclude Include="Emu\Io\guncon3_config.h">
<Filter>Emu\Io</Filter>
</ClInclude>
<ClInclude Include="Emu\Io\topshotelite_config.h">
<Filter>Emu\Io</Filter>
</ClInclude>
<ClInclude Include="Emu\Io\usio_config.h">
<Filter>Emu\Io</Filter>
</ClInclude>

View File

@ -5,6 +5,7 @@
#include "Emu/Io/gem_config.h"
#include "Emu/Io/ghltar_config.h"
#include "Emu/Io/guncon3_config.h"
#include "Emu/Io/topshotelite_config.h"
#include "Emu/Io/turntable_config.h"
#include "Emu/Io/usio_config.h"
#include "util/asm.hpp"
@ -90,6 +91,10 @@ emulated_pad_settings_dialog::emulated_pad_settings_dialog(pad_type type, QWidge
setWindowTitle(tr("Configure Emulated GunCon 3"));
add_tabs<guncon3_btn>(tabs);
break;
case emulated_pad_settings_dialog::pad_type::topshotelite:
setWindowTitle(tr("Configure Emulated Top Shot Elite"));
add_tabs<topshotelite_btn>(tabs);
break;
}
v_layout->addWidget(tabs);
@ -131,6 +136,9 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs)
case pad_type::guncon3:
players = g_cfg_guncon3.players.size();
break;
case pad_type::topshotelite:
players = g_cfg_topshotelite.players.size();
break;
}
m_combos.resize(players);
@ -158,7 +166,7 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs)
combo->setItemData(index, i, button_role::emulated_button);
}
if constexpr (std::is_same_v<T, guncon3_btn>)
if constexpr (std::is_same_v<T, guncon3_btn> || std::is_same_v<T, topshotelite_btn>)
{
for (int p = static_cast<int>(pad_button::mouse_button_1); p <= static_cast<int>(pad_button::mouse_button_8); p++)
{
@ -191,6 +199,9 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs)
case pad_type::guncon3:
saved_btn_id = ::at32(g_cfg_guncon3.players, player)->get_pad_button(static_cast<guncon3_btn>(id));
break;
case pad_type::topshotelite:
saved_btn_id = ::at32(g_cfg_topshotelite.players, player)->get_pad_button(static_cast<topshotelite_btn>(id));
break;
}
combo->setCurrentIndex(combo->findData(static_cast<int>(saved_btn_id)));
@ -226,6 +237,9 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs)
case pad_type::guncon3:
::at32(g_cfg_guncon3.players, player)->set_button(static_cast<guncon3_btn>(id), btn_id);
break;
case pad_type::topshotelite:
::at32(g_cfg_topshotelite.players, player)->set_button(static_cast<topshotelite_btn>(id), btn_id);
break;
}
});
@ -286,6 +300,12 @@ void emulated_pad_settings_dialog::load_config()
cfg_log.notice("Could not load guncon3 config. Using defaults.");
}
break;
case emulated_pad_settings_dialog::pad_type::topshotelite:
if (!g_cfg_topshotelite.load())
{
cfg_log.notice("Could not load topshotelite config. Using defaults.");
}
break;
}
}
@ -311,6 +331,9 @@ void emulated_pad_settings_dialog::save_config()
case emulated_pad_settings_dialog::pad_type::guncon3:
g_cfg_guncon3.save();
break;
case emulated_pad_settings_dialog::pad_type::topshotelite:
g_cfg_topshotelite.save();
break;
}
}
@ -336,6 +359,9 @@ void emulated_pad_settings_dialog::reset_config()
case emulated_pad_settings_dialog::pad_type::guncon3:
g_cfg_guncon3.from_default();
break;
case emulated_pad_settings_dialog::pad_type::topshotelite:
g_cfg_topshotelite.from_default();
break;
}
for (usz player = 0; player < m_combos.size(); player++)
@ -370,6 +396,9 @@ void emulated_pad_settings_dialog::reset_config()
case pad_type::guncon3:
def_btn_id = ::at32(g_cfg_guncon3.players, player)->default_pad_button(static_cast<guncon3_btn>(data.toInt()));
break;
case pad_type::topshotelite:
def_btn_id = ::at32(g_cfg_topshotelite.players, player)->default_pad_button(static_cast<topshotelite_btn>(data.toInt()));
break;
}
combo->setCurrentIndex(combo->findData(static_cast<int>(def_btn_id)));

View File

@ -20,7 +20,8 @@ public:
ghltar,
usio,
ds3gem,
guncon3
guncon3,
topshotelite,
};
emulated_pad_settings_dialog(pad_type type, QWidget* parent = nullptr);

View File

@ -2778,6 +2778,12 @@ void main_window::CreateConnects()
dlg->show();
});
connect(ui->confTopShotEliteAct, &QAction::triggered, this, [this]
{
emulated_pad_settings_dialog* dlg = new emulated_pad_settings_dialog(emulated_pad_settings_dialog::pad_type::topshotelite, this);
dlg->show();
});
connect(ui->actionBasic_Mouse, &QAction::triggered, this, [this]
{
basic_mouse_settings_dialog* dlg = new basic_mouse_settings_dialog(this);

View File

@ -241,6 +241,7 @@
<addaction name="confUSIOAct"/>
<addaction name="confPSMoveDS3Act"/>
<addaction name="confGunCon3Act"/>
<addaction name="confTopShotEliteAct"/>
</widget>
<widget class="QMenu" name="menuMice">
<property name="title">
@ -1344,6 +1345,11 @@
<string>GunCon 3</string>
</property>
</action>
<action name="confTopShotEliteAct">
<property name="text">
<string>Top Shot Elite</string>
</property>
</action>
<action name="actionBasic_Mouse">
<property name="text">
<string>Basic Mouse</string>

View File

@ -182,6 +182,7 @@ pad_settings_dialog::pad_settings_dialog(std::shared_ptr<gui_settings> gui_setti
ui->chooseClass->addItem(tr("PS Move Navigation"), u32{CELL_PAD_PCLASS_TYPE_NAVIGATION});
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("uDraw GameTablet"), u32{CELL_PAD_FAKE_TYPE_GAMETABLET});
connect(ui->chooseClass, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index)
@ -1725,6 +1726,11 @@ void pad_settings_dialog::HandleDeviceClassChange(u32 class_id) const
ui->chooseProduct->addItem(tr("GunCon 3", "GunCon 3 Controller"), static_cast<int>(product.type));
break;
}
case input::product_type::top_shot_elite:
{
ui->chooseProduct->addItem(tr("Top Shot Elite", "Top Shot Elite Controller"), static_cast<int>(product.type));
break;
}
case input::product_type::udraw_gametablet:
{
ui->chooseProduct->addItem(tr("uDraw GameTablet", "uDraw GameTablet Controller"), static_cast<int>(product.type));