mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-04-20 11:36:13 +00:00
Merge branch 'RPCS3:master' into master
This commit is contained in:
commit
c30d318af9
30 changed files with 852 additions and 434 deletions
|
@ -247,7 +247,7 @@ public:
|
|||
u32 hue = 0; // Tracking hue of the motion controller
|
||||
f32 distance_mm{3000.0f}; // Distance from the camera in mm
|
||||
f32 radius{5.0f}; // Radius of the sphere in camera pixels
|
||||
bool radius_valid = true; // If the radius and distance of the sphere was computed.
|
||||
bool radius_valid = false; // If the radius and distance of the sphere was computed. Also used for visibility.
|
||||
|
||||
bool is_calibrating{false}; // Whether or not we are currently calibrating
|
||||
u64 calibration_start_us{0}; // The start timestamp of the calibration in microseconds
|
||||
|
@ -321,13 +321,28 @@ public:
|
|||
|
||||
void update_connections()
|
||||
{
|
||||
connected_controllers = 0;
|
||||
|
||||
const auto update_connection = [this](u32 i, bool connected)
|
||||
{
|
||||
if (connected)
|
||||
{
|
||||
connected_controllers++;
|
||||
controllers[i].status = CELL_GEM_STATUS_READY;
|
||||
controllers[i].port = port_num(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
controllers[i].status = CELL_GEM_STATUS_DISCONNECTED;
|
||||
controllers[i].port = 0;
|
||||
}
|
||||
};
|
||||
|
||||
switch (g_cfg.io.move)
|
||||
{
|
||||
case move_handler::real:
|
||||
case move_handler::fake:
|
||||
{
|
||||
connected_controllers = 0;
|
||||
|
||||
std::lock_guard lock(pad::g_pad_mutex);
|
||||
const auto handler = pad::get_pad_thread(true);
|
||||
if (!handler) break;
|
||||
|
@ -335,51 +350,41 @@ public:
|
|||
for (u32 i = 0; i < CELL_GEM_MAX_NUM; i++)
|
||||
{
|
||||
const auto& pad = ::at32(handler->GetPads(), pad_num(i));
|
||||
const bool connected = (pad && (pad->m_port_status & CELL_PAD_STATUS_CONNECTED) && i < attribute.max_connect);
|
||||
const bool connected = pad && (pad->m_port_status & CELL_PAD_STATUS_CONNECTED) && i < attribute.max_connect;
|
||||
const bool is_real_move = g_cfg.io.move != move_handler::real || pad->m_pad_handler == pad_handler::move;
|
||||
|
||||
if (connected && is_real_move)
|
||||
{
|
||||
connected_controllers++;
|
||||
controllers[i].status = CELL_GEM_STATUS_READY;
|
||||
controllers[i].port = port_num(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
controllers[i].status = CELL_GEM_STATUS_DISCONNECTED;
|
||||
controllers[i].port = 0;
|
||||
}
|
||||
update_connection(i, connected && is_real_move);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case move_handler::mouse:
|
||||
case move_handler::raw_mouse:
|
||||
{
|
||||
connected_controllers = 0;
|
||||
|
||||
auto& handler = g_fxo->get<MouseHandlerBase>();
|
||||
std::lock_guard mouse_lock(handler.mutex);
|
||||
|
||||
const MouseInfo& info = handler.GetInfo();
|
||||
|
||||
for (u32 i = 0; i < CELL_GEM_MAX_NUM; i++)
|
||||
{
|
||||
const bool connected = i < attribute.max_connect && info.status[i] == CELL_MOUSE_STATUS_CONNECTED;
|
||||
|
||||
if (connected)
|
||||
{
|
||||
connected_controllers++;
|
||||
controllers[i].status = CELL_GEM_STATUS_READY;
|
||||
controllers[i].port = port_num(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
controllers[i].status = CELL_GEM_STATUS_DISCONNECTED;
|
||||
controllers[i].port = 0;
|
||||
}
|
||||
update_connection(i, i < attribute.max_connect && info.status[i] == CELL_MOUSE_STATUS_CONNECTED);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
#ifdef HAVE_LIBEVDEV
|
||||
case move_handler::gun:
|
||||
{
|
||||
gun_thread& gun = g_fxo->get<gun_thread>();
|
||||
std::scoped_lock lock(gun.handler.mutex);
|
||||
gun.num_devices = gun.handler.init() ? gun.handler.get_num_guns() : 0;
|
||||
|
||||
for (u32 i = 0; i < CELL_GEM_MAX_NUM; i++)
|
||||
{
|
||||
update_connection(i, i < attribute.max_connect && i < gun.num_devices);
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
case move_handler::null:
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
@ -1598,13 +1603,17 @@ static inline void draw_overlay_cursor(u32 gem_num, const gem_config::gem_contro
|
|||
rsx::overlays::set_cursor(rsx::overlays::cursor_offset::cell_gem + gem_num, x, y, color, 2'000'000, false);
|
||||
}
|
||||
|
||||
static inline void pos_to_gem_image_state(u32 gem_num, const gem_config::gem_controller& controller, vm::ptr<CellGemImageState>& gem_image_state, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max)
|
||||
static inline void pos_to_gem_image_state(u32 gem_num, gem_config::gem_controller& controller, vm::ptr<CellGemImageState>& gem_image_state, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max)
|
||||
{
|
||||
const auto& shared_data = g_fxo->get<gem_camera_shared>();
|
||||
|
||||
if (x_max <= 0) x_max = shared_data.width;
|
||||
if (y_max <= 0) y_max = shared_data.height;
|
||||
|
||||
// Move the cursor out of the screen if we're at the screen border (Time Crisis 4 needs this)
|
||||
if (x_pos <= 0) x_pos -= x_max / 10; else if (x_pos >= x_max) x_pos += x_max / 10;
|
||||
if (y_pos <= 0) y_pos -= y_max / 10; else if (y_pos >= y_max) y_pos += y_max / 10;
|
||||
|
||||
const f32 scaling_width = x_max / static_cast<f32>(shared_data.width);
|
||||
const f32 scaling_height = y_max / static_cast<f32>(shared_data.height);
|
||||
const f32 mmPerPixel = CELL_GEM_SPHERE_RADIUS_MM / controller.radius;
|
||||
|
@ -1629,6 +1638,13 @@ static inline void pos_to_gem_image_state(u32 gem_num, const gem_config::gem_con
|
|||
gem_image_state->projectionx = camera_x / controller.distance_mm;
|
||||
gem_image_state->projectiony = camera_y / controller.distance_mm;
|
||||
|
||||
// Update visibility for fake handlers
|
||||
if (g_cfg.io.move != move_handler::real)
|
||||
{
|
||||
// Let's say the sphere is not visible if the position is at the edge of the screen
|
||||
controller.radius_valid = x_pos > 0 && x_pos < x_max && y_pos > 0 && y_pos < y_max;
|
||||
}
|
||||
|
||||
if (g_cfg.io.show_move_cursor)
|
||||
{
|
||||
draw_overlay_cursor(gem_num, controller, x_pos, y_pos, x_max, y_max);
|
||||
|
@ -1647,6 +1663,10 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con
|
|||
if (x_max <= 0) x_max = shared_data.width;
|
||||
if (y_max <= 0) y_max = shared_data.height;
|
||||
|
||||
// Move the cursor out of the screen if we're at the screen border (Time Crisis 4 needs this)
|
||||
if (x_pos <= 0) x_pos -= x_max / 10; else if (x_pos >= x_max) x_pos += x_max / 10;
|
||||
if (y_pos <= 0) y_pos -= y_max / 10; else if (y_pos >= y_max) y_pos += y_max / 10;
|
||||
|
||||
const f32 scaling_width = x_max / static_cast<f32>(shared_data.width);
|
||||
const f32 scaling_height = y_max / static_cast<f32>(shared_data.height);
|
||||
const f32 mmPerPixel = CELL_GEM_SPHERE_RADIUS_MM / controller.radius;
|
||||
|
@ -1712,6 +1732,13 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con
|
|||
gem_state->quat[3] = q_w;
|
||||
}
|
||||
|
||||
// Update visibility for fake handlers
|
||||
if (g_cfg.io.move != move_handler::real)
|
||||
{
|
||||
// Let's say the sphere is not visible if the position is at the edge of the screen
|
||||
controller.radius_valid = x_pos > 0 && x_pos < x_max && y_pos > 0 && y_pos < y_max;
|
||||
}
|
||||
|
||||
if (g_cfg.io.show_move_cursor)
|
||||
{
|
||||
draw_overlay_cursor(gem_num, controller, x_pos, y_pos, x_max, y_max);
|
||||
|
@ -1730,7 +1757,7 @@ extern bool is_input_allowed();
|
|||
* Unavoidably buttons conflict with DS3 mappings, which is problematic for some games.
|
||||
* \param gem_num gem index to use
|
||||
* \param digital_buttons Bitmask filled with CELL_GEM_CTRL_* values
|
||||
* \param analog_t Analog value of Move's Trigger. Currently mapped to R2.
|
||||
* \param analog_t Analog value of Move's Trigger.
|
||||
* \return true on success, false if controller is disconnected
|
||||
*/
|
||||
static void ds3_input_to_pad(const u32 gem_num, be_t<u16>& digital_buttons, be_t<u16>& analog_t)
|
||||
|
@ -1810,22 +1837,17 @@ static inline void ds3_get_stick_values(u32 gem_num, const std::shared_ptr<Pad>&
|
|||
|
||||
const auto& cfg = ::at32(g_cfg_gem_fake.players, gem_num);
|
||||
cfg->handle_input(pad, true, [&](gem_btn btn, pad_button /*pad_btn*/, u16 value, bool pressed, bool& /*abort*/)
|
||||
{
|
||||
if (!pressed)
|
||||
return;
|
||||
{
|
||||
if (!pressed)
|
||||
return;
|
||||
|
||||
switch (btn)
|
||||
{
|
||||
case gem_btn::x_axis:
|
||||
x_pos = value;
|
||||
break;
|
||||
case gem_btn::y_axis:
|
||||
y_pos = value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
switch (btn)
|
||||
{
|
||||
case gem_btn::x_axis: x_pos = value; break;
|
||||
case gem_btn::y_axis: y_pos = value; break;
|
||||
default: break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
@ -2095,7 +2117,7 @@ static bool mouse_input_to_pad(u32 mouse_no, be_t<u16>& digital_buttons, be_t<u1
|
|||
}
|
||||
});
|
||||
|
||||
analog_t = (digital_buttons & CELL_GEM_CTRL_T) ? 0xFFFF : 0;
|
||||
analog_t = (digital_buttons & CELL_GEM_CTRL_T) ? 255 : 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -2168,7 +2190,7 @@ static bool gun_input_to_pad(u32 gem_no, be_t<u16>& digital_buttons, be_t<u16>&
|
|||
if (gun.handler.get_button(gem_no, gun_button::btn_6) == 1)
|
||||
digital_buttons |= CELL_GEM_CTRL_SQUARE;
|
||||
|
||||
analog_t = gun.handler.get_button(gem_no, gun_button::btn_left) ? 0xFFFF : 0;
|
||||
analog_t = gun.handler.get_button(gem_no, gun_button::btn_left) ? 255 : 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -2657,6 +2679,7 @@ error_code cellGemGetImageState(u32 gem_num, vm::ptr<CellGemImageState> gem_imag
|
|||
cellGem.warning("cellGemGetImageState(gem_num=%d, image_state=&0x%x)", gem_num, gem_image_state);
|
||||
|
||||
auto& gem = g_fxo->get<gem_config>();
|
||||
std::scoped_lock lock(gem.mtx);
|
||||
|
||||
if (!gem.state)
|
||||
{
|
||||
|
@ -2677,10 +2700,6 @@ error_code cellGemGetImageState(u32 gem_num, vm::ptr<CellGemImageState> gem_imag
|
|||
|
||||
gem_image_state->frame_timestamp = shared_data.frame_timestamp_us.load();
|
||||
gem_image_state->timestamp = gem_image_state->frame_timestamp + 10;
|
||||
gem_image_state->r = controller.radius; // Radius in camera pixels
|
||||
gem_image_state->distance = controller.distance_mm;
|
||||
gem_image_state->visible = gem.is_controller_ready(gem_num);
|
||||
gem_image_state->r_valid = controller.radius_valid;
|
||||
|
||||
switch (g_cfg.io.move)
|
||||
{
|
||||
|
@ -2702,6 +2721,11 @@ error_code cellGemGetImageState(u32 gem_num, vm::ptr<CellGemImageState> gem_imag
|
|||
case move_handler::null:
|
||||
fmt::throw_exception("Unreachable");
|
||||
}
|
||||
|
||||
gem_image_state->r = controller.radius; // Radius in camera pixels
|
||||
gem_image_state->distance = controller.distance_mm;
|
||||
gem_image_state->visible = controller.radius_valid && gem.is_controller_ready(gem_num);
|
||||
gem_image_state->r_valid = controller.radius_valid;
|
||||
}
|
||||
|
||||
return CELL_OK;
|
||||
|
|
|
@ -73,6 +73,19 @@ struct UsbPipe
|
|||
u8 endpoint = 0;
|
||||
};
|
||||
|
||||
struct usb_allow_list_entry
|
||||
{
|
||||
u16 id_vendor;
|
||||
u16 id_product_min;
|
||||
u16 id_product_max;
|
||||
std::string_view device_name;
|
||||
u16(*max_device_count)(void);
|
||||
std::shared_ptr<usb_device>(*make_instance)(u32, const std::array<u8, 7>&);
|
||||
auto operator<(const usb_allow_list_entry& r) const
|
||||
{
|
||||
return std::tuple(id_vendor, id_product_min, id_product_max, device_name, max_device_count, make_instance) < std::tuple(r.id_vendor, r.id_product_min, r.id_product_max, device_name, max_device_count, make_instance);
|
||||
}
|
||||
};
|
||||
class usb_handler_thread
|
||||
{
|
||||
public:
|
||||
|
@ -132,6 +145,8 @@ public:
|
|||
shared_mutex mutex_sq;
|
||||
ppu_thread* sq{};
|
||||
|
||||
atomic_t<u64> usb_hotplug_timeout = umax;
|
||||
|
||||
static constexpr auto thread_name = "Usb Manager Thread"sv;
|
||||
|
||||
private:
|
||||
|
@ -140,6 +155,7 @@ private:
|
|||
u32 get_free_transfer_id();
|
||||
|
||||
void send_message(u32 message, u32 tr_id);
|
||||
void perform_scan();
|
||||
|
||||
private:
|
||||
// Counters for device IDs, transfer IDs and pipe IDs
|
||||
|
@ -150,6 +166,101 @@ private:
|
|||
// List of device drivers
|
||||
std::unordered_map<std::string, UsbLdd, fmt::string_hash, std::equal_to<>> ldds;
|
||||
|
||||
const std::vector<usb_allow_list_entry> device_allow_list
|
||||
{
|
||||
// Portals
|
||||
{0x1430, 0x0150, 0x0150, "Skylanders Portal", &usb_device_skylander::get_num_emu_devices, &usb_device_skylander::make_instance},
|
||||
{0x0E6F, 0x0129, 0x0129, "Disney Infinity Base", &usb_device_infinity::get_num_emu_devices, &usb_device_infinity::make_instance},
|
||||
{0x0E6F, 0x0241, 0x0241, "Lego Dimensions Portal", &usb_device_dimensions::get_num_emu_devices, &usb_device_dimensions::make_instance},
|
||||
{0x0E6F, 0x200A, 0x200A, "Kamen Rider Summonride Portal", nullptr, nullptr},
|
||||
|
||||
// Cameras
|
||||
// {0x1415, 0x0020, 0x2000, "Sony Playstation Eye", nullptr, nullptr}, // TODO: verifiy
|
||||
|
||||
// Music devices
|
||||
{0x1415, 0x0000, 0x0000, "Singstar Microphone", nullptr, nullptr},
|
||||
// {0x1415, 0x0020, 0x0020, "SingStar Microphone Wireless", nullptr, nullptr}, // TODO: verifiy
|
||||
|
||||
{0x12BA, 0x00FF, 0x00FF, "Rocksmith Guitar Adapter", nullptr, nullptr},
|
||||
{0x12BA, 0x0100, 0x0100, "Guitar Hero Guitar", nullptr, nullptr},
|
||||
{0x12BA, 0x0120, 0x0120, "Guitar Hero Drums", nullptr, nullptr},
|
||||
{0x12BA, 0x074B, 0x074B, "Guitar Hero Live Guitar", &usb_device_ghltar::get_num_emu_devices, &usb_device_ghltar::make_instance},
|
||||
|
||||
{0x12BA, 0x0140, 0x0140, "DJ Hero Turntable", &usb_device_turntable::get_num_emu_devices, &usb_device_turntable::make_instance},
|
||||
{0x12BA, 0x0200, 0x020F, "Harmonix Guitar", nullptr, nullptr},
|
||||
{0x12BA, 0x0210, 0x021F, "Harmonix Drums", nullptr, nullptr},
|
||||
{0x12BA, 0x2330, 0x233F, "Harmonix Keyboard", nullptr, nullptr},
|
||||
{0x12BA, 0x2430, 0x243F, "Harmonix Button Guitar", nullptr, nullptr},
|
||||
{0x12BA, 0x2530, 0x253F, "Harmonix Real Guitar", nullptr, nullptr},
|
||||
|
||||
{0x1BAD, 0x0004, 0x0004, "Harmonix RB1 Guitar - Wii", nullptr, nullptr},
|
||||
{0x1BAD, 0x0005, 0x0005, "Harmonix RB1 Drums - Wii", nullptr, nullptr},
|
||||
{0x1BAD, 0x3010, 0x301F, "Harmonix RB2 Guitar - Wii", nullptr, nullptr},
|
||||
{0x1BAD, 0x3110, 0x313F, "Harmonix RB2 Drums - Wii", nullptr, nullptr},
|
||||
{0x1BAD, 0x3330, 0x333F, "Harmonix Keyboard - Wii", nullptr, nullptr},
|
||||
{0x1BAD, 0x3430, 0x343F, "Harmonix Button Guitar - Wii", nullptr, nullptr},
|
||||
{0x1BAD, 0x3530, 0x353F, "Harmonix Real Guitar - Wii", nullptr, nullptr},
|
||||
|
||||
//Top Shot Elite controllers
|
||||
{0x12BA, 0x04A0, 0x04A0, "Top Shot Elite", nullptr, nullptr},
|
||||
{0x12BA, 0x04A1, 0x04A1, "Top Shot Fearmaster", nullptr, nullptr},
|
||||
{0x12BA, 0x04B0, 0x04B0, "Rapala Fishing Rod", nullptr, nullptr},
|
||||
|
||||
|
||||
// GT5 Wheels&co
|
||||
{0x046D, 0xC283, 0xC29B, "lgFF_c283_c29b", nullptr, nullptr},
|
||||
{0x044F, 0xB653, 0xB653, "Thrustmaster RGT FFB Pro", nullptr, nullptr},
|
||||
{0x044F, 0xB65A, 0xB65A, "Thrustmaster F430", nullptr, nullptr},
|
||||
{0x044F, 0xB65D, 0xB65D, "Thrustmaster FFB", nullptr, nullptr},
|
||||
{0x044F, 0xB65E, 0xB65E, "Thrustmaster TRS", nullptr, nullptr},
|
||||
{0x044F, 0xB660, 0xB660, "Thrustmaster T500 RS Gear Shift", nullptr, nullptr},
|
||||
|
||||
// GT6
|
||||
{0x2833, 0x0001, 0x0001, "Oculus", nullptr, nullptr},
|
||||
{0x046D, 0xCA03, 0xCA03, "lgFF_ca03_ca03", nullptr, nullptr},
|
||||
|
||||
// Buzz controllers
|
||||
{0x054C, 0x1000, 0x1040, "buzzer0", &usb_device_buzz::get_num_emu_devices, &usb_device_buzz::make_instance},
|
||||
{0x054C, 0x0001, 0x0041, "buzzer1", nullptr, nullptr},
|
||||
{0x054C, 0x0042, 0x0042, "buzzer2", nullptr, nullptr},
|
||||
{0x046D, 0xC220, 0xC220, "buzzer9", nullptr, nullptr},
|
||||
|
||||
// GCon3 Gun
|
||||
{0x0B9A, 0x0800, 0x0800, "guncon3", nullptr, nullptr},
|
||||
|
||||
// uDraw GameTablet
|
||||
{0x20D6, 0xCB17, 0xCB17, "uDraw GameTablet", nullptr, nullptr},
|
||||
|
||||
// DVB-T
|
||||
{0x1415, 0x0003, 0x0003, "PlayTV SCEH-0036", nullptr, nullptr},
|
||||
|
||||
// PSP Devices
|
||||
{0x054C, 0x01C8, 0x01C8, "PSP Type A", nullptr, nullptr},
|
||||
{0x054C, 0x01C9, 0x01C9, "PSP Type B", nullptr, nullptr},
|
||||
{0x054C, 0x01CA, 0x01CA, "PSP Type C", nullptr, nullptr},
|
||||
{0x054C, 0x01CB, 0x01CB, "PSP Type D", nullptr, nullptr},
|
||||
{0x054C, 0x02D2, 0x02D2, "PSP Slim", nullptr, nullptr},
|
||||
|
||||
// 0x0900: "H050 USJ(C) PCB rev00", 0x0910: "USIO PCB rev00"
|
||||
{0x0B9A, 0x0900, 0x0910, "PS3A-USJ", &usb_device_usio::get_num_emu_devices, &usb_device_usio::make_instance},
|
||||
|
||||
// Densha de GO! controller
|
||||
{0x0AE4, 0x0004, 0x0004, "Densha de GO! Type 2 Controller", nullptr, nullptr},
|
||||
|
||||
// EA Active 2 dongle for connecting wristbands & legband
|
||||
{0x21A4, 0xAC27, 0xAC27, "EA Active 2 Dongle", nullptr, nullptr},
|
||||
|
||||
// Tony Hawk RIDE Skateboard
|
||||
{0x12BA, 0x0400, 0x0400, "Tony Hawk RIDE Skateboard Controller", nullptr, nullptr},
|
||||
|
||||
// PSP in UsbPspCm mode
|
||||
{0x054C, 0x01CB, 0x01CB, "UsbPspcm", nullptr, nullptr},
|
||||
|
||||
// Sony Stereo Headsets
|
||||
{0x12BA, 0x0032, 0x0032, "Wireless Stereo Headset", nullptr, nullptr},
|
||||
{0x12BA, 0x0042, 0x0042, "Wireless Stereo Headset", nullptr, nullptr},
|
||||
};
|
||||
|
||||
// List of pipes
|
||||
std::map<u32, UsbPipe> open_pipes;
|
||||
// Transfers infos
|
||||
|
@ -163,8 +274,15 @@ private:
|
|||
// List of devices "connected" to the ps3
|
||||
std::array<u8, 7> location{};
|
||||
std::vector<std::shared_ptr<usb_device>> usb_devices;
|
||||
std::unordered_map<uint64_t, std::shared_ptr<usb_device>> usb_passthrough_devices;
|
||||
|
||||
libusb_context* ctx = nullptr;
|
||||
|
||||
#if LIBUSB_API_VERSION >= 0x01000102
|
||||
libusb_hotplug_callback_handle callback_handle {};
|
||||
#endif
|
||||
|
||||
bool hotplug_supported = false;
|
||||
};
|
||||
|
||||
void LIBUSB_CALL callback_transfer(struct libusb_transfer* transfer)
|
||||
|
@ -177,6 +295,14 @@ void LIBUSB_CALL callback_transfer(struct libusb_transfer* transfer)
|
|||
usbh.transfer_complete(transfer);
|
||||
}
|
||||
|
||||
#if LIBUSB_API_VERSION >= 0x01000102
|
||||
static int LIBUSB_CALL hotplug_callback(libusb_context* /*ctx*/, libusb_device * /*dev*/, libusb_hotplug_event event, void * /*user_data*/)
|
||||
{
|
||||
handle_hotplug_event(event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if LIBUSB_API_VERSION >= 0x0100010A
|
||||
static void LIBUSB_CALL log_cb(libusb_context* /*ctx*/, enum libusb_log_level level, const char* str)
|
||||
{
|
||||
|
@ -205,6 +331,93 @@ static void LIBUSB_CALL log_cb(libusb_context* /*ctx*/, enum libusb_log_level le
|
|||
}
|
||||
#endif
|
||||
|
||||
void usb_handler_thread::perform_scan()
|
||||
{
|
||||
// look if any device which we could be interested in is actually connected
|
||||
libusb_device** list = nullptr;
|
||||
const ssize_t ndev = libusb_get_device_list(ctx, &list);
|
||||
std::set<uint64_t> seen_usb_devices;
|
||||
|
||||
if (ndev < 0)
|
||||
{
|
||||
sys_usbd.error("Failed to get device list: %s", libusb_error_name(static_cast<s32>(ndev)));
|
||||
return;
|
||||
}
|
||||
|
||||
for (ssize_t index = 0; index < ndev; index++)
|
||||
{
|
||||
libusb_device* dev = list[index];
|
||||
libusb_device_descriptor desc;
|
||||
if (int res = libusb_get_device_descriptor(dev, &desc); res < 0)
|
||||
{
|
||||
sys_usbd.error("Failed to get device descriptor: %s", libusb_error_name(res));
|
||||
continue;
|
||||
}
|
||||
|
||||
const u8 port = libusb_get_port_number(dev);
|
||||
const u8 address = libusb_get_device_address(dev);
|
||||
const u64 usb_id = (static_cast<uint64_t>(desc.idVendor) << 48) | (static_cast<uint64_t>(desc.idProduct) << 32) | (static_cast<uint64_t>(port) << 8) | address;
|
||||
|
||||
seen_usb_devices.insert(usb_id);
|
||||
if (usb_passthrough_devices.contains(usb_id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& entry : device_allow_list)
|
||||
{
|
||||
// attach
|
||||
if (desc.idVendor == entry.id_vendor
|
||||
&& desc.idProduct >= entry.id_product_min
|
||||
&& desc.idProduct <= entry.id_product_max)
|
||||
{
|
||||
sys_usbd.success("Found device: %s", std::basic_string(entry.device_name));
|
||||
libusb_ref_device(dev);
|
||||
std::shared_ptr<usb_device_passthrough> usb_dev = std::make_shared<usb_device_passthrough>(dev, desc, get_new_location());
|
||||
connect_usb_device(usb_dev, true);
|
||||
usb_passthrough_devices[usb_id] = usb_dev;
|
||||
}
|
||||
}
|
||||
|
||||
if (desc.idVendor == 0x1209 && desc.idProduct == 0x2882)
|
||||
{
|
||||
sys_usbd.success("Found device: Santroller");
|
||||
// Send the device a specific control transfer so that it jumps to a RPCS3 compatible mode
|
||||
libusb_device_handle* lusb_handle;
|
||||
if (libusb_open(dev, &lusb_handle) == LIBUSB_SUCCESS)
|
||||
{
|
||||
#ifdef __linux__
|
||||
libusb_set_auto_detach_kernel_driver(lusb_handle, true);
|
||||
libusb_claim_interface(lusb_handle, 2);
|
||||
#endif
|
||||
libusb_control_transfer(lusb_handle, +LIBUSB_ENDPOINT_IN | +LIBUSB_REQUEST_TYPE_CLASS | +LIBUSB_RECIPIENT_INTERFACE, 0x01, 0x03f2, 2, nullptr, 0, 5000);
|
||||
libusb_close(lusb_handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
sys_usbd.error("Unable to open Santroller device, make sure Santroller isn't open in the background.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = usb_passthrough_devices.begin(); it != usb_passthrough_devices.end();)
|
||||
{
|
||||
auto& dev = *it;
|
||||
// If a device is no longer visible, disconnect it
|
||||
if (seen_usb_devices.contains(dev.first))
|
||||
{
|
||||
++it;
|
||||
}
|
||||
else
|
||||
{
|
||||
disconnect_usb_device(dev.second, true);
|
||||
it = usb_passthrough_devices.erase(it);
|
||||
}
|
||||
|
||||
}
|
||||
libusb_free_device_list(list, 1);
|
||||
}
|
||||
|
||||
usb_handler_thread::usb_handler_thread()
|
||||
{
|
||||
#if LIBUSB_API_VERSION >= 0x0100010A
|
||||
|
@ -230,164 +443,97 @@ usb_handler_thread::usb_handler_thread()
|
|||
return;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
hotplug_supported = true;
|
||||
#elif LIBUSB_API_VERSION >= 0x01000102
|
||||
if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG))
|
||||
{
|
||||
if (int res = libusb_hotplug_register_callback(ctx, static_cast<libusb_hotplug_event>(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED |
|
||||
LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT), static_cast<libusb_hotplug_flag>(0), LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY,
|
||||
LIBUSB_HOTPLUG_MATCH_ANY, static_cast<libusb_hotplug_callback_fn>(hotplug_callback), nullptr,
|
||||
&callback_handle); res < 0)
|
||||
{
|
||||
sys_usbd.error("Failed to initialize sys_usbd hotplug: %s", libusb_error_name(res));
|
||||
}
|
||||
else
|
||||
{
|
||||
hotplug_supported = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
for (u32 index = 0; index < MAX_SYS_USBD_TRANSFERS; index++)
|
||||
{
|
||||
transfers[index].transfer = libusb_alloc_transfer(8);
|
||||
transfers[index].transfer_id = index;
|
||||
}
|
||||
|
||||
// look if any device which we could be interested in is actually connected
|
||||
libusb_device** list = nullptr;
|
||||
ssize_t ndev = libusb_get_device_list(ctx, &list);
|
||||
|
||||
if (ndev < 0)
|
||||
if (!g_cfg_usio.load())
|
||||
{
|
||||
sys_usbd.error("Failed to get device list: %s", libusb_error_name(static_cast<s32>(ndev)));
|
||||
return;
|
||||
sys_usbd.notice("Could not load usio config. Using defaults.");
|
||||
}
|
||||
|
||||
bool found_skylander = false;
|
||||
bool found_infinity = false;
|
||||
bool found_dimension = false;
|
||||
bool found_usj = false;
|
||||
sys_usbd.notice("USIO config=\n", g_cfg_usio.to_string());
|
||||
|
||||
for (ssize_t index = 0; index < ndev; index++)
|
||||
if (g_cfg.io.ghltar == ghltar_handler::one_controller || g_cfg.io.ghltar == ghltar_handler::two_controllers)
|
||||
{
|
||||
libusb_device_descriptor desc;
|
||||
if (int res = libusb_get_device_descriptor(list[index], &desc); res < 0)
|
||||
if (!g_cfg_ghltar.load())
|
||||
{
|
||||
sys_usbd.error("Failed to get device descriptor: %s", libusb_error_name(res));
|
||||
continue;
|
||||
sys_usbd.notice("Could not load ghltar config. Using defaults.");
|
||||
}
|
||||
|
||||
auto check_device = [&](const u16 id_vendor, const u16 id_product_min, const u16 id_product_max, const char* s_name) -> bool
|
||||
{
|
||||
if (desc.idVendor == id_vendor && desc.idProduct >= id_product_min && desc.idProduct <= id_product_max)
|
||||
{
|
||||
sys_usbd.success("Found device: %s", s_name);
|
||||
libusb_ref_device(list[index]);
|
||||
std::shared_ptr<usb_device_passthrough> usb_dev = std::make_shared<usb_device_passthrough>(list[index], desc, get_new_location());
|
||||
usb_devices.push_back(usb_dev);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Portals
|
||||
if (check_device(0x1430, 0x0150, 0x0150, "Skylanders Portal"))
|
||||
{
|
||||
found_skylander = true;
|
||||
}
|
||||
|
||||
if (check_device(0x0E6F, 0x0129, 0x0129, "Disney Infinity Base"))
|
||||
{
|
||||
found_infinity = true;
|
||||
}
|
||||
|
||||
if (check_device(0x0E6F, 0x0241, 0x0241, "Lego Dimensions Portal"))
|
||||
{
|
||||
found_dimension = true;
|
||||
}
|
||||
|
||||
check_device(0x0E6F, 0x200A, 0x200A, "Kamen Rider Summonride Portal");
|
||||
|
||||
// Cameras
|
||||
// check_device(0x1415, 0x0020, 0x2000, "Sony Playstation Eye"); // TODO: verifiy
|
||||
|
||||
// Music devices
|
||||
check_device(0x1415, 0x0000, 0x0000, "SingStar Microphone");
|
||||
// check_device(0x1415, 0x0020, 0x0020, "SingStar Microphone Wireless"); // TODO: verifiy
|
||||
check_device(0x12BA, 0x0100, 0x0100, "Guitar Hero Guitar");
|
||||
check_device(0x12BA, 0x0120, 0x0120, "Guitar Hero Drums");
|
||||
check_device(0x12BA, 0x074B, 0x074B, "Guitar Hero Live Guitar");
|
||||
|
||||
check_device(0x12BA, 0x0140, 0x0140, "DJ Hero Turntable");
|
||||
check_device(0x12BA, 0x0200, 0x020F, "Harmonix Guitar");
|
||||
check_device(0x12BA, 0x0210, 0x021F, "Harmonix Drums");
|
||||
check_device(0x12BA, 0x2330, 0x233F, "Harmonix Keyboard");
|
||||
check_device(0x12BA, 0x2430, 0x243F, "Harmonix Button Guitar");
|
||||
check_device(0x12BA, 0x2530, 0x253F, "Harmonix Real Guitar");
|
||||
|
||||
check_device(0x1BAD, 0x0004, 0x0004, "Harmonix RB1 Guitar - Wii");
|
||||
check_device(0x1BAD, 0x0005, 0x0005, "Harmonix RB1 Drums - Wii");
|
||||
check_device(0x1BAD, 0x3010, 0x301F, "Harmonix RB2 Guitar - Wii");
|
||||
check_device(0x1BAD, 0x3110, 0x313F, "Harmonix RB2 Drums - Wii");
|
||||
check_device(0x1BAD, 0x3330, 0x333F, "Harmonix Keyboard - Wii");
|
||||
check_device(0x1BAD, 0x3430, 0x343F, "Harmonix Button Guitar - Wii");
|
||||
check_device(0x1BAD, 0x3530, 0x353F, "Harmonix Real Guitar - Wii");
|
||||
|
||||
if (desc.idVendor == 0x1209 && desc.idProduct == 0x2882)
|
||||
{
|
||||
sys_usbd.success("Found device: Santroller");
|
||||
// Send the device a specific control transfer so that it jumps to a RPCS3 compatible mode
|
||||
libusb_device_handle* lusb_handle;
|
||||
if (libusb_open(list[index], &lusb_handle) == LIBUSB_SUCCESS)
|
||||
{
|
||||
#ifdef __linux__
|
||||
libusb_set_auto_detach_kernel_driver(lusb_handle, true);
|
||||
libusb_claim_interface(lusb_handle, 2);
|
||||
#endif
|
||||
libusb_control_transfer(lusb_handle, +LIBUSB_ENDPOINT_IN | +LIBUSB_REQUEST_TYPE_CLASS | +LIBUSB_RECIPIENT_INTERFACE, 0x01, 0x03f2, 2, nullptr, 0, 5000);
|
||||
libusb_close(lusb_handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
sys_usbd.error("Unable to open Santroller device, make sure Santroller isn't open in the background.");
|
||||
}
|
||||
}
|
||||
|
||||
// Top Shot Elite controllers
|
||||
check_device(0x12BA, 0x04A0, 0x04A0, "Top Shot Elite");
|
||||
check_device(0x12BA, 0x04A1, 0x04A1, "Top Shot Fearmaster");
|
||||
check_device(0x12BA, 0x04B0, 0x04B0, "Rapala Fishing Rod");
|
||||
|
||||
// GT5 Wheels&co
|
||||
check_device(0x046D, 0xC283, 0xC29B, "lgFF_c283_c29b");
|
||||
check_device(0x044F, 0xB653, 0xB653, "Thrustmaster RGT FFB Pro");
|
||||
check_device(0x044F, 0xB65A, 0xB65A, "Thrustmaster F430");
|
||||
check_device(0x044F, 0xB65D, 0xB65D, "Thrustmaster FFB");
|
||||
check_device(0x044F, 0xB65E, 0xB65E, "Thrustmaster TRS");
|
||||
check_device(0x044F, 0xB660, 0xB660, "Thrustmaster T500 RS Gear Shift");
|
||||
|
||||
// GT6
|
||||
check_device(0x2833, 0x0001, 0x0001, "Oculus");
|
||||
check_device(0x046D, 0xCA03, 0xCA03, "lgFF_ca03_ca03");
|
||||
|
||||
// Buzz controllers
|
||||
check_device(0x054C, 0x1000, 0x1040, "buzzer0");
|
||||
check_device(0x054C, 0x0001, 0x0041, "buzzer1");
|
||||
check_device(0x054C, 0x0042, 0x0042, "buzzer2");
|
||||
check_device(0x046D, 0xC220, 0xC220, "buzzer9");
|
||||
|
||||
// GunCon3 Gun
|
||||
check_device(0x0B9A, 0x0800, 0x0800, "GunCon3");
|
||||
|
||||
// uDraw GameTablet
|
||||
check_device(0x20D6, 0xCB17, 0xCB17, "uDraw GameTablet");
|
||||
|
||||
// DVB-T
|
||||
check_device(0x1415, 0x0003, 0x0003, "PlayTV SCEH-0036");
|
||||
|
||||
// 0x0900: "H050 USJ(C) PCB rev00", 0x0910: "USIO PCB rev00"
|
||||
if (check_device(0x0B9A, 0x0900, 0x0910, "PS3A-USJ"))
|
||||
{
|
||||
found_usj = true;
|
||||
}
|
||||
|
||||
// Densha de GO! controller
|
||||
check_device(0x0AE4, 0x0004, 0x0004, "Densha de GO! Type 2 Controller");
|
||||
|
||||
// EA Active 2 dongle for connecting wristbands & legband
|
||||
check_device(0x21A4, 0xAC27, 0xAC27, "EA Active 2 Dongle");
|
||||
|
||||
// Tony Hawk RIDE Skateboard
|
||||
check_device(0x12BA, 0x0400, 0x0400, "Tony Hawk RIDE Skateboard Controller");
|
||||
|
||||
// PSP in UsbPspCm mode
|
||||
check_device(0x054C, 0x01CB, 0x01CB, "UsbPspcm");
|
||||
sys_usbd.notice("Ghltar config=\n", g_cfg_ghltar.to_string());
|
||||
}
|
||||
|
||||
libusb_free_device_list(list, 1);
|
||||
if (g_cfg.io.turntable == turntable_handler::one_controller || g_cfg.io.turntable == turntable_handler::two_controllers)
|
||||
{
|
||||
if (!g_cfg_turntable.load())
|
||||
{
|
||||
sys_usbd.notice("Could not load turntable config. Using defaults.");
|
||||
}
|
||||
|
||||
sys_usbd.notice("Turntable config=\n", g_cfg_turntable.to_string());
|
||||
}
|
||||
|
||||
if (g_cfg.io.buzz == buzz_handler::one_controller || g_cfg.io.buzz == buzz_handler::two_controllers)
|
||||
{
|
||||
if (!g_cfg_buzz.load())
|
||||
{
|
||||
sys_usbd.notice("Could not load buzz config. Using defaults.");
|
||||
}
|
||||
|
||||
sys_usbd.notice("Buzz config=\n", g_cfg_buzz.to_string());
|
||||
}
|
||||
|
||||
perform_scan();
|
||||
|
||||
// Set up emulated devices for any devices that are not already being passed through
|
||||
std::map<usb_allow_list_entry, int> emulate_device_check;
|
||||
for (const auto& dev : usb_devices)
|
||||
{
|
||||
for (const auto& entry : device_allow_list)
|
||||
{
|
||||
const u16 idVendor = dev->device._device.idVendor;
|
||||
const u16 idProduct = dev->device._device.idProduct;
|
||||
if (entry.max_device_count != nullptr && (idVendor == entry.id_vendor && idProduct >= entry.id_product_min && idProduct <= entry.id_product_max))
|
||||
{
|
||||
emulate_device_check[entry]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [entry, count] : emulate_device_check)
|
||||
{
|
||||
if (entry.max_device_count && entry.make_instance)
|
||||
{
|
||||
for (int i = count; i < entry.max_device_count(); i++)
|
||||
{
|
||||
sys_usbd.success("Emulating device: %s (%d)", std::basic_string(entry.device_name), i);
|
||||
auto usb_dev = entry.make_instance(i, get_new_location());
|
||||
connect_usb_device(usb_dev, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; i++) // Add VFS USB mass storage devices (/dev_usbXXX) to the USB device list
|
||||
{
|
||||
|
@ -396,37 +542,6 @@ usb_handler_thread::usb_handler_thread()
|
|||
usb_devices.push_back(std::make_shared<usb_device_vfs>(usb_info, get_new_location()));
|
||||
}
|
||||
|
||||
if (!found_skylander)
|
||||
{
|
||||
sys_usbd.notice("Adding emulated skylander");
|
||||
usb_devices.push_back(std::make_shared<usb_device_skylander>(get_new_location()));
|
||||
}
|
||||
|
||||
if (!found_infinity)
|
||||
{
|
||||
sys_usbd.notice("Adding emulated infinity base");
|
||||
usb_devices.push_back(std::make_shared<usb_device_infinity>(get_new_location()));
|
||||
}
|
||||
|
||||
if (!found_dimension)
|
||||
{
|
||||
sys_usbd.notice("Adding emulated dimension toypad");
|
||||
usb_devices.push_back(std::make_shared<usb_device_dimensions>(get_new_location()));
|
||||
}
|
||||
|
||||
if (!found_usj)
|
||||
{
|
||||
if (!g_cfg_usio.load())
|
||||
{
|
||||
sys_usbd.notice("Could not load usio config. Using defaults.");
|
||||
}
|
||||
|
||||
sys_usbd.notice("Adding emulated USIO");
|
||||
usb_devices.push_back(std::make_shared<usb_device_usio>(get_new_location()));
|
||||
|
||||
sys_usbd.notice("USIO config=\n", g_cfg_usio.to_string());
|
||||
}
|
||||
|
||||
const std::vector<std::string> devices_list = fmt::split(g_cfg.io.midi_devices.to_string(), { "@@@" });
|
||||
for (usz index = 0; index < std::min(max_midi_devices, devices_list.size()); index++)
|
||||
{
|
||||
|
@ -458,65 +573,6 @@ usb_handler_thread::usb_handler_thread()
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_cfg.io.ghltar == ghltar_handler::one_controller || g_cfg.io.ghltar == ghltar_handler::two_controllers)
|
||||
{
|
||||
if (!g_cfg_ghltar.load())
|
||||
{
|
||||
sys_usbd.notice("Could not load ghltar config. Using defaults.");
|
||||
}
|
||||
|
||||
sys_usbd.notice("Adding emulated GHLtar (1 player)");
|
||||
usb_devices.push_back(std::make_shared<usb_device_ghltar>(0, get_new_location()));
|
||||
|
||||
if (g_cfg.io.ghltar == ghltar_handler::two_controllers)
|
||||
{
|
||||
sys_usbd.notice("Adding emulated GHLtar (2 players)");
|
||||
usb_devices.push_back(std::make_shared<usb_device_ghltar>(1, get_new_location()));
|
||||
}
|
||||
|
||||
sys_usbd.notice("Ghltar config=\n", g_cfg_ghltar.to_string());
|
||||
}
|
||||
|
||||
if (g_cfg.io.turntable == turntable_handler::one_controller || g_cfg.io.turntable == turntable_handler::two_controllers)
|
||||
{
|
||||
if (!g_cfg_turntable.load())
|
||||
{
|
||||
sys_usbd.notice("Could not load turntable config. Using defaults.");
|
||||
}
|
||||
|
||||
sys_usbd.notice("Adding emulated turntable (1 player)");
|
||||
usb_devices.push_back(std::make_shared<usb_device_turntable>(0, get_new_location()));
|
||||
|
||||
if (g_cfg.io.turntable == turntable_handler::two_controllers)
|
||||
{
|
||||
sys_usbd.notice("Adding emulated turntable (2 players)");
|
||||
usb_devices.push_back(std::make_shared<usb_device_turntable>(1, get_new_location()));
|
||||
}
|
||||
|
||||
sys_usbd.notice("Turntable config=\n", g_cfg_turntable.to_string());
|
||||
}
|
||||
|
||||
if (g_cfg.io.buzz == buzz_handler::one_controller || g_cfg.io.buzz == buzz_handler::two_controllers)
|
||||
{
|
||||
if (!g_cfg_buzz.load())
|
||||
{
|
||||
sys_usbd.notice("Could not load buzz config. Using defaults.");
|
||||
}
|
||||
|
||||
sys_usbd.notice("Adding emulated Buzz! buzzer (1-4 players)");
|
||||
usb_devices.push_back(std::make_shared<usb_device_buzz>(0, 3, get_new_location()));
|
||||
|
||||
if (g_cfg.io.buzz == buzz_handler::two_controllers)
|
||||
{
|
||||
// The current buzz emulation piggybacks on the pad input.
|
||||
// Since there can only be 7 pads connected on a PS3 the 8th player is currently not supported
|
||||
sys_usbd.notice("Adding emulated Buzz! buzzer (5-7 players)");
|
||||
usb_devices.push_back(std::make_shared<usb_device_buzz>(4, 6, get_new_location()));
|
||||
}
|
||||
|
||||
sys_usbd.notice("Buzz config=\n", g_cfg_buzz.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
usb_handler_thread::~usb_handler_thread()
|
||||
|
@ -525,6 +581,7 @@ usb_handler_thread::~usb_handler_thread()
|
|||
handled_devices.clear();
|
||||
open_pipes.clear();
|
||||
usb_devices.clear();
|
||||
usb_passthrough_devices.clear();
|
||||
|
||||
for (u32 index = 0; index < MAX_SYS_USBD_TRANSFERS; index++)
|
||||
{
|
||||
|
@ -532,6 +589,10 @@ usb_handler_thread::~usb_handler_thread()
|
|||
libusb_free_transfer(transfers[index].transfer);
|
||||
}
|
||||
|
||||
#if LIBUSB_API_VERSION >= 0x01000102
|
||||
libusb_hotplug_deregister_callback(ctx, callback_handle);
|
||||
#endif
|
||||
|
||||
if (ctx)
|
||||
libusb_exit(ctx);
|
||||
}
|
||||
|
@ -539,10 +600,21 @@ usb_handler_thread::~usb_handler_thread()
|
|||
void usb_handler_thread::operator()()
|
||||
{
|
||||
timeval lusb_tv{0, 0};
|
||||
|
||||
if (!hotplug_supported)
|
||||
{
|
||||
usb_hotplug_timeout = get_system_time() + 4'000'000ull;
|
||||
}
|
||||
while (ctx && thread_ctrl::state() != thread_state::aborting)
|
||||
{
|
||||
// Todo: Hotplug here?
|
||||
const u64 now = get_system_time();
|
||||
if (now > usb_hotplug_timeout)
|
||||
{
|
||||
// If we did the hotplug scan each cycle the game performance was significantly degraded, so we only perform this scan
|
||||
// every 4 seconds.
|
||||
// On systems where hotplug is native, we wait a little bit for devices to settle before we start the scan
|
||||
perform_scan();
|
||||
usb_hotplug_timeout = hotplug_supported ? umax : get_system_time() + 4'000'000ull;
|
||||
}
|
||||
|
||||
// Process asynchronous requests that are pending
|
||||
libusb_handle_events_timeout_completed(ctx, &lusb_tv, nullptr);
|
||||
|
@ -836,6 +908,7 @@ void usb_handler_thread::connect_usb_device(std::shared_ptr<usb_device> dev, boo
|
|||
if (!dev->open_device())
|
||||
{
|
||||
sys_usbd.error("Failed to open USB device(VID=0x%04x, PID=0x%04x) for LDD <%s>", dev->device._device.idVendor, dev->device._device.idProduct, name);
|
||||
disconnect_usb_device(dev);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -844,6 +917,7 @@ void usb_handler_thread::connect_usb_device(std::shared_ptr<usb_device> dev, boo
|
|||
handled_devices.emplace(dev->assigned_number, std::pair(UsbInternalDevice{0x00, narrow<u8>(dev->assigned_number), 0x02, 0x40}, dev));
|
||||
send_message(SYS_USBD_ATTACH, dev->assigned_number);
|
||||
sys_usbd.success("USB device(VID=0x%04x, PID=0x%04x) matches up with LDD <%s>, assigned as handled_device=0x%x", dev->device._device.idVendor, dev->device._device.idProduct, name, dev->assigned_number);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -855,10 +929,18 @@ void usb_handler_thread::disconnect_usb_device(std::shared_ptr<usb_device> dev,
|
|||
send_message(SYS_USBD_DETACH, dev->assigned_number);
|
||||
sys_usbd.success("USB device(VID=0x%04x, PID=0x%04x) unassigned, handled_device=0x%x", dev->device._device.idVendor, dev->device._device.idProduct, dev->assigned_number);
|
||||
dev->assigned_number = 0;
|
||||
std::erase_if(open_pipes, [&](const auto& val)
|
||||
{
|
||||
return val.second.device == dev;
|
||||
});
|
||||
}
|
||||
|
||||
if (update_usb_devices)
|
||||
usb_devices.erase(find(usb_devices.begin(), usb_devices.end(), dev));
|
||||
{
|
||||
std::erase_if(usb_devices, [&](const auto& val)
|
||||
{
|
||||
return val == dev;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void connect_usb_controller(u8 index, input::product_type type)
|
||||
|
@ -947,6 +1029,14 @@ void connect_usb_controller(u8 index, input::product_type type)
|
|||
}
|
||||
}
|
||||
|
||||
void handle_hotplug_event(bool connected)
|
||||
{
|
||||
if (auto usbh = g_fxo->try_get<named_thread<usb_handler_thread>>())
|
||||
{
|
||||
usbh->usb_hotplug_timeout = get_system_time() + (connected ? 1'000'000ull : 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
error_code sys_usbd_initialize(ppu_thread& ppu, vm::ptr<u32> handle)
|
||||
{
|
||||
|
|
|
@ -89,3 +89,4 @@ error_code sys_usbd_register_extra_ldd(ppu_thread& ppu, u32 handle, vm::cptr<cha
|
|||
error_code sys_usbd_unregister_extra_ldd(ppu_thread& ppu, u32 handle, vm::cptr<char> s_product, u16 slen_product);
|
||||
|
||||
void connect_usb_controller(u8 index, input::product_type);
|
||||
void handle_hotplug_event(bool connected);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "Buzz.h"
|
||||
#include "Emu/Cell/lv2/sys_usbd.h"
|
||||
#include "Emu/Io/buzz_config.h"
|
||||
#include "Emu/system_config.h"
|
||||
#include "Input/pad_thread.h"
|
||||
|
||||
LOG_CHANNEL(buzz_log, "BUZZ");
|
||||
|
@ -85,6 +86,23 @@ usb_device_buzz::~usb_device_buzz()
|
|||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<usb_device> usb_device_buzz::make_instance(u32 controller_index, const std::array<u8, 7>& location)
|
||||
{
|
||||
if (controller_index == 0)
|
||||
{
|
||||
return std::make_shared<usb_device_buzz>(0, 3, location);
|
||||
}
|
||||
|
||||
// The current buzz emulation piggybacks on the pad input.
|
||||
// Since there can only be 7 pads connected on a PS3 the 8th player is currently not supported
|
||||
return std::make_shared<usb_device_buzz>(4, 6, location);
|
||||
}
|
||||
|
||||
u16 usb_device_buzz::get_num_emu_devices()
|
||||
{
|
||||
return static_cast<u16>(g_cfg.io.buzz.get());
|
||||
}
|
||||
|
||||
void usb_device_buzz::control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer)
|
||||
{
|
||||
transfer->fake = true;
|
||||
|
|
|
@ -8,6 +8,9 @@ public:
|
|||
usb_device_buzz(u32 first_controller, u32 last_controller, const std::array<u8, 7>& location);
|
||||
~usb_device_buzz();
|
||||
|
||||
static std::shared_ptr<usb_device> make_instance(u32 controller_index, const std::array<u8, 7>& location);
|
||||
static u16 get_num_emu_devices();
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
@ -573,6 +573,16 @@ usb_device_dimensions::~usb_device_dimensions()
|
|||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<usb_device> usb_device_dimensions::make_instance(u32, const std::array<u8, 7>& location)
|
||||
{
|
||||
return std::make_shared<usb_device_dimensions>(location);
|
||||
}
|
||||
|
||||
u16 usb_device_dimensions::get_num_emu_devices()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
void usb_device_dimensions::control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer)
|
||||
{
|
||||
usb_device_emulated::control_transfer(bmRequestType, bRequest, wValue, wIndex, wLength, buf_size, buf, transfer);
|
||||
|
|
|
@ -71,6 +71,9 @@ public:
|
|||
usb_device_dimensions(const std::array<u8, 7>& location);
|
||||
~usb_device_dimensions();
|
||||
|
||||
static std::shared_ptr<usb_device> make_instance(u32 controller_index, const std::array<u8, 7>& location);
|
||||
static u16 get_num_emu_devices();
|
||||
|
||||
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;
|
||||
void isochronous_transfer(UsbTransfer* transfer) override;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "GHLtar.h"
|
||||
#include "Emu/Cell/lv2/sys_usbd.h"
|
||||
#include "Emu/Io/ghltar_config.h"
|
||||
#include "Emu/system_config.h"
|
||||
#include "Input/pad_thread.h"
|
||||
|
||||
LOG_CHANNEL(ghltar_log, "GHLTAR");
|
||||
|
@ -52,6 +53,16 @@ usb_device_ghltar::~usb_device_ghltar()
|
|||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<usb_device> usb_device_ghltar::make_instance(u32 controller_index, const std::array<u8, 7>& location)
|
||||
{
|
||||
return std::make_shared<usb_device_ghltar>(controller_index, location);
|
||||
}
|
||||
|
||||
u16 usb_device_ghltar::get_num_emu_devices()
|
||||
{
|
||||
return static_cast<u16>(g_cfg.io.ghltar.get());
|
||||
}
|
||||
|
||||
void usb_device_ghltar::control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer)
|
||||
{
|
||||
transfer->fake = true;
|
||||
|
|
|
@ -8,6 +8,9 @@ public:
|
|||
usb_device_ghltar(u32 controller_index, const std::array<u8, 7>& location);
|
||||
~usb_device_ghltar();
|
||||
|
||||
static std::shared_ptr<usb_device> make_instance(u32 controller_index, const std::array<u8, 7>& location);
|
||||
static u16 get_num_emu_devices();
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
@ -373,6 +373,16 @@ usb_device_infinity::~usb_device_infinity()
|
|||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<usb_device> usb_device_infinity::make_instance(u32, const std::array<u8, 7>& location)
|
||||
{
|
||||
return std::make_shared<usb_device_infinity>(location);
|
||||
}
|
||||
|
||||
u16 usb_device_infinity::get_num_emu_devices()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
void usb_device_infinity::control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer)
|
||||
{
|
||||
usb_device_emulated::control_transfer(bmRequestType, bRequest, wValue, wIndex, wLength, buf_size, buf, transfer);
|
||||
|
|
|
@ -60,6 +60,9 @@ public:
|
|||
usb_device_infinity(const std::array<u8, 7>& location);
|
||||
~usb_device_infinity();
|
||||
|
||||
static std::shared_ptr<usb_device> make_instance(u32 controller_index, const std::array<u8, 7>& location);
|
||||
static u16 get_num_emu_devices();
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
@ -208,6 +208,16 @@ usb_device_skylander::~usb_device_skylander()
|
|||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<usb_device> usb_device_skylander::make_instance(u32, const std::array<u8, 7>& location)
|
||||
{
|
||||
return std::make_shared<usb_device_skylander>(location);
|
||||
}
|
||||
|
||||
u16 usb_device_skylander::get_num_emu_devices()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
void usb_device_skylander::control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer)
|
||||
{
|
||||
transfer->fake = true;
|
||||
|
@ -342,7 +352,7 @@ void usb_device_skylander::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoin
|
|||
transfer->fake = true;
|
||||
transfer->expected_count = buf_size;
|
||||
transfer->expected_result = HC_CC_NOERR;
|
||||
|
||||
|
||||
if (endpoint == 0x02)
|
||||
{
|
||||
// Audio transfers are fairly quick(~1ms)
|
||||
|
|
|
@ -46,6 +46,9 @@ public:
|
|||
usb_device_skylander(const std::array<u8, 7>& location);
|
||||
~usb_device_skylander();
|
||||
|
||||
static std::shared_ptr<usb_device> make_instance(u32 controller_index, const std::array<u8, 7>& location);
|
||||
static u16 get_num_emu_devices();
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "Emu/Cell/lv2/sys_usbd.h"
|
||||
#include "Emu/Io/turntable_config.h"
|
||||
#include "Input/pad_thread.h"
|
||||
#include "Emu/system_config.h"
|
||||
|
||||
LOG_CHANNEL(turntable_log, "TURN");
|
||||
|
||||
|
@ -53,6 +54,16 @@ usb_device_turntable::~usb_device_turntable()
|
|||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<usb_device> usb_device_turntable::make_instance(u32 controller_index, const std::array<u8, 7>& location)
|
||||
{
|
||||
return std::make_shared<usb_device_turntable>(controller_index, location);
|
||||
}
|
||||
|
||||
u16 usb_device_turntable::get_num_emu_devices()
|
||||
{
|
||||
return static_cast<u16>(g_cfg.io.turntable.get());
|
||||
}
|
||||
|
||||
void usb_device_turntable::control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer)
|
||||
{
|
||||
transfer->fake = true;
|
||||
|
|
|
@ -8,6 +8,9 @@ public:
|
|||
usb_device_turntable(u32 controller_index, const std::array<u8, 7>& location);
|
||||
~usb_device_turntable();
|
||||
|
||||
static std::shared_ptr<usb_device> make_instance(u32 controller_index, const std::array<u8, 7>& location);
|
||||
static u16 get_num_emu_devices();
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
@ -504,6 +504,8 @@ struct Pad
|
|||
u16 m_vendor_id{0};
|
||||
u16 m_product_id{0};
|
||||
|
||||
u64 m_disconnection_timer{0};
|
||||
|
||||
s32 m_pressure_intensity_button_index{-1}; // Special button index. -1 if not set.
|
||||
bool m_pressure_intensity_button_pressed{}; // Last sensitivity button press state, used for toggle.
|
||||
bool m_pressure_intensity_toggled{}; // Whether the sensitivity is toggled on or off.
|
||||
|
|
|
@ -127,6 +127,16 @@ usb_device_usio::~usb_device_usio()
|
|||
save_backup();
|
||||
}
|
||||
|
||||
std::shared_ptr<usb_device> usb_device_usio::make_instance(u32, const std::array<u8, 7>& location)
|
||||
{
|
||||
return std::make_shared<usb_device_usio>(location);
|
||||
}
|
||||
|
||||
u16 usb_device_usio::get_num_emu_devices()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
void usb_device_usio::control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer)
|
||||
{
|
||||
transfer->fake = true;
|
||||
|
|
|
@ -9,6 +9,9 @@ public:
|
|||
usb_device_usio(const std::array<u8, 7>& location);
|
||||
~usb_device_usio();
|
||||
|
||||
static std::shared_ptr<usb_device> make_instance(u32 controller_index, const std::array<u8, 7>& location);
|
||||
static u16 get_num_emu_devices();
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
@ -62,6 +62,10 @@
|
|||
|
||||
#include "Emu/RSX/GSRender.h"
|
||||
|
||||
#ifdef LLVM_AVAILABLE
|
||||
#include "llvm/Config/llvm-config.h"
|
||||
#endif
|
||||
|
||||
LOG_CHANNEL(sys_log, "SYS");
|
||||
|
||||
// Preallocate 32 MiB
|
||||
|
@ -345,6 +349,19 @@ extern void dump_executable(std::span<const u8> data, const ppu_module<lv2_obj>*
|
|||
|
||||
void Emulator::Init()
|
||||
{
|
||||
// Log LLVM version
|
||||
if (static bool logged_llvm = false; !logged_llvm)
|
||||
{
|
||||
#ifndef LLVM_AVAILABLE
|
||||
sys_log.always()("LLVM version: Compiled without LLVM");
|
||||
#elif defined (LLVM_VERSION_STRING)
|
||||
sys_log.always()("LLVM version: %s", LLVM_VERSION_STRING);
|
||||
#else
|
||||
sys_log.always()("LLVM version: Unknown");
|
||||
#endif
|
||||
logged_llvm = true;
|
||||
}
|
||||
|
||||
jit_runtime::initialize();
|
||||
|
||||
const std::string emu_dir = rpcs3::utils::get_emu_dir();
|
||||
|
@ -1100,47 +1117,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
|||
}
|
||||
else
|
||||
{
|
||||
fs::file save{m_path, fs::isfile + fs::read};
|
||||
|
||||
if (m_path.ends_with(".SAVESTAT") && save && save.size() >= 8 && save.read<u64>() == "RPCS3SAV"_u64)
|
||||
{
|
||||
m_ar = std::make_shared<utils::serial>();
|
||||
m_ar->set_reading_state();
|
||||
|
||||
m_ar->m_file_handler = make_uncompressed_serialization_file_handler(std::move(save));
|
||||
}
|
||||
else if (save && m_path.ends_with(".zst"))
|
||||
{
|
||||
m_ar = std::make_shared<utils::serial>();
|
||||
m_ar->set_reading_state();
|
||||
|
||||
m_ar->m_file_handler = make_compressed_zstd_serialization_file_handler(std::move(save));
|
||||
|
||||
if (m_ar->try_read<u64>().second != "RPCS3SAV"_u64)
|
||||
{
|
||||
m_ar.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ar->pos = 0;
|
||||
}
|
||||
}
|
||||
else if (save && m_path.ends_with(".gz"))
|
||||
{
|
||||
m_ar = std::make_shared<utils::serial>();
|
||||
m_ar->set_reading_state();
|
||||
|
||||
m_ar->m_file_handler = make_compressed_serialization_file_handler(std::move(save));
|
||||
|
||||
if (m_ar->try_read<u64>().second != "RPCS3SAV"_u64)
|
||||
{
|
||||
m_ar.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ar->pos = 0;
|
||||
}
|
||||
}
|
||||
m_ar = make_savestate_reader(m_path);
|
||||
|
||||
m_boot_source_type = CELL_GAME_GAMETYPE_SYS;
|
||||
}
|
||||
|
@ -1172,18 +1149,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
|||
|
||||
if (m_ar)
|
||||
{
|
||||
struct file_header
|
||||
{
|
||||
ENABLE_BITWISE_SERIALIZATION;
|
||||
|
||||
nse_t<u64, 1> magic;
|
||||
bool LE_format;
|
||||
bool state_inspection_support;
|
||||
nse_t<u64, 1> offset;
|
||||
b8 flag_versions_is_following_data;
|
||||
};
|
||||
|
||||
const auto header = m_ar->try_read<file_header>().second;
|
||||
const auto header = m_ar->try_read<savestate_header>().second;
|
||||
|
||||
if (header.magic != "RPCS3SAV"_u64)
|
||||
{
|
||||
|
@ -1312,7 +1278,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
|||
|
||||
if (m_ar->m_max_data != m_ar->pos)
|
||||
{
|
||||
fmt::throw_exception("TAR desrialization failed: read bytes: 0x%x, expected: 0x%x, path='%s', ar: %s", m_ar->pos - (m_ar->m_max_data - size), size, path, *m_ar);
|
||||
fmt::throw_exception("TAR deserialization failed: read bytes: 0x%x, expected: 0x%x, path='%s', ar: %s", m_ar->pos - (m_ar->m_max_data - size), size, path, *m_ar);
|
||||
}
|
||||
|
||||
m_ar->m_max_data = umax;
|
||||
|
|
|
@ -162,6 +162,60 @@ std::vector<version_entry> get_savestate_versioning_data(fs::file&& file, std::s
|
|||
return ver_data;
|
||||
}
|
||||
|
||||
std::shared_ptr<utils::serial> make_savestate_reader(const std::string& path)
|
||||
{
|
||||
std::shared_ptr<utils::serial> ar;
|
||||
|
||||
fs::file save{path, fs::isfile + fs::read};
|
||||
|
||||
if (!save)
|
||||
{
|
||||
return ar;
|
||||
}
|
||||
|
||||
if (path.ends_with(".SAVESTAT") && save.size() >= 8 && save.read<u64>() == "RPCS3SAV"_u64)
|
||||
{
|
||||
ar = std::make_shared<utils::serial>();
|
||||
ar->set_reading_state();
|
||||
|
||||
ar->m_file_handler = make_uncompressed_serialization_file_handler(std::move(save));
|
||||
}
|
||||
else if (path.ends_with(".zst"))
|
||||
{
|
||||
ar = std::make_shared<utils::serial>();
|
||||
ar->set_reading_state();
|
||||
|
||||
ar->m_file_handler = make_compressed_zstd_serialization_file_handler(std::move(save));
|
||||
|
||||
if (ar->try_read<u64>().second != "RPCS3SAV"_u64)
|
||||
{
|
||||
ar.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
ar->pos = 0;
|
||||
}
|
||||
}
|
||||
else if (path.ends_with(".gz"))
|
||||
{
|
||||
ar = std::make_shared<utils::serial>();
|
||||
ar->set_reading_state();
|
||||
|
||||
ar->m_file_handler = make_compressed_serialization_file_handler(std::move(save));
|
||||
|
||||
if (ar->try_read<u64>().second != "RPCS3SAV"_u64)
|
||||
{
|
||||
ar.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
ar->pos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return ar;
|
||||
}
|
||||
|
||||
bool is_savestate_version_compatible(const std::vector<version_entry>& data, bool is_boot_check)
|
||||
{
|
||||
if (data.empty())
|
||||
|
@ -256,6 +310,15 @@ bool is_savestate_compatible(fs::file&& file, std::string_view filepath)
|
|||
return is_savestate_version_compatible(get_savestate_versioning_data(std::move(file), filepath), false);
|
||||
}
|
||||
|
||||
bool is_savestate_compatible(const std::string& filepath)
|
||||
{
|
||||
if (fs::file file{filepath, fs::isfile + fs::read})
|
||||
{
|
||||
return is_savestate_compatible(std::move(file), filepath);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<version_entry> read_used_savestate_versions()
|
||||
{
|
||||
std::vector<version_entry> used_serial;
|
||||
|
|
|
@ -10,6 +10,16 @@ struct version_entry
|
|||
ENABLE_BITWISE_SERIALIZATION;
|
||||
};
|
||||
|
||||
struct savestate_header
|
||||
{
|
||||
ENABLE_BITWISE_SERIALIZATION;
|
||||
|
||||
nse_t<u64, 1> magic;
|
||||
bool LE_format;
|
||||
bool state_inspection_support;
|
||||
nse_t<u64, 1> offset;
|
||||
b8 flag_versions_is_following_data;
|
||||
};
|
||||
|
||||
struct hle_locks_t
|
||||
{
|
||||
|
@ -27,9 +37,11 @@ struct hle_locks_t
|
|||
bool try_finalize(std::function<bool()> test);
|
||||
};
|
||||
|
||||
std::shared_ptr<utils::serial> make_savestate_reader(const std::string& path);
|
||||
bool load_and_check_reserved(utils::serial& ar, usz size);
|
||||
bool is_savestate_version_compatible(const std::vector<version_entry>& data, bool is_boot_check);
|
||||
std::vector<version_entry> get_savestate_versioning_data(fs::file&& file, std::string_view filepath);
|
||||
bool is_savestate_compatible(fs::file&& file, std::string_view filepath);
|
||||
bool is_savestate_compatible(const std::string& filepath);
|
||||
std::vector<version_entry> read_used_savestate_versions();
|
||||
std::string get_savestate_file(std::string_view title_id, std::string_view boot_path, s64 abs_id, s64 rel_id);
|
||||
std::string get_savestate_file(std::string_view title_id, std::string_view boot_path, s64 abs_id, s64 rel_id);
|
||||
|
|
|
@ -56,6 +56,9 @@ struct pad_setting
|
|||
u32 port_status = 0;
|
||||
u32 device_capability = 0;
|
||||
u32 device_type = 0;
|
||||
u32 class_type = 0;
|
||||
u16 vendor_id = 0;
|
||||
u16 product_id = 0;
|
||||
bool is_ldd_pad = false;
|
||||
};
|
||||
|
||||
|
@ -86,6 +89,9 @@ void pad_thread::Init()
|
|||
m_pads[i]->m_port_status,
|
||||
m_pads[i]->m_device_capability,
|
||||
m_pads[i]->m_device_type,
|
||||
m_pads[i]->m_class_type,
|
||||
m_pads[i]->m_vendor_id,
|
||||
m_pads[i]->m_product_id,
|
||||
m_pads[i]->ldd
|
||||
};
|
||||
}
|
||||
|
@ -96,6 +102,9 @@ void pad_thread::Init()
|
|||
CELL_PAD_STATUS_DISCONNECTED,
|
||||
CELL_PAD_CAPABILITY_PS3_CONFORMITY | CELL_PAD_CAPABILITY_PRESS_MODE | CELL_PAD_CAPABILITY_ACTUATOR,
|
||||
CELL_PAD_DEV_TYPE_STANDARD,
|
||||
CELL_PAD_PCLASS_TYPE_STANDARD,
|
||||
0,
|
||||
0,
|
||||
false
|
||||
};
|
||||
}
|
||||
|
@ -197,6 +206,12 @@ void pad_thread::Init()
|
|||
{
|
||||
input_log.notice("Pad %d: config=\n%s", i, cfg->to_string());
|
||||
}
|
||||
|
||||
// If the user changes the emulated controller, then simulate unplugging and plugging in a new controller
|
||||
if (m_pads_connected[i] && (pad_settings[i].class_type != pad->m_class_type || pad_settings[i].vendor_id != pad->m_vendor_id || pad_settings[i].product_id != pad->m_product_id))
|
||||
{
|
||||
pad->m_disconnection_timer = get_system_time() + 30'000ull;
|
||||
}
|
||||
}
|
||||
|
||||
pad->is_fake_pad = ((g_cfg.io.move == move_handler::real || g_cfg.io.move == move_handler::fake) && i >= (static_cast<u32>(CELL_PAD_MAX_PORT_NUM) - static_cast<u32>(CELL_GEM_MAX_NUM)))
|
||||
|
@ -235,6 +250,25 @@ void pad_thread::update_pad_states()
|
|||
for (usz i = 0; i < m_pads.size(); i++)
|
||||
{
|
||||
const auto& pad = m_pads[i];
|
||||
|
||||
// Simulate unplugging and plugging in a new controller
|
||||
if (pad && pad->m_disconnection_timer > 0)
|
||||
{
|
||||
const bool is_connected = pad->m_port_status & CELL_PAD_STATUS_CONNECTED;
|
||||
const u64 now = get_system_time();
|
||||
|
||||
if (is_connected && now < pad->m_disconnection_timer)
|
||||
{
|
||||
pad->m_port_status &= ~CELL_PAD_STATUS_CONNECTED;
|
||||
pad->m_port_status |= CELL_PAD_STATUS_ASSIGN_CHANGES;
|
||||
}
|
||||
else if (!is_connected && now >= pad->m_disconnection_timer)
|
||||
{
|
||||
pad->m_port_status |= CELL_PAD_STATUS_CONNECTED + CELL_PAD_STATUS_ASSIGN_CHANGES;
|
||||
pad->m_disconnection_timer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const bool connected = pad && !pad->is_fake_pad && !!(pad->m_port_status & CELL_PAD_STATUS_CONNECTED);
|
||||
|
||||
if (m_pads_connected[i] == connected)
|
||||
|
@ -603,6 +637,8 @@ void pad_thread::UnregisterLddPad(u32 handle)
|
|||
ensure(handle < m_pads.size());
|
||||
|
||||
m_pads[handle]->ldd = false;
|
||||
m_pads[handle]->m_port_status &= ~CELL_PAD_STATUS_CONNECTED;
|
||||
m_pads[handle]->m_port_status |= CELL_PAD_STATUS_ASSIGN_CHANGES;
|
||||
|
||||
num_ldd_pad--;
|
||||
}
|
||||
|
|
|
@ -665,6 +665,7 @@ int main(int argc, char** argv)
|
|||
logs::stored_message qt{(strcmp(QT_VERSION_STR, qVersion()) != 0) ? sys_log.error : sys_log.notice};
|
||||
qt.text = fmt::format("Qt version: Compiled against Qt %s | Run-time uses Qt %s", QT_VERSION_STR, qVersion());
|
||||
|
||||
// Write current time
|
||||
logs::stored_message time{sys_log.always()};
|
||||
time.text = fmt::format("Current Time: %s", std::chrono::system_clock::now());
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "Emu/Audio/audio_utils.h"
|
||||
#include "Emu/Io/Null/null_camera_handler.h"
|
||||
#include "Emu/Io/Null/null_music_handler.h"
|
||||
#include "Emu/Cell/lv2/sys_usbd.h"
|
||||
#include "Emu/vfs_config.h"
|
||||
#include "util/init_mutex.hpp"
|
||||
#include "util/console.h"
|
||||
|
@ -54,7 +55,8 @@
|
|||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Windows.h"
|
||||
#include <Usbiodef.h>
|
||||
#include <Dbt.h>
|
||||
#endif
|
||||
|
||||
LOG_CHANNEL(gui_log, "GUI");
|
||||
|
@ -73,6 +75,12 @@ gui_application::~gui_application()
|
|||
#ifdef WITH_DISCORD_RPC
|
||||
discord::shutdown();
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
if (m_device_notification_handle && !UnregisterDeviceNotification(m_device_notification_handle))
|
||||
{
|
||||
gui_log.error("UnregisterDeviceNotification() failed: %s", fmt::win_error{GetLastError(), nullptr});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool gui_application::Init()
|
||||
|
@ -196,6 +204,19 @@ bool gui_application::Init()
|
|||
// Install native event filter
|
||||
#ifdef _WIN32 // Currently only needed for raw mouse input on windows
|
||||
installNativeEventFilter(&m_native_event_filter);
|
||||
|
||||
// Enable usb device hotplug events
|
||||
// Currently only needed for hotplug on windows, as libusb handles other platforms
|
||||
DEV_BROADCAST_DEVICEINTERFACE notification_filter {};
|
||||
notification_filter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
|
||||
notification_filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
|
||||
notification_filter.dbcc_classguid = GUID_DEVINTERFACE_USB_DEVICE;
|
||||
|
||||
m_device_notification_handle = RegisterDeviceNotification(reinterpret_cast<HWND>(m_main_window->winId()), ¬ification_filter, DEVICE_NOTIFY_WINDOW_HANDLE);
|
||||
if (!m_device_notification_handle )
|
||||
{
|
||||
gui_log.error("RegisterDeviceNotification() failed: %s", fmt::win_error{GetLastError(), nullptr});
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
|
@ -1180,15 +1201,24 @@ void gui_application::OnAppStateChanged(Qt::ApplicationState state)
|
|||
bool gui_application::native_event_filter::nativeEventFilter([[maybe_unused]] const QByteArray& eventType, [[maybe_unused]] void* message, [[maybe_unused]] qintptr* result)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!Emu.IsRunning() && !g_raw_mouse_handler)
|
||||
if (!Emu.IsRunning() && !Emu.IsStarting() && !g_raw_mouse_handler)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (eventType == "windows_generic_MSG")
|
||||
{
|
||||
if (MSG* msg = static_cast<MSG*>(message); msg && (msg->message == WM_INPUT || msg->message == WM_KEYDOWN || msg->message == WM_KEYUP))
|
||||
if (MSG* msg = static_cast<MSG*>(message); msg && (msg->message == WM_INPUT || msg->message == WM_KEYDOWN || msg->message == WM_KEYUP || msg->message == WM_DEVICECHANGE))
|
||||
{
|
||||
if (msg->message == WM_DEVICECHANGE && (msg->wParam == DBT_DEVICEARRIVAL || msg->wParam == DBT_DEVICEREMOVECOMPLETE))
|
||||
{
|
||||
if (Emu.IsRunning() || Emu.IsStarting())
|
||||
{
|
||||
handle_hotplug_event(msg->wParam == DBT_DEVICEARRIVAL);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auto* handler = g_fxo->try_get<MouseHandlerBase>(); handler && handler->type == mouse_handler::raw)
|
||||
{
|
||||
static_cast<raw_mouse_handler*>(handler)->handle_native_event(*msg);
|
||||
|
|
|
@ -19,6 +19,10 @@
|
|||
#include <functional>
|
||||
#include <deque>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Windows.h"
|
||||
#endif
|
||||
|
||||
class gs_frame;
|
||||
class main_window;
|
||||
class gui_settings;
|
||||
|
@ -117,7 +121,9 @@ private:
|
|||
u64 m_pause_delayed_tag = 0;
|
||||
typename Emulator::stop_counter_t m_emu_focus_out_emulation_id{};
|
||||
bool m_is_pause_on_focus_loss_active = false;
|
||||
|
||||
#ifdef _WIN32
|
||||
HDEVNOTIFY m_device_notification_handle {};
|
||||
#endif
|
||||
private Q_SLOTS:
|
||||
void OnChangeStyleSheetRequest();
|
||||
void OnShortcutChange();
|
||||
|
|
|
@ -120,6 +120,9 @@ namespace gui
|
|||
const gui_save rg_freeze = gui_save(main_window, "recentGamesFrozen", false);
|
||||
const gui_save rg_entries = gui_save(main_window, "recentGamesNames", QVariant::fromValue(q_pair_list()));
|
||||
|
||||
const gui_save rs_freeze = gui_save(main_window, "recentSavestatesFrozen", false);
|
||||
const gui_save rs_entries = gui_save(main_window, "recentSavestatesNames", QVariant::fromValue(q_pair_list()));
|
||||
|
||||
const gui_save ib_skip_version = gui_save(main_window, "infoBoxSkipVersion", "");
|
||||
const gui_save ib_pkg_success = gui_save(main_window, "infoBoxEnabledInstallPKG", true);
|
||||
const gui_save ib_pup_success = gui_save(main_window, "infoBoxEnabledInstallPUP", true);
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
#include "Emu/System.h"
|
||||
#include "Emu/system_utils.hpp"
|
||||
#include "Emu/system_config.h"
|
||||
#include "Emu/savestate_utils.hpp"
|
||||
|
||||
#include "Crypto/unpkg.h"
|
||||
#include "Crypto/unself.h"
|
||||
|
@ -234,9 +235,9 @@ bool main_window::Init([[maybe_unused]] bool with_cli_boot)
|
|||
show(); // needs to be done before creating the thumbnail toolbar
|
||||
|
||||
// enable play options if a recent game exists
|
||||
const bool enable_play_last = !m_recent_game_acts.isEmpty() && m_recent_game_acts.first();
|
||||
const bool enable_play_last = !m_recent_game.actions.isEmpty() && m_recent_game.actions.first();
|
||||
|
||||
const QString start_tooltip = enable_play_last ? tr("Play %0").arg(m_recent_game_acts.first()->text()) : tr("Play");
|
||||
const QString start_tooltip = enable_play_last ? tr("Play %0").arg(m_recent_game.actions.first()->text()) : tr("Play");
|
||||
|
||||
if (enable_play_last)
|
||||
{
|
||||
|
@ -510,9 +511,9 @@ void main_window::OnPlayOrPause()
|
|||
show_boot_error(error);
|
||||
}
|
||||
}
|
||||
else if (!m_recent_game_acts.isEmpty())
|
||||
else if (!m_recent_game.actions.isEmpty())
|
||||
{
|
||||
BootRecentAction(m_recent_game_acts.first());
|
||||
BootRecentAction(m_recent_game.actions.first(), false);
|
||||
}
|
||||
|
||||
return;
|
||||
|
@ -607,7 +608,7 @@ void main_window::Boot(const std::string& path, const std::string& title_id, boo
|
|||
{
|
||||
gui_log.success("Boot successful.");
|
||||
|
||||
AddRecentAction(gui::Recent_Game(qstr(Emu.GetBoot()), qstr(Emu.GetTitleAndTitleID())));
|
||||
AddRecentAction(gui::Recent_Game(qstr(Emu.GetBoot()), qstr(Emu.GetTitleAndTitleID())), is_savestate_compatible(path));
|
||||
|
||||
if (refresh_list)
|
||||
{
|
||||
|
@ -2190,55 +2191,59 @@ void main_window::OnEnableDiscInsert(bool enabled) const
|
|||
ui->insertDiscAct->setEnabled(enabled);
|
||||
}
|
||||
|
||||
void main_window::BootRecentAction(const QAction* act)
|
||||
void main_window::BootRecentAction(const QAction* act, bool is_savestate)
|
||||
{
|
||||
if (Emu.IsRunning())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
recent_game_wrapper& rgw = is_savestate ? m_recent_save : m_recent_game;
|
||||
QMenu* menu = is_savestate ? ui->bootRecentSavestatesMenu : ui->bootRecentMenu;
|
||||
|
||||
const QString pth = act->data().toString();
|
||||
const std::string path = sstr(pth);
|
||||
const std::string path = pth.toStdString();
|
||||
QString name;
|
||||
bool contains_path = false;
|
||||
|
||||
int idx = -1;
|
||||
for (int i = 0; i < m_rg_entries.count(); i++)
|
||||
for (int i = 0; i < rgw.entries.count(); i++)
|
||||
{
|
||||
if (::at32(m_rg_entries, i).first == pth)
|
||||
const auto& entry = rgw.entries[i];
|
||||
if (entry.first == pth)
|
||||
{
|
||||
idx = i;
|
||||
contains_path = true;
|
||||
name = ::at32(m_rg_entries, idx).second;
|
||||
name = entry.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// path is invalid: remove action from list return
|
||||
if ((contains_path && name.isEmpty()) || (!QFileInfo(pth).isDir() && !QFileInfo(pth).isFile()))
|
||||
if ((contains_path && name.isEmpty()) || !fs::exists(path))
|
||||
{
|
||||
if (contains_path)
|
||||
{
|
||||
// clear menu of actions
|
||||
for (QAction* action : m_recent_game_acts)
|
||||
for (QAction* action : rgw.actions)
|
||||
{
|
||||
ui->bootRecentMenu->removeAction(action);
|
||||
menu->removeAction(action);
|
||||
}
|
||||
|
||||
// remove action from list
|
||||
m_rg_entries.removeAt(idx);
|
||||
m_recent_game_acts.removeAt(idx);
|
||||
rgw.entries.removeAt(idx);
|
||||
rgw.actions.removeAt(idx);
|
||||
|
||||
m_gui_settings->SetValue(gui::rg_entries, gui_settings::List2Var(m_rg_entries));
|
||||
m_gui_settings->SetValue(is_savestate ? gui::rs_entries : gui::rg_entries, gui_settings::List2Var(rgw.entries));
|
||||
|
||||
gui_log.error("Recent Game not valid, removed from Boot Recent list: %s", path);
|
||||
|
||||
// refill menu with actions
|
||||
for (int i = 0; i < m_recent_game_acts.count(); i++)
|
||||
for (int i = 0; i < rgw.actions.count(); i++)
|
||||
{
|
||||
m_recent_game_acts[i]->setShortcut(tr("Ctrl+%1").arg(i + 1));
|
||||
m_recent_game_acts[i]->setToolTip(::at32(m_rg_entries, i).second);
|
||||
ui->bootRecentMenu->addAction(m_recent_game_acts[i]);
|
||||
rgw.actions[i]->setShortcut(tr("Ctrl+%1").arg(i + 1));
|
||||
rgw.actions[i]->setToolTip(::at32(rgw.entries, i).second);
|
||||
menu->addAction(rgw.actions[i]);
|
||||
}
|
||||
|
||||
gui_log.warning("Boot Recent list refreshed");
|
||||
|
@ -2253,19 +2258,21 @@ void main_window::BootRecentAction(const QAction* act)
|
|||
Boot(path, "", true);
|
||||
}
|
||||
|
||||
QAction* main_window::CreateRecentAction(const q_string_pair& entry, const uint& sc_idx)
|
||||
QAction* main_window::CreateRecentAction(const q_string_pair& entry, u32 sc_idx, bool is_savestate)
|
||||
{
|
||||
recent_game_wrapper& rgw = is_savestate ? m_recent_save : m_recent_game;
|
||||
|
||||
// if path is not valid remove from list
|
||||
if (entry.second.isEmpty() || (!QFileInfo(entry.first).isDir() && !QFileInfo(entry.first).isFile()))
|
||||
{
|
||||
if (m_rg_entries.contains(entry))
|
||||
if (rgw.entries.contains(entry))
|
||||
{
|
||||
gui_log.warning("Recent Game not valid, removing from Boot Recent list: %s", entry.first);
|
||||
|
||||
const int idx = m_rg_entries.indexOf(entry);
|
||||
m_rg_entries.removeAt(idx);
|
||||
const int idx = rgw.entries.indexOf(entry);
|
||||
rgw.entries.removeAt(idx);
|
||||
|
||||
m_gui_settings->SetValue(gui::rg_entries, gui_settings::List2Var(m_rg_entries));
|
||||
m_gui_settings->SetValue(is_savestate ? gui::rs_entries : gui::rg_entries, gui_settings::List2Var(rgw.entries));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -2290,69 +2297,74 @@ QAction* main_window::CreateRecentAction(const q_string_pair& entry, const uint&
|
|||
}
|
||||
|
||||
// connect boot
|
||||
connect(act, &QAction::triggered, this, [act, this]() {BootRecentAction(act); });
|
||||
connect(act, &QAction::triggered, this, [this, act, is_savestate](){ BootRecentAction(act, is_savestate); });
|
||||
|
||||
return act;
|
||||
}
|
||||
|
||||
void main_window::AddRecentAction(const q_string_pair& entry)
|
||||
void main_window::AddRecentAction(const q_string_pair& entry, bool is_savestate)
|
||||
{
|
||||
QAction* freezeAction = is_savestate ? ui->freezeRecentSavestatesAct : ui->freezeRecentAct;
|
||||
|
||||
// don't change list on freeze
|
||||
if (ui->freezeRecentAct->isChecked())
|
||||
if (freezeAction->isChecked())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// create new action, return if not valid
|
||||
QAction* act = CreateRecentAction(entry, 1);
|
||||
QAction* act = CreateRecentAction(entry, 1, is_savestate);
|
||||
if (!act)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
recent_game_wrapper& rgw = is_savestate ? m_recent_save : m_recent_game;
|
||||
QMenu* menu = is_savestate ? ui->bootRecentSavestatesMenu : ui->bootRecentMenu;
|
||||
|
||||
// clear menu of actions
|
||||
for (QAction* action : m_recent_game_acts)
|
||||
for (QAction* action : rgw.actions)
|
||||
{
|
||||
ui->bootRecentMenu->removeAction(action);
|
||||
menu->removeAction(action);
|
||||
}
|
||||
|
||||
// If path already exists, remove it in order to get it to beginning. Also remove duplicates.
|
||||
for (int i = m_rg_entries.count() - 1; i >= 0; --i)
|
||||
for (int i = rgw.entries.count() - 1; i >= 0; --i)
|
||||
{
|
||||
if (m_rg_entries[i].first == entry.first)
|
||||
if (rgw.entries[i].first == entry.first)
|
||||
{
|
||||
m_rg_entries.removeAt(i);
|
||||
m_recent_game_acts.removeAt(i);
|
||||
rgw.entries.removeAt(i);
|
||||
rgw.actions.removeAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
// remove oldest action at the end if needed
|
||||
if (m_rg_entries.count() == 9)
|
||||
if (rgw.entries.count() == 9)
|
||||
{
|
||||
m_rg_entries.removeLast();
|
||||
m_recent_game_acts.removeLast();
|
||||
rgw.entries.removeLast();
|
||||
rgw.actions.removeLast();
|
||||
}
|
||||
else if (m_rg_entries.count() > 9)
|
||||
else if (rgw.entries.count() > 9)
|
||||
{
|
||||
gui_log.error("Recent games entrylist too big");
|
||||
}
|
||||
|
||||
if (m_rg_entries.count() < 9)
|
||||
if (rgw.entries.count() < 9)
|
||||
{
|
||||
// add new action at the beginning
|
||||
m_rg_entries.prepend(entry);
|
||||
m_recent_game_acts.prepend(act);
|
||||
rgw.entries.prepend(entry);
|
||||
rgw.actions.prepend(act);
|
||||
}
|
||||
|
||||
// refill menu with actions
|
||||
for (int i = 0; i < m_recent_game_acts.count(); i++)
|
||||
for (int i = 0; i < rgw.actions.count(); i++)
|
||||
{
|
||||
m_recent_game_acts[i]->setShortcut(tr("Ctrl+%1").arg(i + 1));
|
||||
m_recent_game_acts[i]->setToolTip(::at32(m_rg_entries, i).second);
|
||||
ui->bootRecentMenu->addAction(m_recent_game_acts[i]);
|
||||
rgw.actions[i]->setShortcut(tr("Ctrl+%1").arg(i + 1));
|
||||
rgw.actions[i]->setToolTip(::at32(rgw.entries, i).second);
|
||||
menu->addAction(rgw.actions[i]);
|
||||
}
|
||||
|
||||
m_gui_settings->SetValue(gui::rg_entries, gui_settings::List2Var(m_rg_entries));
|
||||
m_gui_settings->SetValue(is_savestate ? gui::rs_entries : gui::rg_entries, gui_settings::List2Var(rgw.entries));
|
||||
}
|
||||
|
||||
void main_window::UpdateLanguageActions(const QStringList& language_codes, const QString& language_code)
|
||||
|
@ -2638,26 +2650,59 @@ void main_window::CreateConnects()
|
|||
}
|
||||
});
|
||||
|
||||
connect(ui->bootRecentSavestatesMenu, &QMenu::aboutToShow, this, [this]()
|
||||
{
|
||||
// Enable/Disable Recent Savestates List
|
||||
const bool stopped = Emu.IsStopped();
|
||||
for (QAction* act : ui->bootRecentSavestatesMenu->actions())
|
||||
{
|
||||
if (act != ui->freezeRecentSavestatesAct && act != ui->clearRecentSavestatesAct)
|
||||
{
|
||||
act->setEnabled(stopped);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->clearRecentAct, &QAction::triggered, this, [this]()
|
||||
{
|
||||
if (ui->freezeRecentAct->isChecked())
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_rg_entries.clear();
|
||||
for (QAction* act : m_recent_game_acts)
|
||||
m_recent_game.entries.clear();
|
||||
for (QAction* act : m_recent_game.actions)
|
||||
{
|
||||
ui->bootRecentMenu->removeAction(act);
|
||||
}
|
||||
m_recent_game_acts.clear();
|
||||
m_recent_game.actions.clear();
|
||||
m_gui_settings->SetValue(gui::rg_entries, gui_settings::List2Var(q_pair_list()));
|
||||
});
|
||||
|
||||
connect(ui->clearRecentSavestatesAct, &QAction::triggered, this, [this]()
|
||||
{
|
||||
if (ui->freezeRecentSavestatesAct->isChecked())
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_recent_save.entries.clear();
|
||||
for (QAction* act : m_recent_save.actions)
|
||||
{
|
||||
ui->bootRecentSavestatesMenu->removeAction(act);
|
||||
}
|
||||
m_recent_save.actions.clear();
|
||||
m_gui_settings->SetValue(gui::rs_entries, gui_settings::List2Var(q_pair_list()));
|
||||
});
|
||||
|
||||
connect(ui->freezeRecentAct, &QAction::triggered, this, [this](bool checked)
|
||||
{
|
||||
m_gui_settings->SetValue(gui::rg_freeze, checked);
|
||||
});
|
||||
|
||||
connect(ui->freezeRecentSavestatesAct, &QAction::triggered, this, [this](bool checked)
|
||||
{
|
||||
m_gui_settings->SetValue(gui::rs_freeze, checked);
|
||||
});
|
||||
|
||||
connect(ui->bootInstallPkgAct, &QAction::triggered, this, [this] {InstallPackages(); });
|
||||
connect(ui->bootInstallPupAct, &QAction::triggered, this, [this] {InstallPup(); });
|
||||
|
||||
|
@ -3530,9 +3575,9 @@ void main_window::CreateDockWindows()
|
|||
ui->toolbar_start->setIcon(m_icon_restart);
|
||||
ui->toolbar_start->setText(tr("Restart"));
|
||||
}
|
||||
else if (!m_recent_game_acts.isEmpty()) // Get last played game
|
||||
else if (!m_recent_game.actions.isEmpty()) // Get last played game
|
||||
{
|
||||
tooltip = tr("Play %0").arg(m_recent_game_acts.first()->text());
|
||||
tooltip = tr("Play %0").arg(m_recent_game.actions.first()->text());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -3584,35 +3629,45 @@ void main_window::ConfigureGuiFromSettings()
|
|||
m_mw->restoreState(m_gui_settings->GetValue(gui::mw_mwState).toByteArray());
|
||||
|
||||
ui->freezeRecentAct->setChecked(m_gui_settings->GetValue(gui::rg_freeze).toBool());
|
||||
m_rg_entries = gui_settings::Var2List(m_gui_settings->GetValue(gui::rg_entries));
|
||||
ui->freezeRecentSavestatesAct->setChecked(m_gui_settings->GetValue(gui::rs_freeze).toBool());
|
||||
m_recent_game.entries = gui_settings::Var2List(m_gui_settings->GetValue(gui::rg_entries));
|
||||
m_recent_save.entries = gui_settings::Var2List(m_gui_settings->GetValue(gui::rs_entries));
|
||||
|
||||
// clear recent games menu of actions
|
||||
for (QAction* act : m_recent_game_acts)
|
||||
const auto update_recent_games_menu = [this](bool is_savestate)
|
||||
{
|
||||
ui->bootRecentMenu->removeAction(act);
|
||||
}
|
||||
m_recent_game_acts.clear();
|
||||
recent_game_wrapper& rgw = is_savestate ? m_recent_save : m_recent_game;
|
||||
QMenu* menu = is_savestate ? ui->bootRecentSavestatesMenu : ui->bootRecentMenu;
|
||||
|
||||
// Fill the recent games menu
|
||||
for (int i = 0; i < m_rg_entries.count(); i++)
|
||||
{
|
||||
// adjust old unformatted entries (avoid duplication)
|
||||
m_rg_entries[i] = gui::Recent_Game(m_rg_entries[i].first, m_rg_entries[i].second);
|
||||
|
||||
// create new action
|
||||
QAction* act = CreateRecentAction(m_rg_entries[i], i + 1);
|
||||
|
||||
// add action to menu
|
||||
if (act)
|
||||
// clear recent games menu of actions
|
||||
for (QAction* act : rgw.actions)
|
||||
{
|
||||
m_recent_game_acts.append(act);
|
||||
ui->bootRecentMenu->addAction(act);
|
||||
menu->removeAction(act);
|
||||
}
|
||||
else
|
||||
rgw.actions.clear();
|
||||
|
||||
// Fill the recent games menu
|
||||
for (int i = 0; i < rgw.entries.count(); i++)
|
||||
{
|
||||
i--; // list count is now an entry shorter so we have to repeat the same index in order to load all other entries
|
||||
// adjust old unformatted entries (avoid duplication)
|
||||
rgw.entries[i] = gui::Recent_Game(rgw.entries[i].first, rgw.entries[i].second);
|
||||
|
||||
// create new action
|
||||
QAction* act = CreateRecentAction(rgw.entries[i], i + 1, is_savestate);
|
||||
|
||||
// add action to menu
|
||||
if (act)
|
||||
{
|
||||
rgw.actions.append(act);
|
||||
menu->addAction(act);
|
||||
}
|
||||
else
|
||||
{
|
||||
i--; // list count is now an entry shorter so we have to repeat the same index in order to load all other entries
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
update_recent_games_menu(false);
|
||||
update_recent_games_menu(true);
|
||||
|
||||
ui->showLogAct->setChecked(m_gui_settings->GetValue(gui::mw_logger).toBool());
|
||||
ui->showGameListAct->setChecked(m_gui_settings->GetValue(gui::mw_gamelist).toBool());
|
||||
|
@ -4147,14 +4202,19 @@ void main_window::dropEvent(QDropEvent* event)
|
|||
|
||||
Emu.GracefulShutdown(false);
|
||||
|
||||
if (const auto error = Emu.BootGame(sstr(drop_paths.first()), "", true); error != game_boot_result::no_errors)
|
||||
const std::string path = drop_paths.first().toStdString();
|
||||
|
||||
if (const auto error = Emu.BootGame(path, "", true); error != game_boot_result::no_errors)
|
||||
{
|
||||
gui_log.error("Boot failed: reason: %s, path: %s", error, drop_paths.first());
|
||||
gui_log.error("Boot failed: reason: %s, path: %s", error, path);
|
||||
show_boot_error(error);
|
||||
}
|
||||
else
|
||||
{
|
||||
gui_log.success("Elf Boot from drag and drop done: %s", drop_paths.first());
|
||||
gui_log.success("Elf Boot from drag and drop done: %s", path);
|
||||
|
||||
AddRecentAction(gui::Recent_Game(QString::fromStdString(path), QString::fromStdString(Emu.GetTitleAndTitleID())), is_savestate_compatible(path));
|
||||
|
||||
m_game_list_frame->Refresh(true);
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -175,17 +175,22 @@ private:
|
|||
drop_type IsValidFile(const QMimeData& md, QStringList* drop_paths = nullptr);
|
||||
void AddGamesFromDirs(QStringList&& paths);
|
||||
|
||||
QAction* CreateRecentAction(const q_string_pair& entry, const uint& sc_idx);
|
||||
void BootRecentAction(const QAction* act);
|
||||
void AddRecentAction(const q_string_pair& entry);
|
||||
QAction* CreateRecentAction(const q_string_pair& entry, u32 sc_idx, bool is_savestate);
|
||||
void BootRecentAction(const QAction* act, bool is_savestate);
|
||||
void AddRecentAction(const q_string_pair& entry, bool is_savestate);
|
||||
|
||||
void UpdateLanguageActions(const QStringList& language_codes, const QString& language);
|
||||
void UpdateFilterActions();
|
||||
|
||||
static QString GetCurrentTitle();
|
||||
|
||||
q_pair_list m_rg_entries;
|
||||
QList<QAction*> m_recent_game_acts;
|
||||
struct recent_game_wrapper
|
||||
{
|
||||
q_pair_list entries;
|
||||
QList<QAction*> actions;
|
||||
};
|
||||
recent_game_wrapper m_recent_game {};
|
||||
recent_game_wrapper m_recent_save {};
|
||||
|
||||
std::shared_ptr<gui_game_info> m_selected_game;
|
||||
|
||||
|
|
|
@ -202,10 +202,19 @@
|
|||
<addaction name="createFirmwareCacheAct"/>
|
||||
<addaction name="removeFirmwareCacheAct"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="bootRecentSavestatesMenu">
|
||||
<property name="title">
|
||||
<string>Boot Recent Savestate</string>
|
||||
</property>
|
||||
<addaction name="clearRecentSavestatesAct"/>
|
||||
<addaction name="freezeRecentSavestatesAct"/>
|
||||
<addaction name="separator"/>
|
||||
</widget>
|
||||
<addaction name="bootGameAct"/>
|
||||
<addaction name="bootVSHAct"/>
|
||||
<addaction name="bootElfMenu"/>
|
||||
<addaction name="bootSavestateAct"/>
|
||||
<addaction name="bootRecentSavestatesMenu"/>
|
||||
<addaction name="bootRecentMenu"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="addGamesAct"/>
|
||||
|
@ -1387,6 +1396,16 @@
|
|||
<string>Operating System</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="clearRecentSavestatesAct">
|
||||
<property name="text">
|
||||
<string>List Clear</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="freezeRecentSavestatesAct">
|
||||
<property name="text">
|
||||
<string>List Freeze</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<resources>
|
||||
|
|
|
@ -187,7 +187,6 @@ private:
|
|||
void initialize(utils::serial& ar);
|
||||
void stream_data_prepare_thread_op();
|
||||
void file_writer_thread_op();
|
||||
void blocked_compressed_write(const std::vector<u8>& data);
|
||||
};
|
||||
|
||||
template <typename File> requires (std::is_same_v<std::remove_cvref_t<File>, fs::file>)
|
||||
|
|
Loading…
Add table
Reference in a new issue