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('