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.
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -22,3 +22,5 @@ libs/tweeny
|
|||||||
|
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
out
|
out
|
||||||
|
|
||||||
|
usb_test/
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ add_executable(sphaira
|
|||||||
source/ui/menus/menu_base.cpp
|
source/ui/menus/menu_base.cpp
|
||||||
source/ui/menus/themezer.cpp
|
source/ui/menus/themezer.cpp
|
||||||
source/ui/menus/ghdl.cpp
|
source/ui/menus/ghdl.cpp
|
||||||
|
source/ui/menus/usb_menu.cpp
|
||||||
|
|
||||||
source/ui/error_box.cpp
|
source/ui/error_box.cpp
|
||||||
source/ui/notification.cpp
|
source/ui/notification.cpp
|
||||||
@@ -80,6 +81,7 @@ add_executable(sphaira
|
|||||||
source/yati/container/xci.cpp
|
source/yati/container/xci.cpp
|
||||||
source/yati/source/file.cpp
|
source/yati/source/file.cpp
|
||||||
source/yati/source/stdio.cpp
|
source/yati/source/stdio.cpp
|
||||||
|
source/yati/source/usb.cpp
|
||||||
|
|
||||||
source/yati/nx/es.cpp
|
source/yati/nx/es.cpp
|
||||||
source/yati/nx/keys.cpp
|
source/yati/nx/keys.cpp
|
||||||
|
|||||||
43
sphaira/include/ui/menus/usb_menu.hpp
Normal file
43
sphaira/include/ui/menus/usb_menu.hpp
Normal file
@@ -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<yati::source::Usb> 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
|
||||||
51
sphaira/include/yati/source/usb.hpp
Normal file
51
sphaira/include/yati/source/usb.hpp
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "base.hpp"
|
||||||
|
#include "fs.hpp"
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "ui/menus/irs_menu.hpp"
|
#include "ui/menus/irs_menu.hpp"
|
||||||
#include "ui/menus/themezer.hpp"
|
#include "ui/menus/themezer.hpp"
|
||||||
#include "ui/menus/ghdl.hpp"
|
#include "ui/menus/ghdl.hpp"
|
||||||
|
#include "ui/menus/usb_menu.hpp"
|
||||||
|
|
||||||
#include "ui/sidebar.hpp"
|
#include "ui/sidebar.hpp"
|
||||||
#include "ui/popup_list.hpp"
|
#include "ui/popup_list.hpp"
|
||||||
@@ -317,6 +318,12 @@ MainMenu::MainMenu() {
|
|||||||
WebShow("https://lite.duckduckgo.com/lite");
|
WebShow("https://lite.duckduckgo.com/lite");
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (App::GetApp()->m_install.Get()) {
|
||||||
|
options->Add(std::make_shared<SidebarEntryCallback>("Usb Install"_i18n, [](){
|
||||||
|
App::Push(std::make_shared<menu::usb::Menu>());
|
||||||
|
}));
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<SidebarEntryCallback>("Advanced"_i18n, [this](){
|
options->Add(std::make_shared<SidebarEntryCallback>("Advanced"_i18n, [this](){
|
||||||
|
|||||||
170
sphaira/source/ui/menus/usb_menu.cpp
Normal file
170
sphaira/source/ui/menus/usb_menu.cpp
Normal file
@@ -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 <cstring>
|
||||||
|
|
||||||
|
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<Menu*>(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<yati::source::Usb>(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<ui::ProgressBox>("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
|
||||||
361
sphaira/source/yati/source/usb.cpp
Normal file
361
sphaira/source/yati/source/usb.cpp
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 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<u8>(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<u64>(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
|
||||||
130
tools/usb_total.py
Normal file
130
tools/usb_total.py
Normal file
@@ -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('<I', header[0:4])[0]
|
||||||
|
switch_version = struct.unpack('<I', header[4:8])[0]
|
||||||
|
|
||||||
|
if switch_magic != MAGIC:
|
||||||
|
raise Exception("Unexpected magic {}".format(switch_magic))
|
||||||
|
|
||||||
|
if switch_version != VERSION:
|
||||||
|
raise Exception("Unexpected version {}".format(switch_version))
|
||||||
|
|
||||||
|
send_data = struct.pack('<IIII', MAGIC, VERSION, bcdUSB, count)
|
||||||
|
out_ep.write(data=send_data, timeout=0)
|
||||||
|
|
||||||
|
def wait_for_input(path, in_ep, out_ep):
|
||||||
|
buf = None
|
||||||
|
predicted_off = 0
|
||||||
|
print("now waiting for intput\n")
|
||||||
|
|
||||||
|
with open(path, "rb") as file:
|
||||||
|
while True:
|
||||||
|
header = in_ep.read(24, timeout=0)
|
||||||
|
|
||||||
|
range_offset = struct.unpack('<Q', header[8:16])[0]
|
||||||
|
range_size = struct.unpack('<Q', header[16:24])[0]
|
||||||
|
|
||||||
|
if (range_offset == 0 and range_size == 0):
|
||||||
|
break
|
||||||
|
|
||||||
|
if (buf != None and range_offset == predicted_off and range_size == len(buf)):
|
||||||
|
# print("predicted the read off {} size {}".format(predicted_off, len(buf)))
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
file.seek(range_offset)
|
||||||
|
buf = file.read(range_size)
|
||||||
|
|
||||||
|
if (len(buf) != range_size):
|
||||||
|
# print("off: {} size: {}".format(range_offset, range_size))
|
||||||
|
raise ValueError('bad buf size!!!!!')
|
||||||
|
|
||||||
|
result = out_ep.write(data=buf, timeout=0)
|
||||||
|
if (len(buf) != result):
|
||||||
|
print("off: {} size: {}".format(range_offset, range_size))
|
||||||
|
raise ValueError('bad result!!!!!')
|
||||||
|
|
||||||
|
predicted_off = range_offset + range_size
|
||||||
|
buf = file.read(range_size)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("hello world")
|
||||||
|
|
||||||
|
# check which mode the user has selected.
|
||||||
|
args = len(sys.argv)
|
||||||
|
if (args != 2):
|
||||||
|
print("either run python usb_total.py game.nsp OR drag and drop the game onto the python file (if python is in your path)")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
path = sys.argv[1]
|
||||||
|
files = []
|
||||||
|
|
||||||
|
if os.path.isfile(path) and path.endswith(EXTS):
|
||||||
|
files.append(path)
|
||||||
|
elif os.path.isdir(path):
|
||||||
|
for f in glob.glob(path + "/**/*.*", recursive=True):
|
||||||
|
if os.path.isfile(f) and f.endswith(EXTS):
|
||||||
|
files.append(f)
|
||||||
|
else:
|
||||||
|
raise ValueError('must be a file!')
|
||||||
|
|
||||||
|
# for file in files:
|
||||||
|
# print("found file: {}".format(file))
|
||||||
|
|
||||||
|
# Find the switch
|
||||||
|
print("waiting for switch...\n")
|
||||||
|
dev = None
|
||||||
|
|
||||||
|
while (dev is None):
|
||||||
|
dev = usb.core.find(idVendor=0x057E, idProduct=0x3000)
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
print("found the switch!\n")
|
||||||
|
|
||||||
|
cfg = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
cfg = dev.get_active_configuration()
|
||||||
|
print("found active config")
|
||||||
|
except usb.core.USBError:
|
||||||
|
print("no currently active config")
|
||||||
|
cfg = None
|
||||||
|
|
||||||
|
if cfg is None:
|
||||||
|
dev.set_configuration()
|
||||||
|
cfg = dev.get_active_configuration()
|
||||||
|
|
||||||
|
is_out_ep = lambda ep: usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_OUT
|
||||||
|
is_in_ep = lambda ep: usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_IN
|
||||||
|
out_ep = usb.util.find_descriptor(cfg[(0,0)], custom_match=is_out_ep)
|
||||||
|
in_ep = usb.util.find_descriptor(cfg[(0,0)], custom_match=is_in_ep)
|
||||||
|
assert out_ep is not None
|
||||||
|
assert in_ep is not None
|
||||||
|
|
||||||
|
print("iManufacturer: {} iProduct: {} iSerialNumber: {}".format(dev.manufacturer, dev.product, dev.serial_number))
|
||||||
|
print("bcdUSB: {} bMaxPacketSize0: {}".format(hex(dev.bcdUSB), dev.bMaxPacketSize0))
|
||||||
|
|
||||||
|
try:
|
||||||
|
verify_switch(dev.bcdUSB, len(files), in_ep, out_ep)
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
print("installing file: {}".format(file))
|
||||||
|
wait_for_input(file, in_ep, out_ep)
|
||||||
|
dev.reset()
|
||||||
|
except Exception as inst:
|
||||||
|
print("An exception occurred " + str(inst))
|
||||||
Reference in New Issue
Block a user