initial wireless install stuff (libnx master)

This commit is contained in:
Huntereb 2019-10-17 23:59:34 -04:00
parent 25ce22f1ee
commit e3ed8a5220
54 changed files with 6331 additions and 4 deletions

View file

@ -39,9 +39,9 @@ include $(DEVKITPRO)/libnx/switch_rules
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
TARGET := $(notdir $(CURDIR)) TARGET := $(notdir $(CURDIR))
BUILD := build BUILD := build
SOURCES := source source/ui SOURCES := source source/ui source/data source/install source/nx source/nx/ipc source/util
DATA := data DATA := data
INCLUDES := include include/ui INCLUDES := include include/ui include/data include/install include/nx include/nx/ipc include/util
APP_TITLE := Awoo Installer APP_TITLE := Awoo Installer
APP_AUTHOR := Huntereb APP_AUTHOR := Huntereb
APP_VERSION := 0.0.1 APP_VERSION := 0.0.1

View file

@ -0,0 +1,66 @@
#pragma once
#include <atomic>
#include <switch/types.h>
#include <memory>
#include "nx/ncm.hpp"
namespace tin::data
{
static const size_t BUFFER_SEGMENT_DATA_SIZE = 0x800000; // Approximately 8MB
struct BufferSegment
{
std::atomic_bool isFinalized = false;
u64 writeOffset = 0;
u8 data[BUFFER_SEGMENT_DATA_SIZE] = {0};
};
// Receives data in a circular buffer split into 8MB segments
class BufferedPlaceholderWriter
{
private:
size_t m_totalDataSize = 0;
size_t m_sizeBuffered = 0;
size_t m_sizeWrittenToPlaceholder = 0;
// The current segment to which further data will be appended
u64 m_currentFreeSegment = 0;
BufferSegment* m_currentFreeSegmentPtr = NULL;
// The current segment that will be written to the placeholder
u64 m_currentSegmentToWrite = 0;
BufferSegment* m_currentSegmentToWritePtr = NULL;
std::unique_ptr<BufferSegment[]> m_bufferSegments;
nx::ncm::ContentStorage* m_contentStorage;
NcmNcaId m_ncaId;
public:
static const int NUM_BUFFER_SEGMENTS = 4;
BufferedPlaceholderWriter(nx::ncm::ContentStorage* contentStorage, NcmNcaId ncaId, size_t totalDataSize);
void AppendData(void* source, size_t length);
bool CanAppendData(size_t length);
void WriteSegmentToPlaceholder();
bool CanWriteSegmentToPlaceholder();
// Determine the number of segments required to fit data of this size
u32 CalcNumSegmentsRequired(size_t size);
// Check if there are enough free segments to fit data of this size
bool IsSizeAvailable(size_t size);
bool IsBufferDataComplete();
bool IsPlaceholderComplete();
size_t GetTotalDataSize();
size_t GetSizeBuffered();
size_t GetSizeWrittenToPlaceholder();
void DebugPrintBuffers();
};
}

50
include/data/byte_buffer.hpp Executable file
View file

@ -0,0 +1,50 @@
#pragma once
#include <switch/types.h>
#include <cstring>
#include <vector>
namespace tin::data
{
class ByteBuffer
{
private:
std::vector<u8> m_buffer;
public:
ByteBuffer(size_t reserveSize=0);
size_t GetSize();
u8* GetData(); // TODO: Remove this, it shouldn't be needed
void Resize(size_t size);
void DebugPrintContents();
template <typename T>
T Read(u64 offset)
{
if (offset + sizeof(T) <= m_buffer.size())
return *((T*)&m_buffer.data()[offset]);
T def;
memset(&def, 0, sizeof(T));
return def;
}
template <typename T>
void Write(T data, u64 offset)
{
size_t requiredSize = offset + sizeof(T);
if (requiredSize > m_buffer.size())
m_buffer.resize(requiredSize, 0);
memcpy(m_buffer.data() + offset, &data, sizeof(T));
}
template <typename T>
void Append(T data)
{
this->Write(data, this->GetSize());
}
};
}

29
include/data/byte_stream.hpp Executable file
View file

@ -0,0 +1,29 @@
#pragma once
#include <switch/types.h>
#include "data/byte_buffer.hpp"
namespace tin::data
{
class ByteStream
{
protected:
u64 m_offset = 0;
public:
virtual void ReadBytes(void* dest, size_t length) = 0;
};
// NOTE: This isn't generally useful, it's mainly for things like libpng
// which rely on streams
class BufferedByteStream : public ByteStream
{
private:
ByteBuffer m_byteBuffer;
public:
BufferedByteStream(ByteBuffer buffer);
void ReadBytes(void* dest, size_t length) override;
};
}

19
include/debug.h Executable file
View file

@ -0,0 +1,19 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
#include <switch/types.h>
extern FILE *nxlinkout;
int nxLinkInitialize(void);
void nxLinkExit(void);
void printBytes(FILE* out, u8 *bytes, size_t size, bool includeHeader);
#ifdef __cplusplus
}
#endif

17
include/error.hpp Executable file
View file

@ -0,0 +1,17 @@
#pragma once
#include <cstring>
#include <stdexcept>
#include <stdio.h>
#include "debug.h"
#define ASSERT_OK(rc_out, desc) if (R_FAILED(rc_out)) { char msg[256] = {0}; snprintf(msg, 256-1, "%s:%u: %s. Error code: 0x%08x\n", __func__, __LINE__, desc, rc_out); throw std::runtime_error(msg); }
#define THROW_FORMAT(format, ...) { char error_prefix[512] = {0}; snprintf(error_prefix, 256-1, "%s:%u: ", __func__, __LINE__);\
char formatted_msg[256] = {0}; snprintf(formatted_msg, 256-1, format, ##__VA_ARGS__);\
strncat(error_prefix, formatted_msg, 512-1); throw std::runtime_error(error_prefix); }
#ifdef NXLINK_DEBUG
#define LOG_DEBUG(format, ...) { fprintf(nxlinkout, "%s:%u: ", __func__, __LINE__); fprintf(nxlinkout, format, ##__VA_ARGS__); }
#else
#define LOG_DEBUG(format, ...) ;
#endif

17
include/install/http_nsp.hpp Executable file
View file

@ -0,0 +1,17 @@
#pragma once
#include "install/remote_nsp.hpp"
namespace tin::install::nsp
{
class HTTPNSP : public RemoteNSP
{
public:
tin::network::HTTPDownload m_download;
HTTPNSP(std::string url);
virtual void StreamToPlaceholder(nx::ncm::ContentStorage& contentStorage, NcmNcaId placeholderId) override;
virtual void BufferData(void* buf, off_t offset, size_t size) override;
};
}

47
include/install/install.hpp Executable file
View file

@ -0,0 +1,47 @@
#pragma once
extern "C"
{
#include <switch/services/fs.h>
}
#include <memory>
#include <tuple>
#include <vector>
#include "install/simple_filesystem.hpp"
#include "data/byte_buffer.hpp"
#include "nx/content_meta.hpp"
#include "nx/ipc/tin_ipc.h"
namespace tin::install
{
class Install
{
protected:
const FsStorageId m_destStorageId;
bool m_ignoreReqFirmVersion = false;
nx::ncm::ContentMeta m_contentMeta;
Install(FsStorageId destStorageId, bool ignoreReqFirmVersion);
virtual ~Install();
virtual std::tuple<nx::ncm::ContentMeta, NcmContentInfo> ReadCNMT() = 0;
virtual void InstallContentMetaRecords(tin::data::ByteBuffer& installContentMetaBuf);
virtual void InstallApplicationRecord();
virtual void InstallTicketCert() = 0;
virtual void InstallNCA(const NcmNcaId &ncaId) = 0;
public:
virtual void Prepare();
virtual void Begin();
virtual u64 GetTitleId();
virtual NcmContentMetaType GetContentMetaType();
virtual void DebugPrintInstallData();
};
}

25
include/install/install_nsp.hpp Executable file
View file

@ -0,0 +1,25 @@
#pragma once
#include <switch.h>
#include "install/install.hpp"
#include "install/simple_filesystem.hpp"
#include "nx/content_meta.hpp"
#include "nx/ipc/tin_ipc.h"
namespace tin::install::nsp
{
class NSPInstallTask : public Install
{
private:
tin::install::nsp::SimpleFileSystem* const m_simpleFileSystem;
protected:
std::tuple<nx::ncm::ContentMeta, NcmContentInfo> ReadCNMT() override;
void InstallNCA(const NcmNcaId& ncaId) override;
void InstallTicketCert() override;
public:
NSPInstallTask(tin::install::nsp::SimpleFileSystem& simpleFileSystem, FsStorageId destStorageId, bool ignoreReqFirmVersion);
};
};

View file

@ -0,0 +1,23 @@
#pragma once
#include <switch.h>
#include <string>
#include "install/install.hpp"
#include "install/remote_nsp.hpp"
namespace tin::install::nsp
{
class RemoteNSPInstall : public Install
{
private:
RemoteNSP* m_remoteNSP;
protected:
std::tuple<nx::ncm::ContentMeta, NcmContentInfo> ReadCNMT() override;
void InstallNCA(const NcmNcaId& ncaId) override;
void InstallTicketCert() override;
public:
RemoteNSPInstall(FsStorageId destStorageId, bool ignoreReqFirmVersion, RemoteNSP* remoteNSP);
};
}

View file

@ -0,0 +1,30 @@
#pragma once
#include <switch/services/fs.h>
typedef enum
{
InstallSourceType_None,
InstallSourceType_Extracted,
InstallSourceType_Nsp,
} InstallSourceType;
typedef struct
{
InstallSourceType sourceType;
char path[FS_MAX_PATH];
} InstallContext;
typedef struct
{
char name[FS_MAX_PATH];
size_t size;
size_t nspAbsoluteOffset;
FsFileSystem fsFileSystem;
FsFile fsFile;
} InstallFile;
Result openInstallFile(InstallFile *out, InstallContext *context, const char *name);
Result openInstallFileWithExt(InstallFile *out, InstallContext *context, const char *ext);
Result readInstallFile(InstallContext *context, InstallFile *file, size_t off, void *buff, size_t len);
void closeInstallFile(InstallContext *context, InstallFile *file);

26
include/install/pfs0.hpp Executable file
View file

@ -0,0 +1,26 @@
#pragma once
#include <switch/types.h>
namespace tin::install
{
struct PFS0FileEntry
{
u64 dataOffset;
u64 fileSize;
u32 stringTableOffset;
u32 padding;
} PACKED;
static_assert(sizeof(PFS0FileEntry) == 0x18, "PFS0FileEntry must be 0x18");
struct PFS0BaseHeader
{
u32 magic;
u32 numFiles;
u32 stringTableSize;
u32 reserved;
} PACKED;
static_assert(sizeof(PFS0BaseHeader) == 0x10, "PFS0BaseHeader must be 0x10");
}

35
include/install/remote_nsp.hpp Executable file
View file

@ -0,0 +1,35 @@
#pragma once
#include <functional>
#include <vector>
#include <switch/types.h>
#include "install/pfs0.hpp"
#include "nx/ncm.hpp"
#include "util/network_util.hpp"
namespace tin::install::nsp
{
class RemoteNSP
{
protected:
std::vector<u8> m_headerBytes;
RemoteNSP();
public:
virtual void StreamToPlaceholder(nx::ncm::ContentStorage& contentStorage, NcmNcaId placeholderId) = 0;
virtual void BufferData(void* buf, off_t offset, size_t size) = 0;
virtual void RetrieveHeader();
virtual const PFS0BaseHeader* GetBaseHeader();
virtual u64 GetDataOffset();
virtual const PFS0FileEntry* GetFileEntry(unsigned int index);
virtual const PFS0FileEntry* GetFileEntryByName(std::string name);
virtual const PFS0FileEntry* GetFileEntryByNcaId(const NcmNcaId& ncaId);
virtual const PFS0FileEntry* GetFileEntryByExtension(std::string extension);
virtual const char* GetFileEntryName(const PFS0FileEntry* fileEntry);
};
}

View file

@ -0,0 +1,24 @@
#pragma once
#include <functional>
#include "nx/fs.hpp"
namespace tin::install::nsp
{
class SimpleFileSystem final
{
private:
nx::fs::IFileSystem* m_fileSystem;
public:
const std::string m_rootPath;
const std::string m_absoluteRootPath;
SimpleFileSystem(nx::fs::IFileSystem& fileSystem, std::string rootPath, std::string absoluteRootPath);
~SimpleFileSystem();
nx::fs::IFile OpenFile(std::string path);
bool HasFile(std::string path);
std::string GetFileNameFromExtension(std::string path, std::string extension);
};
}

19
include/install/usb_nsp.hpp Executable file
View file

@ -0,0 +1,19 @@
#pragma once
#include <string>
#include "install/remote_nsp.hpp"
namespace tin::install::nsp
{
class USBNSP : public RemoteNSP
{
private:
std::string m_nspName;
public:
USBNSP(std::string nspName);
virtual void StreamToPlaceholder(nx::ncm::ContentStorage& contentStorage, NcmNcaId placeholderId) override;
virtual void BufferData(void* buf, off_t offset, size_t size) override;
};
}

33
include/install/verify_nsp.hpp Executable file
View file

@ -0,0 +1,33 @@
#pragma once
#ifdef __VERIFY_NSP__
#include <string>
namespace tin::install
{
enum class VerificationErrorType
{
CRITICAL, WARNING
};
enum class VerificationLevel
{
ERROR_ALL, ERROR_CRITICAL_WARN, ERROR_CRITICAL_IGNORE_WARN, WARN_ALL
};
class NSPVerifier
{
private:
std::string m_nspPath;
public:
NSPVerifier(std::string nspPath);
bool PerformVerification();
void PrintCritical(std::string text);
void PrintWarning(std::string text);
void PrintSuccess(std::string text);
};
}
#endif

3
include/netInstall.hpp Executable file
View file

@ -0,0 +1,3 @@
namespace netInstStuff {
bool installNspLan ();
}

51
include/nx/content_meta.hpp Executable file
View file

@ -0,0 +1,51 @@
#pragma once
#include <switch/services/ncm.h>
#include <switch/types.h>
#include <vector>
#include "data/byte_buffer.hpp"
namespace nx::ncm
{
struct PackagedContentInfo
{
u8 hash[0x20];
NcmContentInfo content_info;
} PACKED;
struct PackagedContentMetaHeader
{
u64 title_id;
u32 version;
u8 type;
u8 _0xd;
u16 extended_header_size;
u16 content_count;
u16 content_meta_count;
u8 attributes;
u8 storage_id;
u8 install_type;
bool comitted;
u32 required_system_version;
u32 _0x1c;
};
static_assert(sizeof(PackagedContentMetaHeader) == 0x20, "PackagedContentMetaHeader must be 0x20!");
class ContentMeta final
{
private:
tin::data::ByteBuffer m_bytes;
public:
ContentMeta();
ContentMeta(u8* data, size_t size);
PackagedContentMetaHeader GetPackagedContentMetaHeader();
NcmContentMetaKey GetContentMetaKey();
std::vector<NcmContentInfo> GetContentInfos();
void GetInstallContentMeta(tin::data::ByteBuffer& installContentMetaBuffer, NcmContentInfo& cnmtContentInfo, bool ignoreReqFirmVersion);
};
}

77
include/nx/fs.hpp Executable file
View file

@ -0,0 +1,77 @@
#pragma once
#include <string>
extern "C"
{
#include <switch/types.h>
#include <switch/services/fs.h>
}
#include "nx/ipc/tin_ipc.h"
namespace nx::fs
{
class IFileSystem;
class IFile
{
friend IFileSystem;
private:
FsFile m_file;
IFile(FsFile& file);
public:
// Don't allow copying, or garbage may be closed by the destructor
IFile& operator=(const IFile&) = delete;
IFile(const IFile&) = delete;
~IFile();
void Read(u64 offset, void* buf, size_t size);
u64 GetSize();
};
class IDirectory
{
friend IFileSystem;
private:
FsDir m_dir;
IDirectory(FsDir& dir);
public:
// Don't allow copying, or garbage may be closed by the destructor
IDirectory& operator=(const IDirectory&) = delete;
IDirectory(const IDirectory&) = delete;
~IDirectory();
void Read(u64 inval, FsDirectoryEntry* buf, size_t numEntries);
u64 GetEntryCount();
};
class IFileSystem
{
private:
FsFileSystem m_fileSystem;
public:
// Don't allow copying, or garbage may be closed by the destructor
IFileSystem& operator=(const IFileSystem&) = delete;
IFileSystem(const IFileSystem&) = delete;
IFileSystem();
~IFileSystem();
Result OpenSdFileSystem();
void OpenFileSystemWithId(std::string path, FsFileSystemType fileSystemType, u64 titleId);
void CloseFileSystem();
IFile OpenFile(std::string path);
IDirectory OpenDirectory(std::string path, int flags);
};
}

19
include/nx/ipc/es.h Executable file
View file

@ -0,0 +1,19 @@
#pragma once
#include <switch/services/ncm.h>
typedef struct {
u8 c[0x10];
} RightsId;
Result esInitialize();
void esExit();
Result esImportTicket(void const *tikBuf, size_t tikSize, void const *certBuf, size_t certSize); //1
Result esDeleteTicket(const RightsId *rightsIdBuf, size_t bufSize); //3
Result esGetTitleKey(const RightsId *rightsId, u8 *outBuf, size_t bufSize); //8
Result esCountCommonTicket(u32 *numTickets); //9
Result esCountPersonalizedTicket(u32 *numTickets); // 10
Result esListCommonTicket(u32 *numRightsIdsWritten, RightsId *outBuf, size_t bufSize);
Result esListPersonalizedTicket(u32 *numRightsIdsWritten, RightsId *outBuf, size_t bufSize);
Result esGetCommonTicketData(u64 *unkOut, void *outBuf1, size_t bufSize1, const RightsId* rightsId);

36
include/nx/ipc/ns_ext.h Executable file
View file

@ -0,0 +1,36 @@
#pragma once
#include <switch/services/ns.h>
#include <switch/services/ncm.h>
typedef struct {
u64 titleID;
u64 unk;
u64 size;
} PACKED ApplicationRecord;
typedef struct {
NcmContentMetaKey metaRecord;
u64 storageId;
} PACKED ContentStorageRecord;
Result nsextInitialize(void);
void nsextExit(void);
Result nsCalculateApplicationOccupiedSize(u64 titleID, void *out_buf);
Result nsPushApplicationRecord(u64 title_id, u8 last_modified_event, ContentStorageRecord *content_records_buf, size_t buf_size);
Result nsListApplicationRecordContentMeta(u64 offset, u64 titleID, void *out_buf, size_t out_buf_size, u32 *entries_read_out);
Result nsDeleteApplicationRecord(u64 titleID);
Result nsTouchApplication(u64 titleID);
Result nsLaunchApplication(u64 titleID);
Result nsPushLaunchVersion(u64 titleID, u32 version);
Result nsCountApplicationContentMeta(u64 titleId, u32* countOut);
Result nsCheckApplicationLaunchVersion(u64 titleID);
Result nsDisableApplicationAutoUpdate(u64 titleID);
Result nsGetContentMetaStorage(const NcmContentMetaKey *record, u8 *out);
Result nsBeginInstallApplication(u64 tid, u32 unk, u8 storageId);
Result nsInvalidateAllApplicationControlCache(void);
Result nsInvalidateApplicationControlCache(u64 tid);
Result nsCheckApplicationLaunchRights(u64 tid);
Result nsGetApplicationContentPath(u64 titleId, u8 type, char *outBuf, size_t bufSize);
Result nsWithdrawApplicationUpdateRequest(u64 titleId);

