From 5038fb0c280ff00e98242304b6a30a78a76eea51 Mon Sep 17 00:00:00 2001 From: ITotalJustice <47043333+ITotalJustice@users.noreply.github.com> Date: Sun, 20 Apr 2025 18:04:35 +0100 Subject: [PATCH] add basic usb install support (see commit message). transfers seems to work, although i have done very little testing. i plan to extend the usb script so that it supports normal usb transfers, such as uploading and downloading files to and from the switch. however, most users are likely better off using mtp for said transfers. the usb transfer code was taken from Haze, which is part of Atmosphere. --- .gitignore | 2 + sphaira/CMakeLists.txt | 2 + sphaira/include/ui/menus/usb_menu.hpp | 43 +++ sphaira/include/yati/source/usb.hpp | 51 ++++ sphaira/source/ui/menus/main_menu.cpp | 7 + sphaira/source/ui/menus/usb_menu.cpp | 170 ++++++++++++ sphaira/source/yati/source/usb.cpp | 361 ++++++++++++++++++++++++++ tools/usb_total.py | 130 ++++++++++ 8 files changed, 766 insertions(+) create mode 100644 sphaira/include/ui/menus/usb_menu.hpp create mode 100644 sphaira/include/yati/source/usb.hpp create mode 100644 sphaira/source/ui/menus/usb_menu.cpp create mode 100644 sphaira/source/yati/source/usb.cpp create mode 100644 tools/usb_total.py diff --git a/.gitignore b/.gitignore index b7327b9..1e73ffe 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ libs/tweeny compile_commands.json out + +usb_test/ diff --git a/sphaira/CMakeLists.txt b/sphaira/CMakeLists.txt index e2eee2c..b866558 100644 --- a/sphaira/CMakeLists.txt +++ b/sphaira/CMakeLists.txt @@ -46,6 +46,7 @@ add_executable(sphaira source/ui/menus/menu_base.cpp source/ui/menus/themezer.cpp source/ui/menus/ghdl.cpp + source/ui/menus/usb_menu.cpp source/ui/error_box.cpp source/ui/notification.cpp @@ -80,6 +81,7 @@ add_executable(sphaira source/yati/container/xci.cpp source/yati/source/file.cpp source/yati/source/stdio.cpp + source/yati/source/usb.cpp source/yati/nx/es.cpp source/yati/nx/keys.cpp diff --git a/sphaira/include/ui/menus/usb_menu.hpp b/sphaira/include/ui/menus/usb_menu.hpp new file mode 100644 index 0000000..14f2c42 --- /dev/null +++ b/sphaira/include/ui/menus/usb_menu.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "ui/menus/menu_base.hpp" +#include "yati/source/usb.hpp" + +namespace sphaira::ui::menu::usb { + +enum class State { + // not connected. + None, + // just connected, starts the transfer. + Connected, + // set whilst transfer is in progress. + Progress, + // set when the transfer is finished. + Done, + // failed to connect. + Failed, +}; + +struct Menu final : MenuBase { + Menu(); + ~Menu(); + + void Update(Controller* controller, TouchInfo* touch) override; + void Draw(NVGcontext* vg, Theme* theme) override; + void OnFocusGained() override; + +// this should be private +// private: + std::shared_ptr m_usb_source{}; + bool m_was_mtp_enabled{}; + + Thread m_thread{}; + Mutex m_mutex{}; + // the below are shared across threads, lock with the above mutex! + State m_state{State::None}; + bool m_usb_has_connection{}; + u32 m_usb_speed{}; + u32 m_usb_count{}; +}; + +} // namespace sphaira::ui::menu::usb diff --git a/sphaira/include/yati/source/usb.hpp b/sphaira/include/yati/source/usb.hpp new file mode 100644 index 0000000..1ac51af --- /dev/null +++ b/sphaira/include/yati/source/usb.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "base.hpp" +#include "fs.hpp" +#include + +namespace sphaira::yati::source { + +struct Usb final : Base { + enum { USBModule = 523 }; + + enum : Result { + Result_BadMagic = MAKERESULT(USBModule, 0), + Result_BadVersion = MAKERESULT(USBModule, 1), + Result_BadCount = MAKERESULT(USBModule, 2), + Result_BadTransferSize = MAKERESULT(USBModule, 3), + Result_BadTotalSize = MAKERESULT(USBModule, 4), + }; + + Usb(u64 transfer_timeout); + ~Usb(); + + Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override; + Result Finished() const; + + Result Init(); + Result WaitForConnection(u64 timeout, u32& speed, u32& count); + +private: + enum UsbSessionEndpoint { + UsbSessionEndpoint_In = 0, + UsbSessionEndpoint_Out = 1, + }; + + Result SendCommand(s64 off, s64 size) const; + Result InternalRead(void* buf, s64 off, s64 size) const; + + bool GetConfigured() const; + Event *GetCompletionEvent(UsbSessionEndpoint ep) const; + Result WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) const; + Result TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_urb_id) const; + Result GetTransferResult(UsbSessionEndpoint ep, u32 urb_id, u32 *out_requested_size, u32 *out_transferred_size) const; + Result TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_transferred, u64 timeout) const; + +private: + UsbDsInterface* m_interface{}; + UsbDsEndpoint* m_endpoints[2]{}; + u64 m_transfer_timeout{}; +}; + +} // namespace sphaira::yati::source diff --git a/sphaira/source/ui/menus/main_menu.cpp b/sphaira/source/ui/menus/main_menu.cpp index 287674d..6c2ef8b 100644 --- a/sphaira/source/ui/menus/main_menu.cpp +++ b/sphaira/source/ui/menus/main_menu.cpp @@ -2,6 +2,7 @@ #include "ui/menus/irs_menu.hpp" #include "ui/menus/themezer.hpp" #include "ui/menus/ghdl.hpp" +#include "ui/menus/usb_menu.hpp" #include "ui/sidebar.hpp" #include "ui/popup_list.hpp" @@ -317,6 +318,12 @@ MainMenu::MainMenu() { WebShow("https://lite.duckduckgo.com/lite"); })); } + + if (App::GetApp()->m_install.Get()) { + options->Add(std::make_shared("Usb Install"_i18n, [](){ + App::Push(std::make_shared()); + })); + } })); options->Add(std::make_shared("Advanced"_i18n, [this](){ diff --git a/sphaira/source/ui/menus/usb_menu.cpp b/sphaira/source/ui/menus/usb_menu.cpp new file mode 100644 index 0000000..1cab284 --- /dev/null +++ b/sphaira/source/ui/menus/usb_menu.cpp @@ -0,0 +1,170 @@ +#include "ui/menus/usb_menu.hpp" +#include "yati/yati.hpp" +#include "app.hpp" +#include "defines.hpp" +#include "log.hpp" +#include "ui/nvg_util.hpp" +#include "i18n.hpp" +#include + +namespace sphaira::ui::menu::usb { +namespace { + +constexpr u64 CONNECTION_TIMEOUT = 1e+9 * 3; +constexpr u64 TRANSFER_TIMEOUT = 1e+9 * 5; + +void thread_func(void* user) { + auto app = static_cast(user); + + for (;;) { + if (app->GetToken().stop_requested()) { + break; + } + + const auto rc = app->m_usb_source->WaitForConnection(CONNECTION_TIMEOUT, app->m_usb_speed, app->m_usb_count); + mutexLock(&app->m_mutex); + ON_SCOPE_EXIT(mutexUnlock(&app->m_mutex)); + + if (R_SUCCEEDED(rc)) { + app->m_state = State::Connected; + break; + } else if (R_FAILED(rc) && R_VALUE(rc) != 0xEA01) { + log_write("got: 0x%X value: 0x%X\n", rc, R_VALUE(rc)); + app->m_state = State::Failed; + break; + } + } +} + +} // namespace + +Menu::Menu() : MenuBase{"Irs"_i18n} { + SetAction(Button::B, Action{"Back"_i18n, [this](){ + SetPop(); + }}); + + // if mtp is enabled, disable it for now. + m_was_mtp_enabled = App::GetMtpEnable(); + if (m_was_mtp_enabled) { + App::Notify("Disable MTP for usb install"_i18n); + App::SetMtpEnable(false); + } + + // 3 second timeout for transfers. + m_usb_source = std::make_shared(TRANSFER_TIMEOUT); + if (R_FAILED(m_usb_source->GetOpenResult())) { + log_write("usb init open\n"); + m_state = State::Failed; + } else { + if (R_FAILED(m_usb_source->Init())) { + log_write("usb init failed\n"); + m_state = State::Failed; + } + } + + mutexInit(&m_mutex); + if (m_state != State::Failed) { + threadCreate(&m_thread, thread_func, this, nullptr, 1024*32, 0x2C, 1); + threadStart(&m_thread); + } +} + +Menu::~Menu() { + // signal for thread to exit and wait. + m_stop_source.request_stop(); + threadWaitForExit(&m_thread); + threadClose(&m_thread); + + // free usb source before re-enabling mtp. + log_write("closing data!!!!\n"); + m_usb_source.reset(); + + if (m_was_mtp_enabled) { + App::Notify("Re-enabled MTP"_i18n); + App::SetMtpEnable(true); + } +} + +void Menu::Update(Controller* controller, TouchInfo* touch) { + MenuBase::Update(controller, touch); + + mutexLock(&m_mutex); + ON_SCOPE_EXIT(mutexUnlock(&m_mutex)); + + switch (m_state) { + case State::None: + break; + + case State::Connected: + log_write("set to progress\n"); + m_state = State::Progress; + log_write("got connection\n"); + App::Push(std::make_shared("Installing App"_i18n, [this](auto pbox) mutable -> bool { + log_write("inside progress box\n"); + for (u32 i = 0; i < m_usb_count; i++) { + const auto rc = yati::InstallFromSource(pbox, m_usb_source); + if (R_FAILED(rc)) { + return false; + } + + App::Notify("Installed via usb"_i18n); + m_usb_source->Finished(); + } + + return true; + }, [this](bool result){ + if (result) { + App::Notify("Usb install success!"_i18n); + } else { + App::Notify("Usb install failed!"_i18n); + } + m_state = State::Done; + this->SetPop(); + })); + break; + + case State::Progress: + break; + + case State::Done: + break; + + case State::Failed: + break; + } +} + +void Menu::Draw(NVGcontext* vg, Theme* theme) { + MenuBase::Draw(vg, theme); + + mutexLock(&m_mutex); + ON_SCOPE_EXIT(mutexUnlock(&m_mutex)); + + switch (m_state) { + case State::None: + gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Waiting for connection..."_i18n.c_str()); + break; + + case State::Connected: + break; + + case State::Progress: + gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Transferring data..."_i18n.c_str()); + break; + + case State::Done: + gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Press B to Exit..."_i18n.c_str()); + break; + + case State::Failed: + gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Failed to init usb..."_i18n.c_str()); + this->SetPop(); + break; + } +} + +void Menu::OnFocusGained() { + MenuBase::OnFocusGained(); +} + +} // namespace sphaira::ui::menu::usb diff --git a/sphaira/source/yati/source/usb.cpp b/sphaira/source/yati/source/usb.cpp new file mode 100644 index 0000000..e99ba50 --- /dev/null +++ b/sphaira/source/yati/source/usb.cpp @@ -0,0 +1,361 @@ +/* + * Copyright (c) Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +// Most of the usb transfer code was taken from Haze. +#include "yati/source/usb.hpp" +#include "log.hpp" + +namespace sphaira::yati::source { +namespace { + +constexpr u32 MAGIC = 0x53504841; +constexpr u32 VERSION = 1; + +struct SendHeader { + u32 magic; + u32 version; +}; + +struct RecvHeader { + u32 magic; + u32 version; + u32 bcdUSB; + u32 count; +}; + +} // namespace + +Usb::Usb(u64 transfer_timeout) { + m_open_result = usbDsInitialize(); + m_transfer_timeout = transfer_timeout; +} + +Usb::~Usb() { + if (R_SUCCEEDED(GetOpenResult())) { + usbDsExit(); + } +} + +Result Usb::Init() { + log_write("doing USB init\n"); + R_TRY(m_open_result); + + u8 iManufacturer, iProduct, iSerialNumber; + static const u16 supported_langs[1] = {0x0409}; + // Send language descriptor + R_TRY(usbDsAddUsbLanguageStringDescriptor(NULL, supported_langs, sizeof(supported_langs)/sizeof(u16))); + // Send manufacturer + R_TRY(usbDsAddUsbStringDescriptor(&iManufacturer, "Nintendo")); + // Send product + R_TRY(usbDsAddUsbStringDescriptor(&iProduct, "Nintendo Switch")); + // Send serial number + R_TRY(usbDsAddUsbStringDescriptor(&iSerialNumber, "SerialNumber")); + + // Send device descriptors + struct usb_device_descriptor device_descriptor = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = 0x0110, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 0x40, + .idVendor = 0x057e, + .idProduct = 0x3000, + .bcdDevice = 0x0100, + .iManufacturer = iManufacturer, + .iProduct = iProduct, + .iSerialNumber = iSerialNumber, + .bNumConfigurations = 0x01 + }; + + // Full Speed is USB 1.1 + R_TRY(usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Full, &device_descriptor)); + + // High Speed is USB 2.0 + device_descriptor.bcdUSB = 0x0200; + R_TRY(usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_High, &device_descriptor)); + + // Super Speed is USB 3.0 + device_descriptor.bcdUSB = 0x0300; + // Upgrade packet size to 512 + device_descriptor.bMaxPacketSize0 = 0x09; + R_TRY(usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Super, &device_descriptor)); + + // Define Binary Object Store + const u8 bos[0x16] = { + 0x05, // .bLength + USB_DT_BOS, // .bDescriptorType + 0x16, 0x00, // .wTotalLength + 0x02, // .bNumDeviceCaps + + // USB 2.0 + 0x07, // .bLength + USB_DT_DEVICE_CAPABILITY, // .bDescriptorType + 0x02, // .bDevCapabilityType + 0x02, 0x00, 0x00, 0x00, // dev_capability_data + + // USB 3.0 + 0x0A, // .bLength + USB_DT_DEVICE_CAPABILITY, // .bDescriptorType + 0x03, /* .bDevCapabilityType */ + 0x00, /* .bmAttributes */ + 0x0E, 0x00, /* .wSpeedSupported */ + 0x03, /* .bFunctionalitySupport */ + 0x00, /* .bU1DevExitLat */ + 0x00, 0x00 /* .bU2DevExitLat */ + }; + + R_TRY(usbDsSetBinaryObjectStore(bos, sizeof(bos))); + + struct usb_interface_descriptor interface_descriptor = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = USBDS_DEFAULT_InterfaceNumber, // set below + .bNumEndpoints = static_cast(std::size(m_endpoints)), + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceProtocol = USB_CLASS_VENDOR_SPEC, + }; + + + struct usb_endpoint_descriptor endpoint_descriptor_in = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_ENDPOINT_IN, + .bmAttributes = USB_TRANSFER_TYPE_BULK, + .wMaxPacketSize = 0x40, + }; + + struct usb_endpoint_descriptor endpoint_descriptor_out = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_ENDPOINT_OUT, + .bmAttributes = USB_TRANSFER_TYPE_BULK, + .wMaxPacketSize = 0x40, + }; + + const struct usb_ss_endpoint_companion_descriptor endpoint_companion = { + .bLength = sizeof(struct usb_ss_endpoint_companion_descriptor), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMPANION, + .bMaxBurst = 0x0F, + .bmAttributes = 0x00, + .wBytesPerInterval = 0x00, + }; + + R_TRY(usbDsRegisterInterface(&m_interface)); + + interface_descriptor.bInterfaceNumber = m_interface->interface_index; + endpoint_descriptor_in.bEndpointAddress += interface_descriptor.bInterfaceNumber + 1; + endpoint_descriptor_out.bEndpointAddress += interface_descriptor.bInterfaceNumber + 1; + + // Full Speed Config + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Full, &interface_descriptor, USB_DT_INTERFACE_SIZE)); + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Full, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE)); + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Full, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE)); + + // High Speed Config + endpoint_descriptor_in.wMaxPacketSize = 0x200; + endpoint_descriptor_out.wMaxPacketSize = 0x200; + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, &interface_descriptor, USB_DT_INTERFACE_SIZE)); + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE)); + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE)); + + // Super Speed Config + endpoint_descriptor_in.wMaxPacketSize = 0x400; + endpoint_descriptor_out.wMaxPacketSize = 0x400; + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, &interface_descriptor, USB_DT_INTERFACE_SIZE)); + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE)); + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE)); + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE)); + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE)); + + //Setup endpoints. + R_TRY(usbDsInterface_RegisterEndpoint(m_interface, &m_endpoints[UsbSessionEndpoint_In], endpoint_descriptor_in.bEndpointAddress)); + R_TRY(usbDsInterface_RegisterEndpoint(m_interface, &m_endpoints[UsbSessionEndpoint_Out], endpoint_descriptor_out.bEndpointAddress)); + + R_TRY(usbDsInterface_EnableInterface(m_interface)); + R_TRY(usbDsEnable()); + + log_write("success USB init\n"); + R_SUCCEED(); +} + +Result Usb::WaitForConnection(u64 timeout, u32& speed, u32& count) { + const SendHeader send_header{ + .magic = MAGIC, + .version = VERSION, + }; + + alignas(0x1000) u8 aligned[0x1000]{}; + std::memcpy(aligned, std::addressof(send_header), sizeof(send_header)); + + // send header. + u32 transferredSize; + R_TRY(TransferPacketImpl(false, aligned, sizeof(send_header), &transferredSize, timeout)); + + // receive header. + struct RecvHeader recv_header{}; + R_TRY(TransferPacketImpl(true, aligned, sizeof(recv_header), &transferredSize, timeout)); + + // copy data into header struct. + std::memcpy(&recv_header, aligned, sizeof(recv_header)); + + // validate received header. + R_UNLESS(recv_header.magic == MAGIC, Result_BadMagic); + R_UNLESS(recv_header.version == VERSION, Result_BadVersion); + R_UNLESS(recv_header.count > 0, Result_BadCount); + + count = recv_header.count; + speed = recv_header.bcdUSB; + R_SUCCEED(); +} + +bool Usb::GetConfigured() const { + UsbState usb_state; + usbDsGetState(std::addressof(usb_state)); + return usb_state == UsbState_Configured; +} + +Event *Usb::GetCompletionEvent(UsbSessionEndpoint ep) const { + return std::addressof(m_endpoints[ep]->CompletionEvent); +} + +Result Usb::WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) const { + auto event = GetCompletionEvent(ep); + const auto rc = eventWait(event, timeout); + + if (R_FAILED(rc)) { + R_TRY(usbDsEndpoint_Cancel(m_endpoints[ep])); + eventClear(event); + } + + return rc; +} + +Result Usb::TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_urb_id) const { + return usbDsEndpoint_PostBufferAsync(m_endpoints[ep], buffer, size, out_urb_id); +} + +Result Usb::GetTransferResult(UsbSessionEndpoint ep, u32 urb_id, u32 *out_requested_size, u32 *out_transferred_size) const { + UsbDsReportData report_data; + + R_TRY(eventClear(std::addressof(m_endpoints[ep]->CompletionEvent))); + R_TRY(usbDsEndpoint_GetReportData(m_endpoints[ep], std::addressof(report_data))); + R_TRY(usbDsParseReportData(std::addressof(report_data), urb_id, out_requested_size, out_transferred_size)); + + R_SUCCEED(); +} + +Result Usb::TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_transferred, u64 timeout) const { + u32 urb_id; + + /* If we're not configured yet, wait to become configured first. */ + // R_TRY(usbDsWaitReady(timeout)); + if (!GetConfigured()) { + R_TRY(eventWait(usbDsGetStateChangeEvent(), timeout)); + R_TRY(eventClear(usbDsGetStateChangeEvent())); + R_THROW(0xEA01); + } + + /* Select the appropriate endpoint and begin a transfer. */ + const auto ep = read ? UsbSessionEndpoint_Out : UsbSessionEndpoint_In; + R_TRY(TransferAsync(ep, page, size, std::addressof(urb_id))); + + /* Try to wait for the event. */ + R_TRY(WaitTransferCompletion(ep, timeout)); + + /* Return what we transferred. */ + return GetTransferResult(ep, urb_id, nullptr, out_size_transferred); +} + +Result Usb::SendCommand(s64 off, s64 size) const { + struct { + u32 hash; + u32 magic; + s64 off; + s64 size; + } meta{0, 0, off, size}; + + alignas(0x1000) static u8 aligned[0x1000]{}; + std::memcpy(aligned, std::addressof(meta), sizeof(meta)); + + u32 transferredSize; + return TransferPacketImpl(false, aligned, sizeof(meta), &transferredSize, m_transfer_timeout); +} + +Result Usb::Finished() const { + return SendCommand(0, 0); +} + +Result Usb::InternalRead(void* _buf, s64 off, s64 size) const { + u8* buf = (u8*)_buf; + alignas(0x1000) u8 aligned[0x1000]{}; + const auto stored_size = size; + s64 total = 0; + + while (size) { + auto read_size = size; + auto read_buf = buf; + + if (u64(buf) & 0xFFF) { + read_size = std::min(size, sizeof(aligned) - (u64(buf) & 0xFFF)); + read_buf = aligned; + log_write("unaligned read %zd %zd read_size: %zd align: %zd\n", off, size, read_size, u64(buf) & 0xFFF); + } else if (read_size & 0xFFF) { + if (read_size <= 0xFFF) { + log_write("unaligned small read %zd %zd read_size: %zd align: %zd\n", off, size, read_size, u64(buf) & 0xFFF); + read_buf = aligned; + } else { + log_write("unaligned big read %zd %zd read_size: %zd align: %zd\n", off, size, read_size, u64(buf) & 0xFFF); + // read as much as possible into buffer, the rest will + // be handled in a second read which will be aligned size aligned. + read_size = read_size & ~0xFFF; + } + } + + R_TRY(SendCommand(off, read_size)); + + u32 transferredSize{}; + R_TRY(TransferPacketImpl(true, read_buf, read_size, &transferredSize, m_transfer_timeout)); + R_UNLESS(transferredSize <= read_size, Result_BadTransferSize); + + if (read_buf == aligned) { + std::memcpy(buf, aligned, transferredSize); + } + + if (transferredSize < read_size) { + log_write("reading less than expected! %u vs %zd stored: %zd\n", transferredSize, read_size, stored_size); + } + + off += transferredSize; + buf += transferredSize; + size -= transferredSize; + total += transferredSize; + } + + R_UNLESS(total == stored_size, Result_BadTotalSize); + R_SUCCEED(); +} + +Result Usb::Read(void* buf, s64 off, s64 size, u64* bytes_read) { + R_TRY(GetOpenResult()); + R_TRY(InternalRead(buf, off, size)); + *bytes_read = size; + R_SUCCEED(); +} + +} // namespace sphaira::yati::source diff --git a/tools/usb_total.py b/tools/usb_total.py new file mode 100644 index 0000000..d41ec3e --- /dev/null +++ b/tools/usb_total.py @@ -0,0 +1,130 @@ +# based on usb.py from Tinfoil, by Adubbz. +import struct +import sys +import os +import usb.core +import usb.util +import time +import glob + +# magic number (SPHA) for the script and switch. +MAGIC = 0x53504841 +# version of the usb script. +VERSION = 1 +# list of supported extensions. +EXTS = (".nsp", ".xci", ".nsz", ".xcz") + +def verify_switch(bcdUSB, count, in_ep, out_ep): + header = in_ep.read(8, timeout=0) + switch_magic = struct.unpack('