add basic support for title installing
This commit is contained in:
@@ -1008,6 +1008,10 @@ void App::Draw() {
|
||||
this->queue.presentImage(this->swapchain, slot);
|
||||
}
|
||||
|
||||
auto App::GetApp() -> App* {
|
||||
return g_app;
|
||||
}
|
||||
|
||||
auto App::GetVg() -> NVGcontext* {
|
||||
return g_app->vg;
|
||||
}
|
||||
|
||||
@@ -132,9 +132,9 @@ void log_file_write(const char* msg) {
|
||||
}
|
||||
|
||||
void log_file_fwrite(const char* fmt, ...) {
|
||||
std::va_list v{};
|
||||
va_list v{};
|
||||
va_start(v, fmt);
|
||||
log_write_arg(fmt, v);
|
||||
log_write_arg(fmt, &v);
|
||||
va_end(v);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,18 +14,20 @@ std::FILE* file{};
|
||||
int nxlink_socket{};
|
||||
std::mutex mutex{};
|
||||
|
||||
void log_write_arg_internal(const char* s, std::va_list& v) {
|
||||
void log_write_arg_internal(const char* s, std::va_list* v) {
|
||||
if (file) {
|
||||
std::vfprintf(file, s, v);
|
||||
std::vfprintf(file, s, *v);
|
||||
std::fflush(file);
|
||||
}
|
||||
if (nxlink_socket) {
|
||||
std::vprintf(s, v);
|
||||
std::vprintf(s, *v);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
auto log_file_init() -> bool {
|
||||
std::scoped_lock lock{mutex};
|
||||
if (file) {
|
||||
@@ -70,11 +72,11 @@ void log_write(const char* s, ...) {
|
||||
|
||||
std::va_list v{};
|
||||
va_start(v, s);
|
||||
log_write_arg_internal(s, v);
|
||||
log_write_arg_internal(s, &v);
|
||||
va_end(v);
|
||||
}
|
||||
|
||||
void log_write_arg(const char* s, std::va_list& v) {
|
||||
void log_write_arg(const char* s, va_list* v) {
|
||||
std::scoped_lock lock{mutex};
|
||||
if (!file && !nxlink_socket) {
|
||||
return;
|
||||
@@ -83,4 +85,6 @@ void log_write_arg(const char* s, std::va_list& v) {
|
||||
log_write_arg_internal(s, v);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif
|
||||
|
||||
@@ -7,6 +7,14 @@
|
||||
#include <string_view>
|
||||
#include <span>
|
||||
|
||||
#include "yati/nx/nca.hpp"
|
||||
#include "yati/nx/ncm.hpp"
|
||||
#include "yati/nx/npdm.hpp"
|
||||
#include "yati/nx/ns.hpp"
|
||||
#include "yati/nx/es.hpp"
|
||||
#include "yati/nx/keys.hpp"
|
||||
#include "yati/nx/crypto.hpp"
|
||||
|
||||
#include "owo.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "app.hpp"
|
||||
@@ -23,52 +31,9 @@ constexpr u32 PFS0_EXEFS_HASH_BLOCK_SIZE = 0x10000;
|
||||
constexpr u32 PFS0_LOGO_HASH_BLOCK_SIZE = 0x1000;
|
||||
constexpr u32 PFS0_META_HASH_BLOCK_SIZE = 0x1000;
|
||||
constexpr u32 PFS0_PADDING_SIZE = 0x200;
|
||||
constexpr u32 NCA_SECTION_TOTAL = 0x4;
|
||||
constexpr u32 ROMFS_ENTRY_EMPTY = 0xFFFFFFFF;
|
||||
constexpr u32 ROMFS_FILEPARTITION_OFS = 0x200;
|
||||
|
||||
enum NcaDistributionType {
|
||||
NcaDistributionType_System = 0x0,
|
||||
NcaDistributionType_GameCard = 0x1
|
||||
};
|
||||
|
||||
enum NcaContentType {
|
||||
NcaContentType_Program = 0x0,
|
||||
NcaContentType_Meta = 0x1,
|
||||
NcaContentType_Control = 0x2,
|
||||
NcaContentType_Manual = 0x3,
|
||||
NcaContentType_Data = 0x4,
|
||||
NcaContentType_PublicData = 0x5,
|
||||
};
|
||||
|
||||
enum NcaFileSystemType {
|
||||
NcaFileSystemType_RomFS = 0x0,
|
||||
NcaFileSystemType_PFS0 = 0x1
|
||||
};
|
||||
|
||||
enum NcaHashType {
|
||||
NcaHashType_Auto = 0x0,
|
||||
NcaHashType_HierarchicalSha256 = 0x2,
|
||||
NcaHashType_HierarchicalIntegrity = 0x3
|
||||
};
|
||||
|
||||
enum NcaEncryptionType {
|
||||
NcaEncryptionType_Auto = 0x0,
|
||||
NcaEncryptionType_None = 0x1,
|
||||
NcaEncryptionType_AesCtrOld = 0x2,
|
||||
NcaEncryptionType_AesCtr = 0x3,
|
||||
NcaEncryptionType_AesCtrEx = 0x4
|
||||
};
|
||||
|
||||
enum NsApplicationRecordType {
|
||||
// installed
|
||||
NsApplicationRecordType_Installed = 0x3,
|
||||
// application is gamecard, but gamecard isn't insterted
|
||||
NsApplicationRecordType_GamecardMissing = 0x5,
|
||||
// archived
|
||||
NsApplicationRecordType_Archived = 0xB,
|
||||
};
|
||||
|
||||
// stdio-like wrapper for std::vector
|
||||
struct BufHelper {
|
||||
BufHelper() = default;
|
||||
@@ -124,12 +89,6 @@ struct CnmtHeader {
|
||||
};
|
||||
static_assert(sizeof(CnmtHeader) == 0x20);
|
||||
|
||||
struct NcmContentStorageRecord {
|
||||
NcmContentMetaKey key;
|
||||
u8 storage_id; //
|
||||
u8 padding[0x7];
|
||||
};
|
||||
|
||||
struct NcmContentMetaData {
|
||||
NcmContentMetaHeader header;
|
||||
NcmApplicationMetaExtendedHeader extended;
|
||||
@@ -142,7 +101,7 @@ struct NcaMetaEntry {
|
||||
NcaEntry nca_entry;
|
||||
NcmContentMetaHeader content_meta_header{};
|
||||
NcmContentMetaKey content_meta_key{};
|
||||
NcmContentStorageRecord content_storage_record{};
|
||||
ncm::ContentStorageRecord content_storage_record{};
|
||||
NcmContentMetaData content_meta_data{};
|
||||
};
|
||||
|
||||
@@ -171,61 +130,6 @@ struct FileEntry {
|
||||
|
||||
using FileEntries = std::vector<FileEntry>;
|
||||
|
||||
struct NpdmMeta {
|
||||
u32 magic; // "META"
|
||||
u32 signature_key_generation; // +9.0.0
|
||||
u32 _0x8;
|
||||
u8 flags;
|
||||
u8 _0xD;
|
||||
u8 main_thread_priority;
|
||||
u8 main_thread_core_num;
|
||||
u32 _0x10;
|
||||
u32 sys_resource_size; // +3.0.0
|
||||
u32 version;
|
||||
u32 main_thread_stack_size;
|
||||
char title_name[0x10];
|
||||
char product_code[0x10];
|
||||
u8 _0x40[0x30];
|
||||
u32 aci0_offset;
|
||||
u32 aci0_size;
|
||||
u32 acid_offset;
|
||||
u32 acid_size;
|
||||
};
|
||||
|
||||
struct NpdmAcid {
|
||||
u8 rsa_sig[0x100];
|
||||
u8 rsa_pub[0x100];
|
||||
u32 magic; // "ACID"
|
||||
u32 size;
|
||||
u8 version;
|
||||
u8 _0x209[0x1];
|
||||
u8 _0x20A[0x2];
|
||||
u32 flags;
|
||||
u64 program_id_min;
|
||||
u64 program_id_max;
|
||||
u32 fac_offset;
|
||||
u32 fac_size;
|
||||
u32 sac_offset;
|
||||
u32 sac_size;
|
||||
u32 kac_offset;
|
||||
u32 kac_size;
|
||||
u8 _0x238[0x8];
|
||||
};
|
||||
|
||||
struct NpdmAci0 {
|
||||
u32 magic; // "ACI0"
|
||||
u8 _0x4[0xC];
|
||||
u64 program_id;
|
||||
u8 _0x18[0x8];
|
||||
u32 fac_offset;
|
||||
u32 fac_size;
|
||||
u32 sac_offset;
|
||||
u32 sac_size;
|
||||
u32 kac_offset;
|
||||
u32 kac_size;
|
||||
u8 _0x38[0x8];
|
||||
};
|
||||
|
||||
struct NpdmPatch {
|
||||
char title_name[0x10]{"Application"};
|
||||
char product_code[0x10]{};
|
||||
@@ -238,56 +142,6 @@ struct NcapPatch {
|
||||
u64 tid;
|
||||
};
|
||||
|
||||
struct NcaSectionTableEntry {
|
||||
u32 media_start_offset; // divided by 0x200.
|
||||
u32 media_end_offset; // divided by 0x200.
|
||||
u8 _0x8[0x4]; // unknown.
|
||||
u8 _0xC[0x4]; // unknown.
|
||||
};
|
||||
|
||||
struct LayerRegion {
|
||||
u64 offset;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
struct HierarchicalSha256Data {
|
||||
u8 master_hash[0x20];
|
||||
u32 block_size;
|
||||
u32 layer_count;
|
||||
LayerRegion hash_layer;
|
||||
LayerRegion pfs0_layer;
|
||||
LayerRegion unused_layers[3];
|
||||
u8 _0x78[0x80];
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct HierarchicalIntegrityVerificationLevelInformation {
|
||||
u64 logical_offset;
|
||||
u64 hash_data_size;
|
||||
u32 block_size; // log2
|
||||
u32 _0x14; // reserved
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct InfoLevelHash {
|
||||
u32 max_layers;
|
||||
HierarchicalIntegrityVerificationLevelInformation levels[6];
|
||||
u8 signature_salt[0x20];
|
||||
};
|
||||
|
||||
struct IntegrityMetaInfo {
|
||||
u32 magic; // IVFC
|
||||
u32 version;
|
||||
u32 master_hash_size;
|
||||
InfoLevelHash info_level_hash;
|
||||
u8 master_hash[0x20];
|
||||
u8 _0xE0[0x18];
|
||||
};
|
||||
|
||||
static_assert(sizeof(HierarchicalSha256Data) == 0xF8);
|
||||
static_assert(sizeof(IntegrityMetaInfo) == 0xF8);
|
||||
static_assert(sizeof(HierarchicalSha256Data) == sizeof(IntegrityMetaInfo));
|
||||
|
||||
typedef struct romfs_dirent_ctx {
|
||||
u32 entry_offset;
|
||||
struct romfs_dirent_ctx *parent; /* Parent node */
|
||||
@@ -317,70 +171,6 @@ typedef struct {
|
||||
u64 file_partition_size;
|
||||
} romfs_ctx_t;
|
||||
|
||||
struct NcaFsHeader {
|
||||
u16 version; // always 2.
|
||||
u8 fs_type; // see NcaFileSystemType.
|
||||
u8 hash_type; // see NcaHashType.
|
||||
u8 encryption_type; // see NcaEncryptionType.
|
||||
u8 metadata_hash_type;
|
||||
u8 _0x6[0x2]; // empty.
|
||||
|
||||
union {
|
||||
HierarchicalSha256Data hierarchical_sha256_data;
|
||||
IntegrityMetaInfo integrity_meta_info; // used for romfs
|
||||
} hash_data;
|
||||
|
||||
u8 patch_info[0x40];
|
||||
u64 section_ctr;
|
||||
u8 spares_info[0x30];
|
||||
u8 compression_info[0x28];
|
||||
u8 meta_data_hash_data_info[0x30];
|
||||
u8 reserved[0x30];
|
||||
};
|
||||
|
||||
struct NcaSectionHeaderHash {
|
||||
u8 sha256[0x20];
|
||||
};
|
||||
|
||||
struct NcaKeyArea {
|
||||
u8 area[0x10];
|
||||
};
|
||||
|
||||
struct NcaHeader {
|
||||
u8 rsa_fixed_key[0x100];
|
||||
u8 rsa_npdm[0x100]; // key from npdm.
|
||||
u32 magic;
|
||||
u8 distribution_type; // see NcaDistributionType.
|
||||
u8 content_type; // see NcaContentType.
|
||||
u8 old_key_gen; // see NcaOldKeyGeneration.
|
||||
u8 kaek_index; // see NcaKeyAreaEncryptionKeyIndex.
|
||||
u64 size;
|
||||
u64 title_id;
|
||||
u32 context_id;
|
||||
u32 sdk_version;
|
||||
u8 key_gen; // see NcaKeyGeneration.
|
||||
u8 header_1_sig_key_gen;
|
||||
u8 _0x222[0xE]; // empty.
|
||||
FsRightsId rights_id;
|
||||
|
||||
NcaSectionTableEntry fs_table[NCA_SECTION_TOTAL];
|
||||
NcaSectionHeaderHash fs_header_hash[NCA_SECTION_TOTAL];
|
||||
NcaKeyArea key_area[NCA_SECTION_TOTAL];
|
||||
|
||||
u8 _0x340[0xC0]; // empty.
|
||||
|
||||
NcaFsHeader fs_header[NCA_SECTION_TOTAL];
|
||||
};
|
||||
|
||||
constexpr u8 HEADER_KEK_SRC[0x10] = {
|
||||
0x1F, 0x12, 0x91, 0x3A, 0x4A, 0xCB, 0xF0, 0x0D, 0x4C, 0xDE, 0x3A, 0xF6, 0xD5, 0x23, 0x88, 0x2A
|
||||
};
|
||||
|
||||
constexpr u8 HEADER_KEY_SRC[0x20] = {
|
||||
0x5A, 0x3E, 0xD8, 0x4F, 0xDE, 0xC0, 0xD8, 0x26, 0x31, 0xF7, 0xE2, 0x5D, 0x19, 0x7B, 0xF5, 0xD0,
|
||||
0x1C, 0x9B, 0x7B, 0xFA, 0xF6, 0x28, 0x18, 0x3D, 0x71, 0xF6, 0x4D, 0x73, 0xF1, 0x50, 0xB9, 0xD2
|
||||
};
|
||||
|
||||
auto write_padding(BufHelper& buf, u64 off, u64 block) -> u64 {
|
||||
const u64 size = block - (off % block);
|
||||
if (size) {
|
||||
@@ -632,9 +422,9 @@ auto npdm_patch_kc(std::vector<u8>& npdm, u32 off, u32 size, u32 bitmask, u32 va
|
||||
|
||||
// todo: manually build npdm
|
||||
void patch_npdm(std::vector<u8>& npdm, const NpdmPatch& patch) {
|
||||
NpdmMeta meta{};
|
||||
NpdmAci0 aci0{};
|
||||
NpdmAcid acid{};
|
||||
npdm::Meta meta{};
|
||||
npdm::Aci0 aci0{};
|
||||
npdm::Acid acid{};
|
||||
std::memcpy(&meta, npdm.data(), sizeof(meta));
|
||||
std::memcpy(&aci0, npdm.data() + meta.aci0_offset, sizeof(aci0));
|
||||
std::memcpy(&acid, npdm.data() + meta.acid_offset, sizeof(acid));
|
||||
@@ -805,7 +595,7 @@ void write_nca_padding(BufHelper& buf) {
|
||||
write_padding(buf, buf.tell(), 0x200);
|
||||
}
|
||||
|
||||
void nca_encrypt_header(NcaHeader* header, std::span<const u8> key) {
|
||||
void nca_encrypt_header(nca::Header* header, std::span<const u8> key) {
|
||||
Aes128XtsContext ctx{};
|
||||
aes128XtsContextCreate(&ctx, key.data(), key.data() + 0x10, true);
|
||||
|
||||
@@ -816,41 +606,41 @@ void nca_encrypt_header(NcaHeader* header, std::span<const u8> key) {
|
||||
}
|
||||
}
|
||||
|
||||
void write_nca_section(NcaHeader& nca_header, u8 index, u64 start, u64 end) {
|
||||
void write_nca_section(nca::Header& nca_header, u8 index, u64 start, u64 end) {
|
||||
auto& section = nca_header.fs_table[index];
|
||||
section.media_start_offset = start / 0x200; // 0xC00 / 0x200
|
||||
section.media_end_offset = end / 0x200; // Section end offset / 200
|
||||
section._0x8[0] = 0x1; // Always 1
|
||||
}
|
||||
|
||||
void write_nca_fs_header_pfs0(NcaHeader& nca_header, u8 index, const std::vector<u8>& master_hash, u64 hash_table_size, u32 block_size) {
|
||||
void write_nca_fs_header_pfs0(nca::Header& nca_header, u8 index, const std::vector<u8>& master_hash, u64 hash_table_size, u32 block_size) {
|
||||
auto& fs_header = nca_header.fs_header[index];
|
||||
fs_header.hash_type = NcaHashType_HierarchicalSha256;
|
||||
fs_header.fs_type = NcaFileSystemType_PFS0;
|
||||
fs_header.hash_type = nca::HashType_HierarchicalSha256;
|
||||
fs_header.fs_type = nca::FileSystemType_PFS0;
|
||||
fs_header.version = 0x2; // Always 2
|
||||
fs_header.hash_data.hierarchical_sha256_data.layer_count = 0x2;
|
||||
fs_header.hash_data.hierarchical_sha256_data.block_size = block_size;
|
||||
fs_header.encryption_type = NcaEncryptionType_None;
|
||||
fs_header.encryption_type = nca::EncryptionType_None;
|
||||
fs_header.hash_data.hierarchical_sha256_data.hash_layer.size = hash_table_size;
|
||||
std::memcpy(fs_header.hash_data.hierarchical_sha256_data.master_hash, master_hash.data(), master_hash.size());
|
||||
sha256CalculateHash(&nca_header.fs_header_hash[index], &fs_header, sizeof(fs_header));
|
||||
}
|
||||
|
||||
void write_nca_fs_header_romfs(NcaHeader& nca_header, u8 index) {
|
||||
void write_nca_fs_header_romfs(nca::Header& nca_header, u8 index) {
|
||||
auto& fs_header = nca_header.fs_header[index];
|
||||
fs_header.hash_type = NcaHashType_HierarchicalIntegrity;
|
||||
fs_header.fs_type = NcaFileSystemType_RomFS;
|
||||
fs_header.hash_type = nca::HashType_HierarchicalIntegrity;
|
||||
fs_header.fs_type = nca::FileSystemType_RomFS;
|
||||
fs_header.version = 0x2; // Always 2
|
||||
fs_header.hash_data.integrity_meta_info.magic = 0x43465649;
|
||||
fs_header.hash_data.integrity_meta_info.version = 0x20000; // Always 0x20000
|
||||
fs_header.hash_data.integrity_meta_info.master_hash_size = SHA256_HASH_SIZE;
|
||||
fs_header.hash_data.integrity_meta_info.info_level_hash.max_layers = 0x7;
|
||||
fs_header.encryption_type = NcaEncryptionType_None;
|
||||
fs_header.encryption_type = nca::EncryptionType_None;
|
||||
fs_header.hash_data.integrity_meta_info.info_level_hash.levels[5].block_size = 0x0E; // 0x4000
|
||||
sha256CalculateHash(&nca_header.fs_header_hash[index], &fs_header, sizeof(fs_header));
|
||||
}
|
||||
|
||||
void write_nca_pfs0(NcaHeader& nca_header, u8 index, const FileEntries& entries, u32 block_size, BufHelper& buf) {
|
||||
void write_nca_pfs0(nca::Header& nca_header, u8 index, const FileEntries& entries, u32 block_size, BufHelper& buf) {
|
||||
const auto pfs0 = build_pfs0(entries);
|
||||
const auto pfs0_hash_table = build_pfs0_hash_table(pfs0, block_size);
|
||||
const auto pfs0_master_hash = build_pfs0_master_hash(pfs0_hash_table);
|
||||
@@ -887,7 +677,7 @@ auto ivfc_create_level(const std::vector<u8>& src) -> std::vector<u8> {
|
||||
return buf.buf;
|
||||
}
|
||||
|
||||
void write_nca_romfs(NcaHeader& nca_header, u8 index, const FileEntries& entries, u32 block_size, BufHelper& buf) {
|
||||
void write_nca_romfs(nca::Header& nca_header, u8 index, const FileEntries& entries, u32 block_size, BufHelper& buf) {
|
||||
auto& fs_header = nca_header.fs_header[index];
|
||||
auto& meta_info = fs_header.hash_data.integrity_meta_info;
|
||||
auto& info_level_hash = meta_info.info_level_hash;
|
||||
@@ -921,22 +711,22 @@ void write_nca_romfs(NcaHeader& nca_header, u8 index, const FileEntries& entries
|
||||
write_nca_fs_header_romfs(nca_header, index);
|
||||
}
|
||||
|
||||
void write_nca_header_encypted(NcaHeader& nca_header, u64 tid, std::span<const u8> key, NcaContentType type, BufHelper& buf) {
|
||||
nca_header.magic = 0x3341434E;
|
||||
nca_header.distribution_type = NcaDistributionType_System;
|
||||
void write_nca_header_encypted(nca::Header& nca_header, u64 tid, const keys::Keys& keys, nca::ContentType type, BufHelper& buf) {
|
||||
nca_header.magic = NCA3_MAGIC;
|
||||
nca_header.distribution_type = nca::DistributionType_System;
|
||||
nca_header.content_type = type;
|
||||
nca_header.title_id = tid;
|
||||
nca_header.sdk_version = 0x000C1100;
|
||||
nca_header.size = buf.tell();
|
||||
|
||||
nca_encrypt_header(&nca_header, key);
|
||||
nca_encrypt_header(&nca_header, keys.header_key);
|
||||
buf.seek(0);
|
||||
buf.write(&nca_header, sizeof(nca_header));
|
||||
}
|
||||
|
||||
auto create_program_nca(u64 tid, std::span<const u8> key, const FileEntries& exefs, const FileEntries& romfs, const FileEntries& logo) -> NcaEntry {
|
||||
auto create_program_nca(u64 tid, const keys::Keys& keys, const FileEntries& exefs, const FileEntries& romfs, const FileEntries& logo) -> NcaEntry {
|
||||
BufHelper buf;
|
||||
NcaHeader nca_header{};
|
||||
nca::Header nca_header{};
|
||||
buf.write(&nca_header, sizeof(nca_header));
|
||||
|
||||
write_nca_pfs0(nca_header, 0, exefs, PFS0_EXEFS_HASH_BLOCK_SIZE, buf);
|
||||
@@ -945,23 +735,23 @@ auto create_program_nca(u64 tid, std::span<const u8> key, const FileEntries& exe
|
||||
if (logo.size() == 2 && !logo[0].data.empty() && !logo[1].data.empty()) {
|
||||
write_nca_pfs0(nca_header, 2, logo, PFS0_LOGO_HASH_BLOCK_SIZE, buf);
|
||||
}
|
||||
write_nca_header_encypted(nca_header, tid, key, NcaContentType_Program, buf);
|
||||
write_nca_header_encypted(nca_header, tid, keys, nca::ContentType_Program, buf);
|
||||
|
||||
return {buf, NcmContentType_Program};
|
||||
}
|
||||
|
||||
auto create_control_nca(u64 tid, std::span<const u8> key, const FileEntries& romfs) -> NcaEntry{
|
||||
NcaHeader nca_header{};
|
||||
auto create_control_nca(u64 tid, const keys::Keys& keys, const FileEntries& romfs) -> NcaEntry{
|
||||
nca::Header nca_header{};
|
||||
BufHelper buf;
|
||||
buf.write(&nca_header, sizeof(nca_header));
|
||||
|
||||
write_nca_romfs(nca_header, 0, romfs, IVFC_HASH_BLOCK_SIZE, buf);
|
||||
write_nca_header_encypted(nca_header, tid, key, NcaContentType_Control, buf);
|
||||
write_nca_header_encypted(nca_header, tid, keys, nca::ContentType_Control, buf);
|
||||
|
||||
return {buf, NcmContentType_Control};
|
||||
}
|
||||
|
||||
auto create_meta_nca(u64 tid, std::span<const u8> key, NcmStorageId storage_id, const std::vector<NcaEntry>& ncas) -> NcaMetaEntry {
|
||||
auto create_meta_nca(u64 tid, const keys::Keys& keys, NcmStorageId storage_id, const std::vector<NcaEntry>& ncas) -> NcaMetaEntry {
|
||||
CnmtHeader cnmt_header{};
|
||||
NcmApplicationMetaExtendedHeader cnmt_extended{};
|
||||
NcmPackagedContentInfo packaged_content_info[2]{};
|
||||
@@ -997,10 +787,10 @@ auto create_meta_nca(u64 tid, std::span<const u8> key, NcmStorageId storage_id,
|
||||
std::snprintf(cnmt_name, sizeof(cnmt_name), "Application_%016lX.cnmt", tid);
|
||||
add_file_entry(cnmt, cnmt_name, cnmt_buf.buf.data(), cnmt_buf.buf.size());
|
||||
|
||||
NcaHeader nca_header{};
|
||||
nca::Header nca_header{};
|
||||
buf.write(&nca_header, sizeof(nca_header));
|
||||
write_nca_pfs0(nca_header, 0, cnmt, PFS0_META_HASH_BLOCK_SIZE, buf);
|
||||
write_nca_header_encypted(nca_header, tid, key, NcaContentType_Meta, buf);
|
||||
write_nca_header_encypted(nca_header, tid, keys, nca::ContentType_Meta, buf);
|
||||
|
||||
// entry
|
||||
NcaMetaEntry entry{buf, NcmContentType_Meta};
|
||||
@@ -1040,26 +830,6 @@ auto create_meta_nca(u64 tid, std::span<const u8> key, NcmStorageId storage_id,
|
||||
return entry;
|
||||
}
|
||||
|
||||
Result nsDeleteApplicationRecord(Service* srv, u64 tid) {
|
||||
return serviceDispatchIn(srv, 27, tid);
|
||||
}
|
||||
|
||||
Result nsPushApplicationRecord(Service* srv, u64 tid, const NcmContentStorageRecord* records, u32 count) {
|
||||
const struct {
|
||||
u8 last_modified_event;
|
||||
u8 padding[0x7];
|
||||
u64 tid;
|
||||
} in = { NsApplicationRecordType_Installed, {0}, tid };
|
||||
|
||||
return serviceDispatchIn(srv, 16, in,
|
||||
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In },
|
||||
.buffers = { { records, sizeof(NcmContentStorageRecord) * count } });
|
||||
}
|
||||
|
||||
Result nsInvalidateApplicationControlCache(Service* srv, u64 tid) {
|
||||
return serviceDispatchIn(srv, 404, tid);
|
||||
}
|
||||
|
||||
auto install_forwader_internal(ui::ProgressBox* pbox, OwoConfig& config, NcmStorageId storage_id) -> Result {
|
||||
R_UNLESS(!config.nro_path.empty(), OwoError_BadArgs);
|
||||
// R_UNLESS(!config.icon.empty(), OwoError_BadArgs);
|
||||
@@ -1073,14 +843,8 @@ auto install_forwader_internal(ui::ProgressBox* pbox, OwoConfig& config, NcmStor
|
||||
R_TRY(nsInitialize());
|
||||
ON_SCOPE_EXIT(nsExit());
|
||||
|
||||
// generate header kek
|
||||
u8 header_kek[0x20];
|
||||
R_TRY(splCryptoGenerateAesKek(HEADER_KEK_SRC, 0, 0, header_kek));
|
||||
// gen header key 0
|
||||
u8 key[0x20];
|
||||
R_TRY(splCryptoGenerateAesKey(header_kek, HEADER_KEY_SRC, key));
|
||||
// gen header key 1
|
||||
R_TRY(splCryptoGenerateAesKey(header_kek, HEADER_KEY_SRC + 0x10, key + 0x10));
|
||||
keys::Keys keys;
|
||||
R_TRY(keys::parse_keys(keys, false));
|
||||
|
||||
// fix args to include nro path
|
||||
if (config.args.empty()) {
|
||||
@@ -1124,7 +888,7 @@ auto install_forwader_internal(ui::ProgressBox* pbox, OwoConfig& config, NcmStor
|
||||
patch_npdm(exefs[1].data, npdm_patch);
|
||||
|
||||
nca_entries.emplace_back(
|
||||
create_program_nca(tid, key, exefs, romfs, logo)
|
||||
create_program_nca(tid, keys, exefs, romfs, logo)
|
||||
);
|
||||
} else {
|
||||
nca_entries.emplace_back(
|
||||
@@ -1147,18 +911,18 @@ auto install_forwader_internal(ui::ProgressBox* pbox, OwoConfig& config, NcmStor
|
||||
add_file_entry(romfs, "/icon_AmericanEnglish.dat", config.icon);
|
||||
|
||||
nca_entries.emplace_back(
|
||||
create_control_nca(tid, key, romfs)
|
||||
create_control_nca(tid, keys, romfs)
|
||||
);
|
||||
}
|
||||
|
||||
// create meta
|
||||
NcmContentMetaHeader content_meta_header;
|
||||
NcmContentMetaKey content_meta_key;
|
||||
NcmContentStorageRecord content_storage_record;
|
||||
ncm::ContentStorageRecord content_storage_record;
|
||||
NcmContentMetaData content_meta_data;
|
||||
{
|
||||
pbox->NewTransfer("Creating Meta"_i18n).UpdateTransfer(2, 8);
|
||||
const auto meta_entry = create_meta_nca(tid, key, storage_id, nca_entries);
|
||||
const auto meta_entry = create_meta_nca(tid, keys, storage_id, nca_entries);
|
||||
|
||||
nca_entries.emplace_back(meta_entry.nca_entry);
|
||||
content_meta_header = meta_entry.content_meta_header;
|
||||
@@ -1218,15 +982,15 @@ auto install_forwader_internal(ui::ProgressBox* pbox, OwoConfig& config, NcmStor
|
||||
|
||||
// remove previous application record
|
||||
if (already_installed || hosversionBefore(2,0,0)) {
|
||||
const auto rc = nsDeleteApplicationRecord(srv_ptr, tid);
|
||||
const auto rc = ns::DeleteApplicationRecord(srv_ptr, tid);
|
||||
R_UNLESS(R_SUCCEEDED(rc) || hosversionBefore(2,0,0), rc);
|
||||
}
|
||||
|
||||
R_TRY(nsPushApplicationRecord(srv_ptr, tid, &content_storage_record, 1));
|
||||
R_TRY(ns::PushApplicationRecord(srv_ptr, tid, &content_storage_record, 1));
|
||||
|
||||
// force flush
|
||||
if (already_installed || hosversionBefore(2,0,0)) {
|
||||
const auto rc = nsInvalidateApplicationControlCache(srv_ptr, tid);
|
||||
const auto rc = ns::InvalidateApplicationControlCache(srv_ptr, tid);
|
||||
R_UNLESS(R_SUCCEEDED(rc) || hosversionBefore(2,0,0), rc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#include "owo.hpp"
|
||||
#include "swkbd.hpp"
|
||||
#include "i18n.hpp"
|
||||
#include "yati/yati.hpp"
|
||||
#include "yati/source/file.hpp"
|
||||
|
||||
#include <minIni.h>
|
||||
#include <minizip/unzip.h>
|
||||
@@ -52,6 +54,10 @@ constexpr std::string_view VIDEO_EXTENSIONS[] = {
|
||||
constexpr std::string_view IMAGE_EXTENSIONS[] = {
|
||||
"png", "jpg", "jpeg", "bmp", "gif",
|
||||
};
|
||||
constexpr std::string_view INSTALL_EXTENSIONS[] = {
|
||||
"nsp", "xci", "nsz", "xcz",
|
||||
};
|
||||
|
||||
|
||||
struct RomDatabaseEntry {
|
||||
std::string_view folder;
|
||||
@@ -294,6 +300,8 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
nro_launch(GetNewPathCurrent());
|
||||
}
|
||||
}));
|
||||
} else if (App::GetInstallEnable() && IsExtension(entry.GetExtension(), INSTALL_EXTENSIONS)) {
|
||||
InstallFile(GetEntry());
|
||||
} else {
|
||||
const auto assoc_list = FindFileAssocFor();
|
||||
if (!assoc_list.empty()) {
|
||||
@@ -406,7 +414,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
}
|
||||
log_write("clicked on delete\n");
|
||||
App::Push(std::make_shared<OptionBox>(
|
||||
"Delete Selected files?"_i18n, "No"_i18n, "Yes"_i18n, 1, [this](auto op_index){
|
||||
"Delete Selected files?"_i18n, "No"_i18n, "Yes"_i18n, 0, [this](auto op_index){
|
||||
if (op_index && *op_index) {
|
||||
App::PopToMenu();
|
||||
OnDeleteCallback();
|
||||
@@ -421,7 +429,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Paste"_i18n, [this](){
|
||||
const std::string buf = "Paste "_i18n + std::to_string(m_selected_files.size()) + " file(s)?"_i18n;
|
||||
App::Push(std::make_shared<OptionBox>(
|
||||
buf, "No"_i18n, "Yes"_i18n, 1, [this](auto op_index){
|
||||
buf, "No"_i18n, "Yes"_i18n, 0, [this](auto op_index){
|
||||
if (op_index && *op_index) {
|
||||
App::PopToMenu();
|
||||
OnPasteCallback();
|
||||
@@ -459,6 +467,32 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
}));
|
||||
}
|
||||
|
||||
// if install is enabled, check if all currently selected files are installable.
|
||||
if (m_entries_current.size() && App::GetInstallEnable()) {
|
||||
bool should_install = true;
|
||||
if (!m_selected_count) {
|
||||
should_install = IsExtension(GetEntry().GetExtension(), INSTALL_EXTENSIONS);
|
||||
} else {
|
||||
const auto entries = GetSelectedEntries();
|
||||
for (auto&e : entries) {
|
||||
if (!IsExtension(e.GetExtension(), INSTALL_EXTENSIONS)) {
|
||||
should_install = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (should_install) {
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Install"_i18n, [this](){
|
||||
if (!m_selected_count) {
|
||||
InstallFile(GetEntry());
|
||||
} else {
|
||||
InstallFiles(GetSelectedEntries());
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Advanced"_i18n, [this](){
|
||||
auto options = std::make_shared<Sidebar>("Advanced Options"_i18n, Sidebar::Side::RIGHT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
@@ -628,6 +662,9 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
icon = ThemeEntryID_ICON_VIDEO;
|
||||
} else if (IsExtension(ext, IMAGE_EXTENSIONS)) {
|
||||
icon = ThemeEntryID_ICON_IMAGE;
|
||||
} else if (IsExtension(ext, INSTALL_EXTENSIONS)) {
|
||||
// todo: maybe replace this icon with something else?
|
||||
icon = ThemeEntryID_ICON_NRO;
|
||||
} else if (IsExtension(ext, "zip")) {
|
||||
icon = ThemeEntryID_ICON_ZIP;
|
||||
} else if (IsExtension(ext, "nro")) {
|
||||
@@ -775,6 +812,34 @@ void Menu::InstallForwarder() {
|
||||
));
|
||||
}
|
||||
|
||||
void Menu::InstallFile(const FileEntry& target) {
|
||||
std::vector<FileEntry> targets{target};
|
||||
InstallFiles(targets);
|
||||
}
|
||||
|
||||
void Menu::InstallFiles(const std::vector<FileEntry>& targets) {
|
||||
App::Push(std::make_shared<OptionBox>("Install Selected files?"_i18n, "No"_i18n, "Yes"_i18n, 0, [this, targets](auto op_index){
|
||||
if (op_index && *op_index) {
|
||||
App::PopToMenu();
|
||||
|
||||
App::Push(std::make_shared<ui::ProgressBox>("Installing App"_i18n, [this, targets](auto pbox) mutable -> bool {
|
||||
for (auto& e : targets) {
|
||||
const auto rc = yati::InstallFromFile(pbox, &m_fs->m_fs, GetNewPath(e));
|
||||
if (rc == yati::Result_Cancelled) {
|
||||
break;
|
||||
} else if (R_FAILED(rc)) {
|
||||
return false;
|
||||
} else {
|
||||
App::Notify("Installed " + e.GetName());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
auto Menu::Scan(const fs::FsPath& new_path, bool is_walk_up) -> Result {
|
||||
log_write("new scan path: %s\n", new_path.s);
|
||||
if (!is_walk_up && !m_path.empty() && !m_entries_current.empty()) {
|
||||
|
||||
@@ -323,10 +323,6 @@ MainMenu::MainMenu() {
|
||||
auto options = std::make_shared<Sidebar>("Advanced Options"_i18n, Sidebar::Side::LEFT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
|
||||
SidebarEntryArray::Items install_items;
|
||||
install_items.push_back("System memory"_i18n);
|
||||
install_items.push_back("microSD card"_i18n);
|
||||
|
||||
SidebarEntryArray::Items text_scroll_speed_items;
|
||||
text_scroll_speed_items.push_back("Slow"_i18n);
|
||||
text_scroll_speed_items.push_back("Normal"_i18n);
|
||||
@@ -340,21 +336,94 @@ MainMenu::MainMenu() {
|
||||
App::SetReplaceHbmenuEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Install forwarders"_i18n, App::GetInstallEnable(), [this](bool& enable){
|
||||
App::SetInstallEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Install location"_i18n, install_items, [this](s64& index_out){
|
||||
App::SetInstallSdEnable(index_out);
|
||||
}, (s64)App::GetInstallSdEnable()));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Show install warning"_i18n, App::GetInstallPrompt(), [this](bool& enable){
|
||||
App::SetInstallPrompt(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Text scroll speed"_i18n, text_scroll_speed_items, [this](s64& index_out){
|
||||
App::SetTextScrollSpeed(index_out);
|
||||
}, (s64)App::GetTextScrollSpeed()));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Install options"_i18n, [this](){
|
||||
auto options = std::make_shared<Sidebar>("Install Options"_i18n, Sidebar::Side::LEFT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
|
||||
SidebarEntryArray::Items install_items;
|
||||
install_items.push_back("System memory"_i18n);
|
||||
install_items.push_back("microSD card"_i18n);
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Enable"_i18n, App::GetInstallEnable(), [this](bool& enable){
|
||||
App::SetInstallEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Show install warning"_i18n, App::GetInstallPrompt(), [this](bool& enable){
|
||||
App::SetInstallPrompt(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Install location"_i18n, install_items, [this](s64& index_out){
|
||||
App::SetInstallSdEnable(index_out);
|
||||
}, (s64)App::GetInstallSdEnable()));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Allow downgrade"_i18n, App::GetApp()->m_allow_downgrade.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_allow_downgrade.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Skip if already installed"_i18n, App::GetApp()->m_skip_if_already_installed.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_skip_if_already_installed.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Ticket only"_i18n, App::GetApp()->m_ticket_only.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_ticket_only.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Patch ticket"_i18n, App::GetApp()->m_patch_ticket.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_patch_ticket.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Skip base"_i18n, App::GetApp()->m_skip_base.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_skip_base.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Skip Patch"_i18n, App::GetApp()->m_skip_patch.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_skip_patch.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Skip addon"_i18n, App::GetApp()->m_skip_addon.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_skip_addon.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Skip data patch"_i18n, App::GetApp()->m_skip_data_patch.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_skip_data_patch.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Skip ticket"_i18n, App::GetApp()->m_skip_ticket.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_skip_ticket.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("skip NCA hash verify"_i18n, App::GetApp()->m_skip_nca_hash_verify.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_skip_nca_hash_verify.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Skip RSA header verify"_i18n, App::GetApp()->m_skip_rsa_header_fixed_key_verify.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_skip_rsa_header_fixed_key_verify.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Skip RSA NPDM verify"_i18n, App::GetApp()->m_skip_rsa_npdm_fixed_key_verify.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_skip_rsa_npdm_fixed_key_verify.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Ignore distribution bit"_i18n, App::GetApp()->m_ignore_distribution_bit.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_ignore_distribution_bit.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Convert to standard crypto"_i18n, App::GetApp()->m_convert_to_standard_crypto.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_convert_to_standard_crypto.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Lower master key"_i18n, App::GetApp()->m_lower_master_key.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_lower_master_key.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Lower system version"_i18n, App::GetApp()->m_lower_system_version.Get(), [this](bool& enable){
|
||||
App::GetApp()->m_lower_system_version.Set(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
}));
|
||||
}));
|
||||
}})
|
||||
);
|
||||
|
||||
@@ -102,6 +102,14 @@ auto ProgressBox::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
}
|
||||
}
|
||||
|
||||
auto ProgressBox::SetTitle(const std::string& title) -> ProgressBox& {
|
||||
mutexLock(&m_mutex);
|
||||
m_title = title;
|
||||
mutexUnlock(&m_mutex);
|
||||
Yield();
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto ProgressBox::NewTransfer(const std::string& transfer) -> ProgressBox& {
|
||||
mutexLock(&m_mutex);
|
||||
m_transfer = transfer;
|
||||
|
||||
67
sphaira/source/yati/container/nsp.cpp
Normal file
67
sphaira/source/yati/container/nsp.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "yati/container/nsp.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "log.hpp"
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::yati::container {
|
||||
namespace {
|
||||
|
||||
#define PFS0_MAGIC 0x30534650
|
||||
|
||||
struct Pfs0Header {
|
||||
u32 magic;
|
||||
u32 total_files;
|
||||
u32 string_table_size;
|
||||
u32 padding;
|
||||
};
|
||||
|
||||
struct Pfs0FileTableEntry {
|
||||
u64 data_offset;
|
||||
u64 data_size;
|
||||
u32 name_offset;
|
||||
u32 padding;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Result Nsp::Validate(source::Base* source) {
|
||||
u32 magic;
|
||||
u64 bytes_read;
|
||||
R_TRY(source->Read(std::addressof(magic), 0, sizeof(magic), std::addressof(bytes_read)));
|
||||
R_UNLESS(magic == PFS0_MAGIC, 0x1);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result Nsp::GetCollections(Collections& out) {
|
||||
u64 bytes_read;
|
||||
s64 off = 0;
|
||||
|
||||
// get header
|
||||
Pfs0Header header{};
|
||||
R_TRY(m_source->Read(std::addressof(header), off, sizeof(header), std::addressof(bytes_read)));
|
||||
R_UNLESS(header.magic == PFS0_MAGIC, 0x1);
|
||||
off += bytes_read;
|
||||
|
||||
// get file table
|
||||
std::vector<Pfs0FileTableEntry> file_table(header.total_files);
|
||||
R_TRY(m_source->Read(file_table.data(), off, file_table.size() * sizeof(Pfs0FileTableEntry), std::addressof(bytes_read)))
|
||||
off += bytes_read;
|
||||
|
||||
// get string table
|
||||
std::vector<char> string_table(header.string_table_size);
|
||||
R_TRY(m_source->Read(string_table.data(), off, string_table.size(), std::addressof(bytes_read)))
|
||||
off += bytes_read;
|
||||
|
||||
out.reserve(header.total_files);
|
||||
for (u32 i = 0; i < header.total_files; i++) {
|
||||
CollectionEntry entry;
|
||||
entry.name = string_table.data() + file_table[i].name_offset;
|
||||
entry.offset = off + file_table[i].data_offset;
|
||||
entry.size = file_table[i].data_size;
|
||||
out.emplace_back(entry);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace sphaira::yati::container
|
||||
95
sphaira/source/yati/container/xci.cpp
Normal file
95
sphaira/source/yati/container/xci.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#include "yati/container/xci.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
namespace sphaira::yati::container {
|
||||
namespace {
|
||||
|
||||
#define XCI_MAGIC std::byteswap(0x48454144)
|
||||
#define HFS0_MAGIC 0x30534648
|
||||
#define HFS0_HEADER_OFFSET 0xF000
|
||||
|
||||
struct Hfs0Header {
|
||||
u32 magic;
|
||||
u32 total_files;
|
||||
u32 string_table_size;
|
||||
u32 padding;
|
||||
};
|
||||
|
||||
struct Hfs0FileTableEntry {
|
||||
u64 data_offset;
|
||||
u64 data_size;
|
||||
u32 name_offset;
|
||||
u32 hash_size;
|
||||
u64 padding;
|
||||
u8 hash[0x20];
|
||||
};
|
||||
|
||||
struct Hfs0 {
|
||||
Hfs0Header header{};
|
||||
std::vector<Hfs0FileTableEntry> file_table{};
|
||||
std::vector<std::string> string_table{};
|
||||
s64 data_offset{};
|
||||
};
|
||||
|
||||
Result Hfs0GetPartition(source::Base* source, s64 off, Hfs0& out) {
|
||||
u64 bytes_read;
|
||||
|
||||
// get header
|
||||
R_TRY(source->Read(std::addressof(out.header), off, sizeof(out.header), std::addressof(bytes_read)));
|
||||
R_UNLESS(out.header.magic == HFS0_MAGIC, 0x1);
|
||||
off += bytes_read;
|
||||
|
||||
// get file table
|
||||
out.file_table.resize(out.header.total_files);
|
||||
R_TRY(source->Read(out.file_table.data(), off, out.file_table.size() * sizeof(Hfs0FileTableEntry), std::addressof(bytes_read)))
|
||||
off += bytes_read;
|
||||
|
||||
// get string table
|
||||
std::vector<char> string_table(out.header.string_table_size);
|
||||
R_TRY(source->Read(string_table.data(), off, string_table.size(), std::addressof(bytes_read)))
|
||||
off += bytes_read;
|
||||
|
||||
for (u32 i = 0; i < out.header.total_files; i++) {
|
||||
out.string_table.emplace_back(string_table.data() + out.file_table[i].name_offset);
|
||||
}
|
||||
|
||||
out.data_offset = off;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Result Xci::Validate(source::Base* source) {
|
||||
u32 magic;
|
||||
u64 bytes_read;
|
||||
R_TRY(source->Read(std::addressof(magic), 0x100, sizeof(magic), std::addressof(bytes_read)));
|
||||
R_UNLESS(magic == XCI_MAGIC, 0x1);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result Xci::GetCollections(Collections& out) {
|
||||
Hfs0 root{};
|
||||
R_TRY(Hfs0GetPartition(m_source, HFS0_HEADER_OFFSET, root));
|
||||
|
||||
for (u32 i = 0; i < root.header.total_files; i++) {
|
||||
if (root.string_table[i] == "secure") {
|
||||
Hfs0 secure{};
|
||||
R_TRY(Hfs0GetPartition(m_source, root.data_offset + root.file_table[i].data_offset, secure));
|
||||
|
||||
for (u32 i = 0; i < secure.header.total_files; i++) {
|
||||
CollectionEntry entry;
|
||||
entry.name = secure.string_table[i];
|
||||
entry.offset = secure.data_offset + secure.file_table[i].data_offset;
|
||||
entry.size = secure.file_table[i].data_size;
|
||||
out.emplace_back(entry);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
return 0x1;
|
||||
}
|
||||
|
||||
} // namespace sphaira::yati::container
|
||||
122
sphaira/source/yati/nx/es.cpp
Normal file
122
sphaira/source/yati/nx/es.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
#include "yati/nx/es.hpp"
|
||||
#include "yati/nx/crypto.hpp"
|
||||
#include "yati/nx/nxdumptool_rsa.h"
|
||||
#include "defines.hpp"
|
||||
#include "log.hpp"
|
||||
#include <memory>
|
||||
#include <cstring>
|
||||
|
||||
namespace sphaira::es {
|
||||
namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
Result ImportTicket(Service* srv, const void* tik_buf, u64 tik_size, const void* cert_buf, u64 cert_size) {
|
||||
return serviceDispatch(srv, 1,
|
||||
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In, SfBufferAttr_HipcMapAlias | SfBufferAttr_In },
|
||||
.buffers = { { tik_buf, tik_size }, { cert_buf, cert_size } });
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
TikPropertyMask_None = 0,
|
||||
TikPropertyMask_PreInstallation = BIT(0), ///< Determines if the title comes pre-installed on the device. Most likely unused -- a remnant from previous ticket formats.
|
||||
TikPropertyMask_SharedTitle = BIT(1), ///< Determines if the title holds shared contents only. Most likely unused -- a remnant from previous ticket formats.
|
||||
TikPropertyMask_AllContents = BIT(2), ///< Determines if the content index mask shall be bypassed. Most likely unused -- a remnant from previous ticket formats.
|
||||
TikPropertyMask_DeviceLinkIndepedent = BIT(3), ///< Determines if the console should *not* connect to the Internet to verify if the title's being used by the primary console.
|
||||
TikPropertyMask_Volatile = BIT(4), ///< Determines if the ticket copy inside ticket.bin is available after reboot. Can be encrypted.
|
||||
TikPropertyMask_ELicenseRequired = BIT(5), ///< Determines if the console should connect to the Internet to perform license verification.
|
||||
TikPropertyMask_Count = 6 ///< Total values supported by this enum.
|
||||
} TikPropertyMask;
|
||||
|
||||
Result GetTicketDataOffset(std::span<const u8> ticket, u64& out) {
|
||||
log_write("inside es\n");
|
||||
u32 signature_type;
|
||||
std::memcpy(std::addressof(signature_type), ticket.data(), sizeof(signature_type));
|
||||
|
||||
u32 signature_size;
|
||||
switch (signature_type) {
|
||||
case es::TicketSigantureType_RSA_4096_SHA1: log_write("RSA-4096 PKCS#1 v1.5 with SHA-1\n"); signature_size = 0x200; break;
|
||||
case es::TicketSigantureType_RSA_2048_SHA1: log_write("RSA-2048 PKCS#1 v1.5 with SHA-1\n"); signature_size = 0x100; break;
|
||||
case es::TicketSigantureType_ECDSA_SHA1: log_write("ECDSA with SHA-1\n"); signature_size = 0x3C; break;
|
||||
case es::TicketSigantureType_RSA_4096_SHA256: log_write("RSA-4096 PKCS#1 v1.5 with SHA-256\n"); signature_size = 0x200; break;
|
||||
case es::TicketSigantureType_RSA_2048_SHA256: log_write("RSA-2048 PKCS#1 v1.5 with SHA-256\n"); signature_size = 0x100; break;
|
||||
case es::TicketSigantureType_ECDSA_SHA256: log_write("ECDSA with SHA-256\n"); signature_size = 0x3C; break;
|
||||
case es::TicketSigantureType_HMAC_SHA1_160: log_write("HMAC-SHA1-160\n"); signature_size = 0x14; break;
|
||||
default: log_write("unknown ticket\n"); return 0x1;
|
||||
}
|
||||
|
||||
// align-up to 0x40.
|
||||
out = ((signature_size + sizeof(signature_type)) + 0x3F) & ~0x3F;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result GetTicketData(std::span<const u8> ticket, es::TicketData* out) {
|
||||
u64 data_off;
|
||||
R_TRY(GetTicketDataOffset(ticket, data_off));
|
||||
std::memcpy(out, ticket.data() + data_off, sizeof(*out));
|
||||
|
||||
// validate ticket data.
|
||||
R_UNLESS(out->ticket_version1 == 0x2, Result_InvalidTicketVersion); // must be version 2.
|
||||
R_UNLESS(out->title_key_type == es::TicketTitleKeyType_Common || out->title_key_type == es::TicketTitleKeyType_Personalized, Result_InvalidTicketKeyType);
|
||||
R_UNLESS(out->master_key_revision <= 0x20, Result_InvalidTicketKeyRevision);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result SetTicketData(std::span<u8> ticket, const es::TicketData* in) {
|
||||
u64 data_off;
|
||||
R_TRY(GetTicketDataOffset(ticket, data_off));
|
||||
std::memcpy(ticket.data() + data_off, in, sizeof(*in));
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result GetTitleKey(keys::KeyEntry& out, const TicketData& data, const keys::Keys& keys) {
|
||||
if (data.title_key_type == es::TicketTitleKeyType_Common) {
|
||||
std::memcpy(std::addressof(out), data.title_key_block, sizeof(out));
|
||||
} else if (data.title_key_type == es::TicketTitleKeyType_Personalized) {
|
||||
auto rsa_key = (const es::EticketRsaDeviceKey*)keys.eticket_device_key.key;
|
||||
log_write("personalised ticket\n");
|
||||
log_write("master_key_revision: %u\n", data.master_key_revision);
|
||||
log_write("license_type: %u\n", data.license_type);
|
||||
log_write("properties_bitfield: 0x%X\n", data.properties_bitfield);
|
||||
log_write("device_id: 0x%lX vs 0x%lX\n", data.device_id, std::byteswap(rsa_key->device_id));
|
||||
|
||||
R_UNLESS(data.device_id == std::byteswap(rsa_key->device_id), 0x1);
|
||||
log_write("device id is same\n");
|
||||
|
||||
u8 out_keydata[RSA2048_BYTES]{};
|
||||
size_t out_keydata_size;
|
||||
R_UNLESS(rsa2048OaepDecrypt(out_keydata, sizeof(out_keydata), data.title_key_block, rsa_key->modulus, &rsa_key->public_exponent, sizeof(rsa_key->public_exponent), rsa_key->private_exponent, sizeof(rsa_key->private_exponent), NULL, 0, &out_keydata_size), 0x1);
|
||||
R_UNLESS(out_keydata_size >= sizeof(out), 0x1);
|
||||
std::memcpy(std::addressof(out), out_keydata, sizeof(out));
|
||||
} else {
|
||||
R_THROW(0x1);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result DecryptTitleKey(keys::KeyEntry& out, u8 key_gen, const keys::Keys& keys) {
|
||||
keys::KeyEntry title_kek;
|
||||
R_TRY(keys.GetTitleKek(std::addressof(title_kek), key_gen));
|
||||
crypto::cryptoAes128(std::addressof(out), std::addressof(out), std::addressof(title_kek), false);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
// todo: i thought i already wrote the code for this??
|
||||
// todo: patch the ticket.
|
||||
Result PatchTicket(std::span<u8> ticket, const keys::Keys& keys, bool convert_personalised) {
|
||||
TicketData data;
|
||||
R_TRY(GetTicketData(ticket, &data));
|
||||
|
||||
if (data.title_key_type == es::TicketTitleKeyType_Common) {
|
||||
// todo: verify common signature
|
||||
} else if (data.title_key_type == es::TicketTitleKeyType_Personalized && convert_personalised) {
|
||||
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace sphaira::es
|
||||
130
sphaira/source/yati/nx/keys.cpp
Normal file
130
sphaira/source/yati/nx/keys.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
#include "yati/nx/keys.hpp"
|
||||
#include "yati/nx/nca.hpp"
|
||||
#include "yati/nx/es.hpp"
|
||||
#include "yati/nx/crypto.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "log.hpp"
|
||||
#include <minIni.h>
|
||||
#include <memory>
|
||||
#include <bit>
|
||||
#include <cstring>
|
||||
|
||||
namespace sphaira::keys {
|
||||
namespace {
|
||||
|
||||
constexpr u8 HEADER_KEK_SRC[0x10] = {
|
||||
0x1F, 0x12, 0x91, 0x3A, 0x4A, 0xCB, 0xF0, 0x0D, 0x4C, 0xDE, 0x3A, 0xF6, 0xD5, 0x23, 0x88, 0x2A
|
||||
};
|
||||
|
||||
constexpr u8 HEADER_KEY_SRC[0x20] = {
|
||||
0x5A, 0x3E, 0xD8, 0x4F, 0xDE, 0xC0, 0xD8, 0x26, 0x31, 0xF7, 0xE2, 0x5D, 0x19, 0x7B, 0xF5, 0xD0,
|
||||
0x1C, 0x9B, 0x7B, 0xFA, 0xF6, 0x28, 0x18, 0x3D, 0x71, 0xF6, 0x4D, 0x73, 0xF1, 0x50, 0xB9, 0xD2
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void parse_hex_key(void* key, const char* hex) {
|
||||
char low[0x11]{};
|
||||
char upp[0x11]{};
|
||||
std::memcpy(low, hex, 0x10);
|
||||
std::memcpy(upp, hex + 0x10, 0x10);
|
||||
*(u64*)key = std::byteswap(std::strtoul(low, nullptr, 0x10));
|
||||
*(u64*)((u8*)key + 8) = std::byteswap(std::strtoul(upp, nullptr, 0x10));
|
||||
}
|
||||
|
||||
Result parse_keys(Keys& out, bool read_from_file) {
|
||||
static constexpr auto find_key = [](const char* key, const char* value, const char* search_key, KeySection& key_section) -> bool {
|
||||
if (!std::strncmp(key, search_key, std::strlen(search_key))) {
|
||||
// get key index.
|
||||
char* end;
|
||||
const auto key_value_str = key + std::strlen(search_key);
|
||||
const auto index = std::strtoul(key_value_str, &end, 0x10);
|
||||
if (end && end != key_value_str && index < 0x20) {
|
||||
KeyEntry keak;
|
||||
parse_hex_key(std::addressof(keak), value);
|
||||
key_section[index] = keak;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
static constexpr auto find_key_single = [](const char* key, const char* value, const char* search_key, KeyEntry& key_entry) -> bool {
|
||||
if (!std::strcmp(key, search_key)) {
|
||||
parse_hex_key(std::addressof(key_entry), value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
static constexpr auto cb = [](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) -> int {
|
||||
auto keys = static_cast<Keys*>(UserData);
|
||||
|
||||
auto key_text_key_area_key_app = "key_area_key_application_";
|
||||
auto key_text_key_area_key_oce = "key_area_key_ocean_";
|
||||
auto key_text_key_area_key_sys = "key_area_key_system_";
|
||||
auto key_text_titlekek = "titlekek_";
|
||||
auto key_text_master_key = "master_key_";
|
||||
auto key_text_eticket_rsa_kek = keys->eticket_device_key.generation ? "eticket_rsa_kek_personalized" : "eticket_rsa_kek";
|
||||
|
||||
if (find_key(Key, Value, key_text_key_area_key_app, keys->key_area_key[nca::KeyAreaEncryptionKeyIndex_Application])) {
|
||||
return 1;
|
||||
} else if (find_key(Key, Value, key_text_key_area_key_oce, keys->key_area_key[nca::KeyAreaEncryptionKeyIndex_Ocean])) {
|
||||
return 1;
|
||||
} else if (find_key(Key, Value, key_text_key_area_key_sys, keys->key_area_key[nca::KeyAreaEncryptionKeyIndex_System])) {
|
||||
return 1;
|
||||
} else if (find_key(Key, Value, key_text_titlekek, keys->titlekek)) {
|
||||
return 1;
|
||||
} else if (find_key(Key, Value, key_text_master_key, keys->master_key)) {
|
||||
return 1;
|
||||
} else if (find_key_single(Key, Value, key_text_eticket_rsa_kek, keys->eticket_rsa_kek)) {
|
||||
log_write("found key single: key: %s value %s\n", Key, Value);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
};
|
||||
|
||||
R_TRY(splCryptoInitialize());
|
||||
ON_SCOPE_EXIT(splCryptoExit());
|
||||
|
||||
u8 header_kek[0x20];
|
||||
R_TRY(splCryptoGenerateAesKek(HEADER_KEK_SRC, 0, 0, header_kek));
|
||||
R_TRY(splCryptoGenerateAesKey(header_kek, HEADER_KEY_SRC, out.header_key));
|
||||
R_TRY(splCryptoGenerateAesKey(header_kek, HEADER_KEY_SRC + 0x10, out.header_key + 0x10));
|
||||
|
||||
if (read_from_file) {
|
||||
// get eticket device key, needed for decrypting personalised tickets.
|
||||
R_TRY(setcalInitialize());
|
||||
ON_SCOPE_EXIT(setcalExit());
|
||||
R_TRY(setcalGetEticketDeviceKey(std::addressof(out.eticket_device_key)));
|
||||
|
||||
R_UNLESS(ini_browse(cb, std::addressof(out), "/switch/prod.keys"), 0x1);
|
||||
|
||||
// decrypt eticket device key.
|
||||
if (out.eticket_rsa_kek.IsValid()) {
|
||||
auto rsa_key = (es::EticketRsaDeviceKey*)out.eticket_device_key.key;
|
||||
|
||||
Aes128CtrContext eticket_aes_ctx{};
|
||||
aes128CtrContextCreate(&eticket_aes_ctx, &out.eticket_rsa_kek, rsa_key->ctr);
|
||||
aes128CtrCrypt(&eticket_aes_ctx, &(rsa_key->private_exponent), &(rsa_key->private_exponent), sizeof(es::EticketRsaDeviceKey) - sizeof(rsa_key->ctr));
|
||||
|
||||
const auto public_exponent = std::byteswap(rsa_key->public_exponent);
|
||||
if (public_exponent != 0x10001) {
|
||||
log_write("etick decryption fail: 0x%X\n", public_exponent);
|
||||
if (public_exponent == 0) {
|
||||
log_write("eticket device id is NULL\n");
|
||||
}
|
||||
R_THROW(0x1);
|
||||
} else {
|
||||
log_write("eticket match\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace sphaira::keys
|
||||
154
sphaira/source/yati/nx/nca.cpp
Normal file
154
sphaira/source/yati/nx/nca.cpp
Normal file
@@ -0,0 +1,154 @@
|
||||
#include "yati/nx/nca.hpp"
|
||||
#include "yati/nx/crypto.hpp"
|
||||
#include "yati/nx/nxdumptool_rsa.h"
|
||||
#include "log.hpp"
|
||||
|
||||
namespace sphaira::nca {
|
||||
namespace {
|
||||
|
||||
constexpr u8 g_key_area_key_application_source[0x10] = { 0x7F, 0x59, 0x97, 0x1E, 0x62, 0x9F, 0x36, 0xA1, 0x30, 0x98, 0x06, 0x6F, 0x21, 0x44, 0xC3, 0x0D };
|
||||
constexpr u8 g_key_area_key_ocean_source[0x10] = { 0x32, 0x7D, 0x36, 0x08, 0x5A, 0xD1, 0x75, 0x8D, 0xAB, 0x4E, 0x6F, 0xBA, 0xA5, 0x55, 0xD8, 0x82 };
|
||||
constexpr u8 g_key_area_key_system_source[0x10] = { 0x87, 0x45, 0xF1, 0xBB, 0xA6, 0xBE, 0x79, 0x64, 0x7D, 0x04, 0x8B, 0xA6, 0x7B, 0x5F, 0xDA, 0x4A };
|
||||
|
||||
constexpr const u8* g_key_area_key[] = {
|
||||
g_key_area_key_application_source,
|
||||
g_key_area_key_ocean_source,
|
||||
g_key_area_key_system_source
|
||||
};
|
||||
|
||||
const unsigned char nca_hdr_fixed_key_moduli_retail[0x2][0x100] = { /* Fixed RSA key used to validate NCA signature 0. */
|
||||
{
|
||||
0xBF, 0xBE, 0x40, 0x6C, 0xF4, 0xA7, 0x80, 0xE9, 0xF0, 0x7D, 0x0C, 0x99, 0x61, 0x1D, 0x77, 0x2F,
|
||||
0x96, 0xBC, 0x4B, 0x9E, 0x58, 0x38, 0x1B, 0x03, 0xAB, 0xB1, 0x75, 0x49, 0x9F, 0x2B, 0x4D, 0x58,
|
||||
0x34, 0xB0, 0x05, 0xA3, 0x75, 0x22, 0xBE, 0x1A, 0x3F, 0x03, 0x73, 0xAC, 0x70, 0x68, 0xD1, 0x16,
|
||||
0xB9, 0x04, 0x46, 0x5E, 0xB7, 0x07, 0x91, 0x2F, 0x07, 0x8B, 0x26, 0xDE, 0xF6, 0x00, 0x07, 0xB2,
|
||||
0xB4, 0x51, 0xF8, 0x0D, 0x0A, 0x5E, 0x58, 0xAD, 0xEB, 0xBC, 0x9A, 0xD6, 0x49, 0xB9, 0x64, 0xEF,
|
||||
0xA7, 0x82, 0xB5, 0xCF, 0x6D, 0x70, 0x13, 0xB0, 0x0F, 0x85, 0xF6, 0xA9, 0x08, 0xAA, 0x4D, 0x67,
|
||||
0x66, 0x87, 0xFA, 0x89, 0xFF, 0x75, 0x90, 0x18, 0x1E, 0x6B, 0x3D, 0xE9, 0x8A, 0x68, 0xC9, 0x26,
|
||||
0x04, 0xD9, 0x80, 0xCE, 0x3F, 0x5E, 0x92, 0xCE, 0x01, 0xFF, 0x06, 0x3B, 0xF2, 0xC1, 0xA9, 0x0C,
|
||||
0xCE, 0x02, 0x6F, 0x16, 0xBC, 0x92, 0x42, 0x0A, 0x41, 0x64, 0xCD, 0x52, 0xB6, 0x34, 0x4D, 0xAE,
|
||||
0xC0, 0x2E, 0xDE, 0xA4, 0xDF, 0x27, 0x68, 0x3C, 0xC1, 0xA0, 0x60, 0xAD, 0x43, 0xF3, 0xFC, 0x86,
|
||||
0xC1, 0x3E, 0x6C, 0x46, 0xF7, 0x7C, 0x29, 0x9F, 0xFA, 0xFD, 0xF0, 0xE3, 0xCE, 0x64, 0xE7, 0x35,
|
||||
0xF2, 0xF6, 0x56, 0x56, 0x6F, 0x6D, 0xF1, 0xE2, 0x42, 0xB0, 0x83, 0x40, 0xA5, 0xC3, 0x20, 0x2B,
|
||||
0xCC, 0x9A, 0xAE, 0xCA, 0xED, 0x4D, 0x70, 0x30, 0xA8, 0x70, 0x1C, 0x70, 0xFD, 0x13, 0x63, 0x29,
|
||||
0x02, 0x79, 0xEA, 0xD2, 0xA7, 0xAF, 0x35, 0x28, 0x32, 0x1C, 0x7B, 0xE6, 0x2F, 0x1A, 0xAA, 0x40,
|
||||
0x7E, 0x32, 0x8C, 0x27, 0x42, 0xFE, 0x82, 0x78, 0xEC, 0x0D, 0xEB, 0xE6, 0x83, 0x4B, 0x6D, 0x81,
|
||||
0x04, 0x40, 0x1A, 0x9E, 0x9A, 0x67, 0xF6, 0x72, 0x29, 0xFA, 0x04, 0xF0, 0x9D, 0xE4, 0xF4, 0x03,
|
||||
},
|
||||
{
|
||||
0xAD, 0xE3, 0xE1, 0xFA, 0x04, 0x35, 0xE5, 0xB6, 0xDD, 0x49, 0xEA, 0x89, 0x29, 0xB1, 0xFF, 0xB6,
|
||||
0x43, 0xDF, 0xCA, 0x96, 0xA0, 0x4A, 0x13, 0xDF, 0x43, 0xD9, 0x94, 0x97, 0x96, 0x43, 0x65, 0x48,
|
||||
0x70, 0x58, 0x33, 0xA2, 0x7D, 0x35, 0x7B, 0x96, 0x74, 0x5E, 0x0B, 0x5C, 0x32, 0x18, 0x14, 0x24,
|
||||
0xC2, 0x58, 0xB3, 0x6C, 0x22, 0x7A, 0xA1, 0xB7, 0xCB, 0x90, 0xA7, 0xA3, 0xF9, 0x7D, 0x45, 0x16,
|
||||
0xA5, 0xC8, 0xED, 0x8F, 0xAD, 0x39, 0x5E, 0x9E, 0x4B, 0x51, 0x68, 0x7D, 0xF8, 0x0C, 0x35, 0xC6,
|
||||
0x3F, 0x91, 0xAE, 0x44, 0xA5, 0x92, 0x30, 0x0D, 0x46, 0xF8, 0x40, 0xFF, 0xD0, 0xFF, 0x06, 0xD2,
|
||||
0x1C, 0x7F, 0x96, 0x18, 0xDC, 0xB7, 0x1D, 0x66, 0x3E, 0xD1, 0x73, 0xBC, 0x15, 0x8A, 0x2F, 0x94,
|
||||
0xF3, 0x00, 0xC1, 0x83, 0xF1, 0xCD, 0xD7, 0x81, 0x88, 0xAB, 0xDF, 0x8C, 0xEF, 0x97, 0xDD, 0x1B,
|
||||
0x17, 0x5F, 0x58, 0xF6, 0x9A, 0xE9, 0xE8, 0xC2, 0x2F, 0x38, 0x15, 0xF5, 0x21, 0x07, 0xF8, 0x37,
|
||||
0x90, 0x5D, 0x2E, 0x02, 0x40, 0x24, 0x15, 0x0D, 0x25, 0xB7, 0x26, 0x5D, 0x09, 0xCC, 0x4C, 0xF4,
|
||||
0xF2, 0x1B, 0x94, 0x70, 0x5A, 0x9E, 0xEE, 0xED, 0x77, 0x77, 0xD4, 0x51, 0x99, 0xF5, 0xDC, 0x76,
|
||||
0x1E, 0xE3, 0x6C, 0x8C, 0xD1, 0x12, 0xD4, 0x57, 0xD1, 0xB6, 0x83, 0xE4, 0xE4, 0xFE, 0xDA, 0xE9,
|
||||
0xB4, 0x3B, 0x33, 0xE5, 0x37, 0x8A, 0xDF, 0xB5, 0x7F, 0x89, 0xF1, 0x9B, 0x9E, 0xB0, 0x15, 0xB2,
|
||||
0x3A, 0xFE, 0xEA, 0x61, 0x84, 0x5B, 0x7D, 0x4B, 0x23, 0x12, 0x0B, 0x83, 0x12, 0xF2, 0x22, 0x6B,
|
||||
0xB9, 0x22, 0x96, 0x4B, 0x26, 0x0B, 0x63, 0x5E, 0x96, 0x57, 0x52, 0xA3, 0x67, 0x64, 0x22, 0xCA,
|
||||
0xD0, 0x56, 0x3E, 0x74, 0xB5, 0x98, 0x1F, 0x0D, 0xF8, 0xB3, 0x34, 0xE6, 0x98, 0x68, 0x5A, 0xAD,
|
||||
}
|
||||
};
|
||||
|
||||
const unsigned char acid_fixed_key_moduli_retail[0x2][0x100] = { /* Fixed RSA keys used to validate ACID signatures. */
|
||||
{
|
||||
0xDD, 0xC8, 0xDD, 0xF2, 0x4E, 0x6D, 0xF0, 0xCA, 0x9E, 0xC7, 0x5D, 0xC7, 0x7B, 0xAD, 0xFE, 0x7D,
|
||||
0x23, 0x89, 0x69, 0xB6, 0xF2, 0x06, 0xA2, 0x02, 0x88, 0xE1, 0x55, 0x91, 0xAB, 0xCB, 0x4D, 0x50,
|
||||
0x2E, 0xFC, 0x9D, 0x94, 0x76, 0xD6, 0x4C, 0xD8, 0xFF, 0x10, 0xFA, 0x5E, 0x93, 0x0A, 0xB4, 0x57,
|
||||
0xAC, 0x51, 0xC7, 0x16, 0x66, 0xF4, 0x1A, 0x54, 0xC2, 0xC5, 0x04, 0x3D, 0x1B, 0xFE, 0x30, 0x20,
|
||||
0x8A, 0xAC, 0x6F, 0x6F, 0xF5, 0xC7, 0xB6, 0x68, 0xB8, 0xC9, 0x40, 0x6B, 0x42, 0xAD, 0x11, 0x21,
|
||||
0xE7, 0x8B, 0xE9, 0x75, 0x01, 0x86, 0xE4, 0x48, 0x9B, 0x0A, 0x0A, 0xF8, 0x7F, 0xE8, 0x87, 0xF2,
|
||||
0x82, 0x01, 0xE6, 0xA3, 0x0F, 0xE4, 0x66, 0xAE, 0x83, 0x3F, 0x4E, 0x9F, 0x5E, 0x01, 0x30, 0xA4,
|
||||
0x00, 0xB9, 0x9A, 0xAE, 0x5F, 0x03, 0xCC, 0x18, 0x60, 0xE5, 0xEF, 0x3B, 0x5E, 0x15, 0x16, 0xFE,
|
||||
0x1C, 0x82, 0x78, 0xB5, 0x2F, 0x47, 0x7C, 0x06, 0x66, 0x88, 0x5D, 0x35, 0xA2, 0x67, 0x20, 0x10,
|
||||
0xE7, 0x6C, 0x43, 0x68, 0xD3, 0xE4, 0x5A, 0x68, 0x2A, 0x5A, 0xE2, 0x6D, 0x73, 0xB0, 0x31, 0x53,
|
||||
0x1C, 0x20, 0x09, 0x44, 0xF5, 0x1A, 0x9D, 0x22, 0xBE, 0x12, 0xA1, 0x77, 0x11, 0xE2, 0xA1, 0xCD,
|
||||
0x40, 0x9A, 0xA2, 0x8B, 0x60, 0x9B, 0xEF, 0xA0, 0xD3, 0x48, 0x63, 0xA2, 0xF8, 0xA3, 0x2C, 0x08,
|
||||
0x56, 0x52, 0x2E, 0x60, 0x19, 0x67, 0x5A, 0xA7, 0x9F, 0xDC, 0x3F, 0x3F, 0x69, 0x2B, 0x31, 0x6A,
|
||||
0xB7, 0x88, 0x4A, 0x14, 0x84, 0x80, 0x33, 0x3C, 0x9D, 0x44, 0xB7, 0x3F, 0x4C, 0xE1, 0x75, 0xEA,
|
||||
0x37, 0xEA, 0xE8, 0x1E, 0x7C, 0x77, 0xB7, 0xC6, 0x1A, 0xA2, 0xF0, 0x9F, 0x10, 0x61, 0xCD, 0x7B,
|
||||
0x5B, 0x32, 0x4C, 0x37, 0xEF, 0xB1, 0x71, 0x68, 0x53, 0x0A, 0xED, 0x51, 0x7D, 0x35, 0x22, 0xFD,
|
||||
},
|
||||
{
|
||||
0xE7, 0xAA, 0x25, 0xC8, 0x01, 0xA5, 0x14, 0x6B, 0x01, 0x60, 0x3E, 0xD9, 0x96, 0x5A, 0xBF, 0x90,
|
||||
0xAC, 0xA7, 0xFD, 0x9B, 0x5B, 0xBD, 0x8A, 0x26, 0xB0, 0xCB, 0x20, 0x28, 0x9A, 0x72, 0x12, 0xF5,
|
||||
0x20, 0x65, 0xB3, 0xB9, 0x84, 0x58, 0x1F, 0x27, 0xBC, 0x7C, 0xA2, 0xC9, 0x9E, 0x18, 0x95, 0xCF,
|
||||
0xC2, 0x73, 0x2E, 0x74, 0x8C, 0x66, 0xE5, 0x9E, 0x79, 0x2B, 0xB8, 0x07, 0x0C, 0xB0, 0x4E, 0x8E,
|
||||
0xAB, 0x85, 0x21, 0x42, 0xC4, 0xC5, 0x6D, 0x88, 0x9C, 0xDB, 0x15, 0x95, 0x3F, 0x80, 0xDB, 0x7A,
|
||||
0x9A, 0x7D, 0x41, 0x56, 0x25, 0x17, 0x18, 0x42, 0x4D, 0x8C, 0xAC, 0xA5, 0x7B, 0xDB, 0x42, 0x5D,
|
||||
0x59, 0x35, 0x45, 0x5D, 0x8A, 0x02, 0xB5, 0x70, 0xC0, 0x72, 0x35, 0x46, 0xD0, 0x1D, 0x60, 0x01,
|
||||
0x4A, 0xCC, 0x1C, 0x46, 0xD3, 0xD6, 0x35, 0x52, 0xD6, 0xE1, 0xF8, 0x3B, 0x5D, 0xEA, 0xDD, 0xB8,
|
||||
0xFE, 0x7D, 0x50, 0xCB, 0x35, 0x23, 0x67, 0x8B, 0xB6, 0xE4, 0x74, 0xD2, 0x60, 0xFC, 0xFD, 0x43,
|
||||
0xBF, 0x91, 0x08, 0x81, 0xC5, 0x4F, 0x5D, 0x16, 0x9A, 0xC4, 0x9A, 0xC6, 0xF6, 0xF3, 0xE1, 0xF6,
|
||||
0x5C, 0x07, 0xAA, 0x71, 0x6C, 0x13, 0xA4, 0xB1, 0xB3, 0x66, 0xBF, 0x90, 0x4C, 0x3D, 0xA2, 0xC4,
|
||||
0x0B, 0xB8, 0x3D, 0x7A, 0x8C, 0x19, 0xFA, 0xFF, 0x6B, 0xB9, 0x1F, 0x02, 0xCC, 0xB6, 0xD3, 0x0C,
|
||||
0x7D, 0x19, 0x1F, 0x47, 0xF9, 0xC7, 0x40, 0x01, 0xFA, 0x46, 0xEA, 0x0B, 0xD4, 0x02, 0xE0, 0x3D,
|
||||
0x30, 0x9A, 0x1A, 0x0F, 0xEA, 0xA7, 0x66, 0x55, 0xF7, 0xCB, 0x28, 0xE2, 0xBB, 0x99, 0xE4, 0x83,
|
||||
0xC3, 0x43, 0x03, 0xEE, 0xDC, 0x1F, 0x02, 0x23, 0xDD, 0xD1, 0x2D, 0x39, 0xA4, 0x65, 0x75, 0x03,
|
||||
0xEF, 0x37, 0x9C, 0x06, 0xD6, 0xFA, 0xA1, 0x15, 0xF0, 0xDB, 0x17, 0x47, 0x26, 0x4F, 0x49, 0x03
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Result DecryptKeak(const keys::Keys& keys, Header& header) {
|
||||
const auto key_generation = header.GetKeyGeneration();
|
||||
|
||||
// try with spl.
|
||||
keys::KeyEntry keak;
|
||||
if (R_SUCCEEDED(splCryptoGenerateAesKek(g_key_area_key[header.kaek_index], key_generation, 0, &keak))) {
|
||||
for (auto& key_area : header.key_area) {
|
||||
R_TRY(splCryptoGenerateAesKey(&keak, std::addressof(key_area), std::addressof(key_area)));
|
||||
}
|
||||
} else {
|
||||
// failed with spl, try using keys.
|
||||
R_TRY(keys.GetNcaKeyArea(&keak, key_generation, header.kaek_index));
|
||||
for (auto& key_area : header.key_area) {
|
||||
crypto::cryptoAes128(std::addressof(key_area), std::addressof(key_area), std::addressof(keak), false);
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result EncryptKeak(const keys::Keys& keys, Header& header, u8 key_generation) {
|
||||
header.SetKeyGeneration(key_generation);
|
||||
|
||||
keys::KeyEntry keak;
|
||||
R_TRY(keys.GetNcaKeyArea(&keak, key_generation, header.kaek_index));
|
||||
log_write("re-encrypting with: 0x%X\n", key_generation);
|
||||
|
||||
for (auto& key_area : header.key_area) {
|
||||
crypto::cryptoAes128(std::addressof(key_area), std::addressof(key_area), std::addressof(keak), true);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result VerifyFixedKey(const Header& header) {
|
||||
R_UNLESS(header.sig_key_gen < std::size(nca_hdr_fixed_key_moduli_retail), 0x1);
|
||||
auto mod = nca_hdr_fixed_key_moduli_retail[header.sig_key_gen];
|
||||
|
||||
const u8 E[3] = { 1, 0, 1 };
|
||||
if (!rsa2048VerifySha256BasedPssSignature(&header.magic, 0x200, header.rsa_fixed_key, mod, E, sizeof(E))) {
|
||||
auto new_header = header;
|
||||
// if failed, detect if this is a eshop/xci convert.
|
||||
new_header.distribution_type ^= 1;
|
||||
if (!rsa2048VerifySha256BasedPssSignature(&new_header.magic, 0x200, new_header.rsa_fixed_key, mod, E, sizeof(E))) {
|
||||
log_write("FAILED nca header hash\n");
|
||||
R_THROW(0x1);
|
||||
} else {
|
||||
log_write("WARNING! nca is converted! distribution_type: %u\n", new_header.distribution_type);
|
||||
R_SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace sphaira::nca
|
||||
34
sphaira/source/yati/nx/ncm.cpp
Normal file
34
sphaira/source/yati/nx/ncm.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
#include "yati/nx/ncm.hpp"
|
||||
#include "defines.hpp"
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::ncm {
|
||||
namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
auto GetAppId(const NcmContentMetaKey& key) -> u64 {
|
||||
if (key.type == NcmContentMetaType_Patch) {
|
||||
return key.id ^ 0x800;
|
||||
} else if (key.type == NcmContentMetaType_AddOnContent) {
|
||||
return (key.id ^ 0x1000) & ~0xFFF;
|
||||
} else {
|
||||
return key.id;
|
||||
}
|
||||
}
|
||||
|
||||
Result Delete(NcmContentStorage* cs, const NcmContentId *content_id) {
|
||||
bool has;
|
||||
R_TRY(ncmContentStorageHas(cs, std::addressof(has), content_id));
|
||||
if (has) {
|
||||
R_TRY(ncmContentStorageDelete(cs, content_id));
|
||||
}
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result Register(NcmContentStorage* cs, const NcmContentId *content_id, const NcmPlaceHolderId *placeholder_id) {
|
||||
R_TRY(Delete(cs, content_id));
|
||||
return ncmContentStorageRegister(cs, content_id, placeholder_id);
|
||||
}
|
||||
|
||||
} // namespace sphaira::ncm
|
||||
39
sphaira/source/yati/nx/ns.cpp
Normal file
39
sphaira/source/yati/nx/ns.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "yati/nx/ns.hpp"
|
||||
|
||||
namespace sphaira::ns {
|
||||
namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
Result PushApplicationRecord(Service* srv, u64 tid, const ncm::ContentStorageRecord* records, u32 count) {
|
||||
const struct {
|
||||
u8 last_modified_event;
|
||||
u8 padding[0x7];
|
||||
u64 tid;
|
||||
} in = { ApplicationRecordType_Installed, {0}, tid };
|
||||
|
||||
return serviceDispatchIn(srv, 16, in,
|
||||
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In },
|
||||
.buffers = { { records, sizeof(*records) * count } });
|
||||
}
|
||||
|
||||
Result ListApplicationRecordContentMeta(Service* srv, u64 offset, u64 tid, ncm::ContentStorageRecord* out_records, u32 count, s32* entries_read) {
|
||||
struct {
|
||||
u64 offset;
|
||||
u64 tid;
|
||||
} in = { offset, tid };
|
||||
|
||||
return serviceDispatchInOut(srv, 17, in, *entries_read,
|
||||
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out },
|
||||
.buffers = { { out_records, sizeof(*out_records) * count } });
|
||||
}
|
||||
|
||||
Result DeleteApplicationRecord(Service* srv, u64 tid) {
|
||||
return serviceDispatchIn(srv, 27, tid);
|
||||
}
|
||||
|
||||
Result InvalidateApplicationControlCache(Service* srv, u64 tid) {
|
||||
return serviceDispatchIn(srv, 404, tid);
|
||||
}
|
||||
|
||||
} // namespace sphaira::ns
|
||||
158
sphaira/source/yati/nx/nxdumptool_rsa.c
Normal file
158
sphaira/source/yati/nx/nxdumptool_rsa.c
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* rsa.c
|
||||
*
|
||||
* Copyright (c) 2018-2019, SciresM.
|
||||
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||
*
|
||||
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
|
||||
*
|
||||
* nxdumptool is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* nxdumptool is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "yati/nx/nxdumptool_rsa.h"
|
||||
#include "log.hpp"
|
||||
#include <switch.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <mbedtls/rsa.h>
|
||||
#include <mbedtls/entropy.h>
|
||||
#include <mbedtls/ctr_drbg.h>
|
||||
#include <mbedtls/pk.h>
|
||||
|
||||
#define LOG_MSG_ERROR(...) log_write(__VA_ARGS__)
|
||||
|
||||
/* Function prototypes. */
|
||||
|
||||
static bool rsa2048VerifySha256BasedSignature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size, \
|
||||
bool use_pss);
|
||||
|
||||
bool rsa2048VerifySha256BasedPssSignature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size)
|
||||
{
|
||||
return rsa2048VerifySha256BasedSignature(data, data_size, signature, modulus, public_exponent, public_exponent_size, true);
|
||||
}
|
||||
|
||||
bool rsa2048VerifySha256BasedPkcs1v15Signature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size)
|
||||
{
|
||||
return rsa2048VerifySha256BasedSignature(data, data_size, signature, modulus, public_exponent, public_exponent_size, false);
|
||||
}
|
||||
|
||||
bool rsa2048OaepDecrypt(void *dst, size_t dst_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size, const void *private_exponent, \
|
||||
size_t private_exponent_size, const void *label, size_t label_size, size_t *out_size)
|
||||
{
|
||||
if (!dst || !dst_size || !signature || !modulus || !public_exponent || !public_exponent_size || !private_exponent || !private_exponent_size || (!label && label_size) || (label && !label_size) || \
|
||||
!out_size)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
mbedtls_entropy_context entropy = {0};
|
||||
mbedtls_ctr_drbg_context ctr_drbg = {0};
|
||||
mbedtls_rsa_context rsa = {0};
|
||||
|
||||
const char *pers = __func__;
|
||||
int mbedtls_ret = 0;
|
||||
bool ret = false;
|
||||
|
||||
/* Initialize contexts. */
|
||||
mbedtls_entropy_init(&entropy);
|
||||
mbedtls_ctr_drbg_init(&ctr_drbg);
|
||||
mbedtls_rsa_init(&rsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA256);
|
||||
|
||||
/* Seed the random number generator. */
|
||||
mbedtls_ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const u8*)pers, strlen(pers));
|
||||
if (mbedtls_ret != 0)
|
||||
{
|
||||
LOG_MSG_ERROR("mbedtls_ctr_drbg_seed failed! (%d).", mbedtls_ret);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Import RSA parameters. */
|
||||
mbedtls_ret = mbedtls_rsa_import_raw(&rsa, (const u8*)modulus, RSA2048_BYTES, NULL, 0, NULL, 0, (const u8*)private_exponent, private_exponent_size, (const u8*)public_exponent, public_exponent_size);
|
||||
if (mbedtls_ret != 0)
|
||||
{
|
||||
LOG_MSG_ERROR("mbedtls_rsa_import_raw failed! (%d).", mbedtls_ret);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Derive RSA prime factors. */
|
||||
mbedtls_ret = mbedtls_rsa_complete(&rsa);
|
||||
if (mbedtls_ret != 0)
|
||||
{
|
||||
LOG_MSG_ERROR("mbedtls_rsa_complete failed! (%d).", mbedtls_ret);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Perform RSA-OAEP decryption. */
|
||||
mbedtls_ret = mbedtls_rsa_rsaes_oaep_decrypt(&rsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE, (const u8*)label, label_size, out_size, (const u8*)signature, (u8*)dst, dst_size);
|
||||
if (mbedtls_ret != 0)
|
||||
{
|
||||
LOG_MSG_ERROR("mbedtls_rsa_rsaes_oaep_decrypt failed! (%d).", mbedtls_ret);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = true;
|
||||
|
||||
end:
|
||||
mbedtls_rsa_free(&rsa);
|
||||
mbedtls_ctr_drbg_free(&ctr_drbg);
|
||||
mbedtls_entropy_free(&entropy);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool rsa2048VerifySha256BasedSignature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size, \
|
||||
bool use_pss)
|
||||
{
|
||||
if (!data || !data_size || !signature || !modulus || !public_exponent || !public_exponent_size)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
int mbedtls_ret = 0;
|
||||
mbedtls_rsa_context rsa = {0};
|
||||
u8 hash[SHA256_HASH_SIZE] = {0};
|
||||
bool ret = false;
|
||||
|
||||
/* Initialize RSA context. */
|
||||
mbedtls_rsa_init(&rsa, use_pss ? MBEDTLS_RSA_PKCS_V21 : MBEDTLS_RSA_PKCS_V15, MBEDTLS_MD_SHA256);
|
||||
|
||||
/* Import RSA parameters. */
|
||||
mbedtls_ret = mbedtls_rsa_import_raw(&rsa, (const u8*)modulus, RSA2048_BYTES, NULL, 0, NULL, 0, NULL, 0, (const u8*)public_exponent, public_exponent_size);
|
||||
if (mbedtls_ret != 0)
|
||||
{
|
||||
LOG_MSG_ERROR("mbedtls_rsa_import_raw failed! (%d).", mbedtls_ret);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Calculate SHA-256 checksum for the input data. */
|
||||
sha256CalculateHash(hash, data, data_size);
|
||||
|
||||
/* Verify signature. */
|
||||
mbedtls_ret = (use_pss ? mbedtls_rsa_rsassa_pss_verify(&rsa, NULL, NULL, MBEDTLS_RSA_PUBLIC, MBEDTLS_MD_SHA256, SHA256_HASH_SIZE, hash, (const u8*)signature) : \
|
||||
mbedtls_rsa_rsassa_pkcs1_v15_verify(&rsa, NULL, NULL, MBEDTLS_RSA_PUBLIC, MBEDTLS_MD_SHA256, SHA256_HASH_SIZE, hash, (const u8*)signature));
|
||||
if (mbedtls_ret != 0)
|
||||
{
|
||||
LOG_MSG_ERROR("mbedtls_rsa_rsassa_%s_verify failed! (%d).", use_pss ? "pss" : "pkcs1_v15", mbedtls_ret);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = true;
|
||||
|
||||
end:
|
||||
mbedtls_rsa_free(&rsa);
|
||||
|
||||
return ret;
|
||||
}
|
||||
20
sphaira/source/yati/source/file.cpp
Normal file
20
sphaira/source/yati/source/file.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#include "yati/source/file.hpp"
|
||||
|
||||
namespace sphaira::yati::source {
|
||||
|
||||
File::File(FsFileSystem* fs, const fs::FsPath& path) {
|
||||
m_open_result = fsFsOpenFile(fs, path, FsOpenMode_Read, std::addressof(m_file));
|
||||
}
|
||||
|
||||
File::~File() {
|
||||
if (R_SUCCEEDED(GetOpenResult())) {
|
||||
fsFileClose(std::addressof(m_file));
|
||||
}
|
||||
}
|
||||
|
||||
Result File::Read(void* buf, s64 off, s64 size, u64* bytes_read) {
|
||||
R_TRY(GetOpenResult());
|
||||
return fsFileRead(std::addressof(m_file), off, buf, size, 0, bytes_read);
|
||||
}
|
||||
|
||||
} // namespace sphaira::yati::source
|
||||
28
sphaira/source/yati/source/stdio.cpp
Normal file
28
sphaira/source/yati/source/stdio.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include "yati/source/stdio.hpp"
|
||||
|
||||
namespace sphaira::yati::source {
|
||||
|
||||
Stdio::Stdio(const char* path) {
|
||||
m_file = std::fopen(path, "rb");
|
||||
if (!m_file) {
|
||||
m_open_result = fsdevGetLastResult();
|
||||
}
|
||||
}
|
||||
|
||||
Stdio::~Stdio() {
|
||||
if (R_SUCCEEDED(GetOpenResult())) {
|
||||
std::fclose(m_file);
|
||||
}
|
||||
}
|
||||
|
||||
Result Stdio::Read(void* buf, s64 off, s64 size, u64* bytes_read) {
|
||||
R_TRY(GetOpenResult());
|
||||
|
||||
std::fseek(m_file, off, SEEK_SET);
|
||||
R_TRY(fsdevGetLastResult());
|
||||
|
||||
*bytes_read = std::fread(buf, 1, size, m_file);
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
|
||||
} // namespace sphaira::yati::source
|
||||
1317
sphaira/source/yati/yati.cpp
Normal file
1317
sphaira/source/yati/yati.cpp
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user