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.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
#include "defines.hpp"
|
||||
#include "log.hpp"
|
||||
#include <memory>
|
||||
#include <cstring>
|
||||
|
||||
namespace sphaira::yati::container {
|
||||
namespace {
|
||||
@@ -22,6 +23,38 @@ struct Pfs0FileTableEntry {
|
||||
u32 padding;
|
||||
};
|
||||
|
||||
// stdio-like wrapper for std::vector
|
||||
struct BufHelper {
|
||||
BufHelper() = default;
|
||||
BufHelper(std::span<const u8> data) {
|
||||
write(data);
|
||||
}
|
||||
|
||||
void write(const void* data, u64 size) {
|
||||
if (offset + size >= buf.size()) {
|
||||
buf.resize(offset + size);
|
||||
}
|
||||
std::memcpy(buf.data() + offset, data, size);
|
||||
offset += size;
|
||||
}
|
||||
|
||||
void write(std::span<const u8> data) {
|
||||
write(data.data(), data.size());
|
||||
}
|
||||
|
||||
void seek(u64 where_to) {
|
||||
offset = where_to;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
auto tell() const {
|
||||
return offset;
|
||||
}
|
||||
|
||||
std::vector<u8> buf;
|
||||
u64 offset{};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Result Nsp::GetCollections(Collections& out) {
|
||||
@@ -56,4 +89,48 @@ Result Nsp::GetCollections(Collections& out) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
auto Nsp::Build(std::span<CollectionEntry> entries, s64& size) -> std::vector<u8> {
|
||||
BufHelper buf;
|
||||
|
||||
Pfs0Header header{};
|
||||
std::vector<Pfs0FileTableEntry> file_table(entries.size());
|
||||
std::vector<char> string_table;
|
||||
|
||||
u64 string_offset{};
|
||||
u64 data_offset{};
|
||||
|
||||
for (u32 i = 0; i < entries.size(); i++) {
|
||||
file_table[i].data_offset = data_offset;
|
||||
file_table[i].data_size = entries[i].size;
|
||||
file_table[i].name_offset = string_offset;
|
||||
file_table[i].padding = 0;
|
||||
|
||||
string_table.resize(string_offset + entries[i].name.length() + 1);
|
||||
std::memcpy(string_table.data() + string_offset, entries[i].name.c_str(), entries[i].name.length() + 1);
|
||||
|
||||
data_offset += file_table[i].data_size;
|
||||
string_offset += entries[i].name.length() + 1;
|
||||
}
|
||||
|
||||
// align table
|
||||
string_table.resize((string_table.size() + 0x1F) & ~0x1F);
|
||||
|
||||
header.magic = PFS0_MAGIC;
|
||||
header.total_files = entries.size();
|
||||
header.string_table_size = string_table.size();
|
||||
header.padding = 0;
|
||||
|
||||
buf.write(&header, sizeof(header));
|
||||
buf.write(file_table.data(), sizeof(Pfs0FileTableEntry) * file_table.size());
|
||||
buf.write(string_table.data(), string_table.size());
|
||||
|
||||
// calculate nsp size.
|
||||
size = buf.tell();
|
||||
for (const auto& e : file_table) {
|
||||
size += e.data_size;
|
||||
}
|
||||
|
||||
return buf.buf;
|
||||
}
|
||||
|
||||
} // namespace sphaira::yati::container
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "yati/nx/es.hpp"
|
||||
#include "yati/nx/crypto.hpp"
|
||||
#include "yati/nx/nxdumptool_rsa.h"
|
||||
#include "yati/nx/service_guard.h"
|
||||
#include "defines.hpp"
|
||||
#include "log.hpp"
|
||||
#include <memory>
|
||||
@@ -9,12 +10,124 @@
|
||||
namespace sphaira::es {
|
||||
namespace {
|
||||
|
||||
Service g_esSrv;
|
||||
|
||||
NX_GENERATE_SERVICE_GUARD(es);
|
||||
|
||||
Result _esInitialize() {
|
||||
return smGetService(&g_esSrv, "es");
|
||||
}
|
||||
|
||||
void _esCleanup() {
|
||||
serviceClose(&g_esSrv);
|
||||
}
|
||||
|
||||
Result ListTicket(u32 cmd_id, s32 *out_entries_written, FsRightsId* out_ids, s32 count) {
|
||||
struct {
|
||||
u32 num_rights_ids_written;
|
||||
} out;
|
||||
|
||||
const Result rc = serviceDispatchInOut(&g_esSrv, cmd_id, *out_entries_written, out,
|
||||
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out },
|
||||
.buffers = { { out_ids, count * sizeof(*out_ids) } },
|
||||
);
|
||||
|
||||
if (R_SUCCEEDED(rc) && out_entries_written) *out_entries_written = out.num_rights_ids_written;
|
||||
return rc;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Result ImportTicket(Service* srv, const void* tik_buf, u64 tik_size, const void* cert_buf, u64 cert_size) {
|
||||
return serviceDispatch(srv, 1,
|
||||
Result Initialize() {
|
||||
return esInitialize();
|
||||
}
|
||||
|
||||
void Exit() {
|
||||
esExit();
|
||||
}
|
||||
|
||||
Service* GetServiceSession() {
|
||||
return &g_esSrv;
|
||||
}
|
||||
|
||||
Result ImportTicket(const void* tik_buf, u64 tik_size, const void* cert_buf, u64 cert_size) {
|
||||
return serviceDispatch(&g_esSrv, 1,
|
||||
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In, SfBufferAttr_HipcMapAlias | SfBufferAttr_In },
|
||||
.buffers = { { tik_buf, tik_size }, { cert_buf, cert_size } });
|
||||
.buffers = { { tik_buf, tik_size }, { cert_buf, cert_size } }
|
||||
);
|
||||
}
|
||||
|
||||
Result CountCommonTicket(s32* count) {
|
||||
return serviceDispatchOut(&g_esSrv, 9, *count);
|
||||
}
|
||||
|
||||
Result CountPersonalizedTicket(s32* count) {
|
||||
return serviceDispatchOut(&g_esSrv, 10, *count);
|
||||
}
|
||||
|
||||
Result ListCommonTicket(s32 *out_entries_written, FsRightsId* out_ids, s32 count) {
|
||||
return ListTicket(11, out_entries_written, out_ids, count);
|
||||
}
|
||||
|
||||
Result ListPersonalizedTicket(s32 *out_entries_written, FsRightsId* out_ids, s32 count) {
|
||||
return ListTicket(12, out_entries_written, out_ids, count);
|
||||
}
|
||||
|
||||
Result ListMissingPersonalizedTicket(s32 *out_entries_written, FsRightsId* out_ids, s32 count) {
|
||||
return ListTicket(13, out_entries_written, out_ids, count);
|
||||
}
|
||||
|
||||
Result GetCommonTicketSize(u64 *size_out, const FsRightsId* rightsId) {
|
||||
return serviceDispatchInOut(&g_esSrv, 14, *rightsId, *size_out);
|
||||
}
|
||||
|
||||
Result GetCommonTicketData(u64 *size_out, void *tik_data, u64 tik_size, const FsRightsId* rightsId) {
|
||||
return serviceDispatchInOut(&g_esSrv, 16, *rightsId, *size_out,
|
||||
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out },
|
||||
.buffers = { { tik_data, tik_size } },
|
||||
);
|
||||
}
|
||||
|
||||
Result GetCommonTicketAndCertificateSize(u64 *tik_size_out, u64 *cert_size_out, const FsRightsId* rightsId) {
|
||||
if (hosversionBefore(4,0,0)) {
|
||||
return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
|
||||
}
|
||||
|
||||
struct {
|
||||
u64 ticket_size;
|
||||
u64 cert_size;
|
||||
} out;
|
||||
|
||||
const Result rc = serviceDispatchInOut(&g_esSrv, 22, *rightsId, out);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
*tik_size_out = out.ticket_size;
|
||||
*cert_size_out = out.cert_size;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result GetCommonTicketAndCertificateData(u64 *tik_size_out, u64 *cert_size_out, void* tik_buf, u64 tik_size, void* cert_buf, u64 cert_size, const FsRightsId* rightsId) {
|
||||
if (hosversionBefore(4,0,0)) {
|
||||
return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
|
||||
}
|
||||
|
||||
struct {
|
||||
u64 ticket_size;
|
||||
u64 cert_size;
|
||||
} out;
|
||||
|
||||
const Result rc = serviceDispatchInOut(&g_esSrv, 23, *rightsId, out,
|
||||
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out, SfBufferAttr_HipcMapAlias | SfBufferAttr_Out },
|
||||
.buffers = { { tik_buf, tik_size }, { cert_buf, cert_size } }
|
||||
);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
*tik_size_out = out.ticket_size;
|
||||
*cert_size_out = out.cert_size;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "yati/nx/ncm.hpp"
|
||||
#include "defines.hpp"
|
||||
#include <memory>
|
||||
#include <bit>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace sphaira::ncm {
|
||||
namespace {
|
||||
@@ -25,6 +28,25 @@ auto GetMetaTypeStr(u8 meta_type) -> const char* {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
// taken from nxdumptool
|
||||
auto GetMetaTypeShortStr(u8 meta_type) -> const char* {
|
||||
switch (meta_type) {
|
||||
case NcmContentMetaType_Unknown: return "UNK";
|
||||
case NcmContentMetaType_SystemProgram: return "SYSPRG";
|
||||
case NcmContentMetaType_SystemData: return "SYSDAT";
|
||||
case NcmContentMetaType_SystemUpdate: return "SYSUPD";
|
||||
case NcmContentMetaType_BootImagePackage: return "BIP";
|
||||
case NcmContentMetaType_BootImagePackageSafe: return "BIPS";
|
||||
case NcmContentMetaType_Application: return "BASE";
|
||||
case NcmContentMetaType_Patch: return "UPD";
|
||||
case NcmContentMetaType_AddOnContent: return "DLC";
|
||||
case NcmContentMetaType_Delta: return "DELTA";
|
||||
case NcmContentMetaType_DataPatch: return "DLCUPD";
|
||||
}
|
||||
|
||||
return "UNK";
|
||||
}
|
||||
|
||||
auto GetStorageIdStr(u8 storage_id) -> const char* {
|
||||
switch (storage_id) {
|
||||
case NcmStorageId_None: return "None";
|
||||
@@ -57,6 +79,18 @@ auto GetAppId(const PackagedContentMeta& meta) -> u64 {
|
||||
return GetAppId(meta.meta_type, meta.title_id);
|
||||
}
|
||||
|
||||
auto GetContentIdFromStr(const char* str) -> NcmContentId {
|
||||
char lowerU64[0x11]{};
|
||||
char upperU64[0x11]{};
|
||||
std::memcpy(lowerU64, str, 0x10);
|
||||
std::memcpy(upperU64, str + 0x10, 0x10);
|
||||
|
||||
NcmContentId nca_id{};
|
||||
*(u64*)nca_id.c = std::byteswap(std::strtoul(lowerU64, nullptr, 0x10));
|
||||
*(u64*)(nca_id.c + 8) = std::byteswap(std::strtoul(upperU64, nullptr, 0x10));
|
||||
return nca_id;
|
||||
}
|
||||
|
||||
Result Delete(NcmContentStorage* cs, const NcmContentId *content_id) {
|
||||
bool has;
|
||||
R_TRY(ncmContentStorageHas(cs, std::addressof(has), content_id));
|
||||
|
||||
@@ -18,266 +18,34 @@
|
||||
// The USB protocol was taken from Tinfoil, by Adubbz.
|
||||
|
||||
#include "yati/source/usb.hpp"
|
||||
#include "usb/tinfoil.hpp"
|
||||
#include "log.hpp"
|
||||
#include <ranges>
|
||||
|
||||
namespace sphaira::yati::source {
|
||||
namespace {
|
||||
|
||||
enum USBCmdType : u8 {
|
||||
REQUEST = 0,
|
||||
RESPONSE = 1
|
||||
};
|
||||
|
||||
enum USBCmdId : u32 {
|
||||
EXIT = 0,
|
||||
FILE_RANGE = 1
|
||||
};
|
||||
|
||||
struct NX_PACKED USBCmdHeader {
|
||||
u32 magic;
|
||||
USBCmdType type;
|
||||
u8 padding[0x3] = {0};
|
||||
u32 cmdId;
|
||||
u64 dataSize;
|
||||
u8 reserved[0xC] = {0};
|
||||
};
|
||||
|
||||
struct FileRangeCmdHeader {
|
||||
u64 size;
|
||||
u64 offset;
|
||||
u64 nspNameLen;
|
||||
u64 padding;
|
||||
};
|
||||
|
||||
struct TUSHeader {
|
||||
u32 magic; // TUL0 (Tinfoil Usb List 0)
|
||||
u32 nspListSize;
|
||||
u64 padding;
|
||||
};
|
||||
|
||||
static_assert(sizeof(TUSHeader) == 0x10, "TUSHeader must be 0x10!");
|
||||
static_assert(sizeof(USBCmdHeader) == 0x20, "USBCmdHeader must be 0x20!");
|
||||
namespace tinfoil = usb::tinfoil;
|
||||
|
||||
} // namespace
|
||||
|
||||
Usb::Usb(u64 transfer_timeout) {
|
||||
m_open_result = usbDsInitialize();
|
||||
m_transfer_timeout = transfer_timeout;
|
||||
ueventCreate(GetCancelEvent(), true);
|
||||
// this avoids allocations during transfers.
|
||||
m_aligned.reserve(1024 * 1024 * 16);
|
||||
m_usb = std::make_unique<usb::UsbDs>(transfer_timeout);
|
||||
m_open_result = m_usb->Init();
|
||||
}
|
||||
|
||||
Usb::~Usb() {
|
||||
if (R_SUCCEEDED(GetOpenResult())) {
|
||||
usbDsExit();
|
||||
}
|
||||
}
|
||||
|
||||
Result Usb::Init() {
|
||||
log_write("doing USB init\n");
|
||||
R_TRY(m_open_result);
|
||||
|
||||
SetSysSerialNumber serial_number;
|
||||
R_TRY(setsysInitialize());
|
||||
ON_SCOPE_EXIT(setsysExit());
|
||||
R_TRY(setsysGetSerialNumber(&serial_number));
|
||||
|
||||
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, serial_number.number));
|
||||
|
||||
// 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,
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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
|
||||
endpoint_descriptor_in.wMaxPacketSize = 0x40;
|
||||
endpoint_descriptor_out.wMaxPacketSize = 0x40;
|
||||
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();
|
||||
}
|
||||
|
||||
// the blow code is taken from libnx, with the addition of a uevent to cancel.
|
||||
Result Usb::IsUsbConnected(u64 timeout) {
|
||||
Result rc;
|
||||
UsbState state = UsbState_Detached;
|
||||
|
||||
rc = usbDsGetState(&state);
|
||||
if (R_FAILED(rc)) return rc;
|
||||
if (state == UsbState_Configured) return 0;
|
||||
|
||||
bool has_timeout = timeout != UINT64_MAX;
|
||||
u64 deadline = 0;
|
||||
|
||||
const std::array waiters{
|
||||
waiterForEvent(usbDsGetStateChangeEvent()),
|
||||
waiterForUEvent(GetCancelEvent()),
|
||||
};
|
||||
|
||||
if (has_timeout)
|
||||
deadline = armGetSystemTick() + armNsToTicks(timeout);
|
||||
|
||||
do {
|
||||
if (has_timeout) {
|
||||
s64 remaining = deadline - armGetSystemTick();
|
||||
timeout = remaining > 0 ? armTicksToNs(remaining) : 0;
|
||||
}
|
||||
|
||||
s32 idx;
|
||||
rc = waitObjects(&idx, waiters.data(), waiters.size(), timeout);
|
||||
eventClear(usbDsGetStateChangeEvent());
|
||||
|
||||
// check if we got one of the cancel events.
|
||||
if (R_SUCCEEDED(rc) && idx != 0) {
|
||||
rc = Result_Cancelled; // cancelled.
|
||||
break;
|
||||
}
|
||||
|
||||
rc = usbDsGetState(&state);
|
||||
} while (R_SUCCEEDED(rc) && state != UsbState_Configured && timeout > 0);
|
||||
|
||||
if (R_SUCCEEDED(rc) && state != UsbState_Configured && timeout == 0)
|
||||
return KERNELRESULT(TimedOut);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result Usb::WaitForConnection(u64 timeout, std::vector<std::string>& out_names) {
|
||||
TUSHeader header;
|
||||
R_TRY(TransferAll(true, &header, sizeof(header), timeout));
|
||||
R_UNLESS(header.magic == 0x304C5554, Result_BadMagic);
|
||||
tinfoil::TUSHeader header;
|
||||
R_TRY(m_usb->TransferAll(true, &header, sizeof(header), timeout));
|
||||
R_UNLESS(header.magic == tinfoil::Magic_List0, Result_BadMagic);
|
||||
R_UNLESS(header.nspListSize > 0, Result_BadCount);
|
||||
log_write("USB got header\n");
|
||||
|
||||
std::vector<char> names(header.nspListSize);
|
||||
R_TRY(TransferAll(true, names.data(), names.size(), timeout));
|
||||
R_TRY(m_usb->TransferAll(true, names.data(), names.size(), timeout));
|
||||
|
||||
out_names.clear();
|
||||
for (const auto& name : std::views::split(names, '\n')) {
|
||||
@@ -299,133 +67,42 @@ void Usb::SetFileNameForTranfser(const std::string& name) {
|
||||
m_transfer_file_name = name;
|
||||
}
|
||||
|
||||
Event *Usb::GetCompletionEvent(UsbSessionEndpoint ep) const {
|
||||
return std::addressof(m_endpoints[ep]->CompletionEvent);
|
||||
}
|
||||
|
||||
Result Usb::WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) {
|
||||
auto event = GetCompletionEvent(ep);
|
||||
|
||||
const std::array waiters{
|
||||
waiterForEvent(event),
|
||||
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 != 0) {
|
||||
log_write("got usb cancel event\n");
|
||||
rc = Result_Cancelled; // cancelled.
|
||||
}
|
||||
|
||||
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) {
|
||||
u32 urb_id;
|
||||
|
||||
/* If we're not configured yet, wait to become configured first. */
|
||||
R_TRY(IsUsbConnected(timeout));
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
// while it may seem like a bad idea to transfer data to a buffer and copy it
|
||||
// in practice, this has no impact on performance.
|
||||
// the switch is *massively* bottlenecked by slow io (nand and sd).
|
||||
// so making usb transfers zero-copy provides no benefit other than increased
|
||||
// code complexity and the increase of future bugs if/when sphaira is forked
|
||||
// an changes are made.
|
||||
// yati already goes to great lengths to be zero-copy during installing
|
||||
// by swapping buffers and inflating in-place.
|
||||
Result Usb::TransferAll(bool read, void *data, u32 size, u64 timeout) {
|
||||
auto buf = static_cast<u8*>(data);
|
||||
m_aligned.resize((size + 0xFFF) & ~0xFFF);
|
||||
|
||||
while (size) {
|
||||
if (!read) {
|
||||
std::memcpy(m_aligned.data(), buf, size);
|
||||
}
|
||||
|
||||
u32 out_size_transferred;
|
||||
R_TRY(TransferPacketImpl(read, m_aligned.data(), size, &out_size_transferred, timeout));
|
||||
|
||||
if (read) {
|
||||
std::memcpy(buf, m_aligned.data(), out_size_transferred);
|
||||
}
|
||||
|
||||
buf += out_size_transferred;
|
||||
size -= out_size_transferred;
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result Usb::SendCmdHeader(u32 cmdId, size_t dataSize) {
|
||||
USBCmdHeader header{
|
||||
.magic = 0x30435554, // TUC0 (Tinfoil USB Command 0)
|
||||
.type = USBCmdType::REQUEST,
|
||||
Result Usb::SendCmdHeader(u32 cmdId, size_t dataSize, u64 timeout) {
|
||||
tinfoil::USBCmdHeader header{
|
||||
.magic = tinfoil::Magic_Command0,
|
||||
.type = tinfoil::USBCmdType::REQUEST,
|
||||
.cmdId = cmdId,
|
||||
.dataSize = dataSize,
|
||||
};
|
||||
|
||||
return TransferAll(false, &header, sizeof(header), m_transfer_timeout);
|
||||
return m_usb->TransferAll(false, &header, sizeof(header), timeout);
|
||||
}
|
||||
|
||||
Result Usb::SendFileRangeCmd(u64 off, u64 size) {
|
||||
FileRangeCmdHeader fRangeHeader;
|
||||
Result Usb::SendFileRangeCmd(u64 off, u64 size, u64 timeout) {
|
||||
tinfoil::FileRangeCmdHeader fRangeHeader;
|
||||
fRangeHeader.size = size;
|
||||
fRangeHeader.offset = off;
|
||||
fRangeHeader.nspNameLen = m_transfer_file_name.size();
|
||||
fRangeHeader.padding = 0;
|
||||
|
||||
R_TRY(SendCmdHeader(USBCmdId::FILE_RANGE, sizeof(fRangeHeader) + fRangeHeader.nspNameLen));
|
||||
R_TRY(TransferAll(false, &fRangeHeader, sizeof(fRangeHeader), m_transfer_timeout));
|
||||
R_TRY(TransferAll(false, m_transfer_file_name.data(), m_transfer_file_name.size(), m_transfer_timeout));
|
||||
R_TRY(SendCmdHeader(tinfoil::USBCmdId::FILE_RANGE, sizeof(fRangeHeader) + fRangeHeader.nspNameLen, timeout));
|
||||
R_TRY(m_usb->TransferAll(false, &fRangeHeader, sizeof(fRangeHeader), timeout));
|
||||
R_TRY(m_usb->TransferAll(false, m_transfer_file_name.data(), m_transfer_file_name.size(), timeout));
|
||||
|
||||
USBCmdHeader responseHeader;
|
||||
R_TRY(TransferAll(true, &responseHeader, sizeof(responseHeader), m_transfer_timeout));
|
||||
tinfoil::USBCmdHeader responseHeader;
|
||||
R_TRY(m_usb->TransferAll(true, &responseHeader, sizeof(responseHeader), timeout));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result Usb::Finished() {
|
||||
return SendCmdHeader(USBCmdId::EXIT, 0);
|
||||
Result Usb::Finished(u64 timeout) {
|
||||
return SendCmdHeader(tinfoil::USBCmdId::EXIT, 0, timeout);
|
||||
}
|
||||
|
||||
Result Usb::Read(void* buf, s64 off, s64 size, u64* bytes_read) {
|
||||
R_TRY(GetOpenResult());
|
||||
R_TRY(SendFileRangeCmd(off, size));
|
||||
R_TRY(TransferAll(true, buf, size, m_transfer_timeout));
|
||||
R_TRY(SendFileRangeCmd(off, size, m_usb->GetTransferTimeout()));
|
||||
R_TRY(m_usb->TransferAll(true, buf, size));
|
||||
*bytes_read = size;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
@@ -275,7 +275,6 @@ struct Yati {
|
||||
NcmContentMetaDatabase db{};
|
||||
NcmStorageId storage_id{};
|
||||
|
||||
Service es{};
|
||||
Service ns_app{};
|
||||
std::unique_ptr<container::Base> container{};
|
||||
Config config{};
|
||||
@@ -452,7 +451,7 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
|
||||
for (s64 off = 0; off < size;) {
|
||||
// log_write("looking for section\n");
|
||||
if (!ncz_section || !ncz_section->InRange(written)) {
|
||||
auto it = std::find_if(t->ncz_sections.cbegin(), t->ncz_sections.cend(), [written](auto& e){
|
||||
auto it = std::ranges::find_if(t->ncz_sections, [written](auto& e){
|
||||
return e.InRange(written);
|
||||
});
|
||||
|
||||
@@ -504,6 +503,8 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
|
||||
if (!is_ncz || !decompress_buf_off) {
|
||||
// check nca header
|
||||
if (!decompress_buf_off) {
|
||||
log_write("reading nca header\n");
|
||||
|
||||
nca::Header header{};
|
||||
crypto::cryptoAes128Xts(buf.data(), std::addressof(header), keys.header_key, 0, 0x200, sizeof(header), false);
|
||||
log_write("verifying nca header magic\n");
|
||||
@@ -522,6 +523,7 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
|
||||
}
|
||||
|
||||
t->write_size = header.size;
|
||||
log_write("setting placeholder size: %zu\n", header.size);
|
||||
R_TRY(ncmContentStorageSetPlaceHolderSize(std::addressof(cs), std::addressof(t->nca->placeholder_id), header.size));
|
||||
|
||||
if (!config.ignore_distribution_bit && header.distribution_type == nca::DistributionType_GameCard) {
|
||||
@@ -531,11 +533,13 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
|
||||
|
||||
TikCollection* ticket = nullptr;
|
||||
if (isRightsIdValid(header.rights_id)) {
|
||||
auto it = std::find_if(t->tik.begin(), t->tik.end(), [&header](auto& e){
|
||||
auto it = std::ranges::find_if(t->tik, [&header](auto& e){
|
||||
return !std::memcmp(&header.rights_id, &e.rights_id, sizeof(e.rights_id));
|
||||
});
|
||||
|
||||
log_write("looking for ticket %s\n", hexIdToStr(header.rights_id).str);
|
||||
R_UNLESS(it != t->tik.end(), Result_TicketNotFound);
|
||||
log_write("ticket found\n");
|
||||
it->required = true;
|
||||
ticket = &(*it);
|
||||
}
|
||||
@@ -607,7 +611,7 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
|
||||
// todo: blocks need to use read offset, as the offset + size is compressed range.
|
||||
if (t->ncz_blocks.size()) {
|
||||
if (!ncz_block || !ncz_block->InRange(decompress_buf_off)) {
|
||||
auto it = std::find_if(t->ncz_blocks.cbegin(), t->ncz_blocks.cend(), [decompress_buf_off](auto& e){
|
||||
auto it = std::ranges::find_if(t->ncz_blocks, [decompress_buf_off](auto& e){
|
||||
return e.InRange(decompress_buf_off);
|
||||
});
|
||||
|
||||
@@ -757,13 +761,13 @@ Yati::~Yati() {
|
||||
splCryptoExit();
|
||||
serviceClose(std::addressof(ns_app));
|
||||
nsExit();
|
||||
es::Exit();
|
||||
|
||||
for (size_t i = 0; i < std::size(NCM_STORAGE_IDS); i++) {
|
||||
ncmContentMetaDatabaseClose(std::addressof(ncm_db[i]));
|
||||
ncmContentStorageClose(std::addressof(ncm_cs[i]));
|
||||
}
|
||||
|
||||
serviceClose(std::addressof(es));
|
||||
appletSetMediaPlaybackState(false);
|
||||
|
||||
if (config.boost_mode) {
|
||||
@@ -799,7 +803,7 @@ Result Yati::Setup(const ConfigOverride& override) {
|
||||
R_TRY(splCryptoInitialize());
|
||||
R_TRY(nsInitialize());
|
||||
R_TRY(nsGetApplicationManagerInterface(std::addressof(ns_app)));
|
||||
R_TRY(smGetService(std::addressof(es), "es"));
|
||||
R_TRY(es::Initialize());
|
||||
|
||||
for (size_t i = 0; i < std::size(NCM_STORAGE_IDS); i++) {
|
||||
R_TRY(ncmOpenContentMetaDatabase(std::addressof(ncm_db[i]), NCM_STORAGE_IDS[i]));
|
||||
@@ -960,7 +964,7 @@ Result Yati::InstallCnmtNca(std::span<TikCollection> tickets, CnmtCollection& cn
|
||||
}
|
||||
|
||||
const auto str = hexIdToStr(info.content_id);
|
||||
const auto it = std::find_if(collections.cbegin(), collections.cend(), [&str](auto& e){
|
||||
const auto it = std::ranges::find_if(collections, [&str](auto& e){
|
||||
return e.name.find(str.str) != e.name.npos;
|
||||
});
|
||||
|
||||
@@ -1004,7 +1008,7 @@ Result Yati::InstallCnmtNca(std::span<TikCollection> tickets, CnmtCollection& cn
|
||||
return lhs.type > rhs.type;
|
||||
};
|
||||
|
||||
std::sort(cnmt.ncas.begin(), cnmt.ncas.end(), sorter);
|
||||
std::ranges::sort(cnmt.ncas, sorter);
|
||||
|
||||
log_write("found all cnmts\n");
|
||||
R_SUCCEED();
|
||||
@@ -1017,7 +1021,7 @@ Result Yati::ParseTicketsIntoCollection(std::vector<TikCollection>& tickets, con
|
||||
keys::parse_hex_key(entry.rights_id.c, collection.name.c_str());
|
||||
const auto str = collection.name.substr(0, collection.name.length() - 4) + ".cert";
|
||||
|
||||
const auto cert = std::find_if(collections.cbegin(), collections.cend(), [&str](auto& e){
|
||||
const auto cert = std::ranges::find_if(collections, [&str](auto& e){
|
||||
return e.name.find(str) != e.name.npos;
|
||||
});
|
||||
|
||||
@@ -1117,7 +1121,7 @@ Result Yati::ImportTickets(std::span<TikCollection> tickets) {
|
||||
log_write("patching ticket\n");
|
||||
R_TRY(es::PatchTicket(ticket.ticket, keys));
|
||||
log_write("installing ticket\n");
|
||||
R_TRY(es::ImportTicket(std::addressof(es), ticket.ticket.data(), ticket.ticket.size(), ticket.cert.data(), ticket.cert.size()));
|
||||
R_TRY(es::ImportTicket(ticket.ticket.data(), ticket.ticket.size(), ticket.cert.data(), ticket.cert.size()));
|
||||
ticket.required = false;
|
||||
}
|
||||
}
|
||||
@@ -1317,7 +1321,7 @@ Result InstallInternalStream(ui::ProgressBox* pbox, std::shared_ptr<source::Base
|
||||
return lhs.offset < rhs.offset;
|
||||
};
|
||||
|
||||
std::sort(collections.begin(), collections.end(), sorter);
|
||||
std::ranges::sort(collections, sorter);
|
||||
|
||||
for (const auto& collection : collections) {
|
||||
if (collection.name.ends_with(".nca") || collection.name.ends_with(".ncz")) {
|
||||
@@ -1334,7 +1338,7 @@ Result InstallInternalStream(ui::ProgressBox* pbox, std::shared_ptr<source::Base
|
||||
keys::parse_hex_key(rights_id.c, collection.name.c_str());
|
||||
const auto str = collection.name.substr(0, collection.name.length() - 4) + ".cert";
|
||||
|
||||
auto entry = std::find_if(tickets.begin(), tickets.end(), [&rights_id](auto& e){
|
||||
auto entry = std::ranges::find_if(tickets, [&rights_id](auto& e){
|
||||
return !std::memcmp(&rights_id, &e.rights_id, sizeof(rights_id));
|
||||
});
|
||||
|
||||
@@ -1353,7 +1357,7 @@ Result InstallInternalStream(ui::ProgressBox* pbox, std::shared_ptr<source::Base
|
||||
for (auto& cnmt : cnmts) {
|
||||
// copy nca structs into cnmt.
|
||||
for (auto& cnmt_nca : cnmt.ncas) {
|
||||
auto it = std::find_if(ncas.cbegin(), ncas.cend(), [&cnmt_nca](auto& e){
|
||||
auto it = std::ranges::find_if(ncas, [&cnmt_nca](auto& e){
|
||||
return e.name == cnmt_nca.name;
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user