Files
sphaira/sphaira/source/usb/usbhs.cpp
ITotalJustice bd7eadc6a0 add game dumping, add game transfer (switch2switch) via usb, add multi game selecting, fix bugs (see below).
- added more es commands.
- fixed usb install potential hang if the exit command is sent, but the client stops responding (timeout is now 3s).
- added multi select to the games menu.
- added game dumping.
- added switch2switch support by having a switch act as a usb client to transfer games.
- replace std::find with std::ranges (in a few places).
- fix rounding of icon in progress box being too round.
- fix file copy helper in progress box not updating the progress bar.
2025-05-18 13:46:10 +01:00

203 lines
7.2 KiB
C++

#include "usb/usbhs.hpp"
#include "log.hpp"
#include "defines.hpp"
#include <ranges>
#include <cstring>
namespace sphaira::usb {
namespace {
struct Bcd {
constexpr Bcd(u16 v) : value{v} {}
u8 major() const { return (value >> 8) & 0xFF; }
u8 minor() const { return (value >> 4) & 0xF; }
u8 macro() const { return (value >> 0) & 0xF; }
const u16 value;
};
Result usbHsParseReportData(UsbHsXferReport* reports, u32 count, u32 xferId, u32 *requestedSize, u32 *transferredSize) {
Result rc = 0;
u32 pos;
UsbHsXferReport *entry = NULL;
if (count>8) count = 8;
for(pos=0; pos<count; pos++) {
entry = &reports[pos];
if (entry->xferId == xferId) break;
}
if (pos == count) return MAKERESULT(Module_Libnx, LibnxError_NotFound);
rc = entry->res;
if (R_SUCCEEDED(rc)) {
if (requestedSize) *requestedSize = entry->requestedSize;
if (transferredSize) *transferredSize = entry->transferredSize;
}
return rc;
}
} // namespace
UsbHs::UsbHs(u8 index, const UsbHsInterfaceFilter& filter, u64 transfer_timeout)
: Base{transfer_timeout}
, m_index{index}
, m_filter{filter} {
}
UsbHs::~UsbHs() {
Close();
usbHsDestroyInterfaceAvailableEvent(std::addressof(m_event), m_index);
usbHsExit();
}
Result UsbHs::Init() {
log_write("doing USB init\n");
R_TRY(usbHsInitialize());
R_TRY(usbHsCreateInterfaceAvailableEvent(&m_event, true, m_index, &m_filter));
log_write("success USB init\n");
R_SUCCEED();
}
Result UsbHs::IsUsbConnected(u64 timeout) {
if (m_connected) {
R_SUCCEED();
}
const std::array waiters{
waiterForEvent(&m_event),
waiterForUEvent(GetCancelEvent()),
};
s32 idx;
R_TRY(waitObjects(&idx, waiters.data(), waiters.size(), timeout));
if (idx == waiters.size() - 1) {
return Result_Cancelled;
}
return Connect();
}
Result UsbHs::Connect() {
Close();
s32 total;
R_TRY(usbHsQueryAvailableInterfaces(&m_filter, &m_interface, sizeof(m_interface), &total));
R_TRY(usbHsAcquireUsbIf(&m_s, &m_interface));
const auto bcdUSB = Bcd{m_interface.device_desc.bcdUSB};
const auto bcdDevice = Bcd{m_interface.device_desc.bcdDevice};
// log lsusb style.
log_write("[USBHS] pathstr: %s\n", m_interface.pathstr);
log_write("Bus: %03u Device: %03u ID: %04x:%04x\n\n", m_interface.busID, m_interface.deviceID, m_interface.device_desc.idVendor, m_interface.device_desc.idProduct);
log_write("Device Descriptor:\n");
log_write("\tbLength: %u\n", m_interface.device_desc.bLength);
log_write("\tbDescriptorType: %u\n", m_interface.device_desc.bDescriptorType);
log_write("\tbcdUSB: %u:%u%u\n", bcdUSB.major(), bcdUSB.minor(), bcdUSB.macro());
log_write("\tbDeviceClass: %u\n", m_interface.device_desc.bDeviceClass);
log_write("\tbDeviceSubClass: %u\n", m_interface.device_desc.bDeviceSubClass);
log_write("\tbDeviceProtocol: %u\n", m_interface.device_desc.bDeviceProtocol);
log_write("\tbMaxPacketSize0: %u\n", m_interface.device_desc.bMaxPacketSize0);
log_write("\tidVendor: 0x%x\n", m_interface.device_desc.idVendor);
log_write("\tidProduct: 0x%x\n", m_interface.device_desc.idProduct);
log_write("\tbcdDevice: %u:%u%u\n", bcdDevice.major(), bcdDevice.minor(), bcdDevice.macro());
log_write("\tiManufacturer: %u\n", m_interface.device_desc.iManufacturer);
log_write("\tiProduct: %u\n", m_interface.device_desc.iProduct);
log_write("\tiSerialNumber: %u\n", m_interface.device_desc.iSerialNumber);
log_write("\tbNumConfigurations: %u\n", m_interface.device_desc.bNumConfigurations);
log_write("\tConfiguration Descriptor:\n");
log_write("\t\tbLength: %u\n", m_interface.config_desc.bLength);
log_write("\t\tbDescriptorType: %u\n", m_interface.config_desc.bDescriptorType);
log_write("\t\twTotalLength: %u\n", m_interface.config_desc.wTotalLength);
log_write("\t\tbNumInterfaces: %u\n", m_interface.config_desc.bNumInterfaces);
log_write("\t\tbConfigurationValue: %u\n", m_interface.config_desc.bConfigurationValue);
log_write("\t\tiConfiguration: %u\n", m_interface.config_desc.iConfiguration);
log_write("\t\tbmAttributes: 0x%x\n", m_interface.config_desc.bmAttributes);
log_write("\t\tMaxPower: %u (%u mA)\n", m_interface.config_desc.MaxPower, m_interface.config_desc.MaxPower * 2);
struct usb_endpoint_descriptor invalid_desc{};
for (u8 i = 0; i < std::size(m_s.inf.inf.input_endpoint_descs); i++) {
const auto& desc = m_s.inf.inf.input_endpoint_descs[i];
if (std::memcmp(&desc, &invalid_desc, sizeof(desc))) {
log_write("\t[USBHS] desc[%u] wMaxPacketSize: 0x%X\n", i, desc.wMaxPacketSize);
}
}
auto& input_descs = m_s.inf.inf.input_endpoint_descs[0];
R_TRY(usbHsIfOpenUsbEp(&m_s, &m_endpoints[UsbSessionEndpoint_Out], 1, input_descs.wMaxPacketSize, &input_descs));
auto& output_descs = m_s.inf.inf.output_endpoint_descs[0];
R_TRY(usbHsIfOpenUsbEp(&m_s, &m_endpoints[UsbSessionEndpoint_In], 1, output_descs.wMaxPacketSize, &output_descs));
m_connected = true;
R_SUCCEED();
}
void UsbHs::Close() {
usbHsEpClose(std::addressof(m_endpoints[UsbSessionEndpoint_In]));
usbHsEpClose(std::addressof(m_endpoints[UsbSessionEndpoint_Out]));
usbHsIfClose(std::addressof(m_s));
m_endpoints[UsbSessionEndpoint_In] = {};
m_endpoints[UsbSessionEndpoint_Out] = {};
m_s = {};
m_connected = false;
}
Event *UsbHs::GetCompletionEvent(UsbSessionEndpoint ep) {
return usbHsEpGetXferEvent(&m_endpoints[ep]);
}
Result UsbHs::WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) {
const std::array waiters{
waiterForEvent(GetCompletionEvent(ep)),
waiterForEvent(usbHsGetInterfaceStateChangeEvent()),
waiterForUEvent(GetCancelEvent()),
};
s32 idx;
auto rc = waitObjects(&idx, waiters.data(), waiters.size(), timeout);
// check if we got one of the cancel events.
if (R_SUCCEEDED(rc) && idx == waiters.size() - 1) {
log_write("got usb cancel event\n");
rc = Result_Cancelled;
} else if (R_SUCCEEDED(rc) && idx == waiters.size() - 2) {
log_write("got usb timeout event\n");
rc = KERNELRESULT(TimedOut);
Close();
}
if (R_FAILED(rc)) {
log_write("failed to wait for event\n");
eventClear(GetCompletionEvent(ep));
eventClear(usbHsGetInterfaceStateChangeEvent());
}
return rc;
}
Result UsbHs::TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_xfer_id) {
return usbHsEpPostBufferAsync(&m_endpoints[ep], buffer, size, 0, out_xfer_id);
}
Result UsbHs::GetTransferResult(UsbSessionEndpoint ep, u32 xfer_id, u32 *out_requested_size, u32 *out_transferred_size) {
u32 count;
UsbHsXferReport report_data[8];
R_TRY(eventClear(GetCompletionEvent(ep)));
R_TRY(usbHsEpGetXferReport(&m_endpoints[ep], report_data, std::size(report_data), std::addressof(count)));
R_TRY(usbHsParseReportData(report_data, count, xfer_id, out_requested_size, out_transferred_size));
R_SUCCEED();
}
} // namespace sphaira::usb