12
include/nx/ipc/tin_ipc.h Executable file
View file

@ -0,0 +1,12 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "nx/ipc/es.h"
#include "nx/ipc/ns_ext.h"
#ifdef __cplusplus
}
#endif

37
include/nx/ipc/usb_comms_new.h Executable file
View file

@ -0,0 +1,37 @@
/**
* @file usb_comms.h
* @brief USB comms.
* @author yellows8
* @author plutoo
* @copyright libnx Authors
*/
#pragma once
#include <switch/types.h>
#ifdef __cplusplus
extern "C" {
#endif
/// Initializes usbComms with the default number of interfaces (1)
Result usbCommsInitialize(void);
/// Initializes usbComms with a specific number of interfaces.
Result usbCommsInitializeEx(u32 num_interfaces);
/// Exits usbComms.
void usbCommsExit(void);
/// Read data with the default interface.
size_t usbCommsRead(void* buffer, size_t size);
/// Write data with the default interface.
size_t usbCommsWrite(const void* buffer, size_t size);
/// Same as usbCommsRead except with the specified interface.
size_t usbCommsReadEx(void* buffer, size_t size, u32 interface);
/// Same as usbCommsWrite except with the specified interface.
size_t usbCommsWriteEx(const void* buffer, size_t size, u32 interface);
#ifdef __cplusplus
}
#endif

265
include/nx/ipc/usb_new.h Executable file
View file

@ -0,0 +1,265 @@
/**
* @file usb.h
* @brief USB (usb:*) service IPC wrapper.
* @author SciresM, yellows8
* @copyright libnx Authors
*/
#pragma once
#include <switch/types.h>
#include <switch/services/sm.h>
#include <switch/kernel/event.h>
#ifdef __cplusplus
extern "C" {
#endif
/// usb:ds Switch-as-device<>host USB comms, see also here: http://switchbrew.org/index.php?title=USB_services
/// Names starting with "libusb" were changed to "usb" to avoid collision with actual libusb if it's ever used.
#define USBDS_DEFAULT_InterfaceNumber 0x4 ///Value for usb_interface_descriptor bInterfaceNumber for automatically allocating the actual bInterfaceNumber.
/// Imported from libusb with changed names.
/* Descriptor sizes per descriptor type */
#define USB_DT_INTERFACE_SIZE 9
#define USB_DT_ENDPOINT_SIZE 7
#define USB_DT_DEVICE_SIZE 0x12
#define USB_DT_SS_ENDPOINT_COMPANION_SIZE 6
/// Imported from libusb, with some adjustments.
struct usb_endpoint_descriptor {
uint8_t bLength;
uint8_t bDescriptorType; /// Must match USB_DT_ENDPOINT.
uint8_t bEndpointAddress; /// Should be one of the usb_endpoint_direction values, the endpoint-number is automatically allocated.
uint8_t bmAttributes;
uint16_t wMaxPacketSize;
uint8_t bInterval;
};
/// Imported from libusb, with some adjustments.
struct usb_interface_descriptor {
uint8_t bLength;
uint8_t bDescriptorType; /// Must match USB_DT_INTERFACE.
uint8_t bInterfaceNumber; /// See also USBDS_DEFAULT_InterfaceNumber.
uint8_t bAlternateSetting; /// Must match 0.
uint8_t bNumEndpoints;
uint8_t bInterfaceClass;
uint8_t bInterfaceSubClass;
uint8_t bInterfaceProtocol;
uint8_t iInterface; /// Ignored.
};
/// Imported from libusb, with some adjustments.
struct usb_device_descriptor {
uint8_t bLength;
uint8_t bDescriptorType; /// Must match USB_DT_Device.
uint16_t bcdUSB;
uint8_t bDeviceClass;
uint8_t bDeviceSubClass;
uint8_t bDeviceProtocol;
uint8_t bMaxPacketSize0;
uint16_t idVendor;
uint16_t idProduct;
uint16_t bcdDevice;
uint8_t iManufacturer;
uint8_t iProduct;
uint8_t iSerialNumber;
uint8_t bNumConfigurations;
};
/// Imported from libusb, with some adjustments.
struct usb_ss_endpoint_companion_descriptor {
uint8_t bLength;
uint8_t bDescriptorType; /// Must match USB_DT_SS_ENDPOINT_COMPANION.
uint8_t bMaxBurst;
uint8_t bmAttributes;
uint16_t wBytesPerInterval;
};
/// Imported from libusb, with some adjustments.
struct usb_string_descriptor {
uint8_t bLength;
uint8_t bDescriptorType; /// Must match USB_DT_STRING.
uint16_t wData[0x40];
};
typedef struct {
u16 idVendor; /// VID
u16 idProduct; /// PID
u16 bcdDevice;
char Manufacturer[0x20];
char Product[0x20];
char SerialNumber[0x20];
} UsbDsDeviceInfo;
typedef struct {
u32 id; /// urbId from post-buffer cmds
u32 requestedSize;
u32 transferredSize;
u32 urb_status;
} UsbDsReportEntry;
typedef struct {
UsbDsReportEntry report[8];
u32 report_count;
} UsbDsReportData;
typedef struct {
bool initialized;
u32 interface_index;
Service h;
Event SetupEvent;
Event CtrlInCompletionEvent;
Event CtrlOutCompletionEvent;
} UsbDsInterface;
typedef struct {
bool initialized;
Service h;
Event CompletionEvent;
} UsbDsEndpoint;
typedef enum {
UsbComplexId_Default = 0x2
} UsbComplexId;
typedef enum {
UsbDeviceSpeed_Full = 0x2,
UsbDeviceSpeed_High = 0x3,
UsbDeviceSpeed_Super = 0x4,
} UsbDeviceSpeed;
/// Imported from libusb, with changed names.
enum usb_class_code {
USB_CLASS_PER_INTERFACE = 0,
USB_CLASS_AUDIO = 1,
USB_CLASS_COMM = 2,
USB_CLASS_HID = 3,
USB_CLASS_PHYSICAL = 5,
USB_CLASS_PRINTER = 7,
USB_CLASS_PTP = 6, /* legacy name from libusb-0.1 usb.h */
USB_CLASS_IMAGE = 6,
USB_CLASS_MASS_STORAGE = 8,
USB_CLASS_HUB = 9,
USB_CLASS_DATA = 10,
USB_CLASS_SMART_CARD = 0x0b,
USB_CLASS_CONTENT_SECURITY = 0x0d,
USB_CLASS_VIDEO = 0x0e,
USB_CLASS_PERSONAL_HEALTHCARE = 0x0f,
USB_CLASS_DIAGNOSTIC_DEVICE = 0xdc,
USB_CLASS_WIRELESS = 0xe0,
USB_CLASS_APPLICATION = 0xfe,
USB_CLASS_VENDOR_SPEC = 0xff
};
/// Imported from libusb, with changed names.
enum usb_descriptor_type {
USB_DT_DEVICE = 0x01,
USB_DT_CONFIG = 0x02,
USB_DT_STRING = 0x03,
USB_DT_INTERFACE = 0x04,
USB_DT_ENDPOINT = 0x05,
USB_DT_BOS = 0x0f,
USB_DT_DEVICE_CAPABILITY = 0x10,
USB_DT_HID = 0x21,
USB_DT_REPORT = 0x22,
USB_DT_PHYSICAL = 0x23,
USB_DT_HUB = 0x29,
USB_DT_SUPERSPEED_HUB = 0x2a,
USB_DT_SS_ENDPOINT_COMPANION = 0x30
};
/// Imported from libusb, with changed names.
enum usb_endpoint_direction {
USB_ENDPOINT_IN = 0x80,
USB_ENDPOINT_OUT = 0x00
};
/// Imported from libusb, with changed names.
enum usb_transfer_type {
USB_TRANSFER_TYPE_CONTROL = 0,
USB_TRANSFER_TYPE_ISOCHRONOUS = 1,
USB_TRANSFER_TYPE_BULK = 2,
USB_TRANSFER_TYPE_INTERRUPT = 3,
USB_TRANSFER_TYPE_BULK_STREAM = 4,
};
/// Imported from libusb, with changed names.
enum usb_iso_sync_type {
USB_ISO_SYNC_TYPE_NONE = 0,
USB_ISO_SYNC_TYPE_ASYNC = 1,
USB_ISO_SYNC_TYPE_ADAPTIVE = 2,
USB_ISO_SYNC_TYPE_SYNC = 3
};
/// Imported from libusb, with changed names.
enum usb_iso_usage_type {
USB_ISO_USAGE_TYPE_DATA = 0,
USB_ISO_USAGE_TYPE_FEEDBACK = 1,
USB_ISO_USAGE_TYPE_IMPLICIT = 2,
};
/// Opens a session with usb:ds.
Result usbDsInitialize(void);
/// Closes the usb:ds session. Any interfaces/endpoints which are left open are automatically closed, since otherwise usb-sysmodule won't fully reset usb:ds to defaults.
void usbDsExit(void);
/// Helpers
Result usbDsWaitReady(u64 timeout);
Result usbDsParseReportData(UsbDsReportData *reportdata, u32 urbId, u32 *requestedSize, u32 *transferredSize);
/// IDsService
// Do not provide API access to these functions, as they're handled by usbDsInitialize().
// Result usbDsBindDevice(UsbComplexId complexId);
// Result usbDsBindClientProcess(Handle prochandle);
Event* usbDsGetStateChangeEvent(void);
Result usbDsGetState(u32* out);
/// Removed in 5.0.0
Result usbDsGetDsInterface(UsbDsInterface** out, struct usb_interface_descriptor* descriptor, const char* interface_name);
Result usbDsSetVidPidBcd(const UsbDsDeviceInfo* deviceinfo);
/// Added in 5.0.0
Result usbDsRegisterInterface(UsbDsInterface** out, u32 intf_num);
Result usbDsClearDeviceData(void);
Result usbDsAddUsbStringDescriptor(u8* out_index, const char* string);
Result usbDsAddUsbLanguageStringDescriptor(u8* out_index, const u16* lang_ids, u16 num_langs);
Result usbDsDeleteUsbStringDescriptor(u8 index);
Result usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed speed, struct usb_device_descriptor* descriptor);
Result usbDsSetBinaryObjectStore(void* bos, size_t bos_size);
Result usbDsEnable(void);
Result usbDsDisable(void);
/// IDsInterface
void usbDsInterface_Close(UsbDsInterface* interface);
Result usbDsInterface_GetSetupPacket(UsbDsInterface* interface, void* buffer, size_t size);
Result usbDsInterface_EnableInterface(UsbDsInterface* interface);
Result usbDsInterface_DisableInterface(UsbDsInterface* interface);
Result usbDsInterface_CtrlInPostBufferAsync(UsbDsInterface* interface, void* buffer, size_t size, u32* urbId);
Result usbDsInterface_CtrlOutPostBufferAsync(UsbDsInterface* interface, void* buffer, size_t size, u32* urbId);
Result usbDsInterface_GetCtrlInReportData(UsbDsInterface* interface, UsbDsReportData* out);
Result usbDsInterface_GetCtrlOutReportData(UsbDsInterface* interface, UsbDsReportData* out);
Result usbDsInterface_StallCtrl(UsbDsInterface* interface);
/// Removed in 5.0.0
Result usbDsInterface_GetDsEndpoint(UsbDsInterface* interface, UsbDsEndpoint** endpoint, struct usb_endpoint_descriptor* descriptor);
/// Added in 5.0.0
Result usbDsInterface_RegisterEndpoint(UsbDsInterface* interface, UsbDsEndpoint** endpoint, u8 endpoint_address);
Result usbDsInterface_AppendConfigurationData(UsbDsInterface* interface, UsbDeviceSpeed speed, void* buffer, size_t size);
/// IDsEndpoint
void usbDsEndpoint_Close(UsbDsEndpoint* endpoint);
Result usbDsEndpoint_Cancel(UsbDsEndpoint* endpoint);
Result usbDsEndpoint_PostBufferAsync(UsbDsEndpoint* endpoint, void* buffer, size_t size, u32* urbId);
Result usbDsEndpoint_GetReportData(UsbDsEndpoint* endpoint, UsbDsReportData* out);
Result usbDsEndpoint_StallCtrl(UsbDsEndpoint* endpoint);
Result usbDsEndpoint_SetZlt(UsbDsEndpoint* endpoint, bool zlt);
#ifdef __cplusplus
}
#endif

36
include/nx/ncm.hpp Executable file
View file

@ -0,0 +1,36 @@
#pragma once
#include <string>
extern "C"
{
#include <switch/services/fs.h>
#include <switch/services/ncm.h>
}
#include "nx/ipc/tin_ipc.h"
namespace nx::ncm
{
class ContentStorage final
{
private:
NcmContentStorage m_contentStorage;
public:
// Don't allow copying, or garbage may be closed by the destructor
ContentStorage& operator=(const ContentStorage&) = delete;
ContentStorage(const ContentStorage&) = delete;
ContentStorage(FsStorageId storageId);
~ContentStorage();
void CreatePlaceholder(const NcmNcaId &placeholderId, const NcmNcaId &registeredId, size_t size);
void DeletePlaceholder(const NcmNcaId &placeholderId);
void WritePlaceholder(const NcmNcaId &placeholderId, u64 offset, void *buffer, size_t bufSize);
void Register(const NcmNcaId &placeholderId, const NcmNcaId &registeredId);
void Delete(const NcmNcaId &registeredId);
bool Has(const NcmNcaId &registeredId);
std::string GetPath(const NcmNcaId &registeredId);
};
}

14
include/util/file_util.hpp Executable file
View file

@ -0,0 +1,14 @@
#pragma once
#include <string>
#include <tuple>
#include <vector>
#include "nx/content_meta.hpp"
namespace tin::util
{
NcmContentInfo CreateNSPCNMTContentRecord(const std::string& nspPath);
nx::ncm::ContentMeta GetContentMetaFromNCA(const std::string& ncaPath);
std::vector<std::string> GetNSPList();
}

61
include/util/network_util.hpp Executable file
View file

@ -0,0 +1,61 @@
#pragma once
#include <switch/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <vector>
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
namespace tin::network
{
class HTTPHeader
{
private:
std::string m_url;
std::map<std::string, std::string> m_values;
static size_t ParseHTMLHeader(char* bytes, size_t size, size_t numItems, void* userData);
public:
HTTPHeader(std::string url);
void PerformRequest();
bool HasValue(std::string key);
std::string GetValue(std::string key);
};
class HTTPDownload
{
private:
std::string m_url;
HTTPHeader m_header;
bool m_rangesSupported = false;
static size_t ParseHTMLData(char* bytes, size_t size, size_t numItems, void* userData);
public:
HTTPDownload(std::string url);
void BufferDataRange(void* buffer, size_t offset, size_t size, std::function<void (size_t sizeRead)> progressFunc);
void StreamDataRange(size_t offset, size_t size, std::function<size_t (u8* bytes, size_t size)> streamFunc);
};
size_t WaitReceiveNetworkData(int sockfd, void* buf, size_t len);
size_t WaitSendNetworkData(int sockfd, void* buf, size_t len);
}

19
include/util/title_util.hpp Executable file
View file

@ -0,0 +1,19 @@
#pragma once
#include <switch/types.h>
#include <string>
#include "nx/content_meta.hpp"
#include "nx/ipc/tin_ipc.h"
namespace tin::util
{
u64 GetRightsIdTid(RightsId rightsId);
u64 GetRightsIdKeyGen(RightsId rightsId);
std::string GetNcaIdString(const NcmNcaId& ncaId);
NcmNcaId GetNcaIdFromString(std::string ncaIdStr);
u64 GetBaseTitleId(u64 titleId, NcmContentMetaType contentMetaType);
std::string GetBaseTitleName(u64 baseTitleId);
std::string GetTitleName(u64 titleId, NcmContentMetaType contentMetaType);
}

37
include/util/usb_util.hpp Executable file
View file

@ -0,0 +1,37 @@
#pragma once
#include <switch.h>
#include <string>
namespace tin::util
{
enum USBCmdType : u8
{
REQUEST = 0,
RESPONSE = 1
};
struct USBCmdHeader
{
u32 magic;
USBCmdType type;
u8 padding[0x3] = {0};
u32 cmdId;
u64 dataSize;
u8 reserved[0xC] = {0};
} PACKED;
static_assert(sizeof(USBCmdHeader) == 0x20, "USBCmdHeader must be 0x20!");
class USBCmdManager
{
public:
static void SendCmdHeader(u32 cmdId, size_t dataSize);
static void SendExitCmd();
static USBCmdHeader SendFileRangeCmd(std::string nspName, u64 offset, u64 size);
};
size_t USBRead(void* out, size_t len);
size_t USBWrite(const void* in, size_t len);
}

View file

@ -0,0 +1,192 @@
#include "data/buffered_placeholder_writer.hpp"
#include <climits>
#include <math.h>
#include <algorithm>
#include <exception>
#include "error.hpp"
#include "debug.h"
namespace tin::data
{
BufferedPlaceholderWriter::BufferedPlaceholderWriter(nx::ncm::ContentStorage* contentStorage, NcmNcaId ncaId, size_t totalDataSize) :
m_totalDataSize(totalDataSize), m_contentStorage(contentStorage), m_ncaId(ncaId)
{
// Though currently the number of segments is fixed, we want them allocated on the heap, not the stack
m_bufferSegments = std::make_unique<BufferSegment[]>(NUM_BUFFER_SEGMENTS);
if (m_bufferSegments == nullptr)
THROW_FORMAT("Failed to allocated buffer segments!\n");
m_currentFreeSegmentPtr = &m_bufferSegments[m_currentFreeSegment];
m_currentSegmentToWritePtr = &m_bufferSegments[m_currentSegmentToWrite];
}
void BufferedPlaceholderWriter::AppendData(void* source, size_t length)
{
if (m_sizeBuffered + length > m_totalDataSize)
THROW_FORMAT("Cannot append data as it would exceed the expected total.\n");
size_t dataSizeRemaining = length;
u64 sourceOffset = 0;
while (dataSizeRemaining > 0)
{
size_t bufferSegmentSizeRemaining = BUFFER_SEGMENT_DATA_SIZE - m_currentFreeSegmentPtr->writeOffset;
if (m_currentFreeSegmentPtr->isFinalized)
THROW_FORMAT("Current buffer segment is already finalized!\n");
if (dataSizeRemaining < bufferSegmentSizeRemaining)
{
memcpy(m_currentFreeSegmentPtr->data + m_currentFreeSegmentPtr->writeOffset, (u8*)source + sourceOffset, dataSizeRemaining);
sourceOffset += dataSizeRemaining;
m_currentFreeSegmentPtr->writeOffset += dataSizeRemaining;
dataSizeRemaining = 0;
}
else
{
memcpy(m_currentFreeSegmentPtr->data + m_currentFreeSegmentPtr->writeOffset, (u8*)source + sourceOffset, bufferSegmentSizeRemaining);
dataSizeRemaining -= bufferSegmentSizeRemaining;
sourceOffset += bufferSegmentSizeRemaining;
m_currentFreeSegmentPtr->writeOffset += bufferSegmentSizeRemaining;
m_currentFreeSegmentPtr->isFinalized = true;
m_currentFreeSegment = (m_currentFreeSegment + 1) % NUM_BUFFER_SEGMENTS;
m_currentFreeSegmentPtr = &m_bufferSegments[m_currentFreeSegment];
}
}
m_sizeBuffered += length;
if (m_sizeBuffered == m_totalDataSize)
{
m_currentFreeSegmentPtr->isFinalized = true;
}
}
bool BufferedPlaceholderWriter::CanAppendData(size_t length)
{
if (m_sizeBuffered + length > m_totalDataSize)
return false;
if (!this->IsSizeAvailable(length))
return false;
return true;
}
void BufferedPlaceholderWriter::WriteSegmentToPlaceholder()
{
if (m_sizeWrittenToPlaceholder >= m_totalDataSize)
THROW_FORMAT("Cannot write segment as end of data has already been reached!\n");
if (!m_currentSegmentToWritePtr->isFinalized)
THROW_FORMAT("Cannot write segment as it hasn't been finalized!\n");
// NOTE: The final segment will have leftover data from previous writes, however
// this will be accounted for by this size
size_t sizeToWriteToPlaceholder = std::min(m_totalDataSize - m_sizeWrittenToPlaceholder, BUFFER_SEGMENT_DATA_SIZE);
m_contentStorage->WritePlaceholder(m_ncaId, m_sizeWrittenToPlaceholder, m_currentSegmentToWritePtr->data, sizeToWriteToPlaceholder);
m_currentSegmentToWritePtr->isFinalized = false;
m_currentSegmentToWritePtr->writeOffset = 0;
m_currentSegmentToWrite = (m_currentSegmentToWrite + 1) % NUM_BUFFER_SEGMENTS;
m_currentSegmentToWritePtr = &m_bufferSegments[m_currentSegmentToWrite];
m_sizeWrittenToPlaceholder += sizeToWriteToPlaceholder;
}
bool BufferedPlaceholderWriter::CanWriteSegmentToPlaceholder()
{
if (m_sizeWrittenToPlaceholder >= m_totalDataSize)
return false;
if (!m_currentSegmentToWritePtr->isFinalized)
return false;
return true;
}
u32 BufferedPlaceholderWriter::CalcNumSegmentsRequired(size_t size)
{
//printf("Size: %lu\n", size);
if (m_currentFreeSegmentPtr->isFinalized)
return INT_MAX;
size_t bufferSegmentSizeRemaining = BUFFER_SEGMENT_DATA_SIZE - m_currentFreeSegmentPtr->writeOffset;
//printf("Buffer segment size remaining: %lu\n", bufferSegmentSizeRemaining);
if (size <= bufferSegmentSizeRemaining) return 1;
else
{
double numSegmentsReq = 1 + (double)(size - bufferSegmentSizeRemaining) / (double)BUFFER_SEGMENT_DATA_SIZE;
return ceil(numSegmentsReq);
}
}
bool BufferedPlaceholderWriter::IsSizeAvailable(size_t size)
{
u32 numSegmentsRequired = this->CalcNumSegmentsRequired(size);
if (numSegmentsRequired > NUM_BUFFER_SEGMENTS)
return false;
for (unsigned int i = 0; i < numSegmentsRequired; i++)
{
unsigned int segmentIndex = m_currentFreeSegment + i;
BufferSegment* bufferSegment = &m_bufferSegments[segmentIndex % NUM_BUFFER_SEGMENTS];
if (bufferSegment->isFinalized)
return false;
if (i != 0 && bufferSegment->writeOffset != 0)
THROW_FORMAT("Unexpected non-zero write offset at segment %u (%lu)\n", segmentIndex, bufferSegment->writeOffset);
}
return true;
}
bool BufferedPlaceholderWriter::IsBufferDataComplete()
{
if (m_sizeBuffered > m_totalDataSize)
THROW_FORMAT("Size buffered cannot exceed total data size!\n");
return m_sizeBuffered == m_totalDataSize;
}
bool BufferedPlaceholderWriter::IsPlaceholderComplete()
{
if (m_sizeWrittenToPlaceholder > m_totalDataSize)
THROW_FORMAT("Size written to placeholder cannot exceed total data size!\n");
return m_sizeWrittenToPlaceholder == m_totalDataSize;
}
size_t BufferedPlaceholderWriter::GetTotalDataSize()
{
return m_totalDataSize;
}
size_t BufferedPlaceholderWriter::GetSizeBuffered()
{
return m_sizeBuffered;
}
size_t BufferedPlaceholderWriter::GetSizeWrittenToPlaceholder()
{
return m_sizeWrittenToPlaceholder;
}
void BufferedPlaceholderWriter::DebugPrintBuffers()
{
printf("BufferedPlaceholderWriter Buffers: \n");
for (int i = 0; i < NUM_BUFFER_SEGMENTS; i++)
{
printf("Buffer %u:\n", i);
printBytes(nxlinkout, m_bufferSegments[i].data, BUFFER_SEGMENT_DATA_SIZE, true);
}
}
}

33
source/data/byte_buffer.cpp Executable file
View file

@ -0,0 +1,33 @@
#include "data/byte_buffer.hpp"
#include "error.hpp"
#include "debug.h"
namespace tin::data
{
ByteBuffer::ByteBuffer(size_t reserveSize)
{
m_buffer.resize(reserveSize);
}
size_t ByteBuffer::GetSize()
{
return m_buffer.size();
}
u8* ByteBuffer::GetData()
{
return m_buffer.data();
}
void ByteBuffer::Resize(size_t size)
{
m_buffer.resize(size, 0);
}
void ByteBuffer::DebugPrintContents()
{
fprintf(nxlinkout, "Buffer Size: 0x%lx\n", this->GetSize());
printBytes(nxlinkout, this->GetData(), this->GetSize(), true);
}
}

19
source/data/byte_stream.cpp Executable file
View file

@ -0,0 +1,19 @@
#include "data/byte_stream.hpp"
namespace tin::data
{
BufferedByteStream::BufferedByteStream(ByteBuffer buffer) :
m_byteBuffer(buffer)
{
}
void BufferedByteStream::ReadBytes(void* dest, size_t length)
{
if (m_offset + length > m_byteBuffer.GetSize())
return;
memcpy(dest, m_byteBuffer.GetData() + m_offset, length);
m_offset += length;
}
}

75
source/debug.c Executable file
View file

@ -0,0 +1,75 @@
#include "debug.h"
#include <string.h>
#include <sys/socket.h>
#include <sys/errno.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <switch/runtime/nxlink.h>
#ifdef NXLINK_DEBUG
static int sock = -1;
#endif
FILE *nxlinkout;
int nxLinkInitialize(void)
{
#ifdef NXLINK_DEBUG
int ret = -1;
struct sockaddr_in srv_addr;
sock = socket(AF_INET, SOCK_STREAM, 0);
if (!sock) {
return ret;
}
bzero(&srv_addr, sizeof srv_addr);
srv_addr.sin_family = AF_INET;
srv_addr.sin_addr = __nxlink_host;
srv_addr.sin_port = htons(NXLINK_CLIENT_PORT);
ret = connect(sock, (struct sockaddr *) &srv_addr, sizeof(srv_addr));
if (ret != 0) {
close(sock);
return -1;
}
fflush(nxlinkout);
nxlinkout = fdopen(sock, "w");
setvbuf(nxlinkout, NULL, _IONBF, 0);
#endif
return 0;
}
void nxLinkExit(void)
{
#ifdef NXLINK_DEBUG
fclose(nxlinkout);
#endif
}
void printBytes(FILE* out, u8 *bytes, size_t size, bool includeHeader)
{
if (out == NULL)
return;
int count = 0;
if (includeHeader)
{
fprintf(out, "\n\n00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n");
fprintf(out, "-----------------------------------------------\n");
}
for (int i = 0; i < size; i++)
{
fprintf(out, "%02x ", bytes[i]);
count++;
if ((count % 16) == 0)
fprintf(out, "\n");
}
fprintf(out, "\n");
}

129
source/install/http_nsp.cpp Executable file
View file

@ -0,0 +1,129 @@
#include "install/http_nsp.hpp"
#include <switch.h>
#include <threads.h>
#include "data/buffered_placeholder_writer.hpp"
#include "util/title_util.hpp"
#include "error.hpp"
#include "debug.h"
namespace tin::install::nsp
{
HTTPNSP::HTTPNSP(std::string url) :
m_download(url)
{
}
struct StreamFuncArgs
{
tin::network::HTTPDownload* download;
tin::data::BufferedPlaceholderWriter* bufferedPlaceholderWriter;
u64 pfs0Offset;
u64 ncaSize;
};
int CurlStreamFunc(void* in)
{
StreamFuncArgs* args = reinterpret_cast<StreamFuncArgs*>(in);
auto streamFunc = [&](u8* streamBuf, size_t streamBufSize) -> size_t
{
while (true)
{
if (args->bufferedPlaceholderWriter->CanAppendData(streamBufSize))
break;
}
args->bufferedPlaceholderWriter->AppendData(streamBuf, streamBufSize);
return streamBufSize;
};
args->download->StreamDataRange(args->pfs0Offset, args->ncaSize, streamFunc);
return 0;
}
int PlaceholderWriteFunc(void* in)
{
StreamFuncArgs* args = reinterpret_cast<StreamFuncArgs*>(in);
while (!args->bufferedPlaceholderWriter->IsPlaceholderComplete())
{
if (args->bufferedPlaceholderWriter->CanWriteSegmentToPlaceholder())
args->bufferedPlaceholderWriter->WriteSegmentToPlaceholder();
}
return 0;
}
void HTTPNSP::StreamToPlaceholder(nx::ncm::ContentStorage& contentStorage, NcmNcaId placeholderId)
{
const PFS0FileEntry* fileEntry = this->GetFileEntryByNcaId(placeholderId);
std::string ncaFileName = this->GetFileEntryName(fileEntry);
printf("Retrieving %s\n", ncaFileName.c_str());
size_t ncaSize = fileEntry->fileSize;
tin::data::BufferedPlaceholderWriter bufferedPlaceholderWriter(&contentStorage, placeholderId, ncaSize);
StreamFuncArgs args;
args.download = &m_download;
args.bufferedPlaceholderWriter = &bufferedPlaceholderWriter;
args.pfs0Offset = this->GetDataOffset() + fileEntry->dataOffset;
args.ncaSize = ncaSize;
thrd_t curlThread;
thrd_t writeThread;
thrd_create(&curlThread, CurlStreamFunc, &args);
thrd_create(&writeThread, PlaceholderWriteFunc, &args);
u64 freq = armGetSystemTickFreq();
u64 startTime = armGetSystemTick();
size_t startSizeBuffered = 0;
double speed = 0.0;
consoleUpdate(NULL);
while (!bufferedPlaceholderWriter.IsBufferDataComplete())
{
u64 newTime = armGetSystemTick();
if (newTime - startTime >= freq)
{
size_t newSizeBuffered = bufferedPlaceholderWriter.GetSizeBuffered();
double mbBuffered = (newSizeBuffered / 1000000.0) - (startSizeBuffered / 1000000.0);
double duration = ((double)(newTime - startTime) / (double)freq);
speed = mbBuffered / duration;
startTime = newTime;
startSizeBuffered = newSizeBuffered;
}
u64 totalSizeMB = bufferedPlaceholderWriter.GetTotalDataSize() / 1000000;
u64 downloadSizeMB = bufferedPlaceholderWriter.GetSizeBuffered() / 1000000;
int downloadProgress = (int)(((double)bufferedPlaceholderWriter.GetSizeBuffered() / (double)bufferedPlaceholderWriter.GetTotalDataSize()) * 100.0);
printf("> Download Progress: %lu/%lu MB (%i%s) (%.2f MB/s)\r", downloadSizeMB, totalSizeMB, downloadProgress, "%", speed);
consoleUpdate(NULL);
}
u64 totalSizeMB = bufferedPlaceholderWriter.GetTotalDataSize() / 1000000;
while (!bufferedPlaceholderWriter.IsPlaceholderComplete())
{
u64 installSizeMB = bufferedPlaceholderWriter.GetSizeWrittenToPlaceholder() / 1000000;
int installProgress = (int)(((double)bufferedPlaceholderWriter.GetSizeWrittenToPlaceholder() / (double)bufferedPlaceholderWriter.GetTotalDataSize()) * 100.0);
printf("> Install Progress: %lu/%lu MB (%i%s)\r", installSizeMB, totalSizeMB, installProgress, "%");
consoleUpdate(NULL);
}
thrd_join(curlThread, NULL);
thrd_join(writeThread, NULL);
consoleUpdate(NULL);
}
void HTTPNSP::BufferData(void* buf, off_t offset, size_t size)
{
m_download.BufferDataRange(buf, offset, size, nullptr);
}
}

255
source/install/install.cpp Executable file
View file

@ -0,0 +1,255 @@
#include "install/install.hpp"
#include <switch.h>
#include <cstring>
#include <memory>
#include "error.hpp"
#include "nx/ncm.hpp"
#include "util/title_util.hpp"
// TODO: Check NCA files are present
// TODO: Check tik/cert is present
namespace tin::install
{
Install::Install(FsStorageId destStorageId, bool ignoreReqFirmVersion) :
m_destStorageId(destStorageId), m_ignoreReqFirmVersion(ignoreReqFirmVersion), m_contentMeta()
{
appletSetMediaPlaybackState(true);
}
Install::~Install()
{
appletSetMediaPlaybackState(false);
}
// TODO: Implement RAII on NcmContentMetaDatabase
void Install::InstallContentMetaRecords(tin::data::ByteBuffer& installContentMetaBuf)
{
NcmContentMetaDatabase contentMetaDatabase;
NcmContentMetaKey contentMetaKey = m_contentMeta.GetContentMetaKey();
try
{
ASSERT_OK(ncmOpenContentMetaDatabase(&contentMetaDatabase, m_destStorageId), "Failed to open content meta database");
ASSERT_OK(ncmContentMetaDatabaseSet(&contentMetaDatabase, &contentMetaKey, (NcmContentMetaHeader*)installContentMetaBuf.GetData(), installContentMetaBuf.GetSize()), "Failed to set content records");
ASSERT_OK(ncmContentMetaDatabaseCommit(&contentMetaDatabase), "Failed to commit content records");
}
catch (std::runtime_error& e)
{
serviceClose(&contentMetaDatabase.s);
throw e;
}
serviceClose(&contentMetaDatabase.s);
consoleUpdate(NULL);
}
void Install::InstallApplicationRecord()
{
Result rc = 0;
std::vector<ContentStorageRecord> storageRecords;
u64 baseTitleId = tin::util::GetBaseTitleId(this->GetTitleId(), this->GetContentMetaType());
u32 contentMetaCount = 0;
printf("Base title Id: 0x%lx", baseTitleId);
// TODO: Make custom error with result code field
// 0x410: The record doesn't already exist
if (R_FAILED(rc = nsCountApplicationContentMeta(baseTitleId, &contentMetaCount)) && rc != 0x410)
{
throw std::runtime_error("Failed to count application content meta");
}
rc = 0;
printf("Content meta count: %u\n", contentMetaCount);
// Obtain any existing app record content meta and append it to our vector
if (contentMetaCount > 0)
{
storageRecords.resize(contentMetaCount);
size_t contentStorageBufSize = contentMetaCount * sizeof(ContentStorageRecord);
auto contentStorageBuf = std::make_unique<ContentStorageRecord[]>(contentMetaCount);
u32 entriesRead;
ASSERT_OK(nsListApplicationRecordContentMeta(0, baseTitleId, contentStorageBuf.get(), contentStorageBufSize, &entriesRead), "Failed to list application record content meta");
if (entriesRead != contentMetaCount)
{
throw std::runtime_error("Mismatch between entries read and content meta count");
}
memcpy(storageRecords.data(), contentStorageBuf.get(), contentStorageBufSize);
}
// Add our new content meta
ContentStorageRecord storageRecord;
storageRecord.metaRecord = m_contentMeta.GetContentMetaKey();
storageRecord.storageId = m_destStorageId;
storageRecords.push_back(storageRecord);
// Replace the existing application records with our own
try
{
nsDeleteApplicationRecord(baseTitleId);
}
catch (...) {}
printf("Pushing application record...\n");
ASSERT_OK(nsPushApplicationRecord(baseTitleId, 0x3, storageRecords.data(), storageRecords.size() * sizeof(ContentStorageRecord)), "Failed to push application record");
consoleUpdate(NULL);
}
// Validate and obtain all data needed for install
void Install::Prepare()
{
tin::data::ByteBuffer cnmtBuf;
auto cnmtTuple = this->ReadCNMT();
m_contentMeta = std::get<0>(cnmtTuple);
NcmContentInfo cnmtContentRecord = std::get<1>(cnmtTuple);
nx::ncm::ContentStorage contentStorage(m_destStorageId);
if (!contentStorage.Has(cnmtContentRecord.content_id))
{
printf("Installing CNMT NCA...\n");
this->InstallNCA(cnmtContentRecord.content_id);
}
else
{
printf("CNMT NCA already installed. Proceeding...\n");
}
// Parse data and create install content meta
if (m_ignoreReqFirmVersion)
printf("WARNING: Required system firmware version is being IGNORED!\n");
tin::data::ByteBuffer installContentMetaBuf;
m_contentMeta.GetInstallContentMeta(installContentMetaBuf, cnmtContentRecord, m_ignoreReqFirmVersion);
this->InstallContentMetaRecords(installContentMetaBuf);
this->InstallApplicationRecord();
printf("Installing ticket and cert...\n");
try
{
this->InstallTicketCert();
}
catch (std::runtime_error& e)
{
printf("WARNING: Ticket installation failed! This may not be an issue, depending on your use case.\nProceed with caution!\n");
}
consoleUpdate(NULL);
}
void Install::Begin()
{
printf("Installing NCAs...\n");
consoleUpdate(NULL);
for (auto& record : m_contentMeta.GetContentInfos())
{
printf("Installing from %s\n", tin::util::GetNcaIdString(record.content_id).c_str());
consoleUpdate(NULL);
this->InstallNCA(record.content_id);
}
printf("Post Install Records: \n");
this->DebugPrintInstallData();
}
u64 Install::GetTitleId()
{
return m_contentMeta.GetContentMetaKey().title_id;
}
NcmContentMetaType Install::GetContentMetaType()
{
return static_cast<NcmContentMetaType>(m_contentMeta.GetContentMetaKey().type);
}
void Install::DebugPrintInstallData()
{
#ifdef NXLINK_DEBUG
NcmContentMetaDatabase contentMetaDatabase;
NcmContentMetaKey metaRecord = m_contentMeta.GetContentMetaKey();
u64 baseTitleId = tin::util::GetBaseTitleId(metaRecord.title_id, static_cast<NcmContentMetaType>(metaRecord.type));
u64 updateTitleId = baseTitleId ^ 0x800;
bool hasUpdate = true;
try
{
NcmContentMetaKey latestApplicationContentMetaKey;
NcmContentMetaKey latestPatchContentMetaKey;
ASSERT_OK(ncmOpenContentMetaDatabase(&contentMetaDatabase, m_destStorageId), "Failed to open content meta database");
ASSERT_OK(ncmContentMetaDatabaseGetLatestContentMetaKey(&contentMetaDatabase, &latestApplicationContentMetaKey, baseTitleId), "Failed to get latest application content meta key");
try
{
ASSERT_OK(ncmContentMetaDatabaseGetLatestContentMetaKey(&contentMetaDatabase, &latestPatchContentMetaKey, updateTitleId), "Failed to get latest patch content meta key");
}
catch (std::exception& e)
{
hasUpdate = false;
}
u64 appContentRecordSize;
u64 appContentRecordSizeRead;
ASSERT_OK(ncmContentMetaDatabaseGetSize(&contentMetaDatabase, &appContentRecordSize, &latestApplicationContentMetaKey), "Failed to get application content record size");
auto appContentRecordBuf = std::make_unique<u8[]>(appContentRecordSize);
ASSERT_OK(ncmContentMetaDatabaseGet(&contentMetaDatabase, &latestApplicationContentMetaKey, &appContentRecordSizeRead, (NcmContentMetaHeader*)appContentRecordBuf.get(), appContentRecordSizeRead), "Failed to get app content record size");
if (appContentRecordSize != appContentRecordSizeRead)
{
throw std::runtime_error("Mismatch between app content record size and content record size read");
}
printf("Application content meta key: \n");
printBytes(nxlinkout, (u8*)&latestApplicationContentMetaKey, sizeof(NcmContentMetaKey), true);
printf("Application content meta: \n");
printBytes(nxlinkout, appContentRecordBuf.get(), appContentRecordSize, true);
if (hasUpdate)
{
u64 patchContentRecordsSize;
u64 patchContentRecordSizeRead;
ASSERT_OK(ncmContentMetaDatabaseGetSize(&contentMetaDatabase, &patchContentRecordsSize, &latestPatchContentMetaKey), "Failed to get patch content record size");
auto patchContentRecordBuf = std::make_unique<u8[]>(patchContentRecordsSize);
ASSERT_OK(ncmContentMetaDatabaseGet(&contentMetaDatabase, &latestPatchContentMetaKey, &patchContentRecordSizeRead, (NcmContentMetaHeader*)patchContentRecordBuf.get(), patchContentRecordsSize), "Failed to get patch content record size");
if (patchContentRecordsSize != patchContentRecordSizeRead)
{
throw std::runtime_error("Mismatch between app content record size and content record size read");
}
printf("Patch content meta key: \n");
printBytes(nxlinkout, (u8*)&latestPatchContentMetaKey, sizeof(NcmContentMetaKey), true);
printf("Patch content meta: \n");
printBytes(nxlinkout, patchContentRecordBuf.get(), patchContentRecordsSize, true);
}
else
{
printf("No update records found, or an error occurred.\n");
}
auto appRecordBuf = std::make_unique<u8[]>(0x100);
u32 numEntriesRead;
ASSERT_OK(nsListApplicationRecordContentMeta(0, baseTitleId, appRecordBuf.get(), 0x100, &numEntriesRead), "Failed to list application record content meta");
printf("Application record content meta: \n");
printBytes(nxlinkout, appRecordBuf.get(), 0x100, true);
}
catch (std::runtime_error& e)
{
serviceClose(&contentMetaDatabase.s);
printf("Failed to log install data. Error: %s", e.what());
}
#endif
}
}

136
source/install/install_nsp.cpp Executable file
View file

@ -0,0 +1,136 @@
#include "install/install_nsp.hpp"
#include <cstdlib>
#include <cstring>
#include <stdexcept>
#include <memory>
#include <string>
#include <machine/endian.h>
#include "nx/ncm.hpp"
#include "util/file_util.hpp"
#include "util/title_util.hpp"
#include "debug.h"
#include "error.hpp"
namespace tin::install::nsp
{
NSPInstallTask::NSPInstallTask(tin::install::nsp::SimpleFileSystem& simpleFileSystem, FsStorageId destStorageId, bool ignoreReqFirmVersion) :
Install(destStorageId, ignoreReqFirmVersion), m_simpleFileSystem(&simpleFileSystem)
{
}
std::tuple<nx::ncm::ContentMeta, NcmContentInfo> NSPInstallTask::ReadCNMT()
{
NcmContentInfo cnmtRecord = tin::util::CreateNSPCNMTContentRecord(this->m_simpleFileSystem->m_absoluteRootPath.substr(0, this->m_simpleFileSystem->m_absoluteRootPath.size() - 1));
nx::ncm::ContentStorage contentStorage(m_destStorageId);
this->InstallNCA(cnmtRecord.content_id);
std::string cnmtNCAFullPath = contentStorage.GetPath(cnmtRecord.content_id);
return { tin::util::GetContentMetaFromNCA(cnmtNCAFullPath), cnmtRecord };
}
void NSPInstallTask::InstallTicketCert()
{
// Read the tik file and put it into a buffer
auto tikName = m_simpleFileSystem->GetFileNameFromExtension("", "tik");
printf("> Getting tik size\n");
auto tikFile = m_simpleFileSystem->OpenFile(tikName);
u64 tikSize = tikFile.GetSize();
auto tikBuf = std::make_unique<u8[]>(tikSize);
printf("> Reading tik\n");
tikFile.Read(0x0, tikBuf.get(), tikSize);
// Read the cert file and put it into a buffer
auto certName = m_simpleFileSystem->GetFileNameFromExtension("", "cert");
printf("> Getting cert size\n");
auto certFile = m_simpleFileSystem->OpenFile(certName);
u64 certSize = certFile.GetSize();
auto certBuf = std::make_unique<u8[]>(certSize);
printf("> Reading cert\n");
certFile.Read(0x0, certBuf.get(), certSize);
// Finally, let's actually import the ticket
ASSERT_OK(esImportTicket(tikBuf.get(), tikSize, certBuf.get(), certSize), "Failed to import ticket");
consoleUpdate(NULL);
}
void NSPInstallTask::InstallNCA(const NcmNcaId &ncaId)
{
std::string ncaName = tin::util::GetNcaIdString(ncaId);
if (m_simpleFileSystem->HasFile(ncaName + ".nca"))
ncaName += ".nca";
else if (m_simpleFileSystem->HasFile(ncaName + ".cnmt.nca"))
ncaName += ".cnmt.nca";
else
{
throw std::runtime_error(("Failed to find NCA file " + ncaName + ".nca/.cnmt.nca").c_str());
}
printf("NcaId: %s\n", ncaName.c_str());
printf("Dest storage Id: %u\n", m_destStorageId);
nx::ncm::ContentStorage contentStorage(m_destStorageId);
// Attempt to delete any leftover placeholders
try
{
contentStorage.DeletePlaceholder(ncaId);
}
catch (...) {}
auto ncaFile = m_simpleFileSystem->OpenFile(ncaName);
size_t ncaSize = ncaFile.GetSize();
u64 fileOff = 0;
size_t readSize = 0x400000; // 4MB buff
auto readBuffer = std::make_unique<u8[]>(readSize);
if (readBuffer == NULL)
throw std::runtime_error(("Failed to allocate read buffer for " + ncaName).c_str());
printf("Size: 0x%lx\n", ncaSize);
contentStorage.CreatePlaceholder(ncaId, ncaId, ncaSize);
float progress;
consoleUpdate(NULL);
while (fileOff < ncaSize)
{
// Clear the buffer before we read anything, just to be sure
progress = (float)fileOff / (float)ncaSize;
if (fileOff % (0x400000 * 3) == 0)
printf("> Progress: %lu/%lu MB (%d%s)\r", (fileOff / 1000000), (ncaSize / 1000000), (int)(progress * 100.0), "%");
if (fileOff + readSize >= ncaSize) readSize = ncaSize - fileOff;
ncaFile.Read(fileOff, readBuffer.get(), readSize);
contentStorage.WritePlaceholder(ncaId, fileOff, readBuffer.get(), readSize);
fileOff += readSize;
consoleUpdate(NULL);
}
// Clean up the line for whatever comes next
printf(" \r");
printf("Registering placeholder...\n");
try
{
contentStorage.Register(ncaId, ncaId);
}
catch (...)
{
printf(("Failed to register " + ncaName + ". It may already exist.\n").c_str());
}
try
{
contentStorage.DeletePlaceholder(ncaId);
}
catch (...) {}
consoleUpdate(NULL);
}
}

View file

@ -0,0 +1,124 @@
#include "install/install_nsp_remote.hpp"
#include <machine/endian.h>
#include "nx/fs.hpp"
#include "nx/ncm.hpp"
#include "util/file_util.hpp"
#include "util/title_util.hpp"
#include "debug.h"
#include "error.hpp"
namespace tin::install::nsp
{
RemoteNSPInstall::RemoteNSPInstall(FsStorageId destStorageId, bool ignoreReqFirmVersion, RemoteNSP* remoteNSP) :
Install(destStorageId, ignoreReqFirmVersion), m_remoteNSP(remoteNSP)
{
m_remoteNSP->RetrieveHeader();
}
std::tuple<nx::ncm::ContentMeta, NcmContentInfo> RemoteNSPInstall::ReadCNMT()
{
const PFS0FileEntry* fileEntry = m_remoteNSP->GetFileEntryByExtension("cnmt.nca");
if (fileEntry == nullptr)
THROW_FORMAT("Failed to find cnmt file entry!\n");
std::string cnmtNcaName(m_remoteNSP->GetFileEntryName(fileEntry));
NcmNcaId cnmtContentId = tin::util::GetNcaIdFromString(cnmtNcaName);
size_t cnmtNcaSize = fileEntry->fileSize;
nx::ncm::ContentStorage contentStorage(m_destStorageId);
printf("CNMT Name: %s\n", cnmtNcaName.c_str());
// We install the cnmt nca early to read from it later
this->InstallNCA(cnmtContentId);
std::string cnmtNCAFullPath = contentStorage.GetPath(cnmtContentId);
NcmContentInfo cnmtContentInfo;
cnmtContentInfo.content_id = cnmtContentId;
*(u64*)&cnmtContentInfo.size = cnmtNcaSize & 0xFFFFFFFFFFFF;
cnmtContentInfo.content_type = NcmContentType_Meta;
consoleUpdate(NULL);
return { tin::util::GetContentMetaFromNCA(cnmtNCAFullPath), cnmtContentInfo };
}
void RemoteNSPInstall::InstallNCA(const NcmNcaId& ncaId)
{
const PFS0FileEntry* fileEntry = m_remoteNSP->GetFileEntryByNcaId(ncaId);
std::string ncaFileName = m_remoteNSP->GetFileEntryName(fileEntry);
size_t ncaSize = fileEntry->fileSize;
printf("Installing %s to storage Id %u\n", ncaFileName.c_str(), m_destStorageId);
nx::ncm::ContentStorage contentStorage(m_destStorageId);
// Attempt to delete any leftover placeholders
try
{
contentStorage.DeletePlaceholder(ncaId);
}
catch (...) {}
printf("Size: 0x%lx\n", ncaSize);
contentStorage.CreatePlaceholder(ncaId, ncaId, ncaSize);
m_remoteNSP->StreamToPlaceholder(contentStorage, ncaId);
// Clean up the line for whatever comes next
printf(" \r");
printf("Registering placeholder...\n");
try
{
contentStorage.Register(ncaId, ncaId);
}
catch (...)
{
printf(("Failed to register " + ncaFileName + ". It may already exist.\n").c_str());
}
try
{
contentStorage.DeletePlaceholder(ncaId);
}
catch (...) {}
consoleUpdate(NULL);
}
void RemoteNSPInstall::InstallTicketCert()
{
// Read the tik file and put it into a buffer
const PFS0FileEntry* tikFileEntry = m_remoteNSP->GetFileEntryByExtension("tik");
if (tikFileEntry == nullptr)
{
printf("Remote tik file is missing.\n");
throw std::runtime_error("Remote tik file is not present!");
}
u64 tikSize = tikFileEntry->fileSize;
auto tikBuf = std::make_unique<u8[]>(tikSize);
printf("> Reading tik\n");
m_remoteNSP->BufferData(tikBuf.get(), m_remoteNSP->GetDataOffset() + tikFileEntry->dataOffset, tikSize);
// Read the cert file and put it into a buffer
const PFS0FileEntry* certFileEntry = m_remoteNSP->GetFileEntryByExtension("cert");
if (certFileEntry == nullptr)
{
printf("Remote cert file is missing.\n");
throw std::runtime_error("Remote cert file is not present!");
}
u64 certSize = certFileEntry->fileSize;
auto certBuf = std::make_unique<u8[]>(certSize);
printf("> Reading cert\n");
m_remoteNSP->BufferData(certBuf.get(), m_remoteNSP->GetDataOffset() + certFileEntry->dataOffset, certSize);
// Finally, let's actually import the ticket
ASSERT_OK(esImportTicket(tikBuf.get(), tikSize, certBuf.get(), certSize), "Failed to import ticket");
consoleUpdate(NULL);
}
}

118
source/install/remote_nsp.cpp Executable file
View file

@ -0,0 +1,118 @@
#include "install/remote_nsp.hpp"
#include <threads.h>
#include "data/buffered_placeholder_writer.hpp"
#include "util/title_util.hpp"
#include "error.hpp"
#include "debug.h"
namespace tin::install::nsp
{
RemoteNSP::RemoteNSP()
{
}
// TODO: Do verification: PFS0 magic, sizes not zero
void RemoteNSP::RetrieveHeader()
{
printf("Retrieving remote NSP header...\n");
// Retrieve the base header
m_headerBytes.resize(sizeof(PFS0BaseHeader), 0);
this->BufferData(m_headerBytes.data(), 0x0, sizeof(PFS0BaseHeader));
printf("Base header: \n");
//printBytes(nxlinkout, m_headerBytes.data(), sizeof(PFS0BaseHeader), true);
printf("%.*s",sizeof(PFS0BaseHeader),m_headerBytes.data());
// Retrieve the full header
size_t remainingHeaderSize = this->GetBaseHeader()->numFiles * sizeof(PFS0FileEntry) + this->GetBaseHeader()->stringTableSize;
m_headerBytes.resize(sizeof(PFS0BaseHeader) + remainingHeaderSize, 0);
this->BufferData(m_headerBytes.data() + sizeof(PFS0BaseHeader), sizeof(PFS0BaseHeader), remainingHeaderSize);
printf("Full header: \n");
//printBytes(nxlinkout, m_headerBytes.data(), m_headerBytes.size(), true);
printf("%.*s",m_headerBytes.size(),m_headerBytes.data());
}
const PFS0FileEntry* RemoteNSP::GetFileEntry(unsigned int index)
{
if (index >= this->GetBaseHeader()->numFiles)
THROW_FORMAT("File entry index is out of bounds\n")
size_t fileEntryOffset = sizeof(PFS0BaseHeader) + index * sizeof(PFS0FileEntry);
if (m_headerBytes.size() < fileEntryOffset + sizeof(PFS0FileEntry))
THROW_FORMAT("Header bytes is too small to get file entry!");
return reinterpret_cast<PFS0FileEntry*>(m_headerBytes.data() + fileEntryOffset);
}
const PFS0FileEntry* RemoteNSP::GetFileEntryByExtension(std::string extension)
{
for (unsigned int i = 0; i < this->GetBaseHeader()->numFiles; i++)
{
const PFS0FileEntry* fileEntry = this->GetFileEntry(i);
std::string name(this->GetFileEntryName(fileEntry));
auto foundExtension = name.substr(name.find(".") + 1);
if (foundExtension == extension)
return fileEntry;
}
return nullptr;
}
const PFS0FileEntry* RemoteNSP::GetFileEntryByName(std::string name)
{
for (unsigned int i = 0; i < this->GetBaseHeader()->numFiles; i++)
{
const PFS0FileEntry* fileEntry = this->GetFileEntry(i);
std::string foundName(this->GetFileEntryName(fileEntry));
if (foundName == name)
return fileEntry;
}
return nullptr;
}
const PFS0FileEntry* RemoteNSP::GetFileEntryByNcaId(const NcmNcaId& ncaId)
{
const PFS0FileEntry* fileEntry = nullptr;
std::string ncaIdStr = tin::util::GetNcaIdString(ncaId);
if ((fileEntry = this->GetFileEntryByName(ncaIdStr + ".nca")) == nullptr)
{
if ((fileEntry = this->GetFileEntryByName(ncaIdStr + ".cnmt.nca")) == nullptr)
{
return nullptr;
}
}
return fileEntry;
}
const char* RemoteNSP::GetFileEntryName(const PFS0FileEntry* fileEntry)
{
u64 stringTableStart = sizeof(PFS0BaseHeader) + this->GetBaseHeader()->numFiles * sizeof(PFS0FileEntry);
return reinterpret_cast<const char*>(m_headerBytes.data() + stringTableStart + fileEntry->stringTableOffset);
}
const PFS0BaseHeader* RemoteNSP::GetBaseHeader()
{
if (m_headerBytes.empty())
THROW_FORMAT("Cannot retrieve header as header bytes are empty. Have you retrieved it yet?\n");
return reinterpret_cast<PFS0BaseHeader*>(m_headerBytes.data());
}
u64 RemoteNSP::GetDataOffset()
{
if (m_headerBytes.empty())
THROW_FORMAT("Cannot get data offset as header is empty. Have you retrieved it yet?\n");
return m_headerBytes.size();
}
}

View file

@ -0,0 +1,67 @@
#include "install/simple_filesystem.hpp"
#include <exception>
#include <memory>
#include "nx/fs.hpp"
#include "error.hpp"
namespace tin::install::nsp
{
SimpleFileSystem::SimpleFileSystem(nx::fs::IFileSystem& fileSystem, std::string rootPath, std::string absoluteRootPath) :
m_fileSystem(&fileSystem) , m_rootPath(rootPath), m_absoluteRootPath(absoluteRootPath)
{}
SimpleFileSystem::~SimpleFileSystem() {}
nx::fs::IFile SimpleFileSystem::OpenFile(std::string path)
{
return m_fileSystem->OpenFile(m_rootPath + path);
}
bool SimpleFileSystem::HasFile(std::string path)
{
try
{
printf(("Attempting to find file at " + m_rootPath + path + "\n").c_str());
m_fileSystem->OpenFile(m_rootPath + path);
return true;
}
catch (std::exception& e) {}
return false;
}
std::string SimpleFileSystem::GetFileNameFromExtension(std::string path, std::string extension)
{
nx::fs::IDirectory dir = m_fileSystem->OpenDirectory(m_rootPath + path, FsDirOpenMode_ReadFiles | FsDirOpenMode_ReadDirs);
u64 entryCount = dir.GetEntryCount();
auto dirEntries = std::make_unique<FsDirectoryEntry[]>(entryCount);
dir.Read(0, dirEntries.get(), entryCount);
for (unsigned int i = 0; i < entryCount; i++)
{
FsDirectoryEntry dirEntry = dirEntries[i];
std::string dirEntryName = dirEntry.name;
if (dirEntry.type == FsDirEntryType_Dir)
{
auto subdirPath = path + dirEntryName + "/";
auto subdirFound = this->GetFileNameFromExtension(subdirPath, extension);
if (subdirFound != "")
return subdirFound;
continue;
}
else if (dirEntry.type == FsDirEntryType_File)
{
auto foundExtension = dirEntryName.substr(dirEntryName.find(".") + 1);
if (foundExtension == extension)
return dirEntryName;
}
}
return "";
}
}

149
source/install/usb_nsp.cpp Executable file
View file

@ -0,0 +1,149 @@
#include "install/usb_nsp.hpp"
#include <switch.h>
#include <algorithm>
#include <malloc.h>
#include <threads.h>
#include "data/byte_buffer.hpp"
#include "data/buffered_placeholder_writer.hpp"
#include "util/usb_util.hpp"
#include "error.hpp"
#include "debug.h"
namespace tin::install::nsp
{
USBNSP::USBNSP(std::string nspName) :
m_nspName(nspName)
{
}
struct USBFuncArgs
{
std::string nspName;
tin::data::BufferedPlaceholderWriter* bufferedPlaceholderWriter;
u64 pfs0Offset;
u64 ncaSize;
};
int USBThreadFunc(void* in)
{
USBFuncArgs* args = reinterpret_cast<USBFuncArgs*>(in);
tin::util::USBCmdHeader header = tin::util::USBCmdManager::SendFileRangeCmd(args->nspName, args->pfs0Offset, args->ncaSize);
u8* buf = (u8*)memalign(0x1000, 0x800000);
u64 sizeRemaining = header.dataSize;
size_t tmpSizeRead = 0;
try
{
while (sizeRemaining)
{
tmpSizeRead = usbCommsRead(buf, std::min(sizeRemaining, (u64)0x800000));
//printf("Read bytes\n")
//printBytes(nxlinkout, buf, tmpSizeRead, true);
sizeRemaining -= tmpSizeRead;
while (true)
{
if (args->bufferedPlaceholderWriter->CanAppendData(tmpSizeRead))
break;
}
args->bufferedPlaceholderWriter->AppendData(buf, tmpSizeRead);
}
}
catch (std::exception& e)
{
printf("An error occurred:\n%s\n", e.what());
printf("An error occurred:\n%s", e.what());
}
free(buf);
return 0;
}
int USBPlaceholderWriteFunc(void* in)
{
USBFuncArgs* args = reinterpret_cast<USBFuncArgs*>(in);
while (!args->bufferedPlaceholderWriter->IsPlaceholderComplete())
{
if (args->bufferedPlaceholderWriter->CanWriteSegmentToPlaceholder())
args->bufferedPlaceholderWriter->WriteSegmentToPlaceholder();
}
return 0;
}
void USBNSP::StreamToPlaceholder(nx::ncm::ContentStorage& contentStorage, NcmNcaId placeholderId)
{
const PFS0FileEntry* fileEntry = this->GetFileEntryByNcaId(placeholderId);
std::string ncaFileName = this->GetFileEntryName(fileEntry);
printf("Retrieving %s\n", ncaFileName.c_str());
size_t ncaSize = fileEntry->fileSize;
tin::data::BufferedPlaceholderWriter bufferedPlaceholderWriter(&contentStorage, placeholderId, ncaSize);
USBFuncArgs args;
args.nspName = m_nspName;
args.bufferedPlaceholderWriter = &bufferedPlaceholderWriter;
args.pfs0Offset = this->GetDataOffset() + fileEntry->dataOffset;
args.ncaSize = ncaSize;
thrd_t usbThread;
thrd_t writeThread;
thrd_create(&usbThread, USBThreadFunc, &args);
thrd_create(&writeThread, USBPlaceholderWriteFunc, &args);
u64 freq = armGetSystemTickFreq();
u64 startTime = armGetSystemTick();
size_t startSizeBuffered = 0;
double speed = 0.0;
while (!bufferedPlaceholderWriter.IsBufferDataComplete())
{
u64 newTime = armGetSystemTick();
if (newTime - startTime >= freq)
{
size_t newSizeBuffered = bufferedPlaceholderWriter.GetSizeBuffered();
double mbBuffered = (newSizeBuffered / 1000000.0) - (startSizeBuffered / 1000000.0);
double duration = ((double)(newTime - startTime) / (double)freq);
speed = mbBuffered / duration;
startTime = newTime;
startSizeBuffered = newSizeBuffered;
}
u64 totalSizeMB = bufferedPlaceholderWriter.GetTotalDataSize() / 1000000;
u64 downloadSizeMB = bufferedPlaceholderWriter.GetSizeBuffered() / 1000000;
int downloadProgress = (int)(((double)bufferedPlaceholderWriter.GetSizeBuffered() / (double)bufferedPlaceholderWriter.GetTotalDataSize()) * 100.0);
printf("> Download Progress: %lu/%lu MB (%i%s) (%.2f MB/s)\r", downloadSizeMB, totalSizeMB, downloadProgress, "%", speed);
consoleUpdate(NULL);
}
u64 totalSizeMB = bufferedPlaceholderWriter.GetTotalDataSize() / 1000000;
while (!bufferedPlaceholderWriter.IsPlaceholderComplete())
{
u64 installSizeMB = bufferedPlaceholderWriter.GetSizeWrittenToPlaceholder() / 1000000;
int installProgress = (int)(((double)bufferedPlaceholderWriter.GetSizeWrittenToPlaceholder() / (double)bufferedPlaceholderWriter.GetTotalDataSize()) * 100.0);
printf("> Install Progress: %lu/%lu MB (%i%s)\r", installSizeMB, totalSizeMB, installProgress, "%");
consoleUpdate(NULL);
}
thrd_join(usbThread, NULL);
thrd_join(writeThread, NULL);
}
void USBNSP::BufferData(void* buf, off_t offset, size_t size)
{
tin::util::USBCmdHeader header = tin::util::USBCmdManager::SendFileRangeCmd(m_nspName, offset, size);
tin::util::USBRead(buf, header.dataSize);
}
}

183
source/install/verify_nsp.cpp Executable file
View file

@ -0,0 +1,183 @@
#include "install/verify_nsp.hpp"
#include <exception>
#include <string>
#include <tuple>
#include <switch.h>
#include "install/simple_filesystem.hpp"
#include "install/verify_nsp.hpp"
#include "nx/fs.hpp"
#include "nx/ncm.hpp"
#include "util/file_util.hpp"
#include "util/title_util.hpp"
#ifdef __VERIFY_NSP__
namespace tin::install
{
NSPVerifier::NSPVerifier(std::string nspPath) :
m_nspPath(nspPath)
{
}
bool NSPVerifier::PerformVerification()
{
bool isComplete = true;
nx::fs::IFileSystem nspFileSystem;
try
{
nspFileSystem.OpenFileSystemWithId(m_nspPath, FsFileSystemType_ApplicationPackage, 0);
}
catch (std::exception& e)
{
this->PrintCritical("NSP is invalid and cannot be opened! Error: " + std::string(e.what()));
printf("> NSP Path: %s\n", m_nspPath.c_str());
return false;
}
this->PrintSuccess("PFS0 structure is valid");
tin::install::nsp::SimpleFileSystem simpleFS(nspFileSystem, "/", m_nspPath + "/");
std::string cnmtNCAPath;
NcmContentInfo cnmtContentRecord;
nx::ncm::ContentMeta contentMeta;
try
{
std::tie(cnmtNCAPath, cnmtContentRecord) = tin::util::GetCNMTNCAInfo(m_nspPath);
}
catch (std::exception& e)
{
this->PrintCritical("Failed to find CNMT NCA. Error: " + std::string(e.what()));
return false;
}
try
{
contentMeta = tin::util::GetContentMetaFromNCA(cnmtNCAPath);
}
catch (std::exception& e)
{
this->PrintCritical("Content meta could not be read. Error: " + std::string(e.what()));
return false;
}
this->PrintSuccess("Successfully read content meta.");
if (!simpleFS.HasFile(tin::util::GetNcaIdString(cnmtContentRecord.content_id) + ".cnmt.xml"))
this->PrintWarning("CNMT XML is absent!");
else
this->PrintSuccess("CNMT XML is present.");
auto contentMetaHeader = contentMeta.GetContentMetaHeader();
u64 titleId = tin::util::GetBaseTitleId(contentMetaHeader.titleId, contentMetaHeader.type);
for (NcmContentInfo contentRecord : contentMeta.GetContentRecords())
{
std::string ncaIdStr = tin::util::GetNcaIdString(contentRecord.content_id);
std::string ncaName = ncaIdStr + ".nca";
std::string xmlName = ncaIdStr + ".";
if (simpleFS.HasFile(ncaName))
this->PrintSuccess(ncaName + " is present.");
else
{
this->PrintCritical(ncaName + " is missing!");
return false;
}
// control, legalinfo
switch (contentRecord.contentType)
{
case NcmContentType_Program:
xmlName += "programinfo.xml";
break;
case NcmContentType_Data:
xmlName = "";
break;
case NcmContentType_Control:
try
{
nx::fs::IFileSystem controlFileSystem;
controlFileSystem.OpenFileSystemWithId(m_nspPath + "/" + ncaName, FsFileSystemType_ContentControl, titleId);
}
catch (std::exception& e)
{
this->PrintCritical("Control NCA could not be read. Error: " + std::string(e.what()));
return false;
}
this->PrintSuccess("Control NCA is valid.");
xmlName += "nacp.xml";
break;
case NcmContentType_HtmlDocument:
xmlName += "htmldocument.xml";
break;
case NcmContentType_LegalInformation:
try
{
nx::fs::IFileSystem legalInfoFileSystem;
legalInfoFileSystem.OpenFileSystemWithId(m_nspPath + "/" + ncaName, FsFileSystemType_ContentManual, titleId);
}
catch (std::exception& e)
{
this->PrintCritical("Legal information NCA could not be read. Error: " + std::string(e.what()));
return false;
}
this->PrintSuccess("Legal information NCA is valid.");
xmlName += "legalinfo.xml";
break;
// We ignore delta fragments (for now) since they aren't all included,
// and we don't install them
case NcmContentType_DeltaFragment:
continue;
default:
this->PrintCritical("Unrecognized content type!");
return false;
}
if (xmlName == "")
continue;
if (simpleFS.HasFile(xmlName))
this->PrintSuccess(xmlName + " is present.");
else
{
this->PrintWarning(xmlName + " is missing!");
isComplete = false;
}
}
if (isComplete)
this->PrintSuccess("NSP is valid.");
else
this->PrintWarning("NSP is installable, but incomplete.");
return true;
}
void NSPVerifier::PrintCritical(std::string text)
{
printf("[%sCRITICAL%s] %s\n", CONSOLE_RED, CONSOLE_RESET, text.c_str());
}
void NSPVerifier::PrintWarning(std::string text)
{
printf("[%sWARNING%s] %s\n", CONSOLE_YELLOW, CONSOLE_RESET, text.c_str());
}
void NSPVerifier::PrintSuccess(std::string text)
{
printf("[%sOK%s] %s\n", CONSOLE_GREEN, CONSOLE_RESET, text.c_str());
}
}
#endif

250
source/netInstall.cpp Executable file
View file

@ -0,0 +1,250 @@
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sstream>
#include <curl/curl.h>
#include <switch.h>
#include "nx/ipc/tin_ipc.h"
#include "util/network_util.hpp"
#include "install/install_nsp_remote.hpp"
#include "install/http_nsp.hpp"
#include "install/install.hpp"
#include "debug.h"
#include "error.hpp"
const unsigned int MAX_URL_SIZE = 1024;
const unsigned int MAX_URLS = 256;
const int REMOTE_INSTALL_PORT = 2000;
static int m_serverSocket = 0;
static int m_clientSocket = 0;
std::vector<std::string> m_urls;
FsStorageId m_destStorageId = FsStorageId_SdCard;
void InitializeServerSocket() try
{
// Create a socket
m_serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (m_serverSocket < -1)
{
THROW_FORMAT("Failed to create a server socket. Error code: %u\n", errno);
}
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(REMOTE_INSTALL_PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(m_serverSocket, (struct sockaddr*) &server, sizeof(server)) < 0)
{
THROW_FORMAT("Failed to bind server socket. Error code: %u\n", errno);
}
// Set as non-blocking
fcntl(m_serverSocket, F_SETFL, fcntl(m_serverSocket, F_GETFL, 0) | O_NONBLOCK);
if (listen(m_serverSocket, 5) < 0)
{
THROW_FORMAT("Failed to listen on server socket. Error code: %u\n", errno);
}
}
catch (std::exception& e)
{
printf("Failed to initialize server socket!\n");
fprintf(stdout, "%s", e.what());
if (m_serverSocket != 0)
{
close(m_serverSocket);
m_serverSocket = 0;
}
}
void OnUnwound()
{
printf("unwinding view\n");
if (m_clientSocket != 0)
{
close(m_clientSocket);
m_clientSocket = 0;
}
curl_global_cleanup();
#ifndef NXLINK_DEBUG
socketExit();
#endif
}
void OnDestinationSelected()
{
printf("Setting storage device to sd card automatically\n");
m_destStorageId = FsStorageId_SdCard;
//if (destStr == translate(Translate::NAND))
//{
// m_destStorageId = FsStorageId_NandUser;
//}
for (auto& url : m_urls)
{
tin::install::nsp::HTTPNSP httpNSP(url);
printf("%s %s\n", "NSP_INSTALL_FROM", url.c_str());
// second var is ignoring required version
tin::install::nsp::RemoteNSPInstall install(m_destStorageId, true, &httpNSP);
printf("%s\n", "NSP_INSTALL_PREPARING");
install.Prepare();
printf("Pre Install Records: \n");
install.DebugPrintInstallData();
install.Begin();
printf("Post Install Records: \n");
install.DebugPrintInstallData();
printf("\n");
}
printf("%s\n", "NSP_INSTALL_NETWORK_SENDING_ACK");
// Send 1 byte ack to close the server
u8 ack = 0;
tin::network::WaitSendNetworkData(m_clientSocket, &ack, sizeof(u8));
printf("\n %s", "Done! Press B to stop");
}
void OnNSPSelected(std::string ourUrl)
{
printf("Selecting first nsp automatically\n");
m_urls.push_back(ourUrl);
//select storage type
OnDestinationSelected();
}
void OnSelected()
{
try
{
#ifndef NXLINK_DEBUG
ASSERT_OK(socketInitializeDefault(), "Failed to initialize socket");
#endif
ASSERT_OK(curl_global_init(CURL_GLOBAL_ALL), "Curl failed to initialized");
// Initialize the server socket if it hasn't already been
if (m_serverSocket == 0)
{
InitializeServerSocket();
if (m_serverSocket <= 0)
{
THROW_FORMAT("Server socket failed to initialize.\n");
}
}
struct in_addr addr = {(in_addr_t) gethostid()};
printf("%s %s\n", "Switch IP is ", inet_ntoa(addr));
printf("%s\n", "Waiting for network");
printf("%s\n", "B to cancel");
std::vector<std::string> urls;
bool canceled = false;
// Do this now because otherwise we won't get an opportunity whilst waiting
// in the loop
//consoleUpdate(NULL);
while (true)
{
// Break on input pressed
hidScanInput();
u64 kDown = hidKeysDown(CONTROLLER_P1_AUTO);
//consoleUpdate(NULL);
if (kDown & KEY_B)
{
canceled = true;
break;
}
struct sockaddr_in client;
socklen_t clientLen = sizeof(client);
m_clientSocket = accept(m_serverSocket, (struct sockaddr*)&client, &clientLen);
if (m_clientSocket >= 0)
{
printf("%s\n", "NSP_INSTALL_NETWORK_ACCEPT");
u32 size = 0;
tin::network::WaitReceiveNetworkData(m_clientSocket, &size, sizeof(u32));
size = ntohl(size);
printf("Received url buf size: 0x%x\n", size);
if (size > MAX_URL_SIZE * MAX_URLS)
{
THROW_FORMAT("URL size %x is too large!\n", size);
}
// Make sure the last string is null terminated
auto urlBuf = std::make_unique<char[]>(size+1);
memset(urlBuf.get(), 0, size+1);
tin::network::WaitReceiveNetworkData(m_clientSocket, urlBuf.get(), size);
// Split the string up into individual URLs
std::stringstream urlStream(urlBuf.get());
std::string segment;
std::string nspExt = ".nsp";
while (std::getline(urlStream, segment, '\n'))
{
if (segment.compare(segment.size() - nspExt.size(), nspExt.size(), nspExt) == 0)
urls.push_back(segment);
}
break;
}
else if (errno != EAGAIN)
{
THROW_FORMAT("Failed to open client socket with code %u\n", errno);
}
}
if (!canceled)
{
OnNSPSelected(urls[0]);
}
}
catch (std::runtime_error& e)
{
printf("Failed to perform remote install!\n");
printf("%s", e.what());
fprintf(stdout, "%s", e.what());
}
}
namespace netInstStuff{
bool installNspLan () {
plInitialize();
setInitialize();
ncmInitialize();
nsInitialize();
nsextInitialize();
esInitialize();
OnSelected();
plExit();
setExit();
ncmExit();
nsExit();
nsextExit();
esExit();
printf("Exited install...\n");
return true;
}
}

108
source/nx/content_meta.cpp Executable file
View file

@ -0,0 +1,108 @@
#include "nx/content_meta.hpp"
#include <string.h>
#include "util/title_util.hpp"
#include "debug.h"
#include "error.hpp"
namespace nx::ncm
{
ContentMeta::ContentMeta()
{
m_bytes.Resize(sizeof(PackagedContentMetaHeader));
}
ContentMeta::ContentMeta(u8* data, size_t size) :
m_bytes(size)
{
if (size < sizeof(PackagedContentMetaHeader))
throw std::runtime_error("Content meta data size is too small!");
m_bytes.Resize(size);
memcpy(m_bytes.GetData(), data, size);
}
PackagedContentMetaHeader ContentMeta::GetPackagedContentMetaHeader()
{
return m_bytes.Read<PackagedContentMetaHeader>(0);
}
NcmContentMetaKey ContentMeta::GetContentMetaKey()
{
NcmContentMetaKey metaRecord;
PackagedContentMetaHeader contentMetaHeader = this->GetPackagedContentMetaHeader();
memset(&metaRecord, 0, sizeof(NcmContentMetaKey));
metaRecord.title_id = contentMetaHeader.title_id;
metaRecord.version = contentMetaHeader.version;
metaRecord.type = static_cast<NcmContentMetaType>(contentMetaHeader.type);
return metaRecord;
}
// TODO: Cache this
std::vector<NcmContentInfo> ContentMeta::GetContentInfos()
{
PackagedContentMetaHeader contentMetaHeader = this->GetPackagedContentMetaHeader();
std::vector<NcmContentInfo> contentInfos;
PackagedContentInfo* packagedContentInfos = (PackagedContentInfo*)(m_bytes.GetData() + sizeof(PackagedContentMetaHeader) + contentMetaHeader.extended_header_size);
for (unsigned int i = 0; i < contentMetaHeader.content_count; i++)
{
PackagedContentInfo packagedContentInfo = packagedContentInfos[i];
// Don't install delta fragments. Even patches don't seem to install them.
if (static_cast<u8>(packagedContentInfo.content_info.content_type) <= 5)
{
contentInfos.push_back(packagedContentInfo.content_info);
}
}
return contentInfos;
}
void ContentMeta::GetInstallContentMeta(tin::data::ByteBuffer& installContentMetaBuffer, NcmContentInfo& cnmtNcmContentInfo, bool ignoreReqFirmVersion)
{
PackagedContentMetaHeader packagedContentMetaHeader = this->GetPackagedContentMetaHeader();
std::vector<NcmContentInfo> contentInfos = this->GetContentInfos();
// Setup the content meta header
NcmContentMetaHeader contentMetaHeader;
contentMetaHeader.extended_header_size = packagedContentMetaHeader.extended_header_size;
contentMetaHeader.content_count = contentInfos.size() + 1; // Add one for the cnmt content record
contentMetaHeader.content_meta_count = packagedContentMetaHeader.content_meta_count;
installContentMetaBuffer.Append<NcmContentMetaHeader>(contentMetaHeader);
// Setup the meta extended header
printf("Install content meta pre size: 0x%lx\n", installContentMetaBuffer.GetSize());
installContentMetaBuffer.Resize(installContentMetaBuffer.GetSize() + contentMetaHeader.extended_header_size);
printf("Install content meta post size: 0x%lx\n", installContentMetaBuffer.GetSize());
auto* extendedHeaderSourceBytes = m_bytes.GetData() + sizeof(PackagedContentMetaHeader);
u8* installExtendedHeaderStart = installContentMetaBuffer.GetData() + sizeof(NcmContentMetaHeader);
memcpy(installExtendedHeaderStart, extendedHeaderSourceBytes, contentMetaHeader.extended_header_size);
// Optionally disable the required system version field
if (ignoreReqFirmVersion && (packagedContentMetaHeader.type == NcmContentMetaType_Application || packagedContentMetaHeader.type == NcmContentMetaType_Patch))
{
installContentMetaBuffer.Write<u32>(0, sizeof(NcmContentMetaHeader) + 8);
}
// Setup cnmt content record
installContentMetaBuffer.Append<NcmContentInfo>(cnmtNcmContentInfo);
// Setup the content records
for (auto& contentInfo : contentInfos)
{
installContentMetaBuffer.Append<NcmContentInfo>(contentInfo);
}
if (packagedContentMetaHeader.type == NcmContentMetaType_Patch)
{
NcmPatchMetaExtendedHeader* patchMetaExtendedHeader = (NcmPatchMetaExtendedHeader*)extendedHeaderSourceBytes;
installContentMetaBuffer.Resize(installContentMetaBuffer.GetSize() + patchMetaExtendedHeader->extended_data_size);
}
}
}

135
source/nx/fs.cpp Executable file
View file

@ -0,0 +1,135 @@
#include "nx/fs.hpp"
#include <cstring>
#include "error.hpp"
namespace nx::fs
{
// IFile
IFile::IFile(FsFile& file)
{
m_file = file;
}
IFile::~IFile()
{
fsFileClose(&m_file);
}
void IFile::Read(u64 offset, void* buf, size_t size)
{
u64 sizeRead;
ASSERT_OK(fsFileRead(&m_file, offset, buf, size, FsReadOption_None, &sizeRead), "Failed to read file");
if (sizeRead != size)
{
std::string msg = "Size read " + std::string("" + sizeRead) + " doesn't match expected size " + std::string("" + size);
throw std::runtime_error(msg.c_str());
}
}
u64 IFile::GetSize()
{
u64 sizeOut;
ASSERT_OK(fsFileGetSize(&m_file, &sizeOut), "Failed to get file size");
return sizeOut;
}
// End IFile
// IDirectory
IDirectory::IDirectory(FsDir& dir)
{
m_dir = dir;
}
IDirectory::~IDirectory()
{
fsDirClose(&m_dir);
}
void IDirectory::Read(u64 inval, FsDirectoryEntry* buf, size_t numEntries)
{
size_t entriesRead;
ASSERT_OK(fsDirRead(&m_dir, inval, &entriesRead, numEntries, buf), "Failed to read directory");
/*if (entriesRead != numEntries)
{
std::string msg = "Entries read " + std::string("" + entriesRead) + " doesn't match expected number " + std::string("" + numEntries);
throw std::runtime_error(msg);
}*/
}
u64 IDirectory::GetEntryCount()
{
u64 entryCount = 0;
ASSERT_OK(fsDirGetEntryCount(&m_dir, &entryCount), "Failed to get entry count");
return entryCount;
}
// End IDirectory
IFileSystem::IFileSystem() {}
IFileSystem::~IFileSystem()
{
this->CloseFileSystem();
}
Result IFileSystem::OpenSdFileSystem()
{
ASSERT_OK(fsMountSdcard(&m_fileSystem), "Failed to mount sd card");
return 0;
}
void IFileSystem::OpenFileSystemWithId(std::string path, FsFileSystemType fileSystemType, u64 titleId)
{
Result rc = 0;
if (path.length() >= FS_MAX_PATH)
throw std::runtime_error("Directory path is too long!");
// libnx expects a FS_MAX_PATH-sized buffer
path.reserve(FS_MAX_PATH);
std::string errorMsg = "Failed to open file system with id: " + path;
rc = fsOpenFileSystemWithId(&m_fileSystem, titleId, fileSystemType, path.c_str());
if (rc == 0x236e02)
errorMsg = "File " + path + " is unreadable! You may have a bad dump, fs_mitm may need to be removed, or your firmware version may be too low to decrypt it.";
ASSERT_OK(rc, errorMsg.c_str());
}
void IFileSystem::CloseFileSystem()
{
fsFsClose(&m_fileSystem);
}
IFile IFileSystem::OpenFile(std::string path)
{
if (path.length() >= FS_MAX_PATH)
throw std::runtime_error("Directory path is too long!");
// libnx expects a FS_MAX_PATH-sized buffer
path.reserve(FS_MAX_PATH);
FsFile file;
ASSERT_OK(fsFsOpenFile(&m_fileSystem, path.c_str(), FsOpenMode_Read, &file), ("Failed to open file " + path).c_str());
return IFile(file);
}
IDirectory IFileSystem::OpenDirectory(std::string path, int flags)
{
// Account for null at the end of c strings
if (path.length() >= FS_MAX_PATH)
throw std::runtime_error("Directory path is too long!");
// libnx expects a FS_MAX_PATH-sized buffer
path.reserve(FS_MAX_PATH);
FsDir dir;
ASSERT_OK(fsFsOpenDirectory(&m_fileSystem, path.c_str(), flags, &dir), ("Failed to open directory " + path).c_str());
return IDirectory(dir);
}
}

303
source/nx/ipc/es.c Executable file
View file

@ -0,0 +1,303 @@
#include "nx/ipc/es.h"
#include <string.h>
#include <switch.h>
#include <switch/arm/atomics.h>
static Service g_esSrv;
static u64 g_esRefCnt;
Result esInitialize() {
atomicIncrement64(&g_esRefCnt);
Result rc = smGetService(&g_esSrv, "es");
return rc;
}
void esExit() {
if (atomicDecrement64(&g_esRefCnt) == 0) {
serviceClose(&g_esSrv);
}
}
Result esImportTicket(void const *tikBuf, size_t tikSize, void const *certBuf, size_t certSize)
{
IpcCommand c;
ipcInitialize(&c);
ipcAddSendBuffer(&c, tikBuf, tikSize, BufferType_Normal);
ipcAddSendBuffer(&c, certBuf, certSize, BufferType_Normal);
struct {
u64 magic;
u64 cmd_id;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 1;
Result rc = serviceIpcDispatch(&g_esSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
}
return rc;
}
Result esDeleteTicket(const RightsId *rightsIdBuf, size_t bufSize) {
IpcCommand c;
ipcInitialize(&c);
ipcAddSendBuffer(&c, rightsIdBuf, bufSize, BufferType_Normal);
struct {
u64 magic;
u64 cmd_id;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 3;
Result rc = serviceIpcDispatch(&g_esSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
}
return rc;
}
Result esGetTitleKey(const RightsId *rightsId, u8 *outBuf, size_t bufSize) {
IpcCommand c;
ipcInitialize(&c);
ipcAddRecvBuffer(&c, outBuf, bufSize, BufferType_Normal);
struct {
u64 magic;
u64 cmd_id;
RightsId rights_id;
u32 key_generation;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 8;
raw->key_generation = 0;
memcpy(&raw->rights_id, rightsId, sizeof(RightsId));
Result rc = serviceIpcDispatch(&g_esSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
}
return rc;
}
Result esCountCommonTicket(u32 *numTickets)
{
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 9;
Result rc = serviceIpcDispatch(&g_esSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
u32 num_tickets;
} *resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc)) {
if (numTickets) *numTickets = resp->num_tickets;
}
}
return rc;
}
Result esCountPersonalizedTicket(u32 *numTickets)
{
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 10;
Result rc = serviceIpcDispatch(&g_esSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
u32 num_tickets;
} *resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc)) {
if (numTickets) *numTickets = resp->num_tickets;
}
}
return rc;
}
Result esListCommonTicket(u32 *numRightsIdsWritten, RightsId *outBuf, size_t bufSize) {
IpcCommand c;
ipcInitialize(&c);
ipcAddRecvBuffer(&c, outBuf, bufSize, BufferType_Normal);
struct {
u64 magic;
u64 cmd_id;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 11;
Result rc = serviceIpcDispatch(&g_esSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
u32 num_rights_ids_written;
} *resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc)) {
if (numRightsIdsWritten) *numRightsIdsWritten = resp->num_rights_ids_written;
}
}
return rc;
}
Result esListPersonalizedTicket(u32 *numRightsIdsWritten, RightsId *outBuf, size_t bufSize) {
IpcCommand c;
ipcInitialize(&c);
ipcAddRecvBuffer(&c, outBuf, bufSize, BufferType_Normal);
struct {
u64 magic;
u64 cmd_id;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 12;
Result rc = serviceIpcDispatch(&g_esSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
u32 num_rights_ids_written;
} *resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc)) {
if (numRightsIdsWritten) *numRightsIdsWritten = resp->num_rights_ids_written;
}
}
return rc;
}
Result esGetCommonTicketData(u64 *unkOut, void *outBuf1, size_t bufSize1, const RightsId* rightsId)
{
IpcCommand c;
ipcInitialize(&c);
ipcAddRecvBuffer(&c, outBuf1, bufSize1, BufferType_Normal);
struct {
u64 magic;
u64 cmd_id;
RightsId rights_id;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 16;
memcpy(&raw->rights_id, rightsId, sizeof(RightsId));
Result rc = serviceIpcDispatch(&g_esSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
u64 unk;
} *resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc)) {
if (unkOut) *unkOut = resp->unk;
}
}
return rc;
}

679
source/nx/ipc/ns_ext.c Executable file
View file

@ -0,0 +1,679 @@
#include "nx/ipc/ns_ext.h"
#include <stdio.h>
#include <string.h>
#include <switch.h>
#include <switch/arm/atomics.h>
static Service g_nsAppManSrv, g_nsGetterSrv;
static u64 g_nsRefCnt;
static Result _nsGetInterface(Service* srv_out, u64 cmd_id);
Result nsextInitialize(void)
{
Result rc=0;
atomicIncrement64(&g_nsRefCnt);
if (serviceIsActive(&g_nsGetterSrv) || serviceIsActive(&g_nsAppManSrv))
return 0;
if(!kernelAbove300())
return smGetService(&g_nsAppManSrv, "ns:am");
rc = smGetService(&g_nsGetterSrv, "ns:am2");//TODO: Support the other services?(Only useful when ns:am2 isn't accessible)
if (R_FAILED(rc)) return rc;
rc = _nsGetInterface(&g_nsAppManSrv, 7996);
if (R_FAILED(rc)) serviceClose(&g_nsGetterSrv);
return rc;
}
void nsextExit(void)
{
if (atomicDecrement64(&g_nsRefCnt) == 0) {
serviceClose(&g_nsAppManSrv);
if(!kernelAbove300()) return;
serviceClose(&g_nsGetterSrv);
}
}
static Result _nsGetInterface(Service* srv_out, u64 cmd_id) {
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = cmd_id;
Result rc = serviceIpcDispatch(&g_nsGetterSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc)) {
serviceCreate(srv_out, r.Handles[0]);
}
}
return rc;
}
Result nsPushApplicationRecord(u64 title_id, u8 last_modified_event, ContentStorageRecord *content_records_buf, size_t buf_size)
{
IpcCommand c;
ipcInitialize(&c);
ipcAddSendBuffer(&c, content_records_buf, buf_size, 0);
struct {
u64 magic;
u64 cmd_id;
u8 last_modified_event;
u8 padding[0x7];
u64 title_id;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 16;
raw->last_modified_event = last_modified_event;
raw->title_id = title_id;
Result rc = serviceIpcDispatch(&g_nsAppManSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
}
return rc;
}
Result nsCalculateApplicationOccupiedSize(u64 titleID, void *out_buf)
{
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 titleID;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 11;
raw->titleID = titleID;
Result rc = serviceIpcDispatch(&g_nsAppManSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
u8 out[0x80];
} *resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc)) {
memcpy(out_buf, resp->out, 0x80);
}
}
return rc;
}
Result nsListApplicationRecordContentMeta(u64 offset, u64 titleID, void *out_buf, size_t out_buf_size, u32 *entries_read_out)
{
IpcCommand c;
ipcInitialize(&c);
ipcAddRecvBuffer(&c, out_buf, out_buf_size, 0);
struct {
u64 magic;
u64 cmd_id;
u64 offset;
u64 titleID;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 17;
raw->titleID = titleID;
raw->offset = offset;
Result rc = serviceIpcDispatch(&g_nsAppManSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
u32 entries_read;
} *resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc)) {
if (entries_read_out) *entries_read_out = resp->entries_read;
}
}
return rc;
}
Result nsTouchApplication(u64 titleID)
{
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 titleID;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 904;
raw->titleID = titleID;
Result rc = serviceIpcDispatch(&g_nsAppManSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
u32 entries_read;
} *resp = r.Raw;
rc = resp->result;
}
return rc;
}
Result nsDeleteApplicationRecord(u64 titleID)
{
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 titleID;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 27;
raw->titleID = titleID;
Result rc = serviceIpcDispatch(&g_nsAppManSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
u32 entries_read;
} *resp = r.Raw;
rc = resp->result;
}
return rc;
}
Result nsLaunchApplication(u64 titleID)
{
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 titleID;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 19;
raw->titleID = titleID;
Result rc = serviceIpcDispatch(&g_nsAppManSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
}
return rc;
}
Result nsPushLaunchVersion(u64 titleID, u32 version)
{
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 title_id;
u32 version;
u32 padding;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 36;
raw->title_id = titleID;
raw->version = version;
Result rc = serviceIpcDispatch(&g_nsAppManSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
}
return rc;
}
Result nsCheckApplicationLaunchVersion(u64 titleID)
{
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 title_id;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 38;
raw->title_id = titleID;
Result rc = serviceIpcDispatch(&g_nsAppManSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
}
return rc;
}
Result nsCountApplicationContentMeta(u64 titleId, u32* countOut)
{
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 title_id;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 600;
raw->title_id = titleId;
Result rc = serviceIpcDispatch(&g_nsAppManSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
u32 count;
} *resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc)) {
if (countOut) *countOut = resp->count;
}
}
return rc;
}
Result nsGetContentMetaStorage(const NcmContentMetaKey *record, u8 *out)
{
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
NcmContentMetaKey metaRecord;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 606;
memcpy(&raw->metaRecord, record, sizeof(NcmContentMetaKey));
Result rc = serviceIpcDispatch(&g_nsAppManSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
u8 out;
} *resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc)) {
if (out) *out = resp->out;
}
}
return rc;
}
Result nsBeginInstallApplication(u64 tid, u32 unk, u8 storageId) {
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u32 storageId;
u32 unk;
u64 tid;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 26;
raw->storageId = storageId;
raw->unk = unk;
raw->tid = tid;
Result rc = serviceIpcDispatch(&g_nsAppManSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
}
return rc;
}
Result nsInvalidateAllApplicationControlCache(void) {
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 401;
Result rc = serviceIpcDispatch(&g_nsAppManSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
}
return rc;
}
Result nsInvalidateApplicationControlCache(u64 tid) {
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 tid;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 404;
raw->tid = tid;
Result rc = serviceIpcDispatch(&g_nsAppManSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
}
return rc;
}
Result nsCheckApplicationLaunchRights(u64 tid)
{
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 tid;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 39;
raw->tid = tid;
Result rc = serviceIpcDispatch(&g_nsAppManSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
}
return rc;
}
Result nsGetApplicationContentPath(u64 tid, u8 type, char *out, size_t buf_size) {
IpcCommand c;
ipcInitialize(&c);
ipcAddRecvBuffer(&c, out, buf_size, 0);
struct {
u64 magic;
u64 cmd_id;
u8 padding[0x7];
u8 type;
u64 tid;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 21;
raw->type = type;
raw->tid = tid;
Result rc = serviceIpcDispatch(&g_nsAppManSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
}
return rc;
}
Result nsDisableApplicationAutoUpdate(u64 titleID)
{
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 title_id;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 903;
raw->title_id = titleID;
Result rc = serviceIpcDispatch(&g_nsAppManSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
}
return rc;
}
Result nsWithdrawApplicationUpdateRequest(u64 titleId)
{
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 title_id;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 907;
raw->title_id = titleId;
Result rc = serviceIpcDispatch(&g_nsAppManSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
}
return rc;
}

579
source/nx/ipc/usb_comms_new.c Executable file
View file

@ -0,0 +1,579 @@
#include "nx/ipc/usb_new.h"
#include "nx/ipc/usb_comms_new.h"
#include <string.h>
#include <malloc.h>
#include <switch/types.h>
#include <switch/result.h>
#include <switch/kernel/detect.h>
#include <switch/kernel/rwlock.h>
#include <switch/services/fatal.h>
#define TOTAL_INTERFACES 4
typedef struct {
RwLock lock, lock_in, lock_out;
bool initialized;
UsbDsInterface* interface;
UsbDsEndpoint *endpoint_in, *endpoint_out;
u8 *endpoint_in_buffer, *endpoint_out_buffer;
} usbCommsInterface;
static bool g_usbCommsInitialized = false;
static usbCommsInterface g_usbCommsInterfaces[TOTAL_INTERFACES];
static RwLock g_usbCommsLock;
static Result _usbCommsInterfaceInit1x(u32 intf_ind);
static Result _usbCommsInterfaceInit5x(u32 intf_ind);
static Result _usbCommsInterfaceInit(u32 intf_ind);
static Result _usbCommsWrite(usbCommsInterface *interface, const void* buffer, size_t size, size_t *transferredSize);
Result usbCommsInitializeEx(u32 num_interfaces)
{
Result rc = 0;
rwlockWriteLock(&g_usbCommsLock);
if (g_usbCommsInitialized) {
rc = MAKERESULT(Module_Libnx, LibnxError_AlreadyInitialized);
} else if (num_interfaces > TOTAL_INTERFACES) {
rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory);
} else {
rc = usbDsInitialize();
if (R_SUCCEEDED(rc)) {
if (kernelAbove500()) {
u8 iManufacturer, iProduct, iSerialNumber;
static const u16 supported_langs[1] = {0x0409};
// Send language descriptor
rc = usbDsAddUsbLanguageStringDescriptor(NULL, supported_langs, sizeof(supported_langs)/sizeof(u16));
// Send manufacturer
if (R_SUCCEEDED(rc)) rc = usbDsAddUsbStringDescriptor(&iManufacturer, "Switchbrew");
// Send product
if (R_SUCCEEDED(rc)) rc = usbDsAddUsbStringDescriptor(&iProduct, "libnx USB comms");
// Send serial number
if (R_SUCCEEDED(rc)) rc = usbDsAddUsbStringDescriptor(&iSerialNumber, "SerialNumber");
// Send device descriptors
struct usb_device_descriptor device_descriptor = {
.bLength = USB_DT_DEVICE_SIZE,
.bDescriptorType = USB_DT_DEVICE,
.bcdUSB = 0x0110,
.bDeviceClass = 0x00,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = 0x40,
.idVendor = 0x057e,
.idProduct = 0x3000,
.bcdDevice = 0x0100,
.iManufacturer = iManufacturer,
.iProduct = iProduct,
.iSerialNumber = iSerialNumber,
.bNumConfigurations = 0x01
};
if (R_SUCCEEDED(rc)) rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Full, &device_descriptor);
// High Speed is USB 2.0
device_descriptor.bcdUSB = 0x0200;
if (R_SUCCEEDED(rc)) rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_High, &device_descriptor);
// Super Speed is USB 3.0
device_descriptor.bcdUSB = 0x0300;
// Upgrade packet size to 512
device_descriptor.bMaxPacketSize0 = 0x09;
if (R_SUCCEEDED(rc)) rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Super, &device_descriptor);
// Define Binary Object Store
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, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00
};
if (R_SUCCEEDED(rc)) rc = usbDsSetBinaryObjectStore(bos, sizeof(bos));
}
if (R_SUCCEEDED(rc)) {
for (u32 i = 0; i < num_interfaces; i++) {
usbCommsInterface *intf = &g_usbCommsInterfaces[i];
rwlockWriteLock(&intf->lock);
rwlockWriteLock(&intf->lock_in);
rwlockWriteLock(&intf->lock_out);
rc = _usbCommsInterfaceInit(i);
rwlockWriteUnlock(&intf->lock_out);
rwlockWriteUnlock(&intf->lock_in);
rwlockWriteUnlock(&intf->lock);
if (R_FAILED(rc)) {
break;
}
}
}
}
if (R_SUCCEEDED(rc) && kernelAbove500()) {
rc = usbDsEnable();
}
if (R_FAILED(rc)) {
usbCommsExit();
}
}
if (R_SUCCEEDED(rc)) g_usbCommsInitialized = true;
rwlockWriteUnlock(&g_usbCommsLock);
return rc;
}
Result usbCommsInitialize(void)
{
return usbCommsInitializeEx(1);
}
static void _usbCommsInterfaceFree(usbCommsInterface *interface)
{
rwlockWriteLock(&interface->lock);
if (!interface->initialized) {
rwlockWriteUnlock(&interface->lock);
return;
}
rwlockWriteLock(&interface->lock_in);
rwlockWriteLock(&interface->lock_out);
interface->initialized = 0;
interface->endpoint_in = NULL;
interface->endpoint_out = NULL;
interface->interface = NULL;
free(interface->endpoint_in_buffer);
free(interface->endpoint_out_buffer);
interface->endpoint_in_buffer = NULL;
interface->endpoint_out_buffer = NULL;
rwlockWriteUnlock(&interface->lock_out);
rwlockWriteUnlock(&interface->lock_in);
rwlockWriteUnlock(&interface->lock);
}
void usbCommsExit(void)
{
u32 i;
rwlockWriteLock(&g_usbCommsLock);
usbDsExit();
g_usbCommsInitialized = false;
rwlockWriteUnlock(&g_usbCommsLock);
for (i=0; i<TOTAL_INTERFACES; i++)
{
_usbCommsInterfaceFree(&g_usbCommsInterfaces[i]);
}
}
static Result _usbCommsInterfaceInit(u32 intf_ind)
{
if (kernelAbove500()) {
return _usbCommsInterfaceInit5x(intf_ind);
} else {
return _usbCommsInterfaceInit1x(intf_ind);
}
}
static Result _usbCommsInterfaceInit5x(u32 intf_ind)
{
Result rc = 0;
u32 ep_num = intf_ind + 1;
usbCommsInterface *interface = &g_usbCommsInterfaces[intf_ind];
struct usb_interface_descriptor interface_descriptor = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = intf_ind,
.bNumEndpoints = 2,
.bInterfaceClass = 0xFF,
.bInterfaceSubClass = 0xFF,
.bInterfaceProtocol = 0xFF,
};
struct usb_endpoint_descriptor endpoint_descriptor_in = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_ENDPOINT_IN + ep_num,
.bmAttributes = USB_TRANSFER_TYPE_BULK,
.wMaxPacketSize = 0x40,
};
struct usb_endpoint_descriptor endpoint_descriptor_out = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_ENDPOINT_OUT + ep_num,
.bmAttributes = USB_TRANSFER_TYPE_BULK,
.wMaxPacketSize = 0x40,
};
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,
};
interface->initialized = 1;
//The buffer for PostBufferAsync commands must be 0x1000-byte aligned.
interface->endpoint_in_buffer = memalign(0x1000, 0x1000);
if (interface->endpoint_in_buffer==NULL) rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory);
if (R_SUCCEEDED(rc)) {
interface->endpoint_out_buffer = memalign(0x1000, 0x1000);
if (interface->endpoint_out_buffer==NULL) rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory);
}
if (R_SUCCEEDED(rc)) {
memset(interface->endpoint_in_buffer, 0, 0x1000);
memset(interface->endpoint_out_buffer, 0, 0x1000);
}
if (R_FAILED(rc)) return rc;
rc = usbDsRegisterInterface(&interface->interface, interface_descriptor.bInterfaceNumber);
if (R_FAILED(rc)) return rc;
// Full Speed Config
rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Full, &interface_descriptor, USB_DT_INTERFACE_SIZE);
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Full, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Full, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc)) return rc;
// High Speed Config
endpoint_descriptor_in.wMaxPacketSize = 0x200;
endpoint_descriptor_out.wMaxPacketSize = 0x200;
rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_High, &interface_descriptor, USB_DT_INTERFACE_SIZE);
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_High, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_High, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc)) return rc;
// Super Speed Config
endpoint_descriptor_in.wMaxPacketSize = 0x400;
endpoint_descriptor_out.wMaxPacketSize = 0x400;
rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Super, &interface_descriptor, USB_DT_INTERFACE_SIZE);
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Super, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE);
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Super, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE);
if (R_FAILED(rc)) return rc;
//Setup endpoints.
rc = usbDsInterface_RegisterEndpoint(interface->interface, &interface->endpoint_in, endpoint_descriptor_in.bEndpointAddress);
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_RegisterEndpoint(interface->interface, &interface->endpoint_out, endpoint_descriptor_out.bEndpointAddress);
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_EnableInterface(interface->interface);
if (R_FAILED(rc)) return rc;
return rc;
}
static Result _usbCommsInterfaceInit1x(u32 intf_ind)
{
Result rc = 0;
u32 ep_num = intf_ind + 1;
usbCommsInterface *interface = &g_usbCommsInterfaces[intf_ind];
struct usb_interface_descriptor interface_descriptor = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = intf_ind,
.bInterfaceClass = 0xFF,
.bInterfaceSubClass = 0xFF,
.bInterfaceProtocol = 0xFF,
};
struct usb_endpoint_descriptor endpoint_descriptor_in = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_ENDPOINT_IN + ep_num,
.bmAttributes = USB_TRANSFER_TYPE_BULK,
.wMaxPacketSize = 0x200,
};
struct usb_endpoint_descriptor endpoint_descriptor_out = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_ENDPOINT_OUT + ep_num,
.bmAttributes = USB_TRANSFER_TYPE_BULK,
.wMaxPacketSize = 0x200,
};
interface->initialized = 1;
//The buffer for PostBufferAsync commands must be 0x1000-byte aligned.
interface->endpoint_in_buffer = memalign(0x1000, 0x1000);
if (interface->endpoint_in_buffer==NULL) rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory);
if (R_SUCCEEDED(rc)) {
interface->endpoint_out_buffer = memalign(0x1000, 0x1000);
if (interface->endpoint_out_buffer==NULL) rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory);
}
if (R_SUCCEEDED(rc)) {
memset(interface->endpoint_in_buffer, 0, 0x1000);
memset(interface->endpoint_out_buffer, 0, 0x1000);
}
if (R_FAILED(rc)) return rc;
//Setup interface.
rc = usbDsGetDsInterface(&interface->interface, &interface_descriptor, "usb");
if (R_FAILED(rc)) return rc;
//Setup endpoints.
rc = usbDsInterface_GetDsEndpoint(interface->interface, &interface->endpoint_in, &endpoint_descriptor_in);//device->host
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_GetDsEndpoint(interface->interface, &interface->endpoint_out, &endpoint_descriptor_out);//host->device
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_EnableInterface(interface->interface);
if (R_FAILED(rc)) return rc;
return rc;
}
static Result _usbCommsRead(usbCommsInterface *interface, void* buffer, size_t size, size_t *transferredSize)
{
Result rc=0;
u32 urbId=0;
u8 *bufptr = (u8*)buffer;
u8 *transfer_buffer = NULL;
u8 transfer_type=0;
u32 chunksize=0;
u32 tmp_transferredSize = 0;
size_t total_transferredSize=0;
UsbDsReportData reportdata;
//Makes sure endpoints are ready for data-transfer / wait for init if needed.
rc = usbDsWaitReady(U64_MAX);
if (R_FAILED(rc)) return rc;
while(size)
{
if(((u64)bufptr) & 0xfff)//When bufptr isn't page-aligned copy the data into g_usbComms_endpoint_in_buffer and transfer that, otherwise use the bufptr directly.
{
transfer_buffer = interface->endpoint_out_buffer;
memset(interface->endpoint_out_buffer, 0, 0x1000);
chunksize = 0x1000;
chunksize-= ((u64)bufptr) & 0xfff;//After this transfer, bufptr will be page-aligned(if size is large enough for another transfer).
if (size<chunksize) chunksize = size;
transfer_type = 0;
}
else
{
transfer_buffer = bufptr;
chunksize = size;
transfer_type = 1;
}
//Start a host->device transfer.
rc = usbDsEndpoint_PostBufferAsync(interface->endpoint_out, transfer_buffer, chunksize, &urbId);
if (R_FAILED(rc)) return rc;
//Wait for the transfer to finish.
eventWait(&interface->endpoint_out->CompletionEvent, U64_MAX);
eventClear(&interface->endpoint_out->CompletionEvent);
rc = usbDsEndpoint_GetReportData(interface->endpoint_out, &reportdata);
if (R_FAILED(rc)) return rc;
rc = usbDsParseReportData(&reportdata, urbId, NULL, &tmp_transferredSize);
if (R_FAILED(rc)) return rc;
if (tmp_transferredSize > chunksize) tmp_transferredSize = chunksize;
total_transferredSize+= (size_t)tmp_transferredSize;
if (transfer_type==0) memcpy(bufptr, transfer_buffer, tmp_transferredSize);
bufptr+= tmp_transferredSize;
size-= tmp_transferredSize;
if(tmp_transferredSize < chunksize)break;
}
if (transferredSize) *transferredSize = total_transferredSize;
return rc;
}
static Result _usbCommsWrite(usbCommsInterface *interface, const void* buffer, size_t size, size_t *transferredSize)
{
Result rc=0;
u32 urbId=0;
u32 chunksize=0;
u8 *bufptr = (u8*)buffer;
u8 *transfer_buffer = NULL;
u32 tmp_transferredSize = 0;
size_t total_transferredSize=0;
UsbDsReportData reportdata;
//Makes sure endpoints are ready for data-transfer / wait for init if needed.
rc = usbDsWaitReady(U64_MAX);
if (R_FAILED(rc)) return rc;
while(size)
{
if(((u64)bufptr) & 0xfff)//When bufptr isn't page-aligned copy the data into g_usbComms_endpoint_in_buffer and transfer that, otherwise use the bufptr directly.
{
transfer_buffer = interface->endpoint_in_buffer;
memset(interface->endpoint_in_buffer, 0, 0x1000);
chunksize = 0x1000;
chunksize-= ((u64)bufptr) & 0xfff;//After this transfer, bufptr will be page-aligned(if size is large enough for another transfer).
if (size<chunksize) chunksize = size;
memcpy(interface->endpoint_in_buffer, bufptr, chunksize);
}
else
{
transfer_buffer = bufptr;
chunksize = size;
}
//Start a device->host transfer.
rc = usbDsEndpoint_PostBufferAsync(interface->endpoint_in, transfer_buffer, chunksize, &urbId);
if(R_FAILED(rc))return rc;
//Wait for the transfer to finish.
eventWait(&interface->endpoint_in->CompletionEvent, U64_MAX);
eventClear(&interface->endpoint_in->CompletionEvent);
rc = usbDsEndpoint_GetReportData(interface->endpoint_in, &reportdata);
if (R_FAILED(rc)) return rc;
rc = usbDsParseReportData(&reportdata, urbId, NULL, &tmp_transferredSize);
if (R_FAILED(rc)) return rc;
if (tmp_transferredSize > chunksize) tmp_transferredSize = chunksize;
total_transferredSize+= (size_t)tmp_transferredSize;
bufptr+= tmp_transferredSize;
size-= tmp_transferredSize;
if (tmp_transferredSize < chunksize) break;
}
if (transferredSize) *transferredSize = total_transferredSize;
return rc;
}
size_t usbCommsReadEx(void* buffer, size_t size, u32 interface)
{
size_t transferredSize=0;
u32 state=0;
Result rc, rc2;
usbCommsInterface *inter = &g_usbCommsInterfaces[interface];
bool initialized;
if (interface>=TOTAL_INTERFACES) return 0;
rwlockReadLock(&inter->lock);
initialized = inter->initialized;
rwlockReadUnlock(&inter->lock);
if (!initialized) return 0;
rwlockWriteLock(&inter->lock_out);
rc = _usbCommsRead(inter, buffer, size, &transferredSize);
rwlockWriteUnlock(&inter->lock_out);
if (R_FAILED(rc)) {
rc2 = usbDsGetState(&state);
if (R_SUCCEEDED(rc2)) {
if (state!=5) {
rwlockWriteLock(&inter->lock_out);
rc = _usbCommsRead(&g_usbCommsInterfaces[interface], buffer, size, &transferredSize); //If state changed during transfer, try again. usbDsWaitReady() will be called from this.
rwlockWriteUnlock(&inter->lock_out);
}
}
if (R_FAILED(rc)) fatalSimple(MAKERESULT(Module_Libnx, LibnxError_BadUsbCommsRead));
}
return transferredSize;
}
size_t usbCommsRead(void* buffer, size_t size)
{
return usbCommsReadEx(buffer, size, 0);
}
size_t usbCommsWriteEx(const void* buffer, size_t size, u32 interface)
{
size_t transferredSize=0;
u32 state=0;
Result rc, rc2;
usbCommsInterface *inter = &g_usbCommsInterfaces[interface];
bool initialized;
if (interface>=TOTAL_INTERFACES) return 0;
rwlockReadLock(&inter->lock);
initialized = inter->initialized;
rwlockReadUnlock(&inter->lock);
if (!initialized) return 0;
rwlockWriteLock(&inter->lock_in);
rc = _usbCommsWrite(&g_usbCommsInterfaces[interface], buffer, size, &transferredSize);
rwlockWriteUnlock(&inter->lock_in);
if (R_FAILED(rc)) {
rc2 = usbDsGetState(&state);
if (R_SUCCEEDED(rc2)) {
if (state!=5) {
rwlockWriteLock(&inter->lock_in);
rc = _usbCommsWrite(&g_usbCommsInterfaces[interface], buffer, size, &transferredSize); //If state changed during transfer, try again. usbDsWaitReady() will be called from this.
rwlockWriteUnlock(&inter->lock_in);
}
}
if (R_FAILED(rc)) fatalSimple(MAKERESULT(Module_Libnx, LibnxError_BadUsbCommsWrite));
}
return transferredSize;
}
size_t usbCommsWrite(const void* buffer, size_t size)
{
return usbCommsWriteEx(buffer, size, 0);
}

1077
source/nx/ipc/usb_new.c Executable file

File diff suppressed because it is too large Load diff

54
source/nx/ncm.cpp Executable file
View file

@ -0,0 +1,54 @@
#include "nx/ncm.hpp"
#include "error.hpp"
namespace nx::ncm
{
ContentStorage::ContentStorage(FsStorageId storageId)
{
ASSERT_OK(ncmOpenContentStorage(&m_contentStorage, storageId), "Failed to open NCM ContentStorage");
}
ContentStorage::~ContentStorage()
{
serviceClose(&m_contentStorage.s);
}
void ContentStorage::CreatePlaceholder(const NcmNcaId &placeholderId, const NcmNcaId &registeredId, size_t size)
{
ASSERT_OK(ncmContentStorageCreatePlaceHolder(&m_contentStorage, &placeholderId, &registeredId, size), "Failed to create placeholder");
}
void ContentStorage::DeletePlaceholder(const NcmNcaId &placeholderId)
{
ASSERT_OK(ncmContentStorageDeletePlaceHolder(&m_contentStorage, &placeholderId), "Failed to delete placeholder");
}
void ContentStorage::WritePlaceholder(const NcmNcaId &placeholderId, u64 offset, void *buffer, size_t bufSize)
{
ASSERT_OK(ncmContentStorageWritePlaceHolder(&m_contentStorage, &placeholderId, offset, buffer, bufSize), "Failed to write to placeholder");
}
void ContentStorage::Register(const NcmNcaId &placeholderId, const NcmNcaId &registeredId)
{
ASSERT_OK(ncmContentStorageRegister(&m_contentStorage, &registeredId, &placeholderId), "Failed to register placeholder NCA");
}
void ContentStorage::Delete(const NcmNcaId &registeredId)
{
ASSERT_OK(ncmContentStorageDelete(&m_contentStorage, &registeredId), "Failed to delete registered NCA");
}
bool ContentStorage::Has(const NcmNcaId &registeredId)
{
bool hasNCA = false;
ASSERT_OK(ncmContentStorageHas(&m_contentStorage, &hasNCA, &registeredId), "Failed to check if NCA is present");
return hasNCA;
}
std::string ContentStorage::GetPath(const NcmNcaId &registeredId)
{
char pathBuf[FS_MAX_PATH] = {0};
ASSERT_OK(ncmContentStorageGetPath(&m_contentStorage, pathBuf, FS_MAX_PATH, &registeredId), "Failed to get installed NCA path");
return std::string(pathBuf);
}
}

View file

@ -4,6 +4,7 @@
#include "curl.hpp" #include "curl.hpp"
#include "main.hpp" #include "main.hpp"
#include "unzip.hpp" #include "unzip.hpp"
#include "netInstall.hpp"
#define COLOR(hex) pu::ui::Color::FromHex(hex) #define COLOR(hex) pu::ui::Color::FromHex(hex)
@ -37,12 +38,12 @@ namespace inst::ui {
} }
void MainPage::installMenuItem_Click() { void MainPage::installMenuItem_Click() {
mainApp->CreateShowDialog("Not implemented yet", "", {"Cancel"}, true); if (!netInstStuff::installNspLan()) mainApp->CreateShowDialog("Failure!", "", {"OK"}, true);
return; return;
} }
void MainPage::netInstallMenuItem_Click() { void MainPage::netInstallMenuItem_Click() {
mainApp->CreateShowDialog("Not implemented yet", "", {"Cancel"}, true); mainApp->CreateShowDialog("Not implemented yet", "", {"OK"}, true);
return; return;
} }

95
source/util/file_util.cpp Executable file
View file

@ -0,0 +1,95 @@
#include "util/file_util.hpp"
#include <memory>
#include "install/simple_filesystem.hpp"
#include "nx/fs.hpp"
#include "data/byte_buffer.hpp"
#include "util/title_util.hpp"
namespace tin::util
{
NcmContentInfo CreateNSPCNMTContentRecord(const std::string& nspPath)
{
// Open filesystem
nx::fs::IFileSystem fileSystem;
std::string nspExt = ".nsp";
std::string rootPath = "/";
std::string absolutePath = nspPath + "/";
// Check if this is an nsp file
if (nspPath.compare(nspPath.size() - nspExt.size(), nspExt.size(), nspExt) == 0)
{
fileSystem.OpenFileSystemWithId(nspPath, FsFileSystemType_ApplicationPackage, 0);
}
else // Otherwise assume this is an extracted NSP directory
{
fileSystem.OpenSdFileSystem();
rootPath = nspPath.substr(9) + "/";
absolutePath = nspPath + "/";
}
tin::install::nsp::SimpleFileSystem simpleFS(fileSystem, rootPath, absolutePath);
// Create the path of the cnmt NCA
auto cnmtNCAName = simpleFS.GetFileNameFromExtension("", "cnmt.nca");
auto cnmtNCAFile = simpleFS.OpenFile(cnmtNCAName);
u64 cnmtNCASize = cnmtNCAFile.GetSize();
// Prepare cnmt content record
NcmContentInfo contentRecord;
contentRecord.content_id = tin::util::GetNcaIdFromString(cnmtNCAName);
*(u64*)contentRecord.size = cnmtNCASize & 0xFFFFFFFFFFFF;
contentRecord.content_type = NcmContentType_Meta;
return contentRecord;
}
// NOTE: As of 7.0.0, this will only work with installed cnmt nca paths
nx::ncm::ContentMeta GetContentMetaFromNCA(const std::string& ncaPath)
{
// Create the cnmt filesystem
nx::fs::IFileSystem cnmtNCAFileSystem;
cnmtNCAFileSystem.OpenFileSystemWithId(ncaPath, FsFileSystemType_ContentMeta, 0);
tin::install::nsp::SimpleFileSystem cnmtNCASimpleFileSystem(cnmtNCAFileSystem, "/", ncaPath + "/");
// Find and read the cnmt file
auto cnmtName = cnmtNCASimpleFileSystem.GetFileNameFromExtension("", "cnmt");
auto cnmtFile = cnmtNCASimpleFileSystem.OpenFile(cnmtName);
u64 cnmtSize = cnmtFile.GetSize();
tin::data::ByteBuffer cnmtBuf;
cnmtBuf.Resize(cnmtSize);
cnmtFile.Read(0x0, cnmtBuf.GetData(), cnmtSize);
return nx::ncm::ContentMeta(cnmtBuf.GetData(), cnmtBuf.GetSize());
}
std::vector<std::string> GetNSPList()
{
std::vector<std::string> nspList;
nx::fs::IFileSystem fileSystem;
fileSystem.OpenSdFileSystem();
nx::fs::IDirectory dir = fileSystem.OpenDirectory("/tinfoil/nsp/", FsDirOpenMode_ReadFiles);
u64 entryCount = dir.GetEntryCount();
auto dirEntries = std::make_unique<FsDirectoryEntry[]>(entryCount);
dir.Read(0, dirEntries.get(), entryCount);
for (unsigned int i = 0; i < entryCount; i++)
{
FsDirectoryEntry dirEntry = dirEntries[i];
std::string dirEntryName(dirEntry.name);
std::string ext = ".nsp";
if (dirEntry.type != FsDirEntryType_File || dirEntryName.compare(dirEntryName.size() - ext.size(), ext.size(), ext) != 0)
continue;
nspList.push_back(dirEntry.name);
}
return nspList;
}
}

245
source/util/network_util.cpp Executable file
View file

@ -0,0 +1,245 @@
#include "util/network_util.hpp"
#include <switch.h>
#include <curl/curl.h>
#include <algorithm>
#include <cstring>
#include <sstream>
#include "error.hpp"
namespace tin::network
{
// HTTPHeader
HTTPHeader::HTTPHeader(std::string url) :
m_url(url)
{
}
size_t HTTPHeader::ParseHTMLHeader(char* bytes, size_t size, size_t numItems, void* userData)
{
HTTPHeader* header = reinterpret_cast<HTTPHeader*>(userData);
size_t numBytes = size * numItems;
std::string line(bytes, numBytes);
// Remove any newlines or carriage returns
line.erase(std::remove(line.begin(), line.end(), '\n'), line.end());
line.erase(std::remove(line.begin(), line.end(), '\r'), line.end());
// Split into key and value
if (!line.empty())
{
auto keyEnd = line.find(": ");
if (keyEnd != 0)
{
std::string key = line.substr(0, keyEnd);
std::string value = line.substr(keyEnd + 2);
// Make key lowercase
std::transform(key.begin(), key.end(), key.begin(), ::tolower);
header->m_values[key] = value;
}
}
return numBytes;
}
void HTTPHeader::PerformRequest()
{
// We don't want any existing values to get mixed up with this request
m_values.clear();
CURL* curl = curl_easy_init();
CURLcode rc = (CURLcode)0;
if (!curl)
{
THROW_FORMAT("Failed to initialize curl\n");
}
curl_easy_setopt(curl, CURLOPT_URL, m_url.c_str());
curl_easy_setopt(curl, CURLOPT_NOBODY, true);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "tinfoil");
curl_easy_setopt(curl, CURLOPT_HEADERDATA, this);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, &tin::network::HTTPHeader::ParseHTMLHeader);
rc = curl_easy_perform(curl);
if (rc != CURLE_OK)
{
THROW_FORMAT("Failed to retrieve HTTP Header: %s\n", curl_easy_strerror(rc));
}
u64 httpCode = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
curl_easy_cleanup(curl);
if (httpCode != 200 && httpCode != 204)
{
THROW_FORMAT("Unexpected HTTP response code when retrieving header: %lu\n", httpCode);
}
}
bool HTTPHeader::HasValue(std::string key)
{
return m_values.count(key);
}
std::string HTTPHeader::GetValue(std::string key)
{
return m_values[key];
}
// End HTTPHeader
// HTTPDownload
HTTPDownload::HTTPDownload(std::string url) :
m_url(url), m_header(url)
{
// The header won't be populated until we do this
m_header.PerformRequest();
if (m_header.HasValue("accept-ranges"))
{
m_rangesSupported = m_header.GetValue("accept-ranges") == "bytes";
}
else
{
CURL* curl = curl_easy_init();
CURLcode rc = (CURLcode)0;
if (!curl)
{
THROW_FORMAT("Failed to initialize curl\n");
}
curl_easy_setopt(curl, CURLOPT_URL, m_url.c_str());
curl_easy_setopt(curl, CURLOPT_NOBODY, true);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "tinfoil");
curl_easy_setopt(curl, CURLOPT_RANGE, "0-0");
rc = curl_easy_perform(curl);
if (rc != CURLE_OK)
{
THROW_FORMAT("Failed to retrieve HTTP Header: %s\n", curl_easy_strerror(rc));
}
u64 httpCode = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
curl_easy_cleanup(curl);
m_rangesSupported = httpCode == 206;
}
}
size_t HTTPDownload::ParseHTMLData(char* bytes, size_t size, size_t numItems, void* userData)
{
auto streamFunc = *reinterpret_cast<std::function<size_t (u8* bytes, size_t size)>*>(userData);
size_t numBytes = size * numItems;
if (streamFunc != nullptr)
return streamFunc((u8*)bytes, numBytes);
return numBytes;
}
void HTTPDownload::BufferDataRange(void* buffer, size_t offset, size_t size, std::function<void (size_t sizeRead)> progressFunc)
{
size_t sizeRead = 0;
auto streamFunc = [&](u8* streamBuf, size_t streamBufSize) -> size_t
{
if (sizeRead + streamBufSize > size)
{
printf("New read size 0x%lx would exceed total expected size 0x%lx\n", sizeRead + streamBufSize, size);
return 0;
}
if (progressFunc != nullptr)
progressFunc(sizeRead);
memcpy(reinterpret_cast<u8*>(buffer) + sizeRead, streamBuf, streamBufSize);
sizeRead += streamBufSize;
return streamBufSize;
};
this->StreamDataRange(offset, size, streamFunc);
}
void HTTPDownload::StreamDataRange(size_t offset, size_t size, std::function<size_t (u8* bytes, size_t size)> streamFunc)
{
if (!m_rangesSupported)
{
THROW_FORMAT("Attempted range request when ranges aren't supported!\n");
}
auto writeDataFunc = streamFunc;
CURL* curl = curl_easy_init();
CURLcode rc = (CURLcode)0;
if (!curl)
{
THROW_FORMAT("Failed to initialize curl\n");
}
std::stringstream ss;
ss << offset << "-" << (offset + size - 1);
auto range = ss.str();
// printf("Requesting from range: %s\n", range.c_str());
// printf("Read size: %lx\n", size); NOTE: For some reason including these causes the cursor to disappear?
curl_easy_setopt(curl, CURLOPT_URL, m_url.c_str());
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "tinfoil");
curl_easy_setopt(curl, CURLOPT_RANGE, range.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &writeDataFunc);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &tin::network::HTTPDownload::ParseHTMLData);
rc = curl_easy_perform(curl);
if (rc != CURLE_OK)
{
THROW_FORMAT("Failed to perform range request: %s\n", curl_easy_strerror(rc));
}
u64 httpCode = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
curl_easy_cleanup(curl);
if (httpCode != 206)
{
THROW_FORMAT("Failed to request range! Response code is %lu\n", httpCode);
}
}
// End HTTPDownload
size_t WaitReceiveNetworkData(int sockfd, void* buf, size_t len)
{
int ret = 0;
size_t read = 0;
while ((((ret = recv(sockfd, (u8*)buf + read, len - read, 0)) > 0 && (read += ret) < len) || errno == EAGAIN) && !(hidKeysDown(CONTROLLER_P1_AUTO) & KEY_B))
{
errno = 0;
}
return read;
}
size_t WaitSendNetworkData(int sockfd, void* buf, size_t len)
{
errno = 0;
int ret = 0;
size_t written = 0;
while ((((ret = send(sockfd, (u8*)buf + written, len - written, 0)) > 0 && (written += ret) < len) || errno == EAGAIN) && !(hidKeysDown(CONTROLLER_P1_AUTO) & KEY_B))
{
errno = 0;
}
return written;
}
}

112
source/util/title_util.cpp Executable file
View file

@ -0,0 +1,112 @@
#include "util/title_util.hpp"
#include <machine/endian.h>
#include "error.hpp"
namespace tin::util
{
u64 GetRightsIdTid(RightsId rightsId)
{
return __bswap64(*(u64 *)rightsId.c);
}
u64 GetRightsIdKeyGen(RightsId rightsId)
{
return __bswap64(*(u64 *)(rightsId.c + 8));
}
std::string GetNcaIdString(const NcmNcaId& ncaId)
{
char ncaIdStr[FS_MAX_PATH] = {0};
u64 ncaIdLower = __bswap64(*(u64 *)ncaId.c);
u64 ncaIdUpper = __bswap64(*(u64 *)(ncaId.c + 0x8));
snprintf(ncaIdStr, FS_MAX_PATH, "%016lx%016lx", ncaIdLower, ncaIdUpper);
return std::string(ncaIdStr);
}
NcmNcaId GetNcaIdFromString(std::string ncaIdStr)
{
NcmNcaId ncaId = {0};
char lowerU64[17] = {0};
char upperU64[17] = {0};
memcpy(lowerU64, ncaIdStr.c_str(), 16);
memcpy(upperU64, ncaIdStr.c_str() + 16, 16);
*(u64 *)ncaId.c = __bswap64(strtoul(lowerU64, NULL, 16));
*(u64 *)(ncaId.c + 8) = __bswap64(strtoul(upperU64, NULL, 16));
return ncaId;
}
u64 GetBaseTitleId(u64 titleId, NcmContentMetaType contentMetaType)
{
switch (contentMetaType)
{
case NcmContentMetaType_Patch:
return titleId ^ 0x800;
case NcmContentMetaType_AddOnContent:
return (titleId ^ 0x1000) & ~0xFFF;
default:
return titleId;
}
}
std::string GetBaseTitleName(u64 baseTitleId)
{
Result rc = 0;
NsApplicationControlData appControlData;
size_t sizeRead;
if (R_FAILED(rc = nsGetApplicationControlData(NsApplicationControlSource_Storage, baseTitleId, &appControlData, sizeof(NsApplicationControlData), &sizeRead)))
{
printf("Failed to get application control data. Error code: 0x%08x\n", rc);
return "Unknown";
}
if (sizeRead < sizeof(appControlData.nacp))
{
printf("Incorrect size for nacp\n");
return "Unknown";
}
NacpLanguageEntry *languageEntry;
if (R_FAILED(rc = nacpGetLanguageEntry(&appControlData.nacp, &languageEntry)))
{
printf("Failed to get language entry. Error code: 0x%08x\n", rc);
return "Unknown";
}
if (languageEntry == NULL)
{
printf("Language entry is null! Error code: 0x%08x\n", rc);
return "Unknown";
}
return languageEntry->name;
}
std::string GetTitleName(u64 titleId, NcmContentMetaType contentMetaType)
{
u64 baseTitleId = GetBaseTitleId(titleId, contentMetaType);
std::string titleName = GetBaseTitleName(baseTitleId);
switch (contentMetaType)
{
case NcmContentMetaType_Patch:
titleName += " (Update)";
break;
case NcmContentMetaType_AddOnContent:
titleName += " (DLC)";
break;
default:
break;
}
return titleName;
}
}

82
source/util/usb_util.cpp Executable file
View file

@ -0,0 +1,82 @@
#include "util/usb_util.hpp"
#include "data/byte_buffer.hpp"
#include "debug.h"
#include "error.hpp"
namespace tin::util
{
void USBCmdManager::SendCmdHeader(u32 cmdId, size_t dataSize)
{
USBCmdHeader header;
header.magic = 0x30435554; // TUC0 (Tinfoil USB Command 0)
header.type = USBCmdType::REQUEST;
header.cmdId = cmdId;
header.dataSize = dataSize;
USBWrite(&header, sizeof(USBCmdHeader));
}
void USBCmdManager::SendExitCmd()
{
USBCmdManager::SendCmdHeader(0, 0);
}
USBCmdHeader USBCmdManager::SendFileRangeCmd(std::string nspName, u64 offset, u64 size)
{
struct FileRangeCmdHeader
{
u64 size;
u64 offset;
u64 nspNameLen;
u64 padding;
} fRangeHeader;
fRangeHeader.size = size;
fRangeHeader.offset = offset;
fRangeHeader.nspNameLen = nspName.size();
fRangeHeader.padding = 0;
USBCmdManager::SendCmdHeader(1, sizeof(FileRangeCmdHeader) + fRangeHeader.nspNameLen);
USBWrite(&fRangeHeader, sizeof(FileRangeCmdHeader));
USBWrite(nspName.c_str(), fRangeHeader.nspNameLen);
USBCmdHeader responseHeader;
USBRead(&responseHeader, sizeof(USBCmdHeader));
return responseHeader;
}
size_t USBRead(void* out, size_t len)
{
u8* tmpBuf = (u8*)out;
size_t sizeRemaining = len;
size_t tmpSizeRead = 0;
while (sizeRemaining)
{
tmpSizeRead = usbCommsRead(tmpBuf, sizeRemaining);
tmpBuf += tmpSizeRead;
sizeRemaining -= tmpSizeRead;
}
return len;
}
size_t USBWrite(const void* in, size_t len)
{
const u8 *bufptr = (const u8 *)in;
size_t cursize = len;
size_t tmpsize = 0;
while (cursize)
{
tmpsize = usbCommsWrite(bufptr, cursize);
printf("USB Bytes Written: \n");
printBytes(nxlinkout, (u8*)bufptr, tmpsize, true);
bufptr += tmpsize;
cursize -= tmpsize;
}
return len;
}
}