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:
ITotalJustice
2025-05-18 13:46:10 +01:00
parent 544272925d
commit bd7eadc6a0
24 changed files with 2018 additions and 485 deletions

View File

@@ -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

View File

@@ -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 {

View File

@@ -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));

View File

@@ -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();
}

View File

@@ -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;
});