From e98c7f4e1a535e7841a6e860331cf61177fe748f Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Mon, 25 Feb 2019 11:23:15 +0100 Subject: [PATCH] sys_usbd implementation --- rpcs3/Emu/CMakeLists.txt | 4 + rpcs3/Emu/Cell/lv2/sys_usbd.cpp | 792 ++++++++++++++++++++++------- rpcs3/Emu/Cell/lv2/sys_usbd.h | 211 ++++++-- rpcs3/Emu/Io/Skylander.cpp | 171 +++++++ rpcs3/Emu/Io/Skylander.h | 32 ++ rpcs3/Emu/Io/usb_device.cpp | 208 ++++++++ rpcs3/Emu/Io/usb_device.h | 198 ++++++++ rpcs3/emucore.vcxproj | 6 +- rpcs3/emucore.vcxproj.filters | 9 + rpcs3/rpcs3.vcxproj | 2 + rpcs3/rpcs3.vcxproj.filters | 9 + rpcs3/rpcs3qt/CMakeLists.txt | 1 + rpcs3/rpcs3qt/main_window.cpp | 7 + rpcs3/rpcs3qt/main_window.ui | 6 + rpcs3/rpcs3qt/skylander_dialog.cpp | 556 ++++++++++++++++++++ rpcs3/rpcs3qt/skylander_dialog.h | 57 +++ 16 files changed, 2049 insertions(+), 220 deletions(-) create mode 100644 rpcs3/Emu/Io/Skylander.cpp create mode 100644 rpcs3/Emu/Io/Skylander.h create mode 100644 rpcs3/Emu/Io/usb_device.cpp create mode 100644 rpcs3/Emu/Io/usb_device.h create mode 100644 rpcs3/rpcs3qt/skylander_dialog.cpp create mode 100644 rpcs3/rpcs3qt/skylander_dialog.h diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 74455557e8..2648fee8e6 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -59,6 +59,7 @@ target_sources(rpcs3_emu PRIVATE ../Crypto/ec.cpp ../Crypto/key_vault.cpp ../Crypto/lz.cpp + ../Crypto/md5.cpp ../Crypto/sha1.cpp ../Crypto/unedat.cpp ../Crypto/unpkg.cpp @@ -301,6 +302,8 @@ target_link_libraries(rpcs3_emu target_sources(rpcs3_emu PRIVATE Io/KeyboardHandler.cpp Io/PadHandler.cpp + Io/usb_device.cpp + Io/Skylander.cpp ) @@ -398,6 +401,7 @@ target_link_libraries(rpcs3_emu 3rdparty::ffmpeg 3rdparty::cereal 3rdparty::opengl 3rdparty::stblib 3rdparty::vulkan 3rdparty::glew + 3rdparty::libusb PRIVATE 3rdparty::gsl 3rdparty::xxhash 3rdparty::dx12) diff --git a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp index b5c3f688b0..5806ad27d7 100644 --- a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp @@ -1,167 +1,473 @@ -#include "stdafx.h" +#include "stdafx.h" #include "sys_usbd.h" +#include +#include +#include "Emu/Memory/vm.h" #include "Emu/System.h" +#include "Emu/Cell/PPUThread.h" #include "Emu/Cell/ErrorCodes.h" #include "sys_ppu_thread.h" +#include "Emu/Io/usb_device.h" +#include "Emu/Io/Skylander.h" + +#include LOG_CHANNEL(sys_usbd); -std::vector devices = { - // System devices - usbDevice{ DeviceListUnknownDataType{0x1, 0x2, 0x2, 0x44}, 50, deviceDescriptor{0x12, 0x1, 0x0200, 0, 0, 0, 0x40, 0x054C, 0x0250, 0x0009, 3, 4, 5, 1}}, - usbDevice{ DeviceListUnknownDataType{0x1, 0x3, 0x2, 0xAD}, 326, deviceDescriptor{0x12, 0x1, 0x0200, 0xE0, 0x1, 0x1, 0x40, 0x054C, 0x0267, 0x0001, 1, 2, 0, 1}}, - // USB Drive - usbDevice{ DeviceListUnknownDataType{0x0, 0x4, 0x2, 0x0}, 50, deviceDescriptor{0x12, 0x1, 0x0200, 0, 0, 0, 0x40, 0x1516, 0x1226, 0x0100, 1, 2, 3, 1}}, - // Skylanders Portal - usbDevice{ DeviceListUnknownDataType{0x0, 0x5, 0x2, 0xF8}, 59, deviceDescriptor{0x12, 0x1, 0x0200, 0, 0, 0, 0x20, 0x1430, 0x0150, 0x0100, 1, 2, 0, 1}}, -}; +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + const auto& transfer = get_object(arg); -bool has_register_extra_ldd; -int receive_event_called_count; + std::string datrace; + const char hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + for (int index = 0; index < transfer.actual_length; index++) + { + datrace += hex[transfer.buffer[index] >> 4]; + datrace += hex[(transfer.buffer[index]) & 15]; + datrace += ' '; + } + + fmt::append(out, "TR[r:%d][sz:%d] => %s", (u8)transfer.status, transfer.actual_length, datrace); +} + +void LIBUSB_CALL callback_transfer(struct libusb_transfer* transfer) +{ + auto usbh = g_idm->lock>(0); + if (!usbh) + return; + + usbh->transfer_complete(transfer); +} + +usb_handler_thread::usb_handler_thread() +{ + if (libusb_init(&ctx) < 0) + return; + + // look if any device which we could be interested in is actually connected + libusb_device** list; + ssize_t ndev = libusb_get_device_list(ctx, &list); + + bool found_skylander = false; + + for (ssize_t index = 0; index < ndev; index++) + { + libusb_device_descriptor desc; + libusb_get_device_descriptor(list[index], &desc); + + // clang-format off + 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_dev = std::make_shared(list[index], desc); + usb_devices.push_back(usb_dev); + return true; + } + return false; + }; + // clang-format on + + if (check_device(0x1430, 0x0150, 0x0150, "Skylanders Portal")) + { + found_skylander = true; + } + check_device(0x1415, 0x0000, 0x0000, "Singstar Microphone"); + 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"); + + // 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"); + } + + libusb_free_device_list(list, 1); + + if (!found_skylander) + { + sys_usbd.success("Adding emulated skylander"); + usb_devices.push_back(std::make_shared()); + } + + for (u32 index = 0; index < MAX_SYS_USBD_TRANSFERS; index++) + { + transfers[index].transfer = libusb_alloc_transfer(8); + transfers[index].transfer_id = index; + } +} + +usb_handler_thread::~usb_handler_thread() +{ + while (is_running == true) + { + std::this_thread::sleep_for(1ms); + } + + // Ensures shared_ptr are all cleared before terminating libusb + handled_devices.clear(); + open_pipes.clear(); + usb_devices.clear(); + + if (ctx) + libusb_exit(ctx); + + for (u32 index = 0; index < MAX_SYS_USBD_TRANSFERS; index++) + { + if (transfers[index].transfer) + libusb_free_transfer(transfers[index].transfer); + } +} + +void usb_handler_thread::operator()() +{ + timeval lusb_tv; + memset(&lusb_tv, 0, sizeof(timeval)); + lusb_tv.tv_usec = 200; + + is_running = true; + + while (thread_ctrl::state() != thread_state::aborting && !Emu.IsStopped()) + { + // Todo: Hotplug here? + + // Process asynchronous requests that are pending + libusb_handle_events_timeout_completed(ctx, &lusb_tv, nullptr); + + // Process fake transfers + if (!fake_transfers.empty()) + { + auto usbh = g_idm->lock>(0); + u64 timestamp = get_system_time() - Emu.GetPauseTime(); + + for (auto it = fake_transfers.begin(); it != fake_transfers.end(); it++) + { + auto transfer = *it; + + ASSERT(transfer->busy && transfer->fake); + + if (transfer->expected_time > timestamp) + continue; + + transfer->result = transfer->expected_result; + transfer->count = transfer->expected_count; + transfer->fake = false; + transfer->busy = false; + + fake_transfers.erase(it--); + + send_message(SYS_USBD_TRANSFER_COMPLETE, transfer->transfer_id); + } + } + + // If there is no handled devices usb thread is not actively needed + if (!handled_devices.size()) + std::this_thread::sleep_for(500ms); + else + std::this_thread::sleep_for(200us); + } + + is_running = false; +} + +void usb_handler_thread::send_message(u32 message, u32 tr_id) +{ + sys_usbd.trace("Sending event: arg1=0x%x arg2=0x%x arg3=0x00", message, tr_id); + + usbd_events.push({message, tr_id, 0x00}); + if (receive_threads.size()) + { + lv2_obj::awake(receive_threads.front()); + receive_threads.pop(); + } +} + +void usb_handler_thread::transfer_complete(struct libusb_transfer* transfer) +{ + UsbTransfer* usbd_transfer = (UsbTransfer*)transfer->user_data; + + if (transfer->status != 0) + { + sys_usbd.error("Transfer Error: %d", (s32)transfer->status); + } + + switch (transfer->status) + { + case LIBUSB_TRANSFER_COMPLETED: usbd_transfer->result = HC_CC_NOERR; break; + case LIBUSB_TRANSFER_TIMED_OUT: usbd_transfer->result = EHCI_CC_XACT; break; + case LIBUSB_TRANSFER_OVERFLOW: usbd_transfer->result = EHCI_CC_BABBLE; break; + case LIBUSB_TRANSFER_ERROR: + case LIBUSB_TRANSFER_CANCELLED: + case LIBUSB_TRANSFER_STALL: + case LIBUSB_TRANSFER_NO_DEVICE: + default: usbd_transfer->result = EHCI_CC_HALTED; break; + } + + usbd_transfer->count = transfer->actual_length; + + for (s32 index = 0; index < transfer->num_iso_packets; index++) + { + u8 iso_status; + switch (transfer->iso_packet_desc[index].status) + { + case LIBUSB_TRANSFER_COMPLETED: iso_status = USBD_HC_CC_NOERR; break; + case LIBUSB_TRANSFER_TIMED_OUT: iso_status = USBD_HC_CC_XACT; break; + case LIBUSB_TRANSFER_OVERFLOW: iso_status = USBD_HC_CC_BABBLE; break; + case LIBUSB_TRANSFER_ERROR: + case LIBUSB_TRANSFER_CANCELLED: + case LIBUSB_TRANSFER_STALL: + case LIBUSB_TRANSFER_NO_DEVICE: + default: iso_status = USBD_HC_CC_MISSMF; break; + } + + usbd_transfer->iso_request.packets[index] = ((iso_status & 0xF) << 12 | (transfer->iso_packet_desc[index].actual_length & 0xFFF)); + } + + usbd_transfer->busy = false; + + send_message(SYS_USBD_TRANSFER_COMPLETE, usbd_transfer->transfer_id); + + sys_usbd.trace("Transfer complete(0x%x): %s", usbd_transfer->transfer_id, *transfer); +} + +u32 usb_handler_thread::add_ldd(vm::ptr s_product, u16 slen_product, u16 id_vendor, u16 id_product_min, u16 id_product_max) +{ + UsbLdd new_ldd; + new_ldd.name.resize(slen_product); + memcpy(new_ldd.name.data(), s_product.get_ptr(), (u32)slen_product); + new_ldd.id_vendor = id_vendor; + new_ldd.id_product_min = id_product_min; + new_ldd.id_product_max = id_product_max; + ldds.push_back(new_ldd); + + return (u32)ldds.size(); // TODO: to check +} + +u32 usb_handler_thread::open_pipe(u32 device_handle, u8 endpoint) +{ + open_pipes.emplace(pipe_counter, UsbPipe{handled_devices[device_handle].second, endpoint}); + return pipe_counter++; +} + +bool usb_handler_thread::close_pipe(u32 pipe_id) +{ + return (bool)open_pipes.erase(pipe_id); +} + +bool usb_handler_thread::is_pipe(u32 pipe_id) const +{ + return open_pipes.count(pipe_id) != 0; +} + +const UsbPipe& usb_handler_thread::get_pipe(u32 pipe_id) const +{ + return open_pipes.at(pipe_id); +} + +void usb_handler_thread::check_devices_vs_ldds() +{ + for (const auto& dev : usb_devices) + { + for (const auto& ldd : ldds) + { + if (dev->device._device.idVendor == ldd.id_vendor && dev->device._device.idProduct >= ldd.id_product_min && dev->device._device.idProduct <= ldd.id_product_max && !dev->assigned_number) + { + if (!dev->open_device()) + { + sys_usbd.error("Failed to open device for LDD(VID:0x%x PID:0x%x)", dev->device._device.idVendor, dev->device._device.idProduct); + continue; + } + + sys_usbd.success("Ldd device matchup for <%s>", ldd.name); + + dev->read_descriptors(); + + dev->assigned_number = dev_counter; + handled_devices.emplace(dev_counter, std::pair(UsbInternalDevice{0x00, dev_counter, 0x02, 0x40}, dev)); + send_message(SYS_USBD_ATTACH, dev_counter); + dev_counter++; + return; + } + } + } +} + +bool usb_handler_thread::get_event(vm::ptr& arg1, vm::ptr& arg2, vm::ptr& arg3) +{ + if (usbd_events.size()) + { + const auto& usb_event = usbd_events.front(); + *arg1 = (u64)std::get<0>(usb_event); + *arg2 = (u64)std::get<1>(usb_event); + *arg3 = (u64)std::get<2>(usb_event); + usbd_events.pop(); + sys_usbd.trace("Received event: arg1=0x%x arg2=0x%x arg3=0x%x", *arg1, *arg2, *arg3); + return true; + } + + return false; +} + +void usb_handler_thread::add_to_receive_queue(ppu_thread* ppu) +{ + lv2_obj::sleep(*ppu); + receive_threads.push(ppu); +} + +u32 usb_handler_thread::get_free_transfer_id() +{ + do + { + transfer_counter++; + + if (transfer_counter >= MAX_SYS_USBD_TRANSFERS) + transfer_counter = 0; + } while (transfers[transfer_counter].busy); + + return transfer_counter; +} + +UsbTransfer& usb_handler_thread::get_transfer(u32 transfer_id) +{ + return transfers[transfer_id]; +} -/* - * sys_usbd_initialize changes handle to a semi-unique identifier. - * identifier generation is speculated to be: - * f(n+1) = (f(n) + 2<<9) % 14847 + f_min (probably just f(0)-14847 but it could be different) - * Thanks to @flash-fire for thinking up the identifier generation code. - * TODO: try to get hardware to return not CELL_OK (perhaps set handle to NULL). - */ s32 sys_usbd_initialize(vm::ptr handle) { - sys_usbd.warning("sys_usbd_initialize(0x%x)", handle.get_ptr()); - *handle = 805322496; - has_register_extra_ldd = false; - receive_event_called_count = 0; + sys_usbd.warning("sys_usbd_initialize(handle=*0x%x)", handle); + + auto usbh = g_idm->lock>(id_new); + + if (usbh) + { + usbh.create("Usb Manager Thread"); + } + + *handle = 0x115B; + return CELL_OK; } -s32 sys_usbd_finalize() +s32 sys_usbd_finalize(ppu_thread& ppu, u32 handle) { - sys_usbd.todo("sys_usbd_finalize()"); + sys_usbd.warning("sys_usbd_finalize(handle=0x%x)", handle); + + auto usbh = g_idm->lock>(0); + + if (!usbh) + { + return CELL_OK; + } + + *usbh.get() = thread_state::aborting; + usbh.unlock(); + + while (true) + { + if (ppu.is_stopped()) + { + return 0; + } + + thread_ctrl::wait_for(1000); + + auto usbh = g_idm->lock>(0); + + if (*usbh.get() == thread_state::finished) + { + usbh.unlock(); + break; + } + } + return CELL_OK; } -s32 sys_usbd_get_device_list(u32 handle, vm::ptr device_list, char unknown) +s32 sys_usbd_get_device_list(u32 handle, vm::ptr device_list, u32 max_devices) { - sys_usbd.warning("sys_usbd_get_device_list(handle=%d, device_list=0x%x, unknown=%c)", handle, device_list, unknown); - // Unknown is just 0x7F - for (int i = 0; i < devices.size(); i++) + sys_usbd.warning("sys_usbd_get_device_list(handle=0x%x, device_list=*0x%x, max_devices=0x%x)", handle, device_list, max_devices); + + auto usbh = g_idm->lock>(0); + if (!usbh) { - device_list[i] = devices[i].basicDevice; + return CELL_EINVAL; } - return ::size32(devices); + + u32 i_tocopy = std::min((s32)max_devices, (s32)usbh->handled_devices.size()); + + for (u32 index = 0; index < i_tocopy; index++) + { + device_list[index] = usbh->handled_devices[index].first; + } + + return i_tocopy; } -s32 sys_usbd_register_extra_ldd(u32 handle, vm::ptr lddOps, u16 strLen, u16 vendorID, u16 productID, u16 unk1) +s32 sys_usbd_register_extra_ldd(u32 handle, vm::ptr s_product, u16 slen_product, u16 id_vendor, u16 id_product_min, u16 id_product_max) { - sys_usbd.warning("sys_usbd_register_extra_ldd(handle=%u, lddOps=0x%x, unk1=%u, vendorID=%u, productID=%u, unk2=%u)", handle, lddOps, strLen, vendorID, productID, unk1); - has_register_extra_ldd = true; + sys_usbd.warning("sys_usbd_register_extra_ldd(handle=0x%x, s_product=%s, slen_product=0x%x, id_vendor=0x%x, id_product_min=0x%x, id_product_max=0x%x)", handle, s_product, slen_product, id_vendor, + id_product_min, id_product_max); - // No idea what 9 means. - return 9; + auto usbh = g_idm->lock>(0); + if (!usbh) + { + return CELL_EINVAL; + } + + s32 res = usbh->add_ldd(s_product, slen_product, id_vendor, id_product_min, id_product_max); + usbh->check_devices_vs_ldds(); + + return res; // To check } -s32 sys_usbd_get_descriptor_size(u32 handle, u8 deviceNumber) +s32 sys_usbd_get_descriptor_size(u32 handle, u32 device_handle) { - sys_usbd.warning("sys_usbd_get_descriptor_size(handle=%u, deviceNumber=%u)", handle, deviceNumber); - return devices[deviceNumber-2].descSize; + sys_usbd.trace("sys_usbd_get_descriptor_size(handle=0x%x, deviceNumber=0x%x)", handle, device_handle); + auto usbh = g_idm->lock>(0); + if (!usbh || !usbh->handled_devices.count(device_handle)) + { + return CELL_EINVAL; + } + + return usbh->handled_devices[device_handle].second->device.get_size(); } -s32 sys_usbd_get_descriptor(u32 handle, u8 deviceNumber, vm::ptr descriptor, s64 descSize) +s32 sys_usbd_get_descriptor(u32 handle, u32 device_handle, vm::ptr descriptor, u32 desc_size) { - sys_usbd.warning("sys_usbd_get_descriptor(handle=%u, deviceNumber=%u, descriptor=0x%x, descSize=%u)", handle, deviceNumber, descriptor, descSize); + sys_usbd.trace("sys_usbd_get_descriptor(handle=0x%x, deviceNumber=0x%x, descriptor=0x%x, desc_size=0x%x)", handle, device_handle, descriptor, desc_size); + auto usbh = g_idm->lock>(0); + if (!usbh || !usbh->handled_devices.count(device_handle)) + { + return CELL_EINVAL; + } - // Just gonna have to hack it for now + u8* ptr = (u8*)descriptor.get_ptr(); + usbh->handled_devices[device_handle].second->device.write_data(ptr); - u8 device = deviceNumber-2; - unsigned char* desc = (unsigned char*)descriptor.get_ptr(); - if (device == 0) - { - unsigned char bytes[] = {0x12, 0x1, 0x0, 0x2, 0x0, 0x0, 0x0, 0x40, 0x4c, 0x5, 0x50, 0x2, 0x9, 0x0, 0x3, 0x4, 0x5, 0x1, - 0x9, 0x2, 0x20, 0x0, 0x1, 0x1, 0x0, 0x80, 0xfa, - 0x9, 0x4, 0x0, 0x0, 0x2, 0x8, 0x5, 0x50, 0x0, - 0x7, 0x5, 0x81, 0x2, 0x0, 0x2, 0x0, - 0x7, 0x5, 0x2, 0x2, 0x0, 0x2, 0x0}; - memcpy(desc, bytes, descSize); - } - else if (device == 1) - { - unsigned char bytes[] = {0x12, 0x1, 0x0, 0x2, 0xe0, 0x1, 0x1, 0x40, 0x4c, 0x5, 0x67, 0x2, 0x0, 0x1, 0x1, 0x2, 0x0, 0x1, - 0x9, 0x2, 0x34, 0x1, 0x4, 0x1, 0x0, 0xc0, 0x0, - 0x9, 0x4, 0x0, 0x0, 0x3, 0xe0, 0x1, 0x1, 0x0, - 0x7, 0x5, 0x81, 0x3, 0x40, 0x0, 0x1, - 0x7, 0x5, 0x2, 0x2, 0x0, 0x2, 0x0, - 0x7, 0x5, 0x82, 0x2, 0x0, 0x2, 0x0, - 0x9, 0x4, 0x1, 0x0, 0x2, 0xe0, 0x1, 0x1, 0x0, - 0x7, 0x5, 0x3, 0x1, 0x0, 0x0, 0x4, - 0x7, 0x5, 0x83, 0x1, 0x0, 0x0, 0x4, - 0x9, 0x4, 0x1, 0x1, 0x2, 0xe0, 0x1, 0x1, 0x0, - 0x7, 0x5, 0x3, 0x1, 0xb, 0x0, 0x4, - 0x7, 0x5, 0x83, 0x1, 0xb, 0x0, 0x4, - 0x9, 0x4, 0x1, 0x2, 0x2, 0xe0, 0x1, 0x1, 0x0, - 0x7, 0x5, 0x3, 0x1, 0x13, 0x0, 0x4, - 0x7, 0x5, 0x83, 0x1, 0x13, 0x0, 0x4, - 0x9, 0x4, 0x1, 0x3, 0x2, 0xe0, 0x1, 0x1, 0x0, - 0x7, 0x5, 0x3, 0x1, 0x1b, 0x0, 0x4, - 0x7, 0x5, 0x83, 0x1, 0x1b, 0x0, 0x4, - 0x9, 0x4, 0x1, 0x4, 0x2, 0xe0, 0x1, 0x1, 0x0, - 0x7, 0x5, 0x3, 0x1, 0x23, 0x0, 0x4, - 0x7, 0x5, 0x83, 0x1, 0x23, 0x0, 0x4, - 0x9, 0x4, 0x1, 0x5, 0x2, 0xe0, 0x1, 0x1, 0x0, - 0x7, 0x5, 0x3, 0x1, 0x33, 0x0, 0x4, - 0x7, 0x5, 0x83, 0x1, 0x33, 0x0, 0x4, - 0x9, 0x4, 0x1, 0x6, 0x2, 0xe0, 0x1, 0x1, 0x0, - 0x7, 0x5, 0x3, 0x1, 0x40, 0x0, 0x4, - 0x7, 0x5, 0x83, 0x1, 0x40, 0x0, 0x4, - 0x9, 0x4, 0x2, 0x0, 0x2, 0xe0, 0x1, 0x1, 0x0, - 0x7, 0x5, 0x4, 0x3, 0x40, 0x0, 0x1, - 0x7, 0x5, 0x85, 0x3, 0x40, 0x0, 0x1, - 0x9, 0x4, 0x2, 0x1, 0x2, 0xe0, 0x1, 0x1, 0x0, - 0x7, 0x5, 0x4, 0x3, 0x80, 0x0, 0x1, - 0x7, 0x5, 0x85, 0x3, 0x80, 0x0, 0x1, - 0x9, 0x4, 0x2, 0x2, 0x2, 0xe0, 0x1, 0x1, 0x0, - 0x7, 0x5, 0x4, 0x3, 0x0, 0x1, 0x1, - 0x7, 0x5, 0x85, 0x3, 0x0, 0x1, 0x1, - 0x9, 0x4, 0x2, 0x3, 0x2, 0xe0, 0x1, 0x1, 0x0, - 0x7, 0x5, 0x4, 0x3, 0x0, 0x4, 0x1, - 0x7, 0x5, 0x85, 0x3, 0x0, 0x4, 0x1, - 0x9, 0x4, 0x3, 0x0, 0x0, 0xfe, 0x1, 0x0, 0x0, - 0x7, 0x21, 0x7, 0x88, 0x13, 0xff, 0x3}; - - memcpy(desc, bytes, descSize); - } - else if (device == 2) - { - // USB Drive - unsigned char bytes[] = {0x12, 0x1, 0x0, 0x2, 0x0, 0x0, 0x0, 0x40, 0x16, 0x15, 0x26, 0x12, 0x0, 0x1, 0x1, 0x2, 0x3, 0x1, - 0x9, 0x2, 0x20, 0x0, 0x1, 0x1, 0x0, 0x80, 0xfa, - 0x9, 0x4, 0x0, 0x0, 0x2, 0x8, 0x6, 0x50, 0x0, - 0x7, 0x5, 0x81, 0x2, 0x0, 0x2, 0x0, - 0x7, 0x5, 0x2, 0x2, 0x0, 0x2, 0x0}; - memcpy(desc, bytes, descSize); - } - else if (device == 3 || device == 4) - { - // Skylanders Portal - unsigned char bytes[] = {0x12, 0x1, 0x0, 0x2, 0x0, 0x0, 0x0, 0x20, 0x30, 0x14, 0x50, 0x1, 0x0, 0x1, 0x1, 0x2, 0x0, 0x1, - 0x9, 0x2, 0x29, 0x0, 0x1, 0x1, 0x0, 0x80, 0x96, - 0x9, 0x4, 0x0, 0x0, 0x2, 0x3, 0x0, 0x0, 0x0, - 0x9, 0x21, 0x11, 0x1, 0x0, 0x1, 0x22, 0x1d, 0x0, - 0x7, 0x5, 0x81, 0x3, 0x20, 0x0, 0x1, - 0x7, 0x5, 0x1, 0x3, 0x20, 0x0, 0x1}; - memcpy(desc, bytes, descSize); - } - else - return -1; return CELL_OK; } -s32 sys_usbd_register_ldd() +// This function is used for psp(cellUsbPspcm), dongles in ps3 arcade cabinets(PS3A-USJ), ps2 cam(eyetoy), generic usb camera?(sample_usb2cam) +s32 sys_usbd_register_ldd(u32 handle, vm::ptr s_product, u16 slen_product) { - sys_usbd.todo("sys_usbd_register_ldd()"); + sys_usbd.todo("sys_usbd_register_ldd(handle=0x%x, s_product=%s, slen_product=0x%x)", handle, s_product, slen_product); return CELL_OK; } @@ -171,49 +477,90 @@ s32 sys_usbd_unregister_ldd() return CELL_OK; } -s32 sys_usbd_open_pipe(u32 handle, vm::ptr endDisc) +// TODO: determine what the unknown params are +s32 sys_usbd_open_pipe(u32 handle, u32 device_handle, u32 unk1, u64 unk2, u64 unk3, u32 endpoint, u64 unk4) { - sys_usbd.todo("sys_usbd_open_pipe(handle=%d, endDisc=0x%x)", handle, endDisc); - return CELL_OK; -} - -s32 sys_usbd_open_default_pipe() -{ - sys_usbd.todo("sys_usbd_open_default_pipe()"); - return CELL_OK; -} - -s32 sys_usbd_close_pipe() -{ - sys_usbd.todo("sys_usbd_close_pipe()"); + sys_usbd.warning("sys_usbd_open_pipe(handle=0x%x, device_handle=0x%x, unk1=0x%x, unk2=0x%x, unk3=0x%x, endpoint=0x%x, unk4=0x%x)", handle, device_handle, unk1, unk2, unk3, endpoint, unk4); + auto usbh = g_idm->lock>(0); + if (!usbh || !usbh->handled_devices.count(device_handle)) + { + return CELL_EINVAL; + } + + return usbh->open_pipe(device_handle, (u8)endpoint); +} + +s32 sys_usbd_open_default_pipe(u32 handle, u32 device_handle) +{ + sys_usbd.trace("sys_usbd_open_default_pipe(handle=0x%x, device_handle=0x%x)", handle, device_handle); + + auto usbh = g_idm->lock>(0); + if (!usbh || !usbh->handled_devices.count(device_handle)) + { + return CELL_EINVAL; + } + + return usbh->open_pipe(device_handle, 0); +} + +s32 sys_usbd_close_pipe(u32 handle, u32 pipe_handle) +{ + sys_usbd.todo("sys_usbd_close_pipe(handle=0x%x, pipe_handle=0x%x)", handle, pipe_handle); + + auto usbh = g_idm->lock>(0); + if (!usbh || !usbh->is_pipe(pipe_handle)) + { + return CELL_EINVAL; + } + + usbh->close_pipe(pipe_handle); + return CELL_OK; } +// From RE: +// In libusbd_callback_thread +// *arg1 = 4 will terminate CellUsbd libusbd_callback_thread +// *arg1 = 3 will do some extra processing right away(notification of transfer finishing) +// *arg1 < 1 || *arg1 > 4 are ignored(rewait instantly for event) +// *arg1 == 1 || *arg1 == 2 will send a sys_event to internal CellUsbd event queue with same parameters as received and loop(attach and detach event) s32 sys_usbd_receive_event(ppu_thread& ppu, u32 handle, vm::ptr arg1, vm::ptr arg2, vm::ptr arg3) { - sys_usbd.todo("sys_usbd_receive_event(handle=%u, arg1=0x%x, arg2=0x%x, arg3=0x%x)", handle, arg1, arg2, arg3); + sys_usbd.trace("sys_usbd_receive_event(handle=%u, arg1=*0x%x, arg2=*0x%x, arg3=*0x%x)", handle, arg1, arg2, arg3); - _sys_ppu_thread_exit(ppu, CELL_OK); + while (!Emu.IsStopped()) + { + { + auto usbh = g_idm->lock>(0); + if (!usbh) + { + return CELL_EINVAL; + } - // TODO - /*if (receive_event_called_count == 0) - { - *arg1 = 2; - *arg2 = 5; - *arg3 = 0; - receive_event_called_count++; + if (usbh->get_event(arg1, arg2, arg3)) + { + // hack for Guitar Hero Live + // Attaching the device too fast seems to result in a nullptr along the way + if (*arg1 == SYS_USBD_ATTACH) + std::this_thread::sleep_for(5ms); + + break; + } + + usbh->add_to_receive_queue(&ppu); + } + + while (!ppu.state.test_and_reset(cpu_flag::signal)) + { + if (ppu.is_stopped()) + { + return 0; + } + + thread_ctrl::wait(); + } } - else if (receive_event_called_count == 1) - { - *arg1 = 1; - *arg2 = 6; - *arg3 = 0; - receive_event_called_count++; - } - else - { - _sys_ppu_thread_exit(ppu, CELL_OK); - }*/ + return CELL_OK; } @@ -223,33 +570,128 @@ s32 sys_usbd_detect_event() return CELL_OK; } -s32 sys_usbd_attach() +s32 sys_usbd_attach(u32 handle) { - sys_usbd.todo("sys_usbd_attach()"); + sys_usbd.todo("sys_usbd_attach(handle=0x%x)", handle); return CELL_OK; } -s32 sys_usbd_transfer_data() +s32 sys_usbd_transfer_data(u32 handle, u32 id_pipe, vm::ptr buf, u32 buf_size, vm::ptr request, u32 type_transfer) { - sys_usbd.todo("sys_usbd_transfer_data()"); + sys_usbd.trace("sys_usbd_transfer_data(handle=0x%x, id_pipe=0x%x, buf=*0x%x, buf_length=0x%x, request=*0x%x, type=0x%x)", handle, id_pipe, buf, buf_size, request, type_transfer); + if (sys_usbd.enabled == logs::level::trace && request.addr()) + { + sys_usbd.trace("RequestType:0x%x, Request:0x%x, wValue:0x%x, wIndex:0x%x, wLength:0x%x", request->bmRequestType, request->bRequest, request->wValue, request->wIndex, request->wLength); + if ((request->bmRequestType & 0x80) == 0 && buf && buf_size != 0) + { + std::string datrace; + const char hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + for (u32 index = 0; index < buf_size; index++) + { + datrace += hex[(buf[index] >> 4) & 15]; + datrace += hex[(buf[index]) & 15]; + datrace += ' '; + } + + sys_usbd.trace("Control sent: %s", datrace); + } + } + + auto usbh = g_idm->lock>(0); + if (!usbh || !usbh->is_pipe(id_pipe)) + { + return CELL_EINVAL; + } + + u32 id_transfer = usbh->get_free_transfer_id(); + const auto& pipe = usbh->get_pipe(id_pipe); + auto& transfer = usbh->get_transfer(id_transfer); + + // Default endpoint is control endpoint + if (pipe.endpoint == 0) + { + if (!request.addr()) + { + sys_usbd.error("Tried to use control pipe without proper request pointer"); + return CELL_EINVAL; + } + + // Claiming interface + if (request->bmRequestType == 0 && request->bRequest == 0x09) + { + pipe.device->set_configuration((u8)request->wValue); + pipe.device->set_interface(0); + } + + pipe.device->control_transfer(request->bmRequestType, request->bRequest, request->wValue, request->wIndex, request->wLength, buf_size, buf.get_ptr(), &transfer); + transfer.busy = true; + } + else + { + pipe.device->interrupt_transfer(buf_size, buf.get_ptr(), pipe.endpoint, &transfer); + transfer.busy = true; + } + + if (transfer.fake) + usbh->fake_transfers.push_back(&transfer); + + // returns an identifier specific to the transfer + return id_transfer; +} + +s32 sys_usbd_isochronous_transfer_data(u32 handle, u32 id_pipe, vm::ptr iso_request) +{ + sys_usbd.todo("sys_usbd_isochronous_transfer_data(handle=0x%x, id_pipe=0x%x, iso_request=*0x%x)", handle, id_pipe, iso_request); + auto usbh = g_idm->lock>(0); + if (!usbh || !usbh->is_pipe(id_pipe)) + { + return CELL_EINVAL; + } + + u32 id_transfer = usbh->get_free_transfer_id(); + const auto& pipe = usbh->get_pipe(id_pipe); + auto& transfer = usbh->get_transfer(id_transfer); + + memcpy(&transfer.iso_request, iso_request.get_ptr(), sizeof(UsbDeviceIsoRequest)); + pipe.device->isochronous_transfer(&transfer); + + // returns an identifier specific to the transfer + return id_transfer; +} + +s32 sys_usbd_get_transfer_status(u32 handle, u32 id_transfer, u32 unk1, vm::ptr result, vm::ptr count) +{ + sys_usbd.trace("sys_usbd_get_transfer_status(handle=0x%x, id_transfer=0x%x, unk1=0x%x, result=*0x%x, count=*0x%x)", handle, id_transfer, unk1, result, count); + + auto usbh = g_idm->lock>(0); + if (!usbh) + { + return CELL_EINVAL; + } + + auto& transfer = usbh->get_transfer(id_transfer); + + *result = transfer.result; + *count = transfer.count; + return CELL_OK; } -s32 sys_usbd_isochronous_transfer_data() -{ - sys_usbd.todo("sys_usbd_isochronous_transfer_data()"); - return CELL_OK; -} - -s32 sys_usbd_get_transfer_status() -{ - sys_usbd.todo("sys_usbd_get_transfer_status()"); - return CELL_OK; -} - -s32 sys_usbd_get_isochronous_transfer_status() +s32 sys_usbd_get_isochronous_transfer_status(u32 handle, u32 id_transfer, u32 unk1, vm::ptr request, vm::ptr result) { sys_usbd.todo("sys_usbd_get_isochronous_transfer_status()"); + auto usbh = g_idm->lock>(0); + if (!usbh) + { + return CELL_EINVAL; + } + + auto& transfer = usbh->get_transfer(id_transfer); + + *result = transfer.result; + memcpy(request.get_ptr(), &transfer.iso_request, sizeof(UsbDeviceIsoRequest)); + return CELL_OK; } diff --git a/rpcs3/Emu/Cell/lv2/sys_usbd.h b/rpcs3/Emu/Cell/lv2/sys_usbd.h index 6fdf6e55da..c8022ee0c7 100644 --- a/rpcs3/Emu/Cell/lv2/sys_usbd.h +++ b/rpcs3/Emu/Cell/lv2/sys_usbd.h @@ -1,64 +1,187 @@ -#pragma once +#pragma once +#include +#include #include "Emu/Memory/vm_ptr.h" +#include "Emu/Io/usb_device.h" -// SysCalls +#define MAX_SYS_USBD_TRANSFERS 0x44 -struct DeviceListUnknownDataType +// PS3 internal codes +enum PS3StandardUsbErrors : u32 { - u8 unk1; - u8 deviceID; - u8 unk3; - u8 unk4; -} ; - -/* 0x01 device descriptor */ -struct deviceDescriptor -{ - u8 bLength; /* descriptor size in bytes */ - u8 bDescriptorType; /* constant USB_DESCRIPTOR_TYPE_DEVICE */ - u16 bcdUSB; /* USB spec release compliance number */ - u8 bDeviceClass; /* class code */ - u8 bDeviceSubClass; /* subclass code */ - u8 bDeviceProtocol; /* protocol code */ - u8 bMaxPacketSize0; /* max packet size for endpoint 0 */ - u16 idVendor; /* USB-IF Vendor ID (VID) */ - u16 idProduct; /* Product ID (PID) */ - u16 bcdDevice; /* device release number */ - u8 iManufacturer; /* manufacturer string descriptor index */ - u8 iProduct; /* product string desccriptor index */ - u8 iSerialNumber; /* serial number string descriptor index */ - u8 bNumConfigurations; /* number of configurations */ + HC_CC_NOERR = 0x00, + EHCI_CC_MISSMF = 0x10, + EHCI_CC_XACT = 0x20, + EHCI_CC_BABBLE = 0x30, + EHCI_CC_DATABUF = 0x40, + EHCI_CC_HALTED = 0x50, }; -struct usbDevice +enum PS3IsochronousUsbErrors : u8 { - DeviceListUnknownDataType basicDevice; - s32 descSize; - deviceDescriptor descriptor; + USBD_HC_CC_NOERR = 0x00, + USBD_HC_CC_MISSMF = 0x01, + USBD_HC_CC_XACT = 0x02, + USBD_HC_CC_BABBLE = 0x04, + USBD_HC_CC_DATABUF = 0x08, +}; + +enum SysUsbdEvents : u32 +{ + SYS_USBD_ATTACH = 0x01, + SYS_USBD_DETACH = 0x02, + SYS_USBD_TRANSFER_COMPLETE = 0x03, + SYS_USBD_TERMINATE = 0x04, +}; + +// PS3 internal structures +struct UsbInternalDevice +{ + u8 device_high; // System flag maybe (used in generating actual device number) + u8 device_low; // Just a number identifying the device (used in generating actual device number) + u8 unk3; // ? Seems to always be 2? + u8 unk4; // ? +}; + +struct UsbDeviceRequest +{ + u8 bmRequestType; + u8 bRequest; + be_t wValue; + be_t wIndex; + be_t wLength; +}; + +struct UsbDeviceIsoRequest +{ + vm::ptr buf; + be_t start_frame; + be_t num_packets; + be_t packets[8]; +}; + +// HLE usbd +void LIBUSB_CALL callback_transfer(struct libusb_transfer* transfer); + +struct UsbTransfer +{ + u32 transfer_id = 0; + + s32 result = 0; + u32 count = 0; + UsbDeviceIsoRequest iso_request; + + std::vector setup_buf; + libusb_transfer* transfer = nullptr; + bool busy = false; + + // For fake transfers + bool fake = false; + u64 expected_time = 0; + s32 expected_result = 0; + u32 expected_count = 0; +}; + +struct UsbLdd +{ + std::string name; + u16 id_vendor; + u16 id_product_min; + u16 id_product_max; +}; + +struct UsbPipe +{ + std::shared_ptr device = nullptr; + u8 endpoint = 0; +}; + +class usb_handler_thread +{ +public: + usb_handler_thread(); + ~usb_handler_thread(); + + // Thread loop + void operator()(); + + // Called by the libusb callback function to notify transfer completion + void transfer_complete(libusb_transfer* transfer); + + // LDDs handling functions + u32 add_ldd(vm::ptr s_product, u16 slen_product, u16 id_vendor, u16 id_product_min, u16 id_product_max); + void check_devices_vs_ldds(); + + // Pipe functions + u32 open_pipe(u32 device_handle, u8 endpoint); + bool close_pipe(u32 pipe_id); + bool is_pipe(u32 pipe_id) const; + const UsbPipe& get_pipe(u32 pipe_id) const; + + // Events related functions + bool get_event(vm::ptr& arg1, vm::ptr& arg2, vm::ptr& arg3); + void add_to_receive_queue(ppu_thread* ppu); + + // Transfers related functions + u32 get_free_transfer_id(); + UsbTransfer& get_transfer(u32 transfer_id); + + // Map of devices actively handled by the ps3(device_id, device) + std::map>> handled_devices; + // Fake transfers + std::vector fake_transfers; + +private: + void send_message(u32 message, u32 tr_id); + +private: + // Counters for device IDs, transfer IDs and pipe IDs + atomic_t dev_counter = 1; + u32 transfer_counter = 0; + u32 pipe_counter = 0x10; // Start at 0x10 only for tracing purposes + + // List of device drivers + std::vector ldds; + + // List of pipes + std::map open_pipes; + // Transfers infos + std::array transfers; + + // Queue of pending usbd events + std::queue> usbd_events; + // sys_usbd_receive_event PPU Threads + std::queue receive_threads; + + // List of devices "connected" to the ps3 + std::vector> usb_devices; + + libusb_context* ctx = nullptr; + atomic_t is_running = false; }; s32 sys_usbd_initialize(vm::ptr handle); -s32 sys_usbd_finalize(); -s32 sys_usbd_get_device_list(u32 handle, vm::ptr device_list, char unknown); -s32 sys_usbd_get_descriptor_size(u32 handle, u8 deviceNumber); -s32 sys_usbd_get_descriptor(u32 handle, u8 deviceNumber, vm::ptr descriptor, s64 descSize); -s32 sys_usbd_register_ldd(); +s32 sys_usbd_finalize(ppu_thread& ppu, u32 handle); +s32 sys_usbd_get_device_list(u32 handle, vm::ptr device_list, u32 max_devices); +s32 sys_usbd_get_descriptor_size(u32 handle, u32 device_handle); +s32 sys_usbd_get_descriptor(u32 handle, u32 device_handle, vm::ptr descriptor, u32 desc_size); +s32 sys_usbd_register_ldd(u32 handle, vm::ptr s_product, u16 slen_product); s32 sys_usbd_unregister_ldd(); -s32 sys_usbd_open_pipe(u32 handle, vm::ptr endDisc); -s32 sys_usbd_open_default_pipe(); -s32 sys_usbd_close_pipe(); +s32 sys_usbd_open_pipe(u32 handle, u32 device_handle, u32 unk1, u64 unk2, u64 unk3, u32 endpoint, u64 unk4); +s32 sys_usbd_open_default_pipe(u32 handle, u32 device_handle); +s32 sys_usbd_close_pipe(u32 handle, u32 pipe_handle); s32 sys_usbd_receive_event(ppu_thread& ppu, u32 handle, vm::ptr arg1, vm::ptr arg2, vm::ptr arg3); s32 sys_usbd_detect_event(); -s32 sys_usbd_attach(); -s32 sys_usbd_transfer_data(); -s32 sys_usbd_isochronous_transfer_data(); -s32 sys_usbd_get_transfer_status(); -s32 sys_usbd_get_isochronous_transfer_status(); +s32 sys_usbd_attach(u32 handle); +s32 sys_usbd_transfer_data(u32 handle, u32 id_pipe, vm::ptr buf, u32 buf_size, vm::ptr request, u32 type_transfer); +s32 sys_usbd_isochronous_transfer_data(u32 handle, u32 id_pipe, vm::ptr iso_request); +s32 sys_usbd_get_transfer_status(u32 handle, u32 id_transfer, u32 unk1, vm::ptr result, vm::ptr count); +s32 sys_usbd_get_isochronous_transfer_status(u32 handle, u32 id_transfer, u32 unk1, vm::ptr request, vm::ptr result); s32 sys_usbd_get_device_location(); s32 sys_usbd_send_event(); s32 sys_usbd_event_port_send(); s32 sys_usbd_allocate_memory(); s32 sys_usbd_free_memory(); s32 sys_usbd_get_device_speed(); -s32 sys_usbd_register_extra_ldd(u32 handle, vm::ptr lddOps, u16 strLen, u16 vendorID, u16 productID, u16 unk1); +s32 sys_usbd_register_extra_ldd(u32 handle, vm::ptr s_product, u16 slen_product, u16 id_vendor, u16 id_product_min, u16 id_product_max); diff --git a/rpcs3/Emu/Io/Skylander.cpp b/rpcs3/Emu/Io/Skylander.cpp new file mode 100644 index 0000000000..060112f5ab --- /dev/null +++ b/rpcs3/Emu/Io/Skylander.cpp @@ -0,0 +1,171 @@ +#include "stdafx.h" +#include "Skylander.h" +#include "Emu/Cell/lv2/sys_usbd.h" + +LOG_CHANNEL(skylander_log); + +sky_portal g_skylander; + +void sky_portal::sky_load() +{ + if (!sky_file) + { + skylander_log.error("Tried to load skylander but no skylander is active"); + return; + } + + { + std::lock_guard lock(sky_mutex); + + sky_file.seek(0, fs::seek_set); + sky_file.read(sky_dump, 0x40 * 0x10); + } +} + +void sky_portal::sky_save() +{ + if (!sky_file) + { + skylander_log.error("Tried to save skylander to file but no skylander is active"); + return; + } + + { + std::lock_guard lock(sky_mutex); + + sky_file.seek(0, fs::seek_set); + sky_file.write(sky_dump, 0x40 * 0x10); + } +} + +usb_device_skylander::usb_device_skylander() +{ + device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, UsbDeviceDescriptor{0x0200, 0x00, 0x00, 0x00, 0x20, 0x1430, 0x0150, 0x0100, 0x01, 0x02, 0x00, 0x01}); + auto& config0 = device.add_node(UsbDescriptorNode(USB_DESCRIPTOR_CONFIG, UsbDeviceConfiguration{0x0029, 0x01, 0x01, 0x00, 0x80, 0x96})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, UsbDeviceInterface{0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_HID, UsbDeviceHID{0x0111, 0x00, 0x01, 0x22, 0x001d})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, UsbDeviceEndpoint{0x81, 0x03, 0x0020, 0x01})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, UsbDeviceEndpoint{0x01, 0x03, 0x0020, 0x01})); +} + +usb_device_skylander::~usb_device_skylander() +{ +} + +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; + + // Control transfers are nearly instant + switch (bmRequestType) + { + // HID Host 2 Device + case 0x21: + switch (bRequest) + { + case 0x09: + transfer->expected_count = buf_size; + transfer->expected_result = HC_CC_NOERR; + // 100 usec, control transfers are very fast + transfer->expected_time = get_timestamp() + 100; + + std::array q_result = {}; + + switch (buf[0]) + { + case 'A': + // Activate command + verify(HERE), buf_size == 2; + q_result = {0x41, buf[1], 0xFF, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00}; + + q_queries.push(q_result); + break; + case 'C': + // Set LEDs colour + verify(HERE), buf_size == 4; + break; + case 'Q': + // Queries a block + verify(HERE), buf_size == 3; + + q_result[0] = 'Q'; + q_result[1] = 0x10; + q_result[2] = buf[2]; + + { + std::lock_guard lock(g_skylander.sky_mutex); + memcpy(q_result.data() + 3, g_skylander.sky_dump + (16 * buf[2]), 16); + } + + q_queries.push(q_result); + break; + case 'R': + // Reset + verify(HERE), buf_size == 2; + q_result = { + 0x52, 0x02, 0x0A, 0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + q_queries.push(q_result); + break; + case 'S': + // ? + verify(HERE), buf_size == 1; + break; + case 'W': + // Write a block + verify(HERE), buf_size == 19; + q_result[0] = 'W'; + q_result[1] = 0x10; + q_result[2] = buf[2]; + + { + std::lock_guard lock(g_skylander.sky_mutex); + memcpy(g_skylander.sky_dump + (buf[2] * 16), &buf[3], 16); + } + + g_skylander.sky_save(); + + q_queries.push(q_result); + break; + default: skylander_log.error("Unhandled Query Type: 0x%02X", buf[0]); break; + } + + break; + } + break; + default: + // Follow to default emulated handler + usb_device_emulated::control_transfer(bmRequestType, bRequest, wValue, wIndex, wLength, buf_size, buf, transfer); + break; + } +} + +void usb_device_skylander::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer) +{ + verify(HERE), buf_size == 0x20; + + transfer->fake = true; + transfer->expected_count = buf_size; + transfer->expected_result = HC_CC_NOERR; + // Interrupt transfers are slow(6ms, TODO accurate measurement) + transfer->expected_time = get_timestamp() + 6000; + + if (q_queries.size()) + { + memcpy(buf, q_queries.front().data(), 0x20); + q_queries.pop(); + } + else + { + std::lock_guard lock(g_skylander.sky_mutex); + memset(buf, 0, 0x20); + buf[0] = 0x53; + // Varies per skylander type + buf[1] = (g_skylander.sky_file && !g_skylander.sky_reload) ? 1 : 0; + + buf[5] = interrupt_counter++; + buf[6] = 0x01; + + g_skylander.sky_reload = false; + } +} diff --git a/rpcs3/Emu/Io/Skylander.h b/rpcs3/Emu/Io/Skylander.h new file mode 100644 index 0000000000..0803564dfe --- /dev/null +++ b/rpcs3/Emu/Io/Skylander.h @@ -0,0 +1,32 @@ +#pragma once + +#include "Emu/Io/usb_device.h" +#include +#include + +struct sky_portal +{ + std::mutex sky_mutex; + fs::file sky_file; + bool sky_reload = false; + u8 sky_dump[0x40 * 0x10] = {}; + + void sky_save(); + void sky_load(); +}; + +extern sky_portal g_skylander; + +class usb_device_skylander : public usb_device_emulated +{ +public: + usb_device_skylander(); + ~usb_device_skylander(); + + 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; + +protected: + u8 interrupt_counter = 0; + std::queue> q_queries; +}; diff --git a/rpcs3/Emu/Io/usb_device.cpp b/rpcs3/Emu/Io/usb_device.cpp new file mode 100644 index 0000000000..c49a7c2cd5 --- /dev/null +++ b/rpcs3/Emu/Io/usb_device.cpp @@ -0,0 +1,208 @@ +#include "stdafx.h" +#include "Emu/Memory/vm.h" +#include "Emu/System.h" +#include "Emu/Cell/lv2/sys_time.h" + +#include "Emu/Cell/lv2/sys_usbd.h" +#include "Emu/Io/usb_device.h" + +extern logs::channel sys_usbd; + +////////////////////////////////////////////////////////////////// +// ALL DEVICES /////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////// + +void usb_device::read_descriptors() +{ +} + +bool usb_device::set_configuration(u8 cfg_num) +{ + current_config = cfg_num; + return true; +} + +bool usb_device::set_interface(u8 int_num) +{ + current_interface = int_num; + return true; +} + +u64 usb_device::get_timestamp() +{ + return (get_system_time() - Emu.GetPauseTime()); +} + +////////////////////////////////////////////////////////////////// +// PASSTHROUGH DEVICE //////////////////////////////////////////// +////////////////////////////////////////////////////////////////// +usb_device_passthrough::usb_device_passthrough(libusb_device* _device, libusb_device_descriptor& desc) + : lusb_device(_device) +{ + device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, UsbDeviceDescriptor{desc.bcdUSB, desc.bDeviceClass, desc.bDeviceSubClass, desc.bDeviceProtocol, desc.bMaxPacketSize0, desc.idVendor, desc.idProduct, + desc.bcdDevice, desc.iManufacturer, desc.iProduct, desc.iSerialNumber, desc.bNumConfigurations}); +} + +usb_device_passthrough::~usb_device_passthrough() +{ + if (lusb_handle) + libusb_close(lusb_handle); + + if (lusb_device) + libusb_unref_device(lusb_device); +} + +bool usb_device_passthrough::open_device() +{ + if (libusb_open(lusb_device, &lusb_handle) == LIBUSB_SUCCESS) + { +#ifdef __linux__ + libusb_set_auto_detach_kernel_driver(lusb_handle, true); +#endif + return true; + } + + return false; +} + +void usb_device_passthrough::read_descriptors() +{ + // Directly getting configuration descriptors from the device instead of going through libusb parsing functions as they're not needed + for (u8 index = 0; index < device._device.bNumConfigurations; index++) + { + u8 buf[1000]; + int ssize = libusb_control_transfer(lusb_handle, LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_DEVICE, LIBUSB_REQUEST_GET_DESCRIPTOR, 0x0200 | index, 0, buf, 1000, 0); + if (ssize < 0) + { + sys_usbd.fatal("Couldn't get the config from the device: %d", ssize); + continue; + } + + // Minimalistic parse + auto& conf = device.add_node(UsbDescriptorNode(buf[0], buf[1], &buf[2])); + + for (int index = buf[0]; index < ssize;) + { + conf.add_node(UsbDescriptorNode(buf[index], buf[index + 1], &buf[index + 2])); + index += buf[index]; + } + } +} + +bool usb_device_passthrough::set_configuration(u8 cfg_num) +{ + usb_device::set_configuration(cfg_num); + return (libusb_set_configuration(lusb_handle, cfg_num) == LIBUSB_SUCCESS); +}; + +bool usb_device_passthrough::set_interface(u8 int_num) +{ + usb_device::set_interface(int_num); + return (libusb_claim_interface(lusb_handle, int_num) == LIBUSB_SUCCESS); +} + +void usb_device_passthrough::control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) +{ + if (transfer->setup_buf.size() < buf_size + 8) + transfer->setup_buf.resize(buf_size + 8); + + libusb_fill_control_setup(transfer->setup_buf.data(), bmRequestType, bRequest, wValue, wIndex, buf_size); + memcpy(transfer->setup_buf.data() + 8, buf, buf_size); + libusb_fill_control_transfer(transfer->transfer, lusb_handle, transfer->setup_buf.data(), callback_transfer, (void*)transfer, 0); + libusb_submit_transfer(transfer->transfer); +} + +void usb_device_passthrough::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer) +{ + libusb_fill_interrupt_transfer(transfer->transfer, lusb_handle, endpoint, buf, buf_size, callback_transfer, (void*)transfer, 0); + libusb_submit_transfer(transfer->transfer); +} + +void usb_device_passthrough::isochronous_transfer(UsbTransfer* transfer) +{ + // TODO actual endpoint + // TODO actual size? + libusb_fill_iso_transfer(transfer->transfer, lusb_handle, 0x81, (u8*)transfer->iso_request.buf.get_ptr(), 0xFFFF, transfer->iso_request.num_packets, callback_transfer, (void*)transfer, 0); + + for (u32 index = 0; index < transfer->iso_request.num_packets; index++) + { + transfer->transfer->iso_packet_desc[index].length = transfer->iso_request.packets[index]; + } + + libusb_submit_transfer(transfer->transfer); +} + +////////////////////////////////////////////////////////////////// +// EMULATED DEVICE /////////////////////////////////////////////// +////////////////////////////////////////////////////////////////// +usb_device_emulated::usb_device_emulated() +{ +} + +usb_device_emulated::usb_device_emulated(const UsbDeviceDescriptor& _device) +{ + device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, _device); +} + +bool usb_device_emulated::open_device() +{ + return true; +} + +s32 usb_device_emulated::get_descriptor(u8 type, u8 index, u8* ptr, u32 max_size) +{ + if (type == USB_DESCRIPTOR_STRING) + { + if (index < strings.size()) + { + u8 string_len = (u8)strings[index].size(); + ptr[0] = (string_len * 2) + 2; + ptr[1] = USB_DESCRIPTOR_STRING; + for (u32 i = 0; i < string_len; i++) + { + ptr[2 + (i * 2)] = strings[index].data()[i]; + ptr[3 + (i * 2)] = 0; + } + return (s32)ptr[0]; + } + } + else + { + sys_usbd.error("[Emulated]: Trying to get a descriptor other than string descriptor"); + } + + return -1; +} + +void usb_device_emulated::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 = usb_device::get_timestamp() + 100; + + switch (bmRequestType) + { + case 0: + switch (bRequest) + { + case 0x09: usb_device::set_configuration(wValue); break; + default: sys_usbd.fatal("Unhandled control transfer(0): 0x%x", bRequest); break; + } + break; + default: sys_usbd.fatal("Unhandled control transfer: 0x%x", bmRequestType); break; + } +} + +void usb_device_emulated::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer) +{ +} + +void usb_device_emulated::isochronous_transfer(UsbTransfer* transfer) +{ +} + +void usb_device_emulated::add_string(char* str) +{ + strings.push_back(str); +} diff --git a/rpcs3/Emu/Io/usb_device.h b/rpcs3/Emu/Io/usb_device.h new file mode 100644 index 0000000000..ba5fbcee1c --- /dev/null +++ b/rpcs3/Emu/Io/usb_device.h @@ -0,0 +1,198 @@ +#pragma once + +#include +#include "Emu/Memory/vm.h" + +struct UsbTransfer; + +// Usb descriptors +enum : u8 +{ + USB_DESCRIPTOR_DEVICE = 0x01, + USB_DESCRIPTOR_CONFIG = 0x02, + USB_DESCRIPTOR_STRING = 0x03, + USB_DESCRIPTOR_INTERFACE = 0x04, + USB_DESCRIPTOR_ENDPOINT = 0x05, + USB_DESCRIPTOR_HID = 0x21, + USB_DESCRIPTOR_ACI = 0x24, + USB_DESCRIPTOR_ENDPOINT_ASI = 0x25, +}; + +struct UsbDeviceDescriptor +{ + le_t bcdUSB; + u8 bDeviceClass; + u8 bDeviceSubClass; + u8 bDeviceProtocol; + u8 bMaxPacketSize0; + le_t idVendor; + le_t idProduct; + le_t bcdDevice; + u8 iManufacturer; + u8 iProduct; + u8 iSerialNumber; + u8 bNumConfigurations; +}; + +struct UsbDeviceConfiguration +{ + le_t wTotalLength; + u8 bNumInterfaces; + u8 bConfigurationValue; + u8 iConfiguration; + u8 bmAttributes; + u8 bMaxPower; +}; + +struct UsbDeviceInterface +{ + u8 bInterfaceNumber; + u8 bAlternateSetting; + u8 bNumEndpoints; + u8 bInterfaceClass; + u8 bInterfaceSubClass; + u8 bInterfaceProtocol; + u8 iInterface; +}; + +struct UsbDeviceEndpoint +{ + u8 bEndpointAddress; + u8 bmAttributes; + le_t wMaxPacketSize; + u8 bInterval; +}; + +struct UsbDeviceHID +{ + le_t bcdHID; + u8 bCountryCode; + u8 bNumDescriptors; + u8 bDescriptorType; + le_t wDescriptorLength; +}; + +// Usb descriptor helper +struct UsbDescriptorNode +{ + u8 bLength; + u8 bDescriptorType; + + union { + UsbDeviceDescriptor _device; + UsbDeviceConfiguration _configuration; + UsbDeviceInterface _interface; + UsbDeviceEndpoint _endpoint; + UsbDeviceHID _hid; + u8 data[0xFF]; + }; + + std::vector subnodes; + + UsbDescriptorNode(){}; + template + UsbDescriptorNode(u8 _bDescriptorType, const T& _data) + : bLength(sizeof(T) + 2) + , bDescriptorType(_bDescriptorType) + { + memcpy(data, &_data, sizeof(T)); + } + UsbDescriptorNode(u8 _bLength, u8 _bDescriptorType, u8* _data) + : bLength(_bLength) + , bDescriptorType(_bDescriptorType) + { + memcpy(data, _data, _bLength - 2); + } + + UsbDescriptorNode& add_node(const UsbDescriptorNode& newnode) + { + subnodes.push_back(newnode); + return subnodes.back(); + } + u32 get_size() const + { + u32 nodesize = bLength; + for (const auto& node : subnodes) + { + nodesize += node.get_size(); + } + return nodesize; + } + void write_data(u8*& ptr) + { + ptr[0] = bLength; + ptr[1] = bDescriptorType; + memcpy(ptr + 2, data, bLength - 2); + ptr += bLength; + for (auto& node : subnodes) + { + node.write_data(ptr); + } + } +}; + +class usb_device +{ +public: + virtual bool open_device() = 0; + + virtual void read_descriptors(); + + virtual bool set_configuration(u8 cfg_num); + virtual bool set_interface(u8 int_num); + + virtual void control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) = 0; + virtual void interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer) = 0; + virtual void isochronous_transfer(UsbTransfer* transfer) = 0; + +public: + // device ID if the device has been ldded(0 otherwise) + u32 assigned_number = 0; + // base device descriptor, every other descriptor is a subnode + UsbDescriptorNode device; + +protected: + u8 current_config = 1; + u8 current_interface = 0; + +protected: + static u64 get_timestamp(); +}; + +class usb_device_passthrough : public usb_device +{ +public: + usb_device_passthrough(libusb_device* _device, libusb_device_descriptor& desc); + ~usb_device_passthrough(); + + bool open_device() override; + void read_descriptors() override; + bool set_configuration(u8 cfg_num) override; + bool set_interface(u8 int_num) override; + 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; + +protected: + libusb_device* lusb_device = nullptr; + libusb_device_handle* lusb_handle = nullptr; +}; + +class usb_device_emulated : public usb_device +{ +public: + usb_device_emulated(); + usb_device_emulated(const UsbDeviceDescriptor& _device); + + bool open_device() override; + 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; + + // Emulated specific functions + void add_string(char* str); + s32 get_descriptor(u8 type, u8 index, u8* ptr, u32 max_size); + +protected: + std::vector strings; +}; diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 13ae177561..bd770d43e9 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -61,7 +61,7 @@ Use - ..\3rdparty\zlib;..\llvm\include;..\llvm_build\include;$(VULKAN_SDK)\Include + ..\3rdparty\libusb\libusb;..\3rdparty\zlib;..\llvm\include;..\llvm_build\include;$(VULKAN_SDK)\Include %windir%\sysnative\cmd.exe /c "$(SolutionDir)\Utilities\git-version-gen.cmd" @@ -297,6 +297,8 @@ + + @@ -444,6 +446,8 @@ + + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 56956069d1..1195cf63d1 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -782,6 +782,9 @@ Emu\Cell\lv2 + + Emu\Io + Emu\Cell\lv2 @@ -818,6 +821,9 @@ Crypto + + Emu\Io + @@ -1552,5 +1558,8 @@ Crypto + + Emu\Io + \ No newline at end of file diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index 9d76e97d8c..a2cad4c9ed 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -1108,6 +1108,7 @@ + @@ -1649,6 +1650,7 @@ "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -D%(PreprocessorDefinitions) "-I$(VULKAN_SDK)\Include" "-I.\.." "-I.\..\3rdparty\minidx12\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtQml" "-I$(QTDIR)\include\QtNetwork" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" + diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index 057c20848b..5cc0e4b75f 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -111,6 +111,9 @@ {66e6027b-d3dd-4894-814c-cc4444a4c7df} + + {c25f8f80-cc74-4760-8488-a291b3026b1d} + @@ -743,6 +746,9 @@ rpcs3 + + Gui\skylanders + @@ -832,6 +838,9 @@ Gui + + Gui\skylanders + diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index b7bbaa1176..a5b8952296 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -37,6 +37,7 @@ save_data_list_dialog.cpp save_manager_dialog.cpp settings_dialog.cpp + skylander_dialog.cpp syntax_highlighter.cpp trophy_manager_dialog.cpp trophy_notification_frame.cpp diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 2776436e77..0b1790ea92 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -28,6 +28,7 @@ #include "about_dialog.h" #include "pad_settings_dialog.h" #include "progress_dialog.h" +#include "skylander_dialog.h" #include @@ -1271,6 +1272,12 @@ void main_window::CreateConnects() trop_manager->show(); }); + connect(ui->actionManage_Skylanders_Portal, &QAction::triggered, [=] + { + skylander_dialog* sky_diag = skylander_dialog::get_dlg(this); + sky_diag->show(); + }); + connect(ui->actionManage_Users, &QAction::triggered, [=] { user_manager_dialog user_manager(guiSettings, this); diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index ba49c7b14f..f2b52b2817 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -227,6 +227,7 @@ + @@ -1008,6 +1009,11 @@ Remove Disk Cache + + + Skylanders Portal + + diff --git a/rpcs3/rpcs3qt/skylander_dialog.cpp b/rpcs3/rpcs3qt/skylander_dialog.cpp new file mode 100644 index 0000000000..9d0703b49f --- /dev/null +++ b/rpcs3/rpcs3qt/skylander_dialog.cpp @@ -0,0 +1,556 @@ +#include +#include "Emu/System.h" +#include "Utilities/File.h" +#include "Crypto/md5.h" +#include "Crypto/aes.h" +#include "skylander_dialog.h" +#include "Utilities/BEType.h" +#include "Emu/Io/Skylander.h" + +skylander_dialog* skylander_dialog::inst = nullptr; + +const std::map list_skylanders = { + {0, "Whirlwind"}, + {1, "Sonic Boom"}, + {2, "Warnado"}, + {3, "Lightning Rod"}, + {4, "Bash"}, + {5, "Terrafin"}, + {6, "Dino-Rang"}, + {7, "Prism Break"}, + {8, "Sunburn"}, + {9, "Eruptor"}, + {10, "Ignitor"}, + {11, "Flameslinger"}, + {12, "Zap"}, + {13, "Wham-Shell"}, + {14, "Gill Grunt"}, + {15, "Slam Bam"}, + {16, "Spyro"}, + {17, "Voodood"}, + {18, "Double Trouble"}, + {19, "Trigger Happy"}, + {20, "Drobot"}, + {21, "Drill Sergeant"}, + {22, "Boomer"}, + {23, "Wrecking Ball"}, + {24, "Camo"}, + {25, "Zook"}, + {26, "Stealth Elf"}, + {27, "Stump Smash"}, + {28, "Dark Spyro"}, + {29, "Hex"}, + {30, "Chop Chop"}, + {31, "Ghost Roaster"}, + {32, "Cynder"}, + {100, "Jet Vac"}, + {101, "Swarm"}, + {102, "Crusher"}, + {103, "Flashwing"}, + {104, "Hot Head"}, + {105, "Hot Dog"}, + {106, "Chill"}, + {107, "Thumpback"}, + {108, "Pop Fizz"}, + {109, "Ninjini"}, + {110, "Bouncer"}, + {111, "Sprocket"}, + {112, "Tree Rex"}, + {113, "Shroomboom"}, + {114, "Eye-Brawl"}, + {115, "Fright Rider"}, + {200, "Anvil Rain"}, + {201, "Treasure Chest"}, + {202, "Healing Elixer"}, + {203, "Ghost Swords"}, + {204, "Time Twister"}, + {205, "Sky-Iron Shield"}, + {206, "Winged Boots"}, + {207, "Sparx Dragonfly"}, + {208, "Dragonfire Cannon"}, + {209, "Scorpion Striker Catapult"}, + {230, "Hand Of Fate"}, + {231, "Piggy Bank"}, + {232, "Rocket Ram"}, + {233, "Tiki Speaky"}, + {300, "Dragons Peak"}, + {301, "Empire of Ice"}, + {302, "Pirate Seas"}, + {303, "Darklight Crypt"}, + {304, "Volcanic Vault"}, + {305, "Mirror Of Mystery"}, + {306, "Nightmare Express"}, + {307, "Sunscraper Spire"}, + {308, "Midnight Museum"}, + {404, "Bash"}, + {416, "Spyro"}, + {419, "Trigger Happy"}, + {430, "Chop Chop"}, + {450, "Gusto"}, + {451, "Thunderbolt"}, + {452, "Fling Kong"}, + {453, "Blades"}, + {454, "Wallop"}, + {455, "Head Rush"}, + {456, "Fist Bump"}, + {457, "Rocky Roll"}, + {458, "Wildfire"}, + {459, "Ka Boom"}, + {460, "Trail Blazer"}, + {461, "Torch"}, + {462, "Snap Shot"}, + {463, "Lob Star"}, + {464, "Flip Wreck"}, + {465, "Echo"}, + {466, "Blastermind"}, + {467, "Enigma"}, + {468, "Deja Vu"}, + {469, "Cobra Cadabra"}, + {470, "Jawbreaker"}, + {471, "Gearshift"}, + {472, "Chopper"}, + {473, "Tread Head"}, + {474, "Bushwhack"}, + {475, "Tuff Luck"}, + {476, "Food Fight"}, + {477, "High Five"}, + {478, "Krypt King"}, + {479, "Short Cut"}, + {480, "Bat Spin"}, + {481, "Funny Bone"}, + {482, "Knight light"}, + {483, "Spotlight"}, + {484, "Knight Mare"}, + {485, "Blackout"}, + {502, "Bop"}, + {503, "Spry"}, + {504, "Hijinx"}, + {505, "Terrabite"}, + {506, "Breeze"}, + {507, "Weeruptor"}, + {508, "Pet Vac"}, + {509, "Small Fry"}, + {510, "Drobit"}, + {514, "Gill Runt"}, + {519, "Trigger Snappy"}, + {526, "Whisper Elf"}, + {540, "Barkley"}, + {541, "Thumpling"}, + {542, "Mini Jini"}, + {543, "Eye Small"}, + {1004, "Blast Zone"}, + {1015, "Wash Buckler"}, + {2004, "Blast Zone (Head)"}, + {2015, "Wash Buckler (Head)"}, + {3000, "Scratch"}, + {3001, "Pop Thorn"}, + {3002, "Slobber Tooth"}, + {3003, "Scorp"}, + {3004, "Fryno"}, + {3005, "Smolderdash"}, + {3006, "Bumble Blast"}, + {3007, "Zoo Lou"}, + {3008, "Dune Bug"}, + {3009, "Star Strike"}, + {3010, "Countdown"}, + {3011, "Wind Up"}, + {3012, "Roller Brawl"}, + {3013, "Grim Creeper"}, + {3014, "Rip Tide"}, + {3015, "Punk Shock"}, +}; + +QString cur_sky_file_path; + +skylander_dialog::skylander_dialog(QWidget* parent) + : QDialog(parent) +{ + setWindowTitle(tr("Skylanders Manager")); + setObjectName("skylanders_manager"); + setAttribute(Qt::WA_DeleteOnClose); + setMinimumSize(QSize(700, 450)); + + QVBoxLayout* vbox_panel = new QVBoxLayout(); + + QHBoxLayout* hbox_buttons = new QHBoxLayout(); + QPushButton* button_new = new QPushButton(tr("New"), this); + QPushButton* button_load = new QPushButton(tr("Load"), this); + hbox_buttons->addWidget(button_new); + hbox_buttons->addWidget(button_load); + hbox_buttons->addStretch(); + vbox_panel->addLayout(hbox_buttons); + + edit_curfile = new QLineEdit(cur_sky_file_path); + edit_curfile->setEnabled(false); + vbox_panel->addWidget(edit_curfile); + + QGroupBox* group_skyinfo = new QGroupBox(tr("Skylander Info")); + QVBoxLayout* vbox_group = new QVBoxLayout(); + combo_skylist = new QComboBox(); + for (auto& entry : list_skylanders) + { + combo_skylist->addItem(QString::fromStdString(entry.second), QVariant((int)entry.first)); + } + + combo_skylist->addItem(tr("--Unknown--"), QVariant(0xFFFF)); + + QLabel* label_skyid = new QLabel(tr("Skylander ID:")); + edit_skyid = new QLineEdit(); + QLabel* label_skyxp = new QLabel(tr("Skylander XP:")); + edit_skyxp = new QLineEdit(); + QLabel* label_skymoney = new QLabel(tr("Skylander Money:")); + edit_skymoney = new QLineEdit(); + + vbox_group->addWidget(combo_skylist); + vbox_group->addWidget(label_skyid); + vbox_group->addWidget(edit_skyid); + vbox_group->addWidget(label_skyxp); + vbox_group->addWidget(edit_skyxp); + vbox_group->addWidget(label_skymoney); + vbox_group->addWidget(edit_skymoney); + + QHBoxLayout* sub_group = new QHBoxLayout(); + sub_group->addStretch(); + button_update = new QPushButton(tr("Update"), this); + sub_group->addWidget(button_update); + vbox_group->addLayout(sub_group); + + vbox_group->addStretch(); + group_skyinfo->setLayout(vbox_group); + + vbox_panel->addWidget(group_skyinfo); + + setLayout(vbox_panel); + + connect(button_new, &QAbstractButton::clicked, this, &skylander_dialog::new_skylander); + connect(button_load, &QAbstractButton::clicked, this, &skylander_dialog::load_skylander); + connect(button_update, &QAbstractButton::clicked, this, &skylander_dialog::process_edits); + + update_edits(); + + // clang-format off + connect(combo_skylist, &QComboBox::currentTextChanged, this, [&]() + { + { + std::lock_guard lock(g_skylander.sky_mutex); + u16 sky_id = combo_skylist->itemData(combo_skylist->currentIndex()).toInt(); + if (sky_id != 0xFFFF) + { + (le_t&)g_skylander.sky_dump[0] = (u16)combo_skylist->itemData(combo_skylist->currentIndex()).toInt(); + (le_t&)g_skylander.sky_dump[0x10] = (u16)combo_skylist->itemData(combo_skylist->currentIndex()).toInt(); + (le_t&)g_skylander.sky_dump[0x1E] = skylander_crc16(0xFFFF, g_skylander.sky_dump, 0x1E); + + std::array zero_array = {}; + for (u32 index = 8; index < 0x40; index++) + { + if ((index + 1) % 4) + { + set_block(index, zero_array); + } + } + + set_checksums(); + + g_skylander.sky_reload = true; + } + } + + g_skylander.sky_save(); + update_edits(); + }); + // clang-format on +} + +skylander_dialog::~skylander_dialog() +{ + inst = nullptr; +} + +skylander_dialog* skylander_dialog::get_dlg(QWidget* parent) +{ + if (inst == nullptr) + inst = new skylander_dialog(parent); + + return inst; +} + +u16 skylander_dialog::skylander_crc16(u16 init_value, const u8* buffer, u32 size) +{ + const unsigned short CRC_CCITT_TABLE[256] = {0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, + 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, + 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, + 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, + 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, + 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, + 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, + 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, + 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, + 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, + 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0}; + + u16 tmp; + u16 crc = init_value; + + for (u32 i = 0; i < size; i++) + { + tmp = (crc >> 8) ^ buffer[i]; + crc = (crc << 8) ^ CRC_CCITT_TABLE[tmp]; + } + + return crc; +} + +void skylander_dialog::get_hash(u8 block, std::array& res_hash) +{ + const u8 hash_magic[0x35] = {0x20, 0x43, 0x6F, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x43, 0x29, 0x20, 0x32, 0x30, 0x31, 0x30, 0x20, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x73, 0x69, + 0x6F, 0x6E, 0x2E, 0x20, 0x41, 0x6C, 0x6C, 0x20, 0x52, 0x69, 0x67, 0x68, 0x74, 0x73, 0x20, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x2E, 0x20}; + + mbedtls_md5_context md5_ctx; + + mbedtls_md5_init(&md5_ctx); + mbedtls_md5_starts_ret(&md5_ctx); + mbedtls_md5_update_ret(&md5_ctx, g_skylander.sky_dump, 0x20); + mbedtls_md5_update_ret(&md5_ctx, &block, 1); + mbedtls_md5_update_ret(&md5_ctx, hash_magic, 0x35); + mbedtls_md5_finish_ret(&md5_ctx, res_hash.data()); +} + +void skylander_dialog::set_block(const u8 block, const std::array& to_encrypt) +{ + std::array hash_result; + get_hash(block, hash_result); + + aes_context aes_ctx; + aes_setkey_enc(&aes_ctx, hash_result.data(), 128); + + u8 res_buf[16]; + aes_crypt_ecb(&aes_ctx, AES_ENCRYPT, to_encrypt.data(), res_buf); + + memcpy(g_skylander.sky_dump + (block * 16), res_buf, 16); +} + +void skylander_dialog::get_block(const u8 block, std::array& decrypted) +{ + std::array hash_result; + get_hash(block, hash_result); + + aes_context aes_ctx; + aes_setkey_dec(&aes_ctx, hash_result.data(), 128); + + u8 res_buf[16]; + aes_crypt_ecb(&aes_ctx, AES_DECRYPT, g_skylander.sky_dump + (block * 16), res_buf); + + memcpy(decrypted.data(), res_buf, 16); +} + +u8 skylander_dialog::get_active_block() +{ + std::array dec_0x08; + std::array dec_0x24; + get_block(0x08, dec_0x08); + get_block(0x24, dec_0x24); + + return (dec_0x08[9] < dec_0x24[9]) ? 0x24 : 0x08; +} + +void skylander_dialog::set_checksums() +{ + const std::array sectors = {0x08, 0x24}; + + for (const auto& active : sectors) + { + // clang-format off + // Decrypt and hash a bunch of blocks + auto do_crc_blocks = [&](const u16 start_crc, const std::vector& blocks) -> u16 + { + u16 cur_crc = start_crc; + std::array decrypted_block; + for (const auto& b : blocks) + { + get_block(active + b, decrypted_block); + cur_crc = skylander_crc16(cur_crc, decrypted_block.data(), 16); + } + return cur_crc; + }; + // clang-format on + + std::array decrypted_header, sub_header; + get_block(active, decrypted_header); + get_block(active + 9, sub_header); + + // Type 4 + (le_t&)sub_header[0x0] = 0x0106; + u16 res_crc = skylander_crc16(0xFFFF, sub_header.data(), 16); + (le_t&)sub_header[0x0] = do_crc_blocks(res_crc, {10, 12, 13}); + + // Type 3 + std::array zero_block = {}; + res_crc = do_crc_blocks(0xFFFF, {5, 6, 8}); + for (u32 index = 0; index < 0x0E; index++) + { + res_crc = skylander_crc16(res_crc, zero_block.data(), 16); + } + (le_t&)decrypted_header[0xA] = res_crc; + + // Type 2 + res_crc = do_crc_blocks(0xFFFF, {1, 2, 4}); + (le_t&)decrypted_header[0xC] = res_crc; + + // Type 1 + (le_t&)decrypted_header[0xE] = 5; + (le_t&)decrypted_header[0xE] = skylander_crc16(0xFFFF, decrypted_header.data(), 16); + + set_block(active, decrypted_header); + set_block(active + 9, sub_header); + } +} + +void skylander_dialog::new_skylander() +{ + const QString file_path = QFileDialog::getSaveFileName(this, tr("Create Skylander File"), cur_sky_file_path, tr("Skylander Object (*.sky);;")); + if (file_path.isEmpty()) + return; + + if (g_skylander.sky_file) + g_skylander.sky_file.close(); + + g_skylander.sky_file.open(file_path.toStdString(), fs::read + fs::write + fs::create); + if (!g_skylander.sky_file) + return; + + cur_sky_file_path = file_path; + + { + std::lock_guard lock(g_skylander.sky_mutex); + memset(g_skylander.sky_dump, 0, 0x40 * 0x10); + + // Set the block permissions + (le_t&)g_skylander.sky_dump[0x36] = 0x690F0F0F; + for (u32 index = 1; index < 0x10; index++) + { + (le_t&)g_skylander.sky_dump[(index * 0x40) + 0x36] = 0x69080F7F; + } + + (le_t&)g_skylander.sky_dump[0x1E] = skylander_crc16(0xFFFF, g_skylander.sky_dump, 0x1E); + + std::array zero_array = {}; + for (u32 index = 8; index < 0x40; index++) + { + if ((index + 1) % 4) + { + set_block(index, zero_array); + } + } + + set_checksums(); + + g_skylander.sky_reload = true; + } + + g_skylander.sky_save(); + + edit_curfile->setText(file_path); + update_edits(); +} + +void skylander_dialog::load_skylander() +{ + const QString file_path = QFileDialog::getOpenFileName(this, tr("Select Skylander File"), cur_sky_file_path, tr("Skylander (*.sky);;")); + if (file_path.isEmpty()) + return; + + if (g_skylander.sky_file) + g_skylander.sky_file.close(); + + g_skylander.sky_file.open(file_path.toStdString(), fs::read + fs::write); + if (!g_skylander.sky_file) + return; + + cur_sky_file_path = file_path; + + g_skylander.sky_load(); + + edit_curfile->setText(file_path); + update_edits(); +} + +void skylander_dialog::update_edits() +{ + // clang-format off + auto widget_enabler = [&](bool status) + { + combo_skylist->setEnabled(status); + edit_skyid->setEnabled(status); + edit_skyxp->setEnabled(status); + edit_skymoney->setEnabled(status); + button_update->setEnabled(status); + }; + // clang-format on + + if (!g_skylander.sky_file) + { + widget_enabler(false); + return; + } + else + { + widget_enabler(true); + } + + { + std::lock_guard lock(g_skylander.sky_mutex); + edit_skyid->setText(QString::number((le_t&)g_skylander.sky_dump[0x10])); + + u8 active = get_active_block(); + + std::array decrypted; + get_block(active, decrypted); + u32 xp = ((le_t&)decrypted.data()[0]) & 0xFFFFFF; + edit_skyxp->setText(QString::number(xp)); + u16 money = (le_t&)decrypted[3]; + edit_skymoney->setText(QString::number(money)); + } +} + +void skylander_dialog::process_edits() +{ + { + std::lock_guard lock(g_skylander.sky_mutex); + + bool cast_success = false; + u16 skyID = edit_skyid->text().toInt(&cast_success); + if (cast_success) + { + (le_t&)g_skylander.sky_dump[0x10] = skyID; + (le_t&)g_skylander.sky_dump[0] = skyID; + } + + (le_t&)g_skylander.sky_dump[0x1E] = skylander_crc16(0xFFFF, g_skylander.sky_dump, 0x1E); + + u8 active = get_active_block(); + + std::array decrypted_header; + get_block(active, decrypted_header); + + u32 old_xp = ((le_t&)decrypted_header.data()[0]) & 0xFFFFFF; + u16 old_money = (le_t&)decrypted_header[3]; + + u32 xp = edit_skyxp->text().toInt(&cast_success); + if (!cast_success) + xp = old_xp; + u32 money = edit_skymoney->text().toInt(&cast_success); + if (!cast_success) + money = old_money; + + memcpy(decrypted_header.data(), &xp, 3); + (le_t&)decrypted_header[3] = money; + + set_block(active, decrypted_header); + + set_checksums(); + + g_skylander.sky_reload = true; + } + + g_skylander.sky_save(); +} diff --git a/rpcs3/rpcs3qt/skylander_dialog.h b/rpcs3/rpcs3qt/skylander_dialog.h new file mode 100644 index 0000000000..9df0e9abd4 --- /dev/null +++ b/rpcs3/rpcs3qt/skylander_dialog.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class skylander_dialog : public QDialog +{ +public: + skylander_dialog(QWidget* parent); + ~skylander_dialog(); + static skylander_dialog* get_dlg(QWidget* parent); + + skylander_dialog(skylander_dialog const&) = delete; + void operator=(skylander_dialog const&) = delete; + +protected: + // Update the edits from skylander loaded in memory + void update_edits(); + // Parse edits and apply them to skylander in memory + void process_edits(); + + // Creates a new skylander + void new_skylander(); + // Loads a skylander + void load_skylander(); + + u16 skylander_crc16(u16 init_value, const u8* buffer, u32 size); + // Get hash used for encryption of block + void get_hash(u8 block, std::array& res_hash); + // encrypt a block to memory + void set_block(const u8 block, const std::array& to_encrypt); + // decrypt a block in memory + void get_block(const u8 block, std::array& decrypted); + + // get the active Area(0x08 or 0x24) + u8 get_active_block(); + + void set_checksums(); + +protected: + QLineEdit* edit_curfile = nullptr; + QComboBox* combo_skylist = nullptr; + QLineEdit* edit_skyid = nullptr; + QLineEdit* edit_skyxp = nullptr; + QLineEdit* edit_skymoney = nullptr; + QPushButton* button_update = nullptr; + +private: + static skylander_dialog* inst; +};