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:
@@ -79,6 +79,11 @@ add_executable(sphaira
|
|||||||
source/i18n.cpp
|
source/i18n.cpp
|
||||||
source/ftpsrv_helper.cpp
|
source/ftpsrv_helper.cpp
|
||||||
|
|
||||||
|
source/usb/base.cpp
|
||||||
|
source/usb/usbds.cpp
|
||||||
|
source/usb/usbhs.cpp
|
||||||
|
source/usb/usb_uploader.cpp
|
||||||
|
|
||||||
source/yati/yati.cpp
|
source/yati/yati.cpp
|
||||||
source/yati/container/nsp.cpp
|
source/yati/container/nsp.cpp
|
||||||
source/yati/container/xci.cpp
|
source/yati/container/xci.cpp
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ struct Entry {
|
|||||||
char display_version[0x10]{};
|
char display_version[0x10]{};
|
||||||
NacpLanguageEntry lang{};
|
NacpLanguageEntry lang{};
|
||||||
int image{};
|
int image{};
|
||||||
|
bool selected{};
|
||||||
|
|
||||||
std::shared_ptr<NsApplicationControlData> control{};
|
std::shared_ptr<NsApplicationControlData> control{};
|
||||||
u64 control_size{};
|
u64 control_size{};
|
||||||
@@ -103,11 +104,39 @@ private:
|
|||||||
void FreeEntries();
|
void FreeEntries();
|
||||||
void OnLayoutChange();
|
void OnLayoutChange();
|
||||||
|
|
||||||
|
auto GetSelectedEntries() const {
|
||||||
|
std::vector<Entry> out;
|
||||||
|
for (auto& e : m_entries) {
|
||||||
|
if (e.selected) {
|
||||||
|
out.emplace_back(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_entries.empty() && out.empty()) {
|
||||||
|
out.emplace_back(m_entries[m_index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClearSelection() {
|
||||||
|
for (auto& e : m_entries) {
|
||||||
|
e.selected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_selected_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeleteGames();
|
||||||
|
void DumpGames(u32 flags);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr inline const char* INI_SECTION = "games";
|
static constexpr inline const char* INI_SECTION = "games";
|
||||||
|
static constexpr inline const char* INI_SECTION_DUMP = "dump";
|
||||||
|
|
||||||
std::vector<Entry> m_entries{};
|
std::vector<Entry> m_entries{};
|
||||||
s64 m_index{}; // where i am in the array
|
s64 m_index{}; // where i am in the array
|
||||||
|
s64 m_selected_count{};
|
||||||
std::unique_ptr<List> m_list{};
|
std::unique_ptr<List> m_list{};
|
||||||
Event m_event{};
|
Event m_event{};
|
||||||
bool m_is_reversed{};
|
bool m_is_reversed{};
|
||||||
|
|||||||
101
sphaira/include/usb/base.hpp
Normal file
101
sphaira/include/usb/base.hpp
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <new>
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
namespace sphaira::usb {
|
||||||
|
|
||||||
|
struct Base {
|
||||||
|
enum { USBModule = 523 };
|
||||||
|
|
||||||
|
enum : Result {
|
||||||
|
Result_Cancelled = MAKERESULT(USBModule, 100),
|
||||||
|
};
|
||||||
|
|
||||||
|
Base(u64 transfer_timeout);
|
||||||
|
|
||||||
|
// sets up usb.
|
||||||
|
virtual Result Init() = 0;
|
||||||
|
|
||||||
|
// returns 0 if usb is connected to a device.
|
||||||
|
virtual Result IsUsbConnected(u64 timeout) = 0;
|
||||||
|
|
||||||
|
// transfers a chunk of data, check out_size_transferred for how much was transferred.
|
||||||
|
Result TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_transferred, u64 timeout);
|
||||||
|
Result TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_transferred) {
|
||||||
|
return TransferPacketImpl(read, page, size, out_size_transferred, m_transfer_timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// transfers all data.
|
||||||
|
Result TransferAll(bool read, void *data, u32 size, u64 timeout);
|
||||||
|
Result TransferAll(bool read, void *data, u32 size) {
|
||||||
|
return TransferAll(read, data, size, m_transfer_timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the cancel event.
|
||||||
|
auto GetCancelEvent() {
|
||||||
|
return &m_uevent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cancels an in progress transfer.
|
||||||
|
void Cancel() {
|
||||||
|
ueventSignal(GetCancelEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& GetTransferBuffer() {
|
||||||
|
return m_aligned;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto GetTransferTimeout() const {
|
||||||
|
return m_transfer_timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// custom allocator for std::vector that respects alignment.
|
||||||
|
// https://en.cppreference.com/w/cpp/named_req/Allocator
|
||||||
|
template <typename T, std::size_t Align>
|
||||||
|
struct CustomVectorAllocator {
|
||||||
|
public:
|
||||||
|
// https://en.cppreference.com/w/cpp/memory/new/operator_new
|
||||||
|
auto allocate(std::size_t n) -> T* {
|
||||||
|
n = (n + (Align - 1)) &~ (Align - 1);
|
||||||
|
return new(align) T[n];
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://en.cppreference.com/w/cpp/memory/new/operator_delete
|
||||||
|
auto deallocate(T* p, std::size_t n) noexcept -> void {
|
||||||
|
// ::operator delete[] (p, n, align);
|
||||||
|
::operator delete[] (p, align);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr inline std::align_val_t align{Align};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct PageAllocator : CustomVectorAllocator<T, 0x1000> {
|
||||||
|
using value_type = T; // used by std::vector
|
||||||
|
};
|
||||||
|
|
||||||
|
using PageAlignedVector = std::vector<u8, PageAllocator<u8>>;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
enum UsbSessionEndpoint {
|
||||||
|
UsbSessionEndpoint_In = 0,
|
||||||
|
UsbSessionEndpoint_Out = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual Event *GetCompletionEvent(UsbSessionEndpoint ep) = 0;
|
||||||
|
virtual Result WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) = 0;
|
||||||
|
virtual Result TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_xfer_id) = 0;
|
||||||
|
virtual Result GetTransferResult(UsbSessionEndpoint ep, u32 xfer_id, u32 *out_requested_size, u32 *out_transferred_size) = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
u64 m_transfer_timeout{};
|
||||||
|
UEvent m_uevent{};
|
||||||
|
PageAlignedVector m_aligned{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sphaira::usb
|
||||||
47
sphaira/include/usb/tinfoil.hpp
Normal file
47
sphaira/include/usb/tinfoil.hpp
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
namespace sphaira::usb::tinfoil {
|
||||||
|
|
||||||
|
enum Magic : u32 {
|
||||||
|
Magic_List0 = 0x304C5554, // TUL0 (Tinfoil Usb List 0)
|
||||||
|
Magic_Command0 = 0x30435554, // TUC0 (Tinfoil USB Command 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
enum USBCmdType : u8 {
|
||||||
|
REQUEST = 0,
|
||||||
|
RESPONSE = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
enum USBCmdId : u32 {
|
||||||
|
EXIT = 0,
|
||||||
|
FILE_RANGE = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TUSHeader {
|
||||||
|
u32 magic; // TUL0 (Tinfoil Usb List 0)
|
||||||
|
u32 nspListSize;
|
||||||
|
u64 padding;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NX_PACKED USBCmdHeader {
|
||||||
|
u32 magic; // TUC0 (Tinfoil USB Command 0)
|
||||||
|
USBCmdType type;
|
||||||
|
u8 padding[0x3];
|
||||||
|
u32 cmdId;
|
||||||
|
u64 dataSize;
|
||||||
|
u8 reserved[0xC];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FileRangeCmdHeader {
|
||||||
|
u64 size;
|
||||||
|
u64 offset;
|
||||||
|
u64 nspNameLen;
|
||||||
|
u64 padding;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(TUSHeader) == 0x10, "TUSHeader must be 0x10!");
|
||||||
|
static_assert(sizeof(USBCmdHeader) == 0x20, "USBCmdHeader must be 0x20!");
|
||||||
|
|
||||||
|
} // namespace sphaira::usb::tinfoil
|
||||||
47
sphaira/include/usb/usb_uploader.hpp
Normal file
47
sphaira/include/usb/usb_uploader.hpp
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "usb/usbhs.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <span>
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
namespace sphaira::usb::upload {
|
||||||
|
|
||||||
|
struct Usb {
|
||||||
|
enum { USBModule = 523 };
|
||||||
|
|
||||||
|
enum : Result {
|
||||||
|
Result_BadMagic = MAKERESULT(USBModule, 0),
|
||||||
|
Result_Exit = MAKERESULT(USBModule, 1),
|
||||||
|
Result_BadCount = MAKERESULT(USBModule, 2),
|
||||||
|
Result_BadTransferSize = MAKERESULT(USBModule, 3),
|
||||||
|
Result_BadTotalSize = MAKERESULT(USBModule, 4),
|
||||||
|
Result_BadCommand = MAKERESULT(USBModule, 4),
|
||||||
|
};
|
||||||
|
|
||||||
|
Usb(u64 transfer_timeout);
|
||||||
|
virtual ~Usb();
|
||||||
|
|
||||||
|
virtual Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) = 0;
|
||||||
|
|
||||||
|
Result IsUsbConnected(u64 timeout) {
|
||||||
|
return m_usb->IsUsbConnected(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// waits for connection and then sends file list.
|
||||||
|
Result WaitForConnection(u64 timeout, std::span<const std::string> names);
|
||||||
|
|
||||||
|
// polls for command, executes transfer if possible.
|
||||||
|
// will return Result_Exit if exit command is recieved.
|
||||||
|
Result PollCommands();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Result FileRangeCmd(u64 data_size);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<usb::UsbHs> m_usb;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sphaira::usb::upload
|
||||||
27
sphaira/include/usb/usbds.hpp
Normal file
27
sphaira/include/usb/usbds.hpp
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "base.hpp"
|
||||||
|
|
||||||
|
namespace sphaira::usb {
|
||||||
|
|
||||||
|
// Device Host
|
||||||
|
struct UsbDs final : Base {
|
||||||
|
using Base::Base;
|
||||||
|
~UsbDs();
|
||||||
|
|
||||||
|
Result Init() override;
|
||||||
|
Result IsUsbConnected(u64 timeout) override;
|
||||||
|
Result GetSpeed(UsbDeviceSpeed* out);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Event *GetCompletionEvent(UsbSessionEndpoint ep) override;
|
||||||
|
Result WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) override;
|
||||||
|
Result TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_urb_id) override;
|
||||||
|
Result GetTransferResult(UsbSessionEndpoint ep, u32 urb_id, u32 *out_requested_size, u32 *out_transferred_size) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
UsbDsInterface* m_interface{};
|
||||||
|
UsbDsEndpoint* m_endpoints[2]{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sphaira::usb
|
||||||
33
sphaira/include/usb/usbhs.hpp
Normal file
33
sphaira/include/usb/usbhs.hpp
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "base.hpp"
|
||||||
|
|
||||||
|
namespace sphaira::usb {
|
||||||
|
|
||||||
|
struct UsbHs final : Base {
|
||||||
|
UsbHs(u8 index, const UsbHsInterfaceFilter& filter, u64 transfer_timeout);
|
||||||
|
~UsbHs();
|
||||||
|
|
||||||
|
Result Init() override;
|
||||||
|
Result IsUsbConnected(u64 timeout) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Event *GetCompletionEvent(UsbSessionEndpoint ep) override;
|
||||||
|
Result WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) override;
|
||||||
|
Result TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_xfer_id) override;
|
||||||
|
Result GetTransferResult(UsbSessionEndpoint ep, u32 xfer_id, u32 *out_requested_size, u32 *out_transferred_size) override;
|
||||||
|
|
||||||
|
Result Connect();
|
||||||
|
void Close();
|
||||||
|
|
||||||
|
private:
|
||||||
|
u8 m_index{};
|
||||||
|
UsbHsInterfaceFilter m_filter{};
|
||||||
|
UsbHsInterface m_interface{};
|
||||||
|
UsbHsClientIfSession m_s{};
|
||||||
|
UsbHsClientEpSession m_endpoints[2]{};
|
||||||
|
Event m_event{};
|
||||||
|
bool m_connected{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sphaira::usb
|
||||||
@@ -2,12 +2,16 @@
|
|||||||
|
|
||||||
#include "base.hpp"
|
#include "base.hpp"
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
namespace sphaira::yati::container {
|
namespace sphaira::yati::container {
|
||||||
|
|
||||||
struct Nsp final : Base {
|
struct Nsp final : Base {
|
||||||
using Base::Base;
|
using Base::Base;
|
||||||
Result GetCollections(Collections& out) override;
|
Result GetCollections(Collections& out) override;
|
||||||
|
|
||||||
|
// builds nsp meta data and the size of the entier nsp.
|
||||||
|
static auto Build(std::span<CollectionEntry> collections, s64& size) -> std::vector<u8>;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sphaira::yati::container
|
} // namespace sphaira::yati::container
|
||||||
|
|||||||
@@ -69,7 +69,25 @@ struct EticketRsaDeviceKey {
|
|||||||
static_assert(sizeof(EticketRsaDeviceKey) == 0x240);
|
static_assert(sizeof(EticketRsaDeviceKey) == 0x240);
|
||||||
|
|
||||||
// es functions.
|
// es functions.
|
||||||
Result ImportTicket(Service* srv, const void* tik_buf, u64 tik_size, const void* cert_buf, u64 cert_size);
|
Result Initialize();
|
||||||
|
void Exit();
|
||||||
|
Service* GetServiceSession();
|
||||||
|
|
||||||
|
// todo: find the ipc that gets personalised tickets.
|
||||||
|
// todo: if ipc doesn't exist, manually parse es personalised save.
|
||||||
|
// todo: add personalised -> common ticket conversion.
|
||||||
|
// todo: make the above an option for both dump and install.
|
||||||
|
|
||||||
|
Result ImportTicket(const void* tik_buf, u64 tik_size, const void* cert_buf, u64 cert_size);
|
||||||
|
Result CountCommonTicket(s32* count);
|
||||||
|
Result CountPersonalizedTicket(s32* count);
|
||||||
|
Result ListCommonTicket(s32 *out_entries_written, FsRightsId* out_ids, s32 count);
|
||||||
|
Result ListPersonalizedTicket(s32 *out_entries_written, FsRightsId* out_ids, s32 count);
|
||||||
|
Result ListMissingPersonalizedTicket(s32 *out_entries_written, FsRightsId* out_ids, s32 count); // untested
|
||||||
|
Result GetCommonTicketSize(u64 *size_out, const FsRightsId* rightsId);
|
||||||
|
Result GetCommonTicketData(u64 *size_out, void *tik_data, u64 tik_size, const FsRightsId* rightsId);
|
||||||
|
Result GetCommonTicketAndCertificateSize(u64 *tik_size_out, u64 *cert_size_out, const FsRightsId* rightsId); // [4.0.0+]
|
||||||
|
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); // [4.0.0+]
|
||||||
|
|
||||||
// ticket functions.
|
// ticket functions.
|
||||||
Result GetTicketDataOffset(std::span<const u8> ticket, u64& out);
|
Result GetTicketDataOffset(std::span<const u8> ticket, u64& out);
|
||||||
|
|||||||
@@ -33,11 +33,14 @@ union ExtendedHeader {
|
|||||||
|
|
||||||
auto GetMetaTypeStr(u8 meta_type) -> const char*;
|
auto GetMetaTypeStr(u8 meta_type) -> const char*;
|
||||||
auto GetStorageIdStr(u8 storage_id) -> const char*;
|
auto GetStorageIdStr(u8 storage_id) -> const char*;
|
||||||
|
auto GetMetaTypeShortStr(u8 meta_type) -> const char*;
|
||||||
|
|
||||||
auto GetAppId(u8 meta_type, u64 id) -> u64;
|
auto GetAppId(u8 meta_type, u64 id) -> u64;
|
||||||
auto GetAppId(const NcmContentMetaKey& key) -> u64;
|
auto GetAppId(const NcmContentMetaKey& key) -> u64;
|
||||||
auto GetAppId(const PackagedContentMeta& meta) -> u64;
|
auto GetAppId(const PackagedContentMeta& meta) -> u64;
|
||||||
|
|
||||||
|
auto GetContentIdFromStr(const char* str) -> NcmContentId;
|
||||||
|
|
||||||
Result Delete(NcmContentStorage* cs, const NcmContentId *content_id);
|
Result Delete(NcmContentStorage* cs, const NcmContentId *content_id);
|
||||||
Result Register(NcmContentStorage* cs, const NcmContentId *content_id, const NcmPlaceHolderId *placeholder_id);
|
Result Register(NcmContentStorage* cs, const NcmContentId *content_id, const NcmPlaceHolderId *placeholder_id);
|
||||||
|
|
||||||
|
|||||||
56
sphaira/include/yati/nx/service_guard.h
Normal file
56
sphaira/include/yati/nx/service_guard.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <switch/types.h>
|
||||||
|
#include <switch/result.h>
|
||||||
|
#include <switch/kernel/mutex.h>
|
||||||
|
#include <switch/sf/service.h>
|
||||||
|
#include <switch/services/sm.h>
|
||||||
|
|
||||||
|
typedef struct ServiceGuard {
|
||||||
|
Mutex mutex;
|
||||||
|
u32 refCount;
|
||||||
|
} ServiceGuard;
|
||||||
|
|
||||||
|
NX_INLINE bool serviceGuardBeginInit(ServiceGuard* g)
|
||||||
|
{
|
||||||
|
mutexLock(&g->mutex);
|
||||||
|
return (g->refCount++) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
NX_INLINE Result serviceGuardEndInit(ServiceGuard* g, Result rc, void (*cleanupFunc)(void))
|
||||||
|
{
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
cleanupFunc();
|
||||||
|
--g->refCount;
|
||||||
|
}
|
||||||
|
mutexUnlock(&g->mutex);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
NX_INLINE void serviceGuardExit(ServiceGuard* g, void (*cleanupFunc)(void))
|
||||||
|
{
|
||||||
|
mutexLock(&g->mutex);
|
||||||
|
if (g->refCount && (--g->refCount) == 0)
|
||||||
|
cleanupFunc();
|
||||||
|
mutexUnlock(&g->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define NX_GENERATE_SERVICE_GUARD_PARAMS(name, _paramdecl, _parampass) \
|
||||||
|
\
|
||||||
|
static ServiceGuard g_##name##Guard; \
|
||||||
|
NX_INLINE Result _##name##Initialize _paramdecl; \
|
||||||
|
static void _##name##Cleanup(void); \
|
||||||
|
\
|
||||||
|
Result name##Initialize _paramdecl \
|
||||||
|
{ \
|
||||||
|
Result rc = 0; \
|
||||||
|
if (serviceGuardBeginInit(&g_##name##Guard)) \
|
||||||
|
rc = _##name##Initialize _parampass; \
|
||||||
|
return serviceGuardEndInit(&g_##name##Guard, rc, _##name##Cleanup); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
void name##Exit(void) \
|
||||||
|
{ \
|
||||||
|
serviceGuardExit(&g_##name##Guard, _##name##Cleanup); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define NX_GENERATE_SERVICE_GUARD(name) NX_GENERATE_SERVICE_GUARD_PARAMS(name, (void), ())
|
||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
#include "base.hpp"
|
#include "base.hpp"
|
||||||
#include "fs.hpp"
|
#include "fs.hpp"
|
||||||
|
#include "usb/usbds.hpp"
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <new>
|
#include <memory>
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
|
|
||||||
namespace sphaira::yati::source {
|
namespace sphaira::yati::source {
|
||||||
@@ -19,80 +19,31 @@ struct Usb final : Base {
|
|||||||
Result_BadCount = MAKERESULT(USBModule, 2),
|
Result_BadCount = MAKERESULT(USBModule, 2),
|
||||||
Result_BadTransferSize = MAKERESULT(USBModule, 3),
|
Result_BadTransferSize = MAKERESULT(USBModule, 3),
|
||||||
Result_BadTotalSize = MAKERESULT(USBModule, 4),
|
Result_BadTotalSize = MAKERESULT(USBModule, 4),
|
||||||
Result_Cancelled = MAKERESULT(USBModule, 11),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Usb(u64 transfer_timeout);
|
Usb(u64 transfer_timeout);
|
||||||
~Usb();
|
~Usb();
|
||||||
|
|
||||||
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override;
|
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override;
|
||||||
Result Finished();
|
Result Finished(u64 timeout);
|
||||||
|
|
||||||
|
Result IsUsbConnected(u64 timeout) {
|
||||||
|
return m_usb->IsUsbConnected(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
Result Init();
|
|
||||||
Result IsUsbConnected(u64 timeout);
|
|
||||||
Result WaitForConnection(u64 timeout, std::vector<std::string>& out_names);
|
Result WaitForConnection(u64 timeout, std::vector<std::string>& out_names);
|
||||||
void SetFileNameForTranfser(const std::string& name);
|
void SetFileNameForTranfser(const std::string& name);
|
||||||
|
|
||||||
auto GetCancelEvent() {
|
|
||||||
return &m_uevent;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SignalCancel() override {
|
void SignalCancel() override {
|
||||||
ueventSignal(GetCancelEvent());
|
m_usb->Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
private:
|
||||||
// custom allocator for std::vector that respects alignment.
|
Result SendCmdHeader(u32 cmdId, size_t dataSize, u64 timeout);
|
||||||
// https://en.cppreference.com/w/cpp/named_req/Allocator
|
Result SendFileRangeCmd(u64 offset, u64 size, u64 timeout);
|
||||||
template <typename T, std::size_t Align>
|
|
||||||
struct CustomVectorAllocator {
|
|
||||||
public:
|
|
||||||
// https://en.cppreference.com/w/cpp/memory/new/operator_new
|
|
||||||
auto allocate(std::size_t n) -> T* {
|
|
||||||
return new(align) T[n];
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://en.cppreference.com/w/cpp/memory/new/operator_delete
|
|
||||||
auto deallocate(T* p, std::size_t n) noexcept -> void {
|
|
||||||
::operator delete[] (p, n, align);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static constexpr inline std::align_val_t align{Align};
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct PageAllocator : CustomVectorAllocator<T, 0x1000> {
|
|
||||||
using value_type = T; // used by std::vector
|
|
||||||
};
|
|
||||||
|
|
||||||
using PageAlignedVector = std::vector<u8, PageAllocator<u8>>;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum UsbSessionEndpoint {
|
std::unique_ptr<usb::UsbDs> m_usb;
|
||||||
UsbSessionEndpoint_In = 0,
|
|
||||||
UsbSessionEndpoint_Out = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
Result SendCmdHeader(u32 cmdId, size_t dataSize);
|
|
||||||
Result SendFileRangeCmd(u64 offset, u64 size);
|
|
||||||
|
|
||||||
Event *GetCompletionEvent(UsbSessionEndpoint ep) const;
|
|
||||||
Result WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout);
|
|
||||||
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);
|
|
||||||
Result TransferAll(bool read, void *data, u32 size, u64 timeout);
|
|
||||||
|
|
||||||
private:
|
|
||||||
UsbDsInterface* m_interface{};
|
|
||||||
UsbDsEndpoint* m_endpoints[2]{};
|
|
||||||
u64 m_transfer_timeout{};
|
|
||||||
UEvent m_uevent{};
|
|
||||||
// std::vector<UEvent*> m_cancel_events{};
|
|
||||||
// aligned buffer that transfer data is copied to and from.
|
|
||||||
// a vector is used to avoid multiple alloc within the transfer loop.
|
|
||||||
PageAlignedVector m_aligned{};
|
|
||||||
std::string m_transfer_file_name{};
|
std::string m_transfer_file_name{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,14 @@
|
|||||||
#include "ui/nvg_util.hpp"
|
#include "ui/nvg_util.hpp"
|
||||||
#include "defines.hpp"
|
#include "defines.hpp"
|
||||||
#include "i18n.hpp"
|
#include "i18n.hpp"
|
||||||
|
|
||||||
#include "yati/nx/ncm.hpp"
|
#include "yati/nx/ncm.hpp"
|
||||||
#include "yati/nx/nca.hpp"
|
#include "yati/nx/nca.hpp"
|
||||||
|
#include "yati/nx/es.hpp"
|
||||||
|
#include "yati/container/base.hpp"
|
||||||
|
#include "yati/container/nsp.hpp"
|
||||||
|
|
||||||
|
#include "usb/usb_uploader.hpp"
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -20,30 +26,340 @@
|
|||||||
namespace sphaira::ui::menu::game {
|
namespace sphaira::ui::menu::game {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr NcmStorageId NCM_STORAGE_IDS[]{
|
constexpr u32 ContentMetaTypeToContentFlag(u8 meta_type) {
|
||||||
NcmStorageId_BuiltInUser,
|
if (meta_type & 0x80) {
|
||||||
NcmStorageId_SdCard,
|
return 1 << (meta_type - 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ContentFlag {
|
||||||
|
ContentFlag_Application = ContentMetaTypeToContentFlag(NcmContentMetaType_Application),
|
||||||
|
ContentFlag_Patch = ContentMetaTypeToContentFlag(NcmContentMetaType_Patch),
|
||||||
|
ContentFlag_AddOnContent = ContentMetaTypeToContentFlag(NcmContentMetaType_AddOnContent),
|
||||||
|
ContentFlag_DataPatch = ContentMetaTypeToContentFlag(NcmContentMetaType_DataPatch),
|
||||||
|
ContentFlag_All = ContentFlag_Application | ContentFlag_Patch | ContentFlag_AddOnContent | ContentFlag_DataPatch,
|
||||||
};
|
};
|
||||||
|
|
||||||
NcmContentStorage ncm_cs[2];
|
enum DumpLocationType {
|
||||||
NcmContentMetaDatabase ncm_db[2];
|
DumpLocationType_SdCard,
|
||||||
|
DumpLocationType_UsbS2S,
|
||||||
|
DumpLocationType_DevNull,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DumpLocation {
|
||||||
|
const DumpLocationType type;
|
||||||
|
const char* display_name;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr DumpLocation DUMP_LOCATIONS[]{
|
||||||
|
{ DumpLocationType_SdCard, "microSD card (/dumps/NSP/)" },
|
||||||
|
{ DumpLocationType_UsbS2S, "USB transfer (Switch 2 Switch)" },
|
||||||
|
{ DumpLocationType_DevNull, "/dev/null (Speed Test)" },
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NcmEntry {
|
||||||
|
const NcmStorageId storage_id;
|
||||||
|
NcmContentStorage cs{};
|
||||||
|
NcmContentMetaDatabase db{};
|
||||||
|
|
||||||
|
void Open() {
|
||||||
|
if (R_FAILED(ncmOpenContentMetaDatabase(std::addressof(db), storage_id))) {
|
||||||
|
log_write("\tncmOpenContentMetaDatabase() failed. storage_id: %u\n", storage_id);
|
||||||
|
} else {
|
||||||
|
log_write("\tncmOpenContentMetaDatabase() success. storage_id: %u\n", storage_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (R_FAILED(ncmOpenContentStorage(std::addressof(cs), storage_id))) {
|
||||||
|
log_write("\tncmOpenContentStorage() failed. storage_id: %u\n", storage_id);
|
||||||
|
} else {
|
||||||
|
log_write("\tncmOpenContentStorage() success. storage_id: %u\n", storage_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Close() {
|
||||||
|
ncmContentMetaDatabaseClose(std::addressof(db));
|
||||||
|
ncmContentStorageClose(std::addressof(cs));
|
||||||
|
|
||||||
|
db = {};
|
||||||
|
cs = {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
constinit NcmEntry ncm_entries[] = {
|
||||||
|
// on memory, will become invalid on the gamecard being inserted / removed.
|
||||||
|
{ NcmStorageId_GameCard },
|
||||||
|
// normal (save), will remain valid.
|
||||||
|
{ NcmStorageId_BuiltInUser },
|
||||||
|
{ NcmStorageId_SdCard },
|
||||||
|
};
|
||||||
|
|
||||||
|
auto& GetNcmEntry(u8 storage_id) {
|
||||||
|
auto it = std::ranges::find_if(ncm_entries, [storage_id](auto& e){
|
||||||
|
return storage_id == e.storage_id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (it == std::end(ncm_entries)) {
|
||||||
|
log_write("unable to find valid ncm entry: %u\n", storage_id);
|
||||||
|
return ncm_entries[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return *it;
|
||||||
|
}
|
||||||
|
|
||||||
auto& GetNcmCs(u8 storage_id) {
|
auto& GetNcmCs(u8 storage_id) {
|
||||||
if (storage_id == NcmStorageId_SdCard) {
|
return GetNcmEntry(storage_id).cs;
|
||||||
return ncm_cs[1];
|
|
||||||
}
|
|
||||||
return ncm_cs[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& GetNcmDb(u8 storage_id) {
|
auto& GetNcmDb(u8 storage_id) {
|
||||||
if (storage_id == NcmStorageId_SdCard) {
|
return GetNcmEntry(storage_id).db;
|
||||||
return ncm_db[1];
|
|
||||||
}
|
|
||||||
return ncm_db[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
using MetaEntries = std::vector<NsApplicationContentMetaStatus>;
|
using MetaEntries = std::vector<NsApplicationContentMetaStatus>;
|
||||||
|
|
||||||
|
struct ContentInfoEntry {
|
||||||
|
NsApplicationContentMetaStatus status{};
|
||||||
|
std::vector<NcmContentInfo> content_infos{};
|
||||||
|
std::vector<FsRightsId> rights_ids{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TikEntry {
|
||||||
|
FsRightsId id{};
|
||||||
|
std::vector<u8> tik_data{};
|
||||||
|
std::vector<u8> cert_data{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NspEntry {
|
||||||
|
// application name.
|
||||||
|
std::string application_name{};
|
||||||
|
// name of the nsp (name [id][v0][BASE].nsp).
|
||||||
|
fs::FsPath path{};
|
||||||
|
// tickets and cert data, will be empty if title key crypto isn't used.
|
||||||
|
std::vector<TikEntry> tickets{};
|
||||||
|
// all the collections for this nsp, such as nca's and tickets.
|
||||||
|
std::vector<yati::container::CollectionEntry> collections{};
|
||||||
|
// raw nsp data (header, file table and string table).
|
||||||
|
std::vector<u8> nsp_data{};
|
||||||
|
// size of the entier nsp.
|
||||||
|
s64 nsp_size{};
|
||||||
|
// copy of ncm cs, it is not closed.
|
||||||
|
NcmContentStorage cs{};
|
||||||
|
|
||||||
|
// todo: benchmark manual sdcard read and decryption vs ncm.
|
||||||
|
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) {
|
||||||
|
if (off < nsp_data.size()) {
|
||||||
|
*bytes_read = size = ClipSize(off, size, nsp_data.size());
|
||||||
|
std::memcpy(buf, nsp_data.data() + off, size);
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjust offset.
|
||||||
|
off -= nsp_data.size();
|
||||||
|
|
||||||
|
for (auto& collection : collections) {
|
||||||
|
if (InRange(off, collection.offset, collection.size)) {
|
||||||
|
// adjust offset relative to the collection.
|
||||||
|
off -= collection.offset;
|
||||||
|
*bytes_read = size = ClipSize(off, size, collection.size);
|
||||||
|
|
||||||
|
if (collection.name.ends_with(".nca")) {
|
||||||
|
const auto id = ncm::GetContentIdFromStr(collection.name.c_str());
|
||||||
|
return ncmContentStorageReadContentIdFile(&cs, buf, size, &id, off);
|
||||||
|
} else if (collection.name.ends_with(".tik") || collection.name.ends_with(".cert")) {
|
||||||
|
FsRightsId id;
|
||||||
|
keys::parse_hex_key(&id, collection.name.c_str());
|
||||||
|
|
||||||
|
const auto it = std::ranges::find_if(tickets, [&id](auto& e){
|
||||||
|
return !std::memcmp(&id, &e.id, sizeof(id));
|
||||||
|
});
|
||||||
|
R_UNLESS(it != tickets.end(), 0x1);
|
||||||
|
|
||||||
|
const auto& data = collection.name.ends_with(".tik") ? it->tik_data : it->cert_data;
|
||||||
|
std::memcpy(buf, data.data() + off, size);
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log_write("did not find collection...\n");
|
||||||
|
return 0x1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static auto InRange(s64 off, s64 offset, s64 size) -> bool {
|
||||||
|
return off < offset + size && off >= offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
static auto ClipSize(s64 off, s64 size, s64 file_size) -> s64 {
|
||||||
|
return std::min(size, file_size - off);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BaseSource {
|
||||||
|
virtual ~BaseSource() = default;
|
||||||
|
virtual Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NspSource final : BaseSource {
|
||||||
|
NspSource(std::span<NspEntry> entries) : m_entries{entries} { }
|
||||||
|
|
||||||
|
Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) override {
|
||||||
|
const auto it = std::ranges::find_if(m_entries, [&path](auto& e){
|
||||||
|
return path == e.path;
|
||||||
|
});
|
||||||
|
R_UNLESS(it != m_entries.end(), 0x1);
|
||||||
|
|
||||||
|
return it->Read(buf, off, size, bytes_read);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto GetEntries() const -> std::span<const NspEntry> {
|
||||||
|
return m_entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::span<NspEntry> m_entries{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UsbTest final : usb::upload::Usb {
|
||||||
|
UsbTest(ProgressBox* pbox, std::span<NspEntry> entries) : Usb{UINT64_MAX} {
|
||||||
|
m_source = std::make_unique<NspSource>(entries);
|
||||||
|
m_pbox = pbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) override {
|
||||||
|
if (m_path != path) {
|
||||||
|
m_path = path;
|
||||||
|
m_pbox->NewTransfer(m_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
R_TRY(m_source->Read(path, buf, off, size, bytes_read));
|
||||||
|
m_offset += *bytes_read;
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<NspSource> m_source{};
|
||||||
|
ProgressBox* m_pbox{};
|
||||||
|
std::string m_path{};
|
||||||
|
s64 m_offset{};
|
||||||
|
};
|
||||||
|
|
||||||
|
Result DumpNspToFile(ProgressBox* pbox, std::span<NspEntry> entries) {
|
||||||
|
static constexpr fs::FsPath DUMP_PATH{"/dumps/NSP"};
|
||||||
|
constexpr s64 BIG_FILE_SIZE = 1024ULL*1024ULL*1024ULL*4ULL;
|
||||||
|
|
||||||
|
fs::FsNativeSd fs{};
|
||||||
|
R_TRY(fs.GetFsOpenResult());
|
||||||
|
fs.CreateDirectoryRecursively(DUMP_PATH);
|
||||||
|
|
||||||
|
auto source = std::make_unique<NspSource>(entries);
|
||||||
|
for (const auto& e : entries) {
|
||||||
|
pbox->SetTitle(e.application_name);
|
||||||
|
pbox->NewTransfer(e.path);
|
||||||
|
|
||||||
|
const auto temp_path = fs::AppendPath(DUMP_PATH, e.path + ".temp");
|
||||||
|
fs.DeleteFile(temp_path);
|
||||||
|
|
||||||
|
const auto flags = e.nsp_size >= BIG_FILE_SIZE ? FsCreateOption_BigFile : 0;
|
||||||
|
R_TRY(fs.CreateFile(temp_path, e.nsp_size, flags));
|
||||||
|
ON_SCOPE_EXIT(fs.DeleteFile(temp_path));
|
||||||
|
|
||||||
|
{
|
||||||
|
FsFile file;
|
||||||
|
R_TRY(fs.OpenFile(temp_path, FsOpenMode_Write, &file));
|
||||||
|
ON_SCOPE_EXIT(fsFileClose(&file));
|
||||||
|
|
||||||
|
s64 offset{};
|
||||||
|
std::vector<u8> buf(1024*1024*4); // 4MiB
|
||||||
|
|
||||||
|
while (offset < e.nsp_size) {
|
||||||
|
if (pbox->ShouldExit()) {
|
||||||
|
R_THROW(0xFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 bytes_read;
|
||||||
|
R_TRY(source->Read(e.path, buf.data(), offset, buf.size(), &bytes_read));
|
||||||
|
pbox->Yield();
|
||||||
|
|
||||||
|
R_TRY(fsFileWrite(&file, offset, buf.data(), bytes_read, FsWriteOption_None));
|
||||||
|
pbox->Yield();
|
||||||
|
|
||||||
|
pbox->UpdateTransfer(offset, e.nsp_size);
|
||||||
|
offset += bytes_read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto path = fs::AppendPath(DUMP_PATH, e.path);
|
||||||
|
fs.DeleteFile(path);
|
||||||
|
R_TRY(fs.RenameFile(temp_path, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DumpNspToUsbS2S(ProgressBox* pbox, std::span<NspEntry> entries) {
|
||||||
|
std::vector<std::string> file_list;
|
||||||
|
for (auto& e : entries) {
|
||||||
|
file_list.emplace_back(e.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto usb = std::make_unique<UsbTest>(pbox, entries);
|
||||||
|
constexpr u64 timeout = 1e+9;
|
||||||
|
|
||||||
|
// todo: display progress bar during usb transfer.
|
||||||
|
while (!pbox->ShouldExit()) {
|
||||||
|
if (R_SUCCEEDED(usb->IsUsbConnected(timeout))) {
|
||||||
|
pbox->NewTransfer("USB connected, sending file list");
|
||||||
|
if (R_SUCCEEDED(usb->WaitForConnection(timeout, file_list))) {
|
||||||
|
pbox->NewTransfer("Sent file list, waiting for command...");
|
||||||
|
|
||||||
|
while (!pbox->ShouldExit()) {
|
||||||
|
const auto rc = usb->PollCommands();
|
||||||
|
if (rc == usb->Result_Exit) {
|
||||||
|
log_write("got exit command\n");
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
R_TRY(rc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
pbox->NewTransfer("waiting for usb connection...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DumpNspToDevNull(ProgressBox* pbox, std::span<NspEntry> entries) {
|
||||||
|
auto source = std::make_unique<NspSource>(entries);
|
||||||
|
for (const auto& e : entries) {
|
||||||
|
pbox->SetTitle(e.application_name);
|
||||||
|
pbox->NewTransfer(e.path);
|
||||||
|
|
||||||
|
s64 offset{};
|
||||||
|
std::vector<u8> buf(1024*1024*4); // 4MiB
|
||||||
|
|
||||||
|
while (offset < e.nsp_size) {
|
||||||
|
if (pbox->ShouldExit()) {
|
||||||
|
R_THROW(0xFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 bytes_read;
|
||||||
|
R_TRY(source->Read(e.path, buf.data(), offset, buf.size(), &bytes_read));
|
||||||
|
pbox->Yield();
|
||||||
|
|
||||||
|
pbox->UpdateTransfer(offset, e.nsp_size);
|
||||||
|
offset += bytes_read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
Result Notify(Result rc, const std::string& error_message) {
|
Result Notify(Result rc, const std::string& error_message) {
|
||||||
if (R_FAILED(rc)) {
|
if (R_FAILED(rc)) {
|
||||||
App::Push(std::make_shared<ui::ErrorBox>(rc,
|
App::Push(std::make_shared<ui::ErrorBox>(rc,
|
||||||
@@ -56,19 +372,26 @@ Result Notify(Result rc, const std::string& error_message) {
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result GetMetaEntries(u64 id, MetaEntries& out) {
|
Result GetMetaEntries(u64 id, MetaEntries& out, u32 flags = ContentFlag_All) {
|
||||||
s32 count;
|
for (s32 i = 0; ; i++) {
|
||||||
R_TRY(nsCountApplicationContentMeta(id, &count));
|
s32 count;
|
||||||
|
NsApplicationContentMetaStatus status;
|
||||||
|
R_TRY(nsListApplicationContentMetaStatus(id, i, &status, 1, &count));
|
||||||
|
|
||||||
out.resize(count);
|
if (!count) {
|
||||||
R_TRY(nsListApplicationContentMetaStatus(id, 0, out.data(), out.size(), &count));
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & ContentMetaTypeToContentFlag(status.meta_type)) {
|
||||||
|
out.emplace_back(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
out.resize(count);
|
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result GetMetaEntries(const Entry& e, MetaEntries& out) {
|
Result GetMetaEntries(const Entry& e, MetaEntries& out, u32 flags = ContentFlag_All) {
|
||||||
return GetMetaEntries(e.app_id, out);
|
return GetMetaEntries(e.app_id, out, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
// also sets the status to error.
|
// also sets the status to error.
|
||||||
@@ -102,7 +425,7 @@ Result LoadControlManual(u64 id, ThreadResultData& data) {
|
|||||||
R_UNLESS(!entries.empty(), 0x1);
|
R_UNLESS(!entries.empty(), 0x1);
|
||||||
|
|
||||||
const auto& ee = entries.back();
|
const auto& ee = entries.back();
|
||||||
if (ee.storageID != NcmStorageId_SdCard && ee.storageID != NcmStorageId_BuiltInUser) {
|
if (ee.storageID != NcmStorageId_SdCard && ee.storageID != NcmStorageId_BuiltInUser && ee.storageID != NcmStorageId_GameCard) {
|
||||||
return 0x1;
|
return 0x1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,6 +516,200 @@ void LoadControlEntry(Entry& e, bool force_image_load = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// taken from nxdumptool.
|
||||||
|
void utilsReplaceIllegalCharacters(char *str, bool ascii_only)
|
||||||
|
{
|
||||||
|
static const char g_illegalFileSystemChars[] = "\\/:*?\"<>|";
|
||||||
|
|
||||||
|
size_t str_size = 0, cur_pos = 0;
|
||||||
|
|
||||||
|
if (!str || !(str_size = strlen(str))) return;
|
||||||
|
|
||||||
|
u8 *ptr1 = (u8*)str, *ptr2 = ptr1;
|
||||||
|
ssize_t units = 0;
|
||||||
|
u32 code = 0;
|
||||||
|
bool repl = false;
|
||||||
|
|
||||||
|
while(cur_pos < str_size)
|
||||||
|
{
|
||||||
|
units = decode_utf8(&code, ptr1);
|
||||||
|
if (units < 0) break;
|
||||||
|
|
||||||
|
if (code < 0x20 || (!ascii_only && code == 0x7F) || (ascii_only && code >= 0x7F) || \
|
||||||
|
(units == 1 && memchr(g_illegalFileSystemChars, (int)code, std::size(g_illegalFileSystemChars))))
|
||||||
|
{
|
||||||
|
if (!repl)
|
||||||
|
{
|
||||||
|
*ptr2++ = '_';
|
||||||
|
repl = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ptr2 != ptr1) memmove(ptr2, ptr1, (size_t)units);
|
||||||
|
ptr2 += units;
|
||||||
|
repl = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr1 += units;
|
||||||
|
cur_pos += (size_t)units;
|
||||||
|
}
|
||||||
|
|
||||||
|
*ptr2 = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
auto isRightsIdValid(FsRightsId id) -> bool {
|
||||||
|
FsRightsId empty_id{};
|
||||||
|
return 0 != std::memcmp(std::addressof(id), std::addressof(empty_id), sizeof(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HashStr {
|
||||||
|
char str[0x21];
|
||||||
|
};
|
||||||
|
|
||||||
|
HashStr hexIdToStr(auto id) {
|
||||||
|
HashStr str{};
|
||||||
|
const auto id_lower = std::byteswap(*(u64*)id.c);
|
||||||
|
const auto id_upper = std::byteswap(*(u64*)(id.c + 0x8));
|
||||||
|
std::snprintf(str.str, 0x21, "%016lx%016lx", id_lower, id_upper);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto BuildNspPath(const Entry& e, const NsApplicationContentMetaStatus& status) -> fs::FsPath {
|
||||||
|
fs::FsPath name_buf = e.GetName();
|
||||||
|
utilsReplaceIllegalCharacters(name_buf, true);
|
||||||
|
|
||||||
|
char version[sizeof(NacpStruct::display_version) + 1]{};
|
||||||
|
if (status.meta_type == NcmContentMetaType_Patch) {
|
||||||
|
std::snprintf(version, sizeof(version), "%s ", e.GetDisplayVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::FsPath path;
|
||||||
|
std::snprintf(path, sizeof(path), "%s %s[%016lX][v%u][%s].nsp", name_buf.s, version, status.application_id, status.version, ncm::GetMetaTypeShortStr(status.meta_type));
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result BuildContentEntry(const NsApplicationContentMetaStatus& status, ContentInfoEntry& out) {
|
||||||
|
auto& cs = GetNcmCs(status.storageID);
|
||||||
|
auto& db = GetNcmDb(status.storageID);
|
||||||
|
const auto app_id = ncm::GetAppId(status.meta_type, status.application_id);
|
||||||
|
|
||||||
|
auto id_min = status.application_id;
|
||||||
|
auto id_max = status.application_id;
|
||||||
|
// workaround N bug where they don't check the full range in the ID filter.
|
||||||
|
// https://github.com/Atmosphere-NX/Atmosphere/blob/1d3f3c6e56b994b544fc8cd330c400205d166159/libraries/libstratosphere/source/ncm/ncm_on_memory_content_meta_database_impl.cpp#L22
|
||||||
|
if (status.storageID == NcmStorageId_None || status.storageID == NcmStorageId_GameCard) {
|
||||||
|
id_min -= 1;
|
||||||
|
id_max += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 meta_total;
|
||||||
|
s32 meta_entries_written;
|
||||||
|
NcmContentMetaKey key;
|
||||||
|
R_TRY(ncmContentMetaDatabaseList(std::addressof(db), std::addressof(meta_total), std::addressof(meta_entries_written), std::addressof(key), 1, (NcmContentMetaType)status.meta_type, app_id, id_min, id_max, NcmContentInstallType_Full));
|
||||||
|
log_write("ncmContentMetaDatabaseList(): AppId: %016lX Id: %016lX total: %d written: %d storageID: %u key.id %016lX\n", app_id, status.application_id, meta_total, meta_entries_written, status.storageID, key.id);
|
||||||
|
R_UNLESS(meta_total == 1, 0x1);
|
||||||
|
R_UNLESS(meta_entries_written == 1, 0x1);
|
||||||
|
|
||||||
|
for (s32 i = 0; ; i++) {
|
||||||
|
s32 entries_written;
|
||||||
|
NcmContentInfo info_out;
|
||||||
|
R_TRY(ncmContentMetaDatabaseListContentInfo(std::addressof(db), std::addressof(entries_written), std::addressof(info_out), 1, std::addressof(key), i));
|
||||||
|
|
||||||
|
if (!entries_written) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we need to fetch tickets.
|
||||||
|
NcmRightsId ncm_rights_id;
|
||||||
|
R_TRY(ncmContentStorageGetRightsIdFromContentId(std::addressof(cs), std::addressof(ncm_rights_id), std::addressof(info_out.content_id), FsContentAttributes_All));
|
||||||
|
|
||||||
|
const auto rights_id = ncm_rights_id.rights_id;
|
||||||
|
if (isRightsIdValid(rights_id)) {
|
||||||
|
const auto it = std::ranges::find_if(out.rights_ids, [&rights_id](auto& e){
|
||||||
|
return !std::memcmp(&e, &rights_id, sizeof(rights_id));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (it == out.rights_ids.end()) {
|
||||||
|
out.rights_ids.emplace_back(rights_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.content_infos.emplace_back(info_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.status = status;
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result BuildNspEntry(const Entry& e, const ContentInfoEntry& info, NspEntry& out) {
|
||||||
|
out.application_name = e.GetName();
|
||||||
|
out.path = BuildNspPath(e, info.status);
|
||||||
|
s64 offset{};
|
||||||
|
|
||||||
|
for (auto& rights_id : info.rights_ids) {
|
||||||
|
TikEntry entry{rights_id};
|
||||||
|
log_write("rights id is valid, fetching common ticket and cert\n");
|
||||||
|
|
||||||
|
u64 tik_size;
|
||||||
|
u64 cert_size;
|
||||||
|
R_TRY(es::GetCommonTicketAndCertificateSize(&tik_size, &cert_size, &rights_id));
|
||||||
|
log_write("got tik_size: %zu cert_size: %zu\n", tik_size, cert_size);
|
||||||
|
|
||||||
|
entry.tik_data.resize(tik_size);
|
||||||
|
entry.cert_data.resize(cert_size);
|
||||||
|
R_TRY(es::GetCommonTicketAndCertificateData(&tik_size, &cert_size, entry.tik_data.data(), entry.tik_data.size(), entry.cert_data.data(), entry.cert_data.size(), &rights_id));
|
||||||
|
log_write("got tik_data: %zu cert_data: %zu\n", tik_size, cert_size);
|
||||||
|
|
||||||
|
char tik_name[0x200];
|
||||||
|
std::snprintf(tik_name, sizeof(tik_name), "%s%s", hexIdToStr(rights_id).str, ".tik");
|
||||||
|
|
||||||
|
char cert_name[0x200];
|
||||||
|
std::snprintf(cert_name, sizeof(cert_name), "%s%s", hexIdToStr(rights_id).str, ".cert");
|
||||||
|
|
||||||
|
out.collections.emplace_back(tik_name, offset, entry.tik_data.size());
|
||||||
|
offset += entry.tik_data.size();
|
||||||
|
|
||||||
|
out.collections.emplace_back(cert_name, offset, entry.cert_data.size());
|
||||||
|
offset += entry.cert_data.size();
|
||||||
|
|
||||||
|
out.tickets.emplace_back(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& e : info.content_infos) {
|
||||||
|
char nca_name[0x200];
|
||||||
|
std::snprintf(nca_name, sizeof(nca_name), "%s%s", hexIdToStr(e.content_id).str, e.content_type == NcmContentType_Meta ? ".cnmt.nca" : ".nca");
|
||||||
|
|
||||||
|
u64 size;
|
||||||
|
ncmContentInfoSizeToU64(std::addressof(e), std::addressof(size));
|
||||||
|
|
||||||
|
out.collections.emplace_back(nca_name, offset, size);
|
||||||
|
offset += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.nsp_data = yati::container::Nsp::Build(out.collections, out.nsp_size);
|
||||||
|
out.cs = GetNcmCs(info.status.storageID);
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result BuildNspEntries(Entry& e, u32 flags, std::vector<NspEntry>& out) {
|
||||||
|
LoadControlEntry(e);
|
||||||
|
|
||||||
|
MetaEntries meta_entries;
|
||||||
|
R_TRY(GetMetaEntries(e, meta_entries, flags));
|
||||||
|
|
||||||
|
for (const auto& status : meta_entries) {
|
||||||
|
ContentInfoEntry info;
|
||||||
|
R_TRY(BuildContentEntry(status, info));
|
||||||
|
|
||||||
|
NspEntry nsp;
|
||||||
|
R_TRY(BuildNspEntry(e, info, nsp));
|
||||||
|
out.emplace_back(nsp);
|
||||||
|
}
|
||||||
|
|
||||||
|
R_UNLESS(!out.empty(), 0x1);
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
void FreeEntry(NVGcontext* vg, Entry& e) {
|
void FreeEntry(NVGcontext* vg, Entry& e) {
|
||||||
nvgDeleteImage(vg, e.image);
|
nvgDeleteImage(vg, e.image);
|
||||||
e.image = 0;
|
e.image = 0;
|
||||||
@@ -267,7 +784,7 @@ void ThreadData::Push(u64 id) {
|
|||||||
mutexLock(&m_mutex_id);
|
mutexLock(&m_mutex_id);
|
||||||
ON_SCOPE_EXIT(mutexUnlock(&m_mutex_id));
|
ON_SCOPE_EXIT(mutexUnlock(&m_mutex_id));
|
||||||
|
|
||||||
const auto it = std::find(m_ids.begin(), m_ids.end(), id);
|
const auto it = std::ranges::find(m_ids, id);
|
||||||
if (it == m_ids.end()) {
|
if (it == m_ids.end()) {
|
||||||
m_ids.emplace_back(id);
|
m_ids.emplace_back(id);
|
||||||
ueventSignal(&m_uevent);
|
ueventSignal(&m_uevent);
|
||||||
@@ -290,6 +807,33 @@ void ThreadData::Pop(std::vector<ThreadResultData>& out) {
|
|||||||
|
|
||||||
Menu::Menu() : grid::Menu{"Games"_i18n} {
|
Menu::Menu() : grid::Menu{"Games"_i18n} {
|
||||||
this->SetActions(
|
this->SetActions(
|
||||||
|
std::make_pair(Button::L3, Action{[this](){
|
||||||
|
if (m_entries.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_entries[m_index].selected ^= 1;
|
||||||
|
|
||||||
|
if (m_entries[m_index].selected) {
|
||||||
|
m_selected_count++;
|
||||||
|
} else {
|
||||||
|
m_selected_count--;
|
||||||
|
}
|
||||||
|
}}),
|
||||||
|
std::make_pair(Button::R3, Action{[this](){
|
||||||
|
if (m_entries.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_selected_count == m_entries.size()) {
|
||||||
|
ClearSelection();
|
||||||
|
} else {
|
||||||
|
m_selected_count = m_entries.size();
|
||||||
|
for (auto& e : m_entries) {
|
||||||
|
e.selected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}),
|
||||||
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
|
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
|
||||||
SetPop();
|
SetPop();
|
||||||
}}),
|
}}),
|
||||||
@@ -395,6 +939,27 @@ Menu::Menu() : grid::Menu{"Games"_i18n} {
|
|||||||
));
|
));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
options->Add(std::make_shared<SidebarEntryCallback>("Dump"_i18n, [this](){
|
||||||
|
auto options = std::make_shared<Sidebar>("Select content to dump"_i18n, Sidebar::Side::RIGHT);
|
||||||
|
ON_SCOPE_EXIT(App::Push(options));
|
||||||
|
|
||||||
|
options->Add(std::make_shared<SidebarEntryCallback>("Dump All"_i18n, [this](){
|
||||||
|
DumpGames(ContentFlag_All);
|
||||||
|
}, true));
|
||||||
|
options->Add(std::make_shared<SidebarEntryCallback>("Dump Application"_i18n, [this](){
|
||||||
|
DumpGames(ContentFlag_Application);
|
||||||
|
}, true));
|
||||||
|
options->Add(std::make_shared<SidebarEntryCallback>("Dump Patch"_i18n, [this](){
|
||||||
|
DumpGames(ContentFlag_Patch);
|
||||||
|
}, true));
|
||||||
|
options->Add(std::make_shared<SidebarEntryCallback>("Dump AddOnContent"_i18n, [this](){
|
||||||
|
DumpGames(ContentFlag_AddOnContent);
|
||||||
|
}, true));
|
||||||
|
options->Add(std::make_shared<SidebarEntryCallback>("Dump DataPatch"_i18n, [this](){
|
||||||
|
DumpGames(ContentFlag_DataPatch);
|
||||||
|
}, true));
|
||||||
|
}, true));
|
||||||
|
|
||||||
// completely deletes the application record and all data.
|
// completely deletes the application record and all data.
|
||||||
options->Add(std::make_shared<SidebarEntryCallback>("Delete"_i18n, [this](){
|
options->Add(std::make_shared<SidebarEntryCallback>("Delete"_i18n, [this](){
|
||||||
const auto buf = "Are you sure you want to delete "_i18n + m_entries[m_index].GetName() + "?";
|
const auto buf = "Are you sure you want to delete "_i18n + m_entries[m_index].GetName() + "?";
|
||||||
@@ -402,22 +967,7 @@ Menu::Menu() : grid::Menu{"Games"_i18n} {
|
|||||||
buf,
|
buf,
|
||||||
"Back"_i18n, "Delete"_i18n, 0, [this](auto op_index){
|
"Back"_i18n, "Delete"_i18n, 0, [this](auto op_index){
|
||||||
if (op_index && *op_index) {
|
if (op_index && *op_index) {
|
||||||
const auto rc = nsDeleteApplicationCompletely(m_entries[m_index].app_id);
|
DeleteGames();
|
||||||
Notify(rc, "Failed to delete application");
|
|
||||||
}
|
|
||||||
}, m_entries[m_index].image
|
|
||||||
));
|
|
||||||
}, true));
|
|
||||||
|
|
||||||
// removes installed data but keeps the record, basically archiving.
|
|
||||||
options->Add(std::make_shared<SidebarEntryCallback>("Delete entity"_i18n, [this](){
|
|
||||||
const auto buf = "Are you sure you want to delete "_i18n + m_entries[m_index].GetName() + "?";
|
|
||||||
App::Push(std::make_shared<OptionBox>(
|
|
||||||
buf,
|
|
||||||
"Back"_i18n, "Delete"_i18n, 0, [this](auto op_index){
|
|
||||||
if (op_index && *op_index) {
|
|
||||||
const auto rc = nsDeleteApplicationEntity(m_entries[m_index].app_id);
|
|
||||||
Notify(rc, "Failed to delete application");
|
|
||||||
}
|
}
|
||||||
}, m_entries[m_index].image
|
}, m_entries[m_index].image
|
||||||
));
|
));
|
||||||
@@ -430,10 +980,10 @@ Menu::Menu() : grid::Menu{"Games"_i18n} {
|
|||||||
|
|
||||||
nsInitialize();
|
nsInitialize();
|
||||||
nsGetApplicationRecordUpdateSystemEvent(&m_event);
|
nsGetApplicationRecordUpdateSystemEvent(&m_event);
|
||||||
|
es::Initialize();
|
||||||
|
|
||||||
for (size_t i = 0; i < std::size(NCM_STORAGE_IDS); i++) {
|
for (auto& e : ncm_entries) {
|
||||||
ncmOpenContentMetaDatabase(std::addressof(ncm_db[i]), NCM_STORAGE_IDS[i]);
|
e.Open();
|
||||||
ncmOpenContentStorage(std::addressof(ncm_cs[i]), NCM_STORAGE_IDS[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
threadCreate(&m_thread, ThreadFunc, &m_thread_data, nullptr, 1024*32, 0x30, 1);
|
threadCreate(&m_thread, ThreadFunc, &m_thread_data, nullptr, 1024*32, 0x30, 1);
|
||||||
@@ -443,14 +993,14 @@ Menu::Menu() : grid::Menu{"Games"_i18n} {
|
|||||||
Menu::~Menu() {
|
Menu::~Menu() {
|
||||||
m_thread_data.Close();
|
m_thread_data.Close();
|
||||||
|
|
||||||
for (size_t i = 0; i < std::size(NCM_STORAGE_IDS); i++) {
|
for (auto& e : ncm_entries) {
|
||||||
ncmContentMetaDatabaseClose(std::addressof(ncm_db[i]));
|
e.Close();
|
||||||
ncmContentStorageClose(std::addressof(ncm_cs[i]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FreeEntries();
|
FreeEntries();
|
||||||
eventClose(&m_event);
|
eventClose(&m_event);
|
||||||
nsExit();
|
nsExit();
|
||||||
|
es::Exit();
|
||||||
|
|
||||||
threadWaitForExit(&m_thread);
|
threadWaitForExit(&m_thread);
|
||||||
threadClose(&m_thread);
|
threadClose(&m_thread);
|
||||||
@@ -489,7 +1039,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
|||||||
m_thread_data.Pop(data);
|
m_thread_data.Pop(data);
|
||||||
|
|
||||||
for (const auto& d : data) {
|
for (const auto& d : data) {
|
||||||
const auto it = std::find_if(m_entries.begin(), m_entries.end(), [&d](auto& e) {
|
const auto it = std::ranges::find_if(m_entries, [&d](auto& e) {
|
||||||
return e.app_id == d.id;
|
return e.app_id == d.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -499,7 +1049,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_list->Draw(vg, theme, m_entries.size(), [this, &image_load_count](auto* vg, auto* theme, auto v, auto pos) {
|
m_list->Draw(vg, theme, m_entries.size(), [this, &image_load_count](auto* vg, auto* theme, auto v, auto pos) {
|
||||||
// const auto& [x, y, w, h] = v;
|
const auto& [x, y, w, h] = v;
|
||||||
auto& e = m_entries[pos];
|
auto& e = m_entries[pos];
|
||||||
|
|
||||||
if (e.status == NacpLoadStatus::None) {
|
if (e.status == NacpLoadStatus::None) {
|
||||||
@@ -516,6 +1066,11 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
|||||||
|
|
||||||
const auto selected = pos == m_index;
|
const auto selected = pos == m_index;
|
||||||
DrawEntry(vg, theme, m_layout.Get(), v, selected, e.image, e.GetName(), e.GetAuthor(), e.GetDisplayVersion());
|
DrawEntry(vg, theme, m_layout.Get(), v, selected, e.image, e.GetName(), e.GetAuthor(), e.GetDisplayVersion());
|
||||||
|
|
||||||
|
if (e.selected) {
|
||||||
|
gfx::drawRect(vg, v, nvgRGBA(0, 0, 0, 180), 5);
|
||||||
|
gfx::drawText(vg, x + w / 2, y + h / 2, 24.f, "\uE14B", nullptr, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_SELECTED));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -594,8 +1149,7 @@ void Menu::ScanHomebrew() {
|
|||||||
log_write("games found: %zu time_taken: %.2f seconds %zu ms %zu ns\n", m_entries.size(), ts.GetSecondsD(), ts.GetMs(), ts.GetNs());
|
log_write("games found: %zu time_taken: %.2f seconds %zu ms %zu ns\n", m_entries.size(), ts.GetSecondsD(), ts.GetMs(), ts.GetNs());
|
||||||
this->Sort();
|
this->Sort();
|
||||||
SetIndex(0);
|
SetIndex(0);
|
||||||
|
ClearSelection();
|
||||||
// m_thread_data.Push(m_entries);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::Sort() {
|
void Menu::Sort() {
|
||||||
@@ -604,12 +1158,12 @@ void Menu::Sort() {
|
|||||||
|
|
||||||
if (order == OrderType_Ascending) {
|
if (order == OrderType_Ascending) {
|
||||||
if (!m_is_reversed) {
|
if (!m_is_reversed) {
|
||||||
std::reverse(m_entries.begin(), m_entries.end());
|
std::ranges::reverse(m_entries);
|
||||||
m_is_reversed = true;
|
m_is_reversed = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (m_is_reversed) {
|
if (m_is_reversed) {
|
||||||
std::reverse(m_entries.begin(), m_entries.end());
|
std::ranges::reverse(m_entries);
|
||||||
m_is_reversed = false;
|
m_is_reversed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -660,4 +1214,76 @@ void Menu::OnLayoutChange() {
|
|||||||
grid::Menu::OnLayoutChange(m_list, m_layout.Get());
|
grid::Menu::OnLayoutChange(m_list, m_layout.Get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Menu::DeleteGames() {
|
||||||
|
App::Push(std::make_shared<ProgressBox>(0, "Deleting Games"_i18n, "", [this](auto pbox) -> bool {
|
||||||
|
auto targets = GetSelectedEntries();
|
||||||
|
|
||||||
|
for (s64 i = 0; i < std::size(targets); i++) {
|
||||||
|
auto& e = targets[i];
|
||||||
|
|
||||||
|
LoadControlEntry(e);
|
||||||
|
pbox->SetTitle(e.GetName());
|
||||||
|
pbox->UpdateTransfer(i + 1, std::size(targets));
|
||||||
|
nsDeleteApplicationCompletely(e.app_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}, [this](bool success){
|
||||||
|
ClearSelection();
|
||||||
|
m_dirty = true;
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
App::Notify("Delete successfull!");
|
||||||
|
} else {
|
||||||
|
App::Notify("Delete failed!");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::DumpGames(u32 flags) {
|
||||||
|
PopupList::Items items;
|
||||||
|
for (const auto&p : DUMP_LOCATIONS) {
|
||||||
|
items.emplace_back(p.display_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
App::Push(std::make_shared<PopupList>(
|
||||||
|
"Select dump location"_i18n, items, [this, flags](auto op_index){
|
||||||
|
if (!op_index) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto index = *op_index;
|
||||||
|
App::Push(std::make_shared<ProgressBox>(0, "Dumping Games"_i18n, "", [this, index, flags](auto pbox) -> bool {
|
||||||
|
auto targets = GetSelectedEntries();
|
||||||
|
|
||||||
|
std::vector<NspEntry> nsp_entries;
|
||||||
|
for (auto& e : targets) {
|
||||||
|
BuildNspEntries(e, flags, nsp_entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index == DumpLocationType_SdCard) {
|
||||||
|
return R_SUCCEEDED(DumpNspToFile(pbox, nsp_entries));
|
||||||
|
} else if (index == DumpLocationType_UsbS2S) {
|
||||||
|
return R_SUCCEEDED(DumpNspToUsbS2S(pbox, nsp_entries));
|
||||||
|
} else if (index == DumpLocationType_DevNull) {
|
||||||
|
return R_SUCCEEDED(DumpNspToDevNull(pbox, nsp_entries));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}, [this](bool success){
|
||||||
|
ClearSelection();
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
App::Notify("Dump successfull!");
|
||||||
|
log_write("dump successfull!!!\n");
|
||||||
|
} else {
|
||||||
|
App::Notify("Dump failed!");
|
||||||
|
log_write("dump failed!!!\n");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace sphaira::ui::menu::game
|
} // namespace sphaira::ui::menu::game
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ namespace {
|
|||||||
|
|
||||||
constexpr u64 CONNECTION_TIMEOUT = UINT64_MAX;
|
constexpr u64 CONNECTION_TIMEOUT = UINT64_MAX;
|
||||||
constexpr u64 TRANSFER_TIMEOUT = UINT64_MAX;
|
constexpr u64 TRANSFER_TIMEOUT = UINT64_MAX;
|
||||||
|
constexpr u64 FINISHED_TIMEOUT = 1e+9 * 3; // 3 seconds.
|
||||||
|
|
||||||
void thread_func(void* user) {
|
void thread_func(void* user) {
|
||||||
auto app = static_cast<Menu*>(user);
|
auto app = static_cast<Menu*>(user);
|
||||||
@@ -22,7 +23,7 @@ void thread_func(void* user) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto rc = app->m_usb_source->IsUsbConnected(CONNECTION_TIMEOUT);
|
const auto rc = app->m_usb_source->IsUsbConnected(CONNECTION_TIMEOUT);
|
||||||
if (rc == app->m_usb_source->Result_Cancelled) {
|
if (rc == ::sphaira::usb::UsbDs::Result_Cancelled) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,11 +72,6 @@ Menu::Menu() : MenuBase{"USB"_i18n} {
|
|||||||
if (R_FAILED(m_usb_source->GetOpenResult())) {
|
if (R_FAILED(m_usb_source->GetOpenResult())) {
|
||||||
log_write("usb init open\n");
|
log_write("usb init open\n");
|
||||||
m_state = State::Failed;
|
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);
|
mutexInit(&m_mutex);
|
||||||
@@ -114,7 +110,7 @@ void Menu::Update(Controller* controller, TouchInfo* touch) {
|
|||||||
m_state = State::Progress;
|
m_state = State::Progress;
|
||||||
log_write("got connection\n");
|
log_write("got connection\n");
|
||||||
App::Push(std::make_shared<ui::ProgressBox>(0, "Installing "_i18n, "", [this](auto pbox) mutable -> bool {
|
App::Push(std::make_shared<ui::ProgressBox>(0, "Installing "_i18n, "", [this](auto pbox) mutable -> bool {
|
||||||
ON_SCOPE_EXIT(m_usb_source->Finished());
|
ON_SCOPE_EXIT(m_usb_source->Finished(FINISHED_TIMEOUT));
|
||||||
|
|
||||||
log_write("inside progress box\n");
|
log_write("inside progress box\n");
|
||||||
for (const auto& file_name : m_names) {
|
for (const auto& file_name : m_names) {
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ auto ProgressBox::Draw(NVGcontext* vg, Theme* theme) -> void {
|
|||||||
nvgIntersectScissor(vg, GetX(), GetY(), GetW(), GetH());
|
nvgIntersectScissor(vg, GetX(), GetY(), GetW(), GetH());
|
||||||
|
|
||||||
if (m_image) {
|
if (m_image) {
|
||||||
gfx::drawImage(vg, GetX() + 30, GetY() + 30, 128, 128, m_image, 10);
|
gfx::drawImage(vg, GetX() + 30, GetY() + 30, 128, 128, m_image, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
// shapes.
|
// shapes.
|
||||||
@@ -234,7 +234,7 @@ auto ProgressBox::CopyFile(const fs::FsPath& src_path, const fs::FsPath& dst_pat
|
|||||||
R_TRY(fsFileSetSize(&dst_file, src_size));
|
R_TRY(fsFileSetSize(&dst_file, src_size));
|
||||||
|
|
||||||
s64 offset{};
|
s64 offset{};
|
||||||
std::vector<u8> buf(1024*1024*8); // 8MiB
|
std::vector<u8> buf(1024*1024*4); // 4MiB
|
||||||
|
|
||||||
while (offset < src_size) {
|
while (offset < src_size) {
|
||||||
if (ShouldExit()) {
|
if (ShouldExit()) {
|
||||||
@@ -248,6 +248,7 @@ auto ProgressBox::CopyFile(const fs::FsPath& src_path, const fs::FsPath& dst_pat
|
|||||||
R_TRY(fsFileWrite(&dst_file, offset, buf.data(), bytes_read, FsWriteOption_None));
|
R_TRY(fsFileWrite(&dst_file, offset, buf.data(), bytes_read, FsWriteOption_None));
|
||||||
Yield();
|
Yield();
|
||||||
|
|
||||||
|
UpdateTransfer(offset, src_size);
|
||||||
offset += bytes_read;
|
offset += bytes_read;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
98
sphaira/source/usb/base.cpp
Normal file
98
sphaira/source/usb/base.cpp
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// The USB transfer code was taken from Haze (part of Atmosphere).
|
||||||
|
|
||||||
|
#include "usb/base.hpp"
|
||||||
|
#include "log.hpp"
|
||||||
|
#include "defines.hpp"
|
||||||
|
#include <ranges>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace sphaira::usb {
|
||||||
|
|
||||||
|
Base::Base(u64 transfer_timeout) {
|
||||||
|
m_transfer_timeout = transfer_timeout;
|
||||||
|
ueventCreate(GetCancelEvent(), true);
|
||||||
|
// this avoids allocations during transfers.
|
||||||
|
m_aligned.reserve(1024 * 1024 * 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Base::TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_transferred, u64 timeout) {
|
||||||
|
u32 xfer_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(xfer_id)));
|
||||||
|
|
||||||
|
/* Try to wait for the event. */
|
||||||
|
R_TRY(WaitTransferCompletion(ep, timeout));
|
||||||
|
|
||||||
|
/* Return what we transferred. */
|
||||||
|
return GetTransferResult(ep, xfer_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.
|
||||||
|
|
||||||
|
// NOTE: it is now possible to request the transfer buffer using GetTransferBuffer(),
|
||||||
|
// which will always be aligned and have the size aligned.
|
||||||
|
// this allows for zero-copy transferrs to take place.
|
||||||
|
// this is used in usb_upload.cpp.
|
||||||
|
// do note that this relies of the host sending / receiving buffers of an aligned size.
|
||||||
|
Result Base::TransferAll(bool read, void *data, u32 size, u64 timeout) {
|
||||||
|
auto buf = static_cast<u8*>(data);
|
||||||
|
auto transfer_buf = m_aligned.data();
|
||||||
|
const auto alias = buf == transfer_buf;
|
||||||
|
|
||||||
|
if (!alias) {
|
||||||
|
m_aligned.resize(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (size) {
|
||||||
|
if (!alias && !read) {
|
||||||
|
std::memcpy(transfer_buf, buf, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 out_size_transferred;
|
||||||
|
R_TRY(TransferPacketImpl(read, transfer_buf, size, &out_size_transferred, timeout));
|
||||||
|
|
||||||
|
if (!alias && read) {
|
||||||
|
std::memcpy(buf, transfer_buf, out_size_transferred);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alias) {
|
||||||
|
transfer_buf += out_size_transferred;
|
||||||
|
} else {
|
||||||
|
buf += out_size_transferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
size -= out_size_transferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sphaira::usb
|
||||||
112
sphaira/source/usb/usb_uploader.cpp
Normal file
112
sphaira/source/usb/usb_uploader.cpp
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
// The USB protocol was taken from Tinfoil, by Adubbz.
|
||||||
|
|
||||||
|
#include "usb/usb_uploader.hpp"
|
||||||
|
#include "usb/tinfoil.hpp"
|
||||||
|
#include "log.hpp"
|
||||||
|
#include "defines.hpp"
|
||||||
|
|
||||||
|
namespace sphaira::usb::upload {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
namespace tinfoil = usb::tinfoil;
|
||||||
|
|
||||||
|
const UsbHsInterfaceFilter FILTER{
|
||||||
|
.Flags = UsbHsInterfaceFilterFlags_idVendor |
|
||||||
|
UsbHsInterfaceFilterFlags_idProduct |
|
||||||
|
UsbHsInterfaceFilterFlags_bcdDevice_Min |
|
||||||
|
UsbHsInterfaceFilterFlags_bcdDevice_Max |
|
||||||
|
UsbHsInterfaceFilterFlags_bDeviceClass |
|
||||||
|
UsbHsInterfaceFilterFlags_bDeviceSubClass |
|
||||||
|
UsbHsInterfaceFilterFlags_bDeviceProtocol |
|
||||||
|
UsbHsInterfaceFilterFlags_bInterfaceClass |
|
||||||
|
UsbHsInterfaceFilterFlags_bInterfaceSubClass |
|
||||||
|
UsbHsInterfaceFilterFlags_bInterfaceProtocol,
|
||||||
|
.idVendor = 0x057e,
|
||||||
|
.idProduct = 0x3000,
|
||||||
|
.bcdDevice_Min = 0x0100,
|
||||||
|
.bcdDevice_Max = 0x0100,
|
||||||
|
.bDeviceClass = 0x00,
|
||||||
|
.bDeviceSubClass = 0x00,
|
||||||
|
.bDeviceProtocol = 0x00,
|
||||||
|
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
|
||||||
|
.bInterfaceSubClass = USB_CLASS_VENDOR_SPEC,
|
||||||
|
.bInterfaceProtocol = USB_CLASS_VENDOR_SPEC,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr u8 INDEX = 0;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Usb::Usb(u64 transfer_timeout) {
|
||||||
|
m_usb = std::make_unique<usb::UsbHs>(INDEX, FILTER, transfer_timeout);
|
||||||
|
m_usb->Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
Usb::~Usb() {
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Usb::WaitForConnection(u64 timeout, std::span<const std::string> names) {
|
||||||
|
R_TRY(m_usb->IsUsbConnected(timeout));
|
||||||
|
|
||||||
|
std::string names_list;
|
||||||
|
for (auto& name : names) {
|
||||||
|
names_list += name + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
tinfoil::TUSHeader header{};
|
||||||
|
header.magic = tinfoil::Magic_List0;
|
||||||
|
header.nspListSize = names_list.length();
|
||||||
|
|
||||||
|
R_TRY(m_usb->TransferAll(false, &header, sizeof(header), timeout));
|
||||||
|
R_TRY(m_usb->TransferAll(false, names_list.data(), names_list.length(), timeout));
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Usb::PollCommands() {
|
||||||
|
tinfoil::USBCmdHeader header;
|
||||||
|
R_TRY(m_usb->TransferAll(true, &header, sizeof(header)));
|
||||||
|
R_UNLESS(header.magic == tinfoil::Magic_Command0, Result_BadMagic);
|
||||||
|
|
||||||
|
if (header.cmdId == tinfoil::USBCmdId::EXIT) {
|
||||||
|
return Result_Exit;
|
||||||
|
} else if (header.cmdId == tinfoil::USBCmdId::FILE_RANGE) {
|
||||||
|
return FileRangeCmd(header.dataSize);
|
||||||
|
} else {
|
||||||
|
return Result_BadCommand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Usb::FileRangeCmd(u64 data_size) {
|
||||||
|
tinfoil::FileRangeCmdHeader header;
|
||||||
|
R_TRY(m_usb->TransferAll(true, &header, sizeof(header)));
|
||||||
|
|
||||||
|
std::string path(header.nspNameLen, '\0');
|
||||||
|
R_TRY(m_usb->TransferAll(true, path.data(), header.nspNameLen));
|
||||||
|
|
||||||
|
// send response header.
|
||||||
|
R_TRY(m_usb->TransferAll(false, &header, sizeof(header)));
|
||||||
|
|
||||||
|
s64 curr_off = 0x0;
|
||||||
|
s64 end_off = header.size;
|
||||||
|
s64 read_size = header.size;
|
||||||
|
|
||||||
|
// use transfer buffer directly to avoid copy overhead.
|
||||||
|
auto& buf = m_usb->GetTransferBuffer();
|
||||||
|
buf.resize(header.size);
|
||||||
|
|
||||||
|
while (curr_off < end_off) {
|
||||||
|
if (curr_off + read_size >= end_off) {
|
||||||
|
read_size = end_off - curr_off;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 bytes_read;
|
||||||
|
R_TRY(Read(path, buf.data(), header.offset + curr_off, read_size, &bytes_read));
|
||||||
|
R_TRY(m_usb->TransferAll(false, buf.data(), bytes_read));
|
||||||
|
curr_off += bytes_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sphaira::usb::upload
|
||||||
272
sphaira/source/usb/usbds.cpp
Normal file
272
sphaira/source/usb/usbds.cpp
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
#include "usb/usbds.hpp"
|
||||||
|
#include "log.hpp"
|
||||||
|
#include "defines.hpp"
|
||||||
|
#include <ranges>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace sphaira::usb {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// untested, should work tho.
|
||||||
|
// TODO: pr missing speed fields to libnx.
|
||||||
|
Result usbDsGetSpeed(u32 *out) {
|
||||||
|
if (hosversionBefore(8,0,0)) {
|
||||||
|
return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceAssumeDomain(usbDsGetServiceSession());
|
||||||
|
return serviceDispatchOut(usbDsGetServiceSession(), hosversionAtLeast(11,0,0) ? 11 : 12, *out);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
UsbDs::~UsbDs() {
|
||||||
|
usbDsExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result UsbDs::Init() {
|
||||||
|
log_write("doing USB init\n");
|
||||||
|
R_TRY(usbDsInitialize());
|
||||||
|
|
||||||
|
static SetSysSerialNumber serial_number{};
|
||||||
|
R_TRY(setsysInitialize());
|
||||||
|
ON_SCOPE_EXIT(setsysExit());
|
||||||
|
R_TRY(setsysGetSerialNumber(&serial_number));
|
||||||
|
|
||||||
|
u8 iManufacturer, iProduct, iSerialNumber;
|
||||||
|
static constexpr u16 supported_langs[1] = {0x0409};
|
||||||
|
// Send language descriptor
|
||||||
|
R_TRY(usbDsAddUsbLanguageStringDescriptor(nullptr, supported_langs, std::size(supported_langs)));
|
||||||
|
// 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 below code is taken from libnx, with the addition of a uevent to cancel.
|
||||||
|
Result UsbDs::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 == waiters.size() - 1) {
|
||||||
|
rc = Result_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 UsbDs::GetSpeed(UsbDeviceSpeed* out) {
|
||||||
|
return usbDsGetSpeed((u32*)out);
|
||||||
|
}
|
||||||
|
|
||||||
|
Event *UsbDs::GetCompletionEvent(UsbSessionEndpoint ep) {
|
||||||
|
return std::addressof(m_endpoints[ep]->CompletionEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result UsbDs::WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) {
|
||||||
|
const std::array waiters{
|
||||||
|
waiterForEvent(GetCompletionEvent(ep)),
|
||||||
|
waiterForEvent(usbDsGetStateChangeEvent()),
|
||||||
|
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 == waiters.size() - 1) {
|
||||||
|
log_write("got usb cancel event\n");
|
||||||
|
rc = Result_Cancelled;
|
||||||
|
} else if (R_SUCCEEDED(rc) && idx == waiters.size() - 2) {
|
||||||
|
log_write("got usbDsGetStateChangeEvent() event\n");
|
||||||
|
rc = KERNELRESULT(TimedOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
R_TRY(usbDsEndpoint_Cancel(m_endpoints[ep]));
|
||||||
|
eventClear(GetCompletionEvent(ep));
|
||||||
|
eventClear(usbDsGetStateChangeEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result UsbDs::TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_urb_id) {
|
||||||
|
return usbDsEndpoint_PostBufferAsync(m_endpoints[ep], buffer, size, out_urb_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result UsbDs::GetTransferResult(UsbSessionEndpoint ep, u32 urb_id, u32 *out_requested_size, u32 *out_transferred_size) {
|
||||||
|
UsbDsReportData report_data;
|
||||||
|
|
||||||
|
R_TRY(eventClear(GetCompletionEvent(ep)));
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sphaira::usb
|
||||||
202
sphaira/source/usb/usbhs.cpp
Normal file
202
sphaira/source/usb/usbhs.cpp
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
#include "usb/usbhs.hpp"
|
||||||
|
#include "log.hpp"
|
||||||
|
#include "defines.hpp"
|
||||||
|
#include <ranges>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace sphaira::usb {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct Bcd {
|
||||||
|
constexpr Bcd(u16 v) : value{v} {}
|
||||||
|
|
||||||
|
u8 major() const { return (value >> 8) & 0xFF; }
|
||||||
|
u8 minor() const { return (value >> 4) & 0xF; }
|
||||||
|
u8 macro() const { return (value >> 0) & 0xF; }
|
||||||
|
|
||||||
|
const u16 value;
|
||||||
|
};
|
||||||
|
|
||||||
|
Result usbHsParseReportData(UsbHsXferReport* reports, u32 count, u32 xferId, u32 *requestedSize, u32 *transferredSize) {
|
||||||
|
Result rc = 0;
|
||||||
|
u32 pos;
|
||||||
|
UsbHsXferReport *entry = NULL;
|
||||||
|
if (count>8) count = 8;
|
||||||
|
|
||||||
|
for(pos=0; pos<count; pos++) {
|
||||||
|
entry = &reports[pos];
|
||||||
|
if (entry->xferId == xferId) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos == count) return MAKERESULT(Module_Libnx, LibnxError_NotFound);
|
||||||
|
rc = entry->res;
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
if (requestedSize) *requestedSize = entry->requestedSize;
|
||||||
|
if (transferredSize) *transferredSize = entry->transferredSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
UsbHs::UsbHs(u8 index, const UsbHsInterfaceFilter& filter, u64 transfer_timeout)
|
||||||
|
: Base{transfer_timeout}
|
||||||
|
, m_index{index}
|
||||||
|
, m_filter{filter} {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
UsbHs::~UsbHs() {
|
||||||
|
Close();
|
||||||
|
usbHsDestroyInterfaceAvailableEvent(std::addressof(m_event), m_index);
|
||||||
|
usbHsExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result UsbHs::Init() {
|
||||||
|
log_write("doing USB init\n");
|
||||||
|
R_TRY(usbHsInitialize());
|
||||||
|
R_TRY(usbHsCreateInterfaceAvailableEvent(&m_event, true, m_index, &m_filter));
|
||||||
|
log_write("success USB init\n");
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result UsbHs::IsUsbConnected(u64 timeout) {
|
||||||
|
if (m_connected) {
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::array waiters{
|
||||||
|
waiterForEvent(&m_event),
|
||||||
|
waiterForUEvent(GetCancelEvent()),
|
||||||
|
};
|
||||||
|
|
||||||
|
s32 idx;
|
||||||
|
R_TRY(waitObjects(&idx, waiters.data(), waiters.size(), timeout));
|
||||||
|
|
||||||
|
if (idx == waiters.size() - 1) {
|
||||||
|
return Result_Cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result UsbHs::Connect() {
|
||||||
|
Close();
|
||||||
|
|
||||||
|
s32 total;
|
||||||
|
R_TRY(usbHsQueryAvailableInterfaces(&m_filter, &m_interface, sizeof(m_interface), &total));
|
||||||
|
R_TRY(usbHsAcquireUsbIf(&m_s, &m_interface));
|
||||||
|
|
||||||
|
const auto bcdUSB = Bcd{m_interface.device_desc.bcdUSB};
|
||||||
|
const auto bcdDevice = Bcd{m_interface.device_desc.bcdDevice};
|
||||||
|
|
||||||
|
// log lsusb style.
|
||||||
|
log_write("[USBHS] pathstr: %s\n", m_interface.pathstr);
|
||||||
|
log_write("Bus: %03u Device: %03u ID: %04x:%04x\n\n", m_interface.busID, m_interface.deviceID, m_interface.device_desc.idVendor, m_interface.device_desc.idProduct);
|
||||||
|
|
||||||
|
log_write("Device Descriptor:\n");
|
||||||
|
log_write("\tbLength: %u\n", m_interface.device_desc.bLength);
|
||||||
|
log_write("\tbDescriptorType: %u\n", m_interface.device_desc.bDescriptorType);
|
||||||
|
log_write("\tbcdUSB: %u:%u%u\n", bcdUSB.major(), bcdUSB.minor(), bcdUSB.macro());
|
||||||
|
log_write("\tbDeviceClass: %u\n", m_interface.device_desc.bDeviceClass);
|
||||||
|
log_write("\tbDeviceSubClass: %u\n", m_interface.device_desc.bDeviceSubClass);
|
||||||
|
log_write("\tbDeviceProtocol: %u\n", m_interface.device_desc.bDeviceProtocol);
|
||||||
|
log_write("\tbMaxPacketSize0: %u\n", m_interface.device_desc.bMaxPacketSize0);
|
||||||
|
log_write("\tidVendor: 0x%x\n", m_interface.device_desc.idVendor);
|
||||||
|
log_write("\tidProduct: 0x%x\n", m_interface.device_desc.idProduct);
|
||||||
|
log_write("\tbcdDevice: %u:%u%u\n", bcdDevice.major(), bcdDevice.minor(), bcdDevice.macro());
|
||||||
|
log_write("\tiManufacturer: %u\n", m_interface.device_desc.iManufacturer);
|
||||||
|
log_write("\tiProduct: %u\n", m_interface.device_desc.iProduct);
|
||||||
|
log_write("\tiSerialNumber: %u\n", m_interface.device_desc.iSerialNumber);
|
||||||
|
log_write("\tbNumConfigurations: %u\n", m_interface.device_desc.bNumConfigurations);
|
||||||
|
|
||||||
|
log_write("\tConfiguration Descriptor:\n");
|
||||||
|
log_write("\t\tbLength: %u\n", m_interface.config_desc.bLength);
|
||||||
|
log_write("\t\tbDescriptorType: %u\n", m_interface.config_desc.bDescriptorType);
|
||||||
|
log_write("\t\twTotalLength: %u\n", m_interface.config_desc.wTotalLength);
|
||||||
|
log_write("\t\tbNumInterfaces: %u\n", m_interface.config_desc.bNumInterfaces);
|
||||||
|
log_write("\t\tbConfigurationValue: %u\n", m_interface.config_desc.bConfigurationValue);
|
||||||
|
log_write("\t\tiConfiguration: %u\n", m_interface.config_desc.iConfiguration);
|
||||||
|
log_write("\t\tbmAttributes: 0x%x\n", m_interface.config_desc.bmAttributes);
|
||||||
|
log_write("\t\tMaxPower: %u (%u mA)\n", m_interface.config_desc.MaxPower, m_interface.config_desc.MaxPower * 2);
|
||||||
|
|
||||||
|
struct usb_endpoint_descriptor invalid_desc{};
|
||||||
|
for (u8 i = 0; i < std::size(m_s.inf.inf.input_endpoint_descs); i++) {
|
||||||
|
const auto& desc = m_s.inf.inf.input_endpoint_descs[i];
|
||||||
|
if (std::memcmp(&desc, &invalid_desc, sizeof(desc))) {
|
||||||
|
log_write("\t[USBHS] desc[%u] wMaxPacketSize: 0x%X\n", i, desc.wMaxPacketSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& input_descs = m_s.inf.inf.input_endpoint_descs[0];
|
||||||
|
R_TRY(usbHsIfOpenUsbEp(&m_s, &m_endpoints[UsbSessionEndpoint_Out], 1, input_descs.wMaxPacketSize, &input_descs));
|
||||||
|
|
||||||
|
auto& output_descs = m_s.inf.inf.output_endpoint_descs[0];
|
||||||
|
R_TRY(usbHsIfOpenUsbEp(&m_s, &m_endpoints[UsbSessionEndpoint_In], 1, output_descs.wMaxPacketSize, &output_descs));
|
||||||
|
|
||||||
|
m_connected = true;
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UsbHs::Close() {
|
||||||
|
usbHsEpClose(std::addressof(m_endpoints[UsbSessionEndpoint_In]));
|
||||||
|
usbHsEpClose(std::addressof(m_endpoints[UsbSessionEndpoint_Out]));
|
||||||
|
usbHsIfClose(std::addressof(m_s));
|
||||||
|
|
||||||
|
m_endpoints[UsbSessionEndpoint_In] = {};
|
||||||
|
m_endpoints[UsbSessionEndpoint_Out] = {};
|
||||||
|
m_s = {};
|
||||||
|
m_connected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Event *UsbHs::GetCompletionEvent(UsbSessionEndpoint ep) {
|
||||||
|
return usbHsEpGetXferEvent(&m_endpoints[ep]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result UsbHs::WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) {
|
||||||
|
const std::array waiters{
|
||||||
|
waiterForEvent(GetCompletionEvent(ep)),
|
||||||
|
waiterForEvent(usbHsGetInterfaceStateChangeEvent()),
|
||||||
|
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 == waiters.size() - 1) {
|
||||||
|
log_write("got usb cancel event\n");
|
||||||
|
rc = Result_Cancelled;
|
||||||
|
} else if (R_SUCCEEDED(rc) && idx == waiters.size() - 2) {
|
||||||
|
log_write("got usb timeout event\n");
|
||||||
|
rc = KERNELRESULT(TimedOut);
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
log_write("failed to wait for event\n");
|
||||||
|
eventClear(GetCompletionEvent(ep));
|
||||||
|
eventClear(usbHsGetInterfaceStateChangeEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result UsbHs::TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_xfer_id) {
|
||||||
|
return usbHsEpPostBufferAsync(&m_endpoints[ep], buffer, size, 0, out_xfer_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result UsbHs::GetTransferResult(UsbSessionEndpoint ep, u32 xfer_id, u32 *out_requested_size, u32 *out_transferred_size) {
|
||||||
|
u32 count;
|
||||||
|
UsbHsXferReport report_data[8];
|
||||||
|
|
||||||
|
R_TRY(eventClear(GetCompletionEvent(ep)));
|
||||||
|
R_TRY(usbHsEpGetXferReport(&m_endpoints[ep], report_data, std::size(report_data), std::addressof(count)));
|
||||||
|
R_TRY(usbHsParseReportData(report_data, count, xfer_id, out_requested_size, out_transferred_size));
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sphaira::usb
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "defines.hpp"
|
#include "defines.hpp"
|
||||||
#include "log.hpp"
|
#include "log.hpp"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
namespace sphaira::yati::container {
|
namespace sphaira::yati::container {
|
||||||
namespace {
|
namespace {
|
||||||
@@ -22,6 +23,38 @@ struct Pfs0FileTableEntry {
|
|||||||
u32 padding;
|
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
|
} // namespace
|
||||||
|
|
||||||
Result Nsp::GetCollections(Collections& out) {
|
Result Nsp::GetCollections(Collections& out) {
|
||||||
@@ -56,4 +89,48 @@ Result Nsp::GetCollections(Collections& out) {
|
|||||||
R_SUCCEED();
|
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
|
} // namespace sphaira::yati::container
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "yati/nx/es.hpp"
|
#include "yati/nx/es.hpp"
|
||||||
#include "yati/nx/crypto.hpp"
|
#include "yati/nx/crypto.hpp"
|
||||||
#include "yati/nx/nxdumptool_rsa.h"
|
#include "yati/nx/nxdumptool_rsa.h"
|
||||||
|
#include "yati/nx/service_guard.h"
|
||||||
#include "defines.hpp"
|
#include "defines.hpp"
|
||||||
#include "log.hpp"
|
#include "log.hpp"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -9,12 +10,124 @@
|
|||||||
namespace sphaira::es {
|
namespace sphaira::es {
|
||||||
namespace {
|
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
|
} // namespace
|
||||||
|
|
||||||
Result ImportTicket(Service* srv, const void* tik_buf, u64 tik_size, const void* cert_buf, u64 cert_size) {
|
Result Initialize() {
|
||||||
return serviceDispatch(srv, 1,
|
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 },
|
.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 {
|
typedef enum {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#include "yati/nx/ncm.hpp"
|
#include "yati/nx/ncm.hpp"
|
||||||
#include "defines.hpp"
|
#include "defines.hpp"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <bit>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
namespace sphaira::ncm {
|
namespace sphaira::ncm {
|
||||||
namespace {
|
namespace {
|
||||||
@@ -25,6 +28,25 @@ auto GetMetaTypeStr(u8 meta_type) -> const char* {
|
|||||||
return "Unknown";
|
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* {
|
auto GetStorageIdStr(u8 storage_id) -> const char* {
|
||||||
switch (storage_id) {
|
switch (storage_id) {
|
||||||
case NcmStorageId_None: return "None";
|
case NcmStorageId_None: return "None";
|
||||||
@@ -57,6 +79,18 @@ auto GetAppId(const PackagedContentMeta& meta) -> u64 {
|
|||||||
return GetAppId(meta.meta_type, meta.title_id);
|
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) {
|
Result Delete(NcmContentStorage* cs, const NcmContentId *content_id) {
|
||||||
bool has;
|
bool has;
|
||||||
R_TRY(ncmContentStorageHas(cs, std::addressof(has), content_id));
|
R_TRY(ncmContentStorageHas(cs, std::addressof(has), content_id));
|
||||||
|
|||||||
@@ -18,266 +18,34 @@
|
|||||||
// The USB protocol was taken from Tinfoil, by Adubbz.
|
// The USB protocol was taken from Tinfoil, by Adubbz.
|
||||||
|
|
||||||
#include "yati/source/usb.hpp"
|
#include "yati/source/usb.hpp"
|
||||||
|
#include "usb/tinfoil.hpp"
|
||||||
#include "log.hpp"
|
#include "log.hpp"
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
|
|
||||||
namespace sphaira::yati::source {
|
namespace sphaira::yati::source {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
enum USBCmdType : u8 {
|
namespace tinfoil = usb::tinfoil;
|
||||||
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
|
} // namespace
|
||||||
|
|
||||||
Usb::Usb(u64 transfer_timeout) {
|
Usb::Usb(u64 transfer_timeout) {
|
||||||
m_open_result = usbDsInitialize();
|
m_usb = std::make_unique<usb::UsbDs>(transfer_timeout);
|
||||||
m_transfer_timeout = transfer_timeout;
|
m_open_result = m_usb->Init();
|
||||||
ueventCreate(GetCancelEvent(), true);
|
|
||||||
// this avoids allocations during transfers.
|
|
||||||
m_aligned.reserve(1024 * 1024 * 16);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Usb::~Usb() {
|
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) {
|
Result Usb::WaitForConnection(u64 timeout, std::vector<std::string>& out_names) {
|
||||||
TUSHeader header;
|
tinfoil::TUSHeader header;
|
||||||
R_TRY(TransferAll(true, &header, sizeof(header), timeout));
|
R_TRY(m_usb->TransferAll(true, &header, sizeof(header), timeout));
|
||||||
R_UNLESS(header.magic == 0x304C5554, Result_BadMagic);
|
R_UNLESS(header.magic == tinfoil::Magic_List0, Result_BadMagic);
|
||||||
R_UNLESS(header.nspListSize > 0, Result_BadCount);
|
R_UNLESS(header.nspListSize > 0, Result_BadCount);
|
||||||
log_write("USB got header\n");
|
log_write("USB got header\n");
|
||||||
|
|
||||||
std::vector<char> names(header.nspListSize);
|
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();
|
out_names.clear();
|
||||||
for (const auto& name : std::views::split(names, '\n')) {
|
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;
|
m_transfer_file_name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
Event *Usb::GetCompletionEvent(UsbSessionEndpoint ep) const {
|
Result Usb::SendCmdHeader(u32 cmdId, size_t dataSize, u64 timeout) {
|
||||||
return std::addressof(m_endpoints[ep]->CompletionEvent);
|
tinfoil::USBCmdHeader header{
|
||||||
}
|
.magic = tinfoil::Magic_Command0,
|
||||||
|
.type = tinfoil::USBCmdType::REQUEST,
|
||||||
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,
|
|
||||||
.cmdId = cmdId,
|
.cmdId = cmdId,
|
||||||
.dataSize = dataSize,
|
.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) {
|
Result Usb::SendFileRangeCmd(u64 off, u64 size, u64 timeout) {
|
||||||
FileRangeCmdHeader fRangeHeader;
|
tinfoil::FileRangeCmdHeader fRangeHeader;
|
||||||
fRangeHeader.size = size;
|
fRangeHeader.size = size;
|
||||||
fRangeHeader.offset = off;
|
fRangeHeader.offset = off;
|
||||||
fRangeHeader.nspNameLen = m_transfer_file_name.size();
|
fRangeHeader.nspNameLen = m_transfer_file_name.size();
|
||||||
fRangeHeader.padding = 0;
|
fRangeHeader.padding = 0;
|
||||||
|
|
||||||
R_TRY(SendCmdHeader(USBCmdId::FILE_RANGE, sizeof(fRangeHeader) + fRangeHeader.nspNameLen));
|
R_TRY(SendCmdHeader(tinfoil::USBCmdId::FILE_RANGE, sizeof(fRangeHeader) + fRangeHeader.nspNameLen, timeout));
|
||||||
R_TRY(TransferAll(false, &fRangeHeader, sizeof(fRangeHeader), m_transfer_timeout));
|
R_TRY(m_usb->TransferAll(false, &fRangeHeader, sizeof(fRangeHeader), timeout));
|
||||||
R_TRY(TransferAll(false, m_transfer_file_name.data(), m_transfer_file_name.size(), m_transfer_timeout));
|
R_TRY(m_usb->TransferAll(false, m_transfer_file_name.data(), m_transfer_file_name.size(), timeout));
|
||||||
|
|
||||||
USBCmdHeader responseHeader;
|
tinfoil::USBCmdHeader responseHeader;
|
||||||
R_TRY(TransferAll(true, &responseHeader, sizeof(responseHeader), m_transfer_timeout));
|
R_TRY(m_usb->TransferAll(true, &responseHeader, sizeof(responseHeader), timeout));
|
||||||
|
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result Usb::Finished() {
|
Result Usb::Finished(u64 timeout) {
|
||||||
return SendCmdHeader(USBCmdId::EXIT, 0);
|
return SendCmdHeader(tinfoil::USBCmdId::EXIT, 0, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result Usb::Read(void* buf, s64 off, s64 size, u64* bytes_read) {
|
Result Usb::Read(void* buf, s64 off, s64 size, u64* bytes_read) {
|
||||||
R_TRY(GetOpenResult());
|
R_TRY(GetOpenResult());
|
||||||
R_TRY(SendFileRangeCmd(off, size));
|
R_TRY(SendFileRangeCmd(off, size, m_usb->GetTransferTimeout()));
|
||||||
R_TRY(TransferAll(true, buf, size, m_transfer_timeout));
|
R_TRY(m_usb->TransferAll(true, buf, size));
|
||||||
*bytes_read = size;
|
*bytes_read = size;
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -275,7 +275,6 @@ struct Yati {
|
|||||||
NcmContentMetaDatabase db{};
|
NcmContentMetaDatabase db{};
|
||||||
NcmStorageId storage_id{};
|
NcmStorageId storage_id{};
|
||||||
|
|
||||||
Service es{};
|
|
||||||
Service ns_app{};
|
Service ns_app{};
|
||||||
std::unique_ptr<container::Base> container{};
|
std::unique_ptr<container::Base> container{};
|
||||||
Config config{};
|
Config config{};
|
||||||
@@ -452,7 +451,7 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
|
|||||||
for (s64 off = 0; off < size;) {
|
for (s64 off = 0; off < size;) {
|
||||||
// log_write("looking for section\n");
|
// log_write("looking for section\n");
|
||||||
if (!ncz_section || !ncz_section->InRange(written)) {
|
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);
|
return e.InRange(written);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -504,6 +503,8 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
|
|||||||
if (!is_ncz || !decompress_buf_off) {
|
if (!is_ncz || !decompress_buf_off) {
|
||||||
// check nca header
|
// check nca header
|
||||||
if (!decompress_buf_off) {
|
if (!decompress_buf_off) {
|
||||||
|
log_write("reading nca header\n");
|
||||||
|
|
||||||
nca::Header header{};
|
nca::Header header{};
|
||||||
crypto::cryptoAes128Xts(buf.data(), std::addressof(header), keys.header_key, 0, 0x200, sizeof(header), false);
|
crypto::cryptoAes128Xts(buf.data(), std::addressof(header), keys.header_key, 0, 0x200, sizeof(header), false);
|
||||||
log_write("verifying nca header magic\n");
|
log_write("verifying nca header magic\n");
|
||||||
@@ -522,6 +523,7 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t->write_size = header.size;
|
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));
|
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) {
|
if (!config.ignore_distribution_bit && header.distribution_type == nca::DistributionType_GameCard) {
|
||||||
@@ -531,11 +533,13 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
|
|||||||
|
|
||||||
TikCollection* ticket = nullptr;
|
TikCollection* ticket = nullptr;
|
||||||
if (isRightsIdValid(header.rights_id)) {
|
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));
|
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);
|
R_UNLESS(it != t->tik.end(), Result_TicketNotFound);
|
||||||
|
log_write("ticket found\n");
|
||||||
it->required = true;
|
it->required = true;
|
||||||
ticket = &(*it);
|
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.
|
// todo: blocks need to use read offset, as the offset + size is compressed range.
|
||||||
if (t->ncz_blocks.size()) {
|
if (t->ncz_blocks.size()) {
|
||||||
if (!ncz_block || !ncz_block->InRange(decompress_buf_off)) {
|
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);
|
return e.InRange(decompress_buf_off);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -757,13 +761,13 @@ Yati::~Yati() {
|
|||||||
splCryptoExit();
|
splCryptoExit();
|
||||||
serviceClose(std::addressof(ns_app));
|
serviceClose(std::addressof(ns_app));
|
||||||
nsExit();
|
nsExit();
|
||||||
|
es::Exit();
|
||||||
|
|
||||||
for (size_t i = 0; i < std::size(NCM_STORAGE_IDS); i++) {
|
for (size_t i = 0; i < std::size(NCM_STORAGE_IDS); i++) {
|
||||||
ncmContentMetaDatabaseClose(std::addressof(ncm_db[i]));
|
ncmContentMetaDatabaseClose(std::addressof(ncm_db[i]));
|
||||||
ncmContentStorageClose(std::addressof(ncm_cs[i]));
|
ncmContentStorageClose(std::addressof(ncm_cs[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceClose(std::addressof(es));
|
|
||||||
appletSetMediaPlaybackState(false);
|
appletSetMediaPlaybackState(false);
|
||||||
|
|
||||||
if (config.boost_mode) {
|
if (config.boost_mode) {
|
||||||
@@ -799,7 +803,7 @@ Result Yati::Setup(const ConfigOverride& override) {
|
|||||||
R_TRY(splCryptoInitialize());
|
R_TRY(splCryptoInitialize());
|
||||||
R_TRY(nsInitialize());
|
R_TRY(nsInitialize());
|
||||||
R_TRY(nsGetApplicationManagerInterface(std::addressof(ns_app)));
|
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++) {
|
for (size_t i = 0; i < std::size(NCM_STORAGE_IDS); i++) {
|
||||||
R_TRY(ncmOpenContentMetaDatabase(std::addressof(ncm_db[i]), 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 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;
|
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;
|
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");
|
log_write("found all cnmts\n");
|
||||||
R_SUCCEED();
|
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());
|
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 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;
|
return e.name.find(str) != e.name.npos;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1117,7 +1121,7 @@ Result Yati::ImportTickets(std::span<TikCollection> tickets) {
|
|||||||
log_write("patching ticket\n");
|
log_write("patching ticket\n");
|
||||||
R_TRY(es::PatchTicket(ticket.ticket, keys));
|
R_TRY(es::PatchTicket(ticket.ticket, keys));
|
||||||
log_write("installing ticket\n");
|
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;
|
ticket.required = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1317,7 +1321,7 @@ Result InstallInternalStream(ui::ProgressBox* pbox, std::shared_ptr<source::Base
|
|||||||
return lhs.offset < rhs.offset;
|
return lhs.offset < rhs.offset;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::sort(collections.begin(), collections.end(), sorter);
|
std::ranges::sort(collections, sorter);
|
||||||
|
|
||||||
for (const auto& collection : collections) {
|
for (const auto& collection : collections) {
|
||||||
if (collection.name.ends_with(".nca") || collection.name.ends_with(".ncz")) {
|
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());
|
keys::parse_hex_key(rights_id.c, collection.name.c_str());
|
||||||
const auto str = collection.name.substr(0, collection.name.length() - 4) + ".cert";
|
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));
|
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) {
|
for (auto& cnmt : cnmts) {
|
||||||
// copy nca structs into cnmt.
|
// copy nca structs into cnmt.
|
||||||
for (auto& cnmt_nca : cnmt.ncas) {
|
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;
|
return e.name == cnmt_nca.name;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user