Browse Source
Merge pull request #2975 from shinyquagsire23/archive-ncch-container-and-override
Merge pull request #2975 from shinyquagsire23/archive-ncch-container-and-override
file_sys/archive_ncch: use NCCHs/.apps instead of .romfs files, NCCH section overridence_cpp
committed by
GitHub
7 changed files with 581 additions and 78 deletions
-
1src/core/CMakeLists.txt
-
29src/core/file_sys/archive_ncch.cpp
-
245src/core/file_sys/ncch_container.cpp
-
30src/core/file_sys/ncch_container.h
-
212src/core/file_sys/title_metadata.cpp
-
125src/core/file_sys/title_metadata.h
-
17src/core/loader/ncch.cpp
@ -0,0 +1,212 @@ |
|||||
|
// Copyright 2017 Citra Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <cinttypes>
|
||||
|
#include <cryptopp/sha.h>
|
||||
|
#include "common/alignment.h"
|
||||
|
#include "common/file_util.h"
|
||||
|
#include "common/logging/log.h"
|
||||
|
#include "core/file_sys/title_metadata.h"
|
||||
|
#include "core/loader/loader.h"
|
||||
|
|
||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
// FileSys namespace
|
||||
|
|
||||
|
namespace FileSys { |
||||
|
|
||||
|
static u32 GetSignatureSize(u32 signature_type) { |
||||
|
switch (signature_type) { |
||||
|
case Rsa4096Sha1: |
||||
|
case Rsa4096Sha256: |
||||
|
return 0x200; |
||||
|
|
||||
|
case Rsa2048Sha1: |
||||
|
case Rsa2048Sha256: |
||||
|
return 0x100; |
||||
|
|
||||
|
case EllipticSha1: |
||||
|
case EcdsaSha256: |
||||
|
return 0x3C; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Loader::ResultStatus TitleMetadata::Load() { |
||||
|
FileUtil::IOFile file(filepath, "rb"); |
||||
|
if (!file.IsOpen()) |
||||
|
return Loader::ResultStatus::Error; |
||||
|
|
||||
|
if (!file.ReadBytes(&signature_type, sizeof(u32_be))) |
||||
|
return Loader::ResultStatus::Error; |
||||
|
|
||||
|
// Signature lengths are variable, and the body follows the signature
|
||||
|
u32 signature_size = GetSignatureSize(signature_type); |
||||
|
|
||||
|
tmd_signature.resize(signature_size); |
||||
|
if (!file.ReadBytes(&tmd_signature[0], signature_size)) |
||||
|
return Loader::ResultStatus::Error; |
||||
|
|
||||
|
// The TMD body start position is rounded to the nearest 0x40 after the signature
|
||||
|
size_t body_start = Common::AlignUp(signature_size + sizeof(u32), 0x40); |
||||
|
file.Seek(body_start, SEEK_SET); |
||||
|
|
||||
|
// Read our TMD body, then load the amount of ContentChunks specified
|
||||
|
if (file.ReadBytes(&tmd_body, sizeof(TitleMetadata::Body)) != sizeof(TitleMetadata::Body)) |
||||
|
return Loader::ResultStatus::Error; |
||||
|
|
||||
|
for (u16 i = 0; i < tmd_body.content_count; i++) { |
||||
|
ContentChunk chunk; |
||||
|
if (file.ReadBytes(&chunk, sizeof(ContentChunk)) == sizeof(ContentChunk)) { |
||||
|
tmd_chunks.push_back(chunk); |
||||
|
} else { |
||||
|
LOG_ERROR(Service_FS, "Malformed TMD %s, failed to load content chunk index %u!", |
||||
|
filepath.c_str(), i); |
||||
|
return Loader::ResultStatus::ErrorInvalidFormat; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return Loader::ResultStatus::Success; |
||||
|
} |
||||
|
|
||||
|
Loader::ResultStatus TitleMetadata::Save() { |
||||
|
FileUtil::IOFile file(filepath, "wb"); |
||||
|
if (!file.IsOpen()) |
||||
|
return Loader::ResultStatus::Error; |
||||
|
|
||||
|
if (!file.WriteBytes(&signature_type, sizeof(u32_be))) |
||||
|
return Loader::ResultStatus::Error; |
||||
|
|
||||
|
// Signature lengths are variable, and the body follows the signature
|
||||
|
u32 signature_size = GetSignatureSize(signature_type); |
||||
|
|
||||
|
if (!file.WriteBytes(tmd_signature.data(), signature_size)) |
||||
|
return Loader::ResultStatus::Error; |
||||
|
|
||||
|
// The TMD body start position is rounded to the nearest 0x40 after the signature
|
||||
|
size_t body_start = Common::AlignUp(signature_size + sizeof(u32), 0x40); |
||||
|
file.Seek(body_start, SEEK_SET); |
||||
|
|
||||
|
// Update our TMD body values and hashes
|
||||
|
tmd_body.content_count = static_cast<u16>(tmd_chunks.size()); |
||||
|
|
||||
|
// TODO(shinyquagsire23): Do TMDs with more than one contentinfo exist?
|
||||
|
// For now we'll just adjust the first index to hold all content chunks
|
||||
|
// and ensure that no further content info data exists.
|
||||
|
tmd_body.contentinfo = {}; |
||||
|
tmd_body.contentinfo[0].index = 0; |
||||
|
tmd_body.contentinfo[0].command_count = static_cast<u16>(tmd_chunks.size()); |
||||
|
|
||||
|
CryptoPP::SHA256 chunk_hash; |
||||
|
for (u16 i = 0; i < tmd_body.content_count; i++) { |
||||
|
chunk_hash.Update(reinterpret_cast<u8*>(&tmd_chunks[i]), sizeof(ContentChunk)); |
||||
|
} |
||||
|
chunk_hash.Final(tmd_body.contentinfo[0].hash.data()); |
||||
|
|
||||
|
CryptoPP::SHA256 contentinfo_hash; |
||||
|
for (size_t i = 0; i < tmd_body.contentinfo.size(); i++) { |
||||
|
chunk_hash.Update(reinterpret_cast<u8*>(&tmd_body.contentinfo[i]), sizeof(ContentInfo)); |
||||
|
} |
||||
|
chunk_hash.Final(tmd_body.contentinfo_hash.data()); |
||||
|
|
||||
|
// Write our TMD body, then write each of our ContentChunks
|
||||
|
if (file.WriteBytes(&tmd_body, sizeof(TitleMetadata::Body)) != sizeof(TitleMetadata::Body)) |
||||
|
return Loader::ResultStatus::Error; |
||||
|
|
||||
|
for (u16 i = 0; i < tmd_body.content_count; i++) { |
||||
|
ContentChunk chunk = tmd_chunks[i]; |
||||
|
if (file.WriteBytes(&chunk, sizeof(ContentChunk)) != sizeof(ContentChunk)) |
||||
|
return Loader::ResultStatus::Error; |
||||
|
} |
||||
|
|
||||
|
return Loader::ResultStatus::Success; |
||||
|
} |
||||
|
|
||||
|
u64 TitleMetadata::GetTitleID() const { |
||||
|
return tmd_body.title_id; |
||||
|
} |
||||
|
|
||||
|
u32 TitleMetadata::GetTitleType() const { |
||||
|
return tmd_body.title_type; |
||||
|
} |
||||
|
|
||||
|
u16 TitleMetadata::GetTitleVersion() const { |
||||
|
return tmd_body.title_version; |
||||
|
} |
||||
|
|
||||
|
u64 TitleMetadata::GetSystemVersion() const { |
||||
|
return tmd_body.system_version; |
||||
|
} |
||||
|
|
||||
|
size_t TitleMetadata::GetContentCount() const { |
||||
|
return tmd_chunks.size(); |
||||
|
} |
||||
|
|
||||
|
u32 TitleMetadata::GetBootContentID() const { |
||||
|
return tmd_chunks[TMDContentIndex::Main].id; |
||||
|
} |
||||
|
|
||||
|
u32 TitleMetadata::GetManualContentID() const { |
||||
|
return tmd_chunks[TMDContentIndex::Manual].id; |
||||
|
} |
||||
|
|
||||
|
u32 TitleMetadata::GetDLPContentID() const { |
||||
|
return tmd_chunks[TMDContentIndex::DLP].id; |
||||
|
} |
||||
|
|
||||
|
void TitleMetadata::SetTitleID(u64 title_id) { |
||||
|
tmd_body.title_id = title_id; |
||||
|
} |
||||
|
|
||||
|
void TitleMetadata::SetTitleType(u32 type) { |
||||
|
tmd_body.title_type = type; |
||||
|
} |
||||
|
|
||||
|
void TitleMetadata::SetTitleVersion(u16 version) { |
||||
|
tmd_body.title_version = version; |
||||
|
} |
||||
|
|
||||
|
void TitleMetadata::SetSystemVersion(u64 version) { |
||||
|
tmd_body.system_version = version; |
||||
|
} |
||||
|
|
||||
|
void TitleMetadata::AddContentChunk(const ContentChunk& chunk) { |
||||
|
tmd_chunks.push_back(chunk); |
||||
|
} |
||||
|
|
||||
|
void TitleMetadata::Print() const { |
||||
|
LOG_DEBUG(Service_FS, "%s - %u chunks", filepath.c_str(), |
||||
|
static_cast<u32>(tmd_body.content_count)); |
||||
|
|
||||
|
// Content info describes ranges of content chunks
|
||||
|
LOG_DEBUG(Service_FS, "Content info:"); |
||||
|
for (size_t i = 0; i < tmd_body.contentinfo.size(); i++) { |
||||
|
if (tmd_body.contentinfo[i].command_count == 0) |
||||
|
break; |
||||
|
|
||||
|
LOG_DEBUG(Service_FS, " Index %04X, Command Count %04X", |
||||
|
static_cast<u32>(tmd_body.contentinfo[i].index), |
||||
|
static_cast<u32>(tmd_body.contentinfo[i].command_count)); |
||||
|
} |
||||
|
|
||||
|
// For each content info, print their content chunk range
|
||||
|
for (size_t i = 0; i < tmd_body.contentinfo.size(); i++) { |
||||
|
u16 index = static_cast<u16>(tmd_body.contentinfo[i].index); |
||||
|
u16 count = static_cast<u16>(tmd_body.contentinfo[i].command_count); |
||||
|
|
||||
|
if (count == 0) |
||||
|
continue; |
||||
|
|
||||
|
LOG_DEBUG(Service_FS, "Content chunks for content info index %zu:", i); |
||||
|
for (u16 j = index; j < index + count; j++) { |
||||
|
// Don't attempt to print content we don't have
|
||||
|
if (j > tmd_body.content_count) |
||||
|
break; |
||||
|
|
||||
|
const ContentChunk& chunk = tmd_chunks[j]; |
||||
|
LOG_DEBUG(Service_FS, " ID %08X, Index %04X, Type %04x, Size %016" PRIX64, |
||||
|
static_cast<u32>(chunk.id), static_cast<u32>(chunk.index), |
||||
|
static_cast<u32>(chunk.type), static_cast<u64>(chunk.size)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} // namespace FileSys
|
||||
@ -0,0 +1,125 @@ |
|||||
|
// Copyright 2017 Citra Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <string> |
||||
|
#include <vector> |
||||
|
#include "common/common_types.h" |
||||
|
#include "common/swap.h" |
||||
|
|
||||
|
namespace Loader { |
||||
|
enum class ResultStatus; |
||||
|
} |
||||
|
|
||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////// |
||||
|
// FileSys namespace |
||||
|
|
||||
|
namespace FileSys { |
||||
|
|
||||
|
enum TMDSignatureType : u32 { |
||||
|
Rsa4096Sha1 = 0x10000, |
||||
|
Rsa2048Sha1 = 0x10001, |
||||
|
EllipticSha1 = 0x10002, |
||||
|
Rsa4096Sha256 = 0x10003, |
||||
|
Rsa2048Sha256 = 0x10004, |
||||
|
EcdsaSha256 = 0x10005 |
||||
|
}; |
||||
|
|
||||
|
enum TMDContentTypeFlag : u16 { |
||||
|
Encrypted = 1 << 1, |
||||
|
Disc = 1 << 2, |
||||
|
CFM = 1 << 3, |
||||
|
Optional = 1 << 14, |
||||
|
Shared = 1 << 15 |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Helper which implements an interface to read and write Title Metadata (TMD) files. |
||||
|
* If a file path is provided and the file exists, it can be parsed and used, otherwise |
||||
|
* it must be created. The TMD file can then be interpreted, modified and/or saved. |
||||
|
*/ |
||||
|
class TitleMetadata { |
||||
|
public: |
||||
|
struct ContentChunk { |
||||
|
u32_be id; |
||||
|
u16_be index; |
||||
|
u16_be type; |
||||
|
u64_be size; |
||||
|
std::array<u8, 0x20> hash; |
||||
|
}; |
||||
|
|
||||
|
static_assert(sizeof(ContentChunk) == 0x30, "TMD ContentChunk structure size is wrong"); |
||||
|
|
||||
|
struct ContentInfo { |
||||
|
u16_be index; |
||||
|
u16_be command_count; |
||||
|
std::array<u8, 0x20> hash; |
||||
|
}; |
||||
|
|
||||
|
static_assert(sizeof(ContentInfo) == 0x24, "TMD ContentInfo structure size is wrong"); |
||||
|
|
||||
|
#pragma pack(push, 1) |
||||
|
|
||||
|
struct Body { |
||||
|
std::array<u8, 0x40> issuer; |
||||
|
u8 version; |
||||
|
u8 ca_crl_version; |
||||
|
u8 signer_crl_version; |
||||
|
u8 reserved; |
||||
|
u64_be system_version; |
||||
|
u64_be title_id; |
||||
|
u32_be title_type; |
||||
|
u16_be group_id; |
||||
|
u32_be savedata_size; |
||||
|
u32_be srl_private_savedata_size; |
||||
|
std::array<u8, 4> reserved_2; |
||||
|
u8 srl_flag; |
||||
|
std::array<u8, 0x31> reserved_3; |
||||
|
u32_be access_rights; |
||||
|
u16_be title_version; |
||||
|
u16_be content_count; |
||||
|
u16_be boot_content; |
||||
|
std::array<u8, 2> reserved_4; |
||||
|
std::array<u8, 0x20> contentinfo_hash; |
||||
|
std::array<ContentInfo, 64> contentinfo; |
||||
|
}; |
||||
|
|
||||
|
static_assert(sizeof(Body) == 0x9C4, "TMD body structure size is wrong"); |
||||
|
|
||||
|
#pragma pack(pop) |
||||
|
|
||||
|
explicit TitleMetadata(std::string& path) : filepath(std::move(path)) {} |
||||
|
Loader::ResultStatus Load(); |
||||
|
Loader::ResultStatus Save(); |
||||
|
|
||||
|
u64 GetTitleID() const; |
||||
|
u32 GetTitleType() const; |
||||
|
u16 GetTitleVersion() const; |
||||
|
u64 GetSystemVersion() const; |
||||
|
size_t GetContentCount() const; |
||||
|
u32 GetBootContentID() const; |
||||
|
u32 GetManualContentID() const; |
||||
|
u32 GetDLPContentID() const; |
||||
|
|
||||
|
void SetTitleID(u64 title_id); |
||||
|
void SetTitleType(u32 type); |
||||
|
void SetTitleVersion(u16 version); |
||||
|
void SetSystemVersion(u64 version); |
||||
|
void AddContentChunk(const ContentChunk& chunk); |
||||
|
|
||||
|
void Print() const; |
||||
|
|
||||
|
private: |
||||
|
enum TMDContentIndex { Main = 0, Manual = 1, DLP = 2 }; |
||||
|
|
||||
|
Body tmd_body; |
||||
|
u32_be signature_type; |
||||
|
std::vector<u8> tmd_signature; |
||||
|
std::vector<ContentChunk> tmd_chunks; |
||||
|
|
||||
|
std::string filepath; |
||||
|
}; |
||||
|
|
||||
|
} // namespace FileSys |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue