mirror of
https://github.com/Huntereb/Awoo-Installer
synced 2024-11-22 11:43:18 +00:00
initial wireless install stuff (libnx master)
This commit is contained in:
parent
25ce22f1ee
commit
e3ed8a5220
54 changed files with 6331 additions and 4 deletions
4
Makefile
4
Makefile
|
@ -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
|
||||||
|
|
66
include/data/buffered_placeholder_writer.hpp
Executable file
66
include/data/buffered_placeholder_writer.hpp
Executable 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
50
include/data/byte_buffer.hpp
Executable 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
29
include/data/byte_stream.hpp
Executable 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
19
include/debug.h
Executable 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
17
include/error.hpp
Executable 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
17
include/install/http_nsp.hpp
Executable 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
47
include/install/install.hpp
Executable 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
25
include/install/install_nsp.hpp
Executable 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);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
23
include/install/install_nsp_remote.hpp
Executable file
23
include/install/install_nsp_remote.hpp
Executable 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);
|
||||||
|
};
|
||||||
|
}
|
30
include/install/install_source.h
Executable file
30
include/install/install_source.h
Executable 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
26
include/install/pfs0.hpp
Executable 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
35
include/install/remote_nsp.hpp
Executable 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);
|
||||||
|
};
|
||||||
|
}
|
24
include/install/simple_filesystem.hpp
Executable file
24
include/install/simple_filesystem.hpp
Executable 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
19
include/install/usb_nsp.hpp
Executable 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
33
include/install/verify_nsp.hpp
Executable 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
3
include/netInstall.hpp
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
namespace netInstStuff {
|
||||||
|
bool installNspLan ();
|
||||||
|
}
|
51
include/nx/content_meta.hpp
Executable file
51
include/nx/content_meta.hpp
Executable 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
77
include/nx/fs.hpp
Executable 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
19
include/nx/ipc/es.h
Executable 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
36
include/nx/ipc/ns_ext.h
Executable 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
12
include/nx/ipc/tin_ipc.h
Executable 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
37
include/nx/ipc/usb_comms_new.h
Executable 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
265
include/nx/ipc/usb_new.h
Executable 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
36
include/nx/ncm.hpp
Executable 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 ®isteredId, 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 ®isteredId);
|
||||||
|
void Delete(const NcmNcaId ®isteredId);
|
||||||
|
bool Has(const NcmNcaId ®isteredId);
|
||||||
|
std::string GetPath(const NcmNcaId ®isteredId);
|
||||||
|
};
|
||||||
|
}
|
14
include/util/file_util.hpp
Executable file
14
include/util/file_util.hpp
Executable 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
61
include/util/network_util.hpp
Executable 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
19
include/util/title_util.hpp
Executable 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
37
include/util/usb_util.hpp
Executable 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);
|
||||||
|
}
|
192
source/data/buffered_placeholder_writer.cpp
Executable file
192
source/data/buffered_placeholder_writer.cpp
Executable 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
33
source/data/byte_buffer.cpp
Executable 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
19
source/data/byte_stream.cpp
Executable 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
75
source/debug.c
Executable 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
129
source/install/http_nsp.cpp
Executable 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
255
source/install/install.cpp
Executable 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
136
source/install/install_nsp.cpp
Executable 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);
|
||||||
|
}
|
||||||
|
}
|
124
source/install/install_nsp_remote.cpp
Executable file
124
source/install/install_nsp_remote.cpp
Executable 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
118
source/install/remote_nsp.cpp
Executable 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();
|
||||||
|
}
|
||||||
|
}
|
67
source/install/simple_filesystem.cpp
Executable file
67
source/install/simple_filesystem.cpp
Executable 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
149
source/install/usb_nsp.cpp
Executable 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
183
source/install/verify_nsp.cpp
Executable 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
250
source/netInstall.cpp
Executable 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
108
source/nx/content_meta.cpp
Executable 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
135
source/nx/fs.cpp
Executable 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
303
source/nx/ipc/es.c
Executable 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
679
source/nx/ipc/ns_ext.c
Executable 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
579
source/nx/ipc/usb_comms_new.c
Executable 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
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
54
source/nx/ncm.cpp
Executable 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 ®isteredId, size_t size)
|
||||||
|
{
|
||||||
|
ASSERT_OK(ncmContentStorageCreatePlaceHolder(&m_contentStorage, &placeholderId, ®isteredId, 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 ®isteredId)
|
||||||
|
{
|
||||||
|
ASSERT_OK(ncmContentStorageRegister(&m_contentStorage, ®isteredId, &placeholderId), "Failed to register placeholder NCA");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContentStorage::Delete(const NcmNcaId ®isteredId)
|
||||||
|
{
|
||||||
|
ASSERT_OK(ncmContentStorageDelete(&m_contentStorage, ®isteredId), "Failed to delete registered NCA");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ContentStorage::Has(const NcmNcaId ®isteredId)
|
||||||
|
{
|
||||||
|
bool hasNCA = false;
|
||||||
|
ASSERT_OK(ncmContentStorageHas(&m_contentStorage, &hasNCA, ®isteredId), "Failed to check if NCA is present");
|
||||||
|
return hasNCA;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ContentStorage::GetPath(const NcmNcaId ®isteredId)
|
||||||
|
{
|
||||||
|
char pathBuf[FS_MAX_PATH] = {0};
|
||||||
|
ASSERT_OK(ncmContentStorageGetPath(&m_contentStorage, pathBuf, FS_MAX_PATH, ®isteredId), "Failed to get installed NCA path");
|
||||||
|
return std::string(pathBuf);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
95
source/util/file_util.cpp
Executable 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
245
source/util/network_util.cpp
Executable 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
112
source/util/title_util.cpp
Executable 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
82
source/util/usb_util.cpp
Executable 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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue