summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorshinyquagsire23 <mtinc2@gmail.com>2017-10-01 18:32:43 +0200
committershinyquagsire23 <mtinc2@gmail.com>2017-10-01 18:53:45 +0200
commit8e10c9bb2e8690055ba07003ebd53a5215f82f8f (patch)
tree46148de876e830563d95940374bb34bb511e5f79
parentfile_sys/ncch_container: add RomFS, ExeFS override to allow for backward compatibility with existing .romfs system archive dumps (diff)
downloadyuzu-8e10c9bb2e8690055ba07003ebd53a5215f82f8f.tar
yuzu-8e10c9bb2e8690055ba07003ebd53a5215f82f8f.tar.gz
yuzu-8e10c9bb2e8690055ba07003ebd53a5215f82f8f.tar.bz2
yuzu-8e10c9bb2e8690055ba07003ebd53a5215f82f8f.tar.lz
yuzu-8e10c9bb2e8690055ba07003ebd53a5215f82f8f.tar.xz
yuzu-8e10c9bb2e8690055ba07003ebd53a5215f82f8f.tar.zst
yuzu-8e10c9bb2e8690055ba07003ebd53a5215f82f8f.zip
-rw-r--r--src/core/CMakeLists.txt1
-rw-r--r--src/core/file_sys/title_metadata.cpp212
-rw-r--r--src/core/file_sys/title_metadata.h125
3 files changed, 338 insertions, 0 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 3ed619991..2618da18c 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -29,6 +29,7 @@ set(SRCS
file_sys/ncch_container.cpp
file_sys/path_parser.cpp
file_sys/savedata_archive.cpp
+ file_sys/title_metadata.cpp
frontend/camera/blank_camera.cpp
frontend/camera/factory.cpp
frontend/camera/interface.cpp
diff --git a/src/core/file_sys/title_metadata.cpp b/src/core/file_sys/title_metadata.cpp
new file mode 100644
index 000000000..1ef8840a0
--- /dev/null
+++ b/src/core/file_sys/title_metadata.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
diff --git a/src/core/file_sys/title_metadata.h b/src/core/file_sys/title_metadata.h
new file mode 100644
index 000000000..1fc157bf3
--- /dev/null
+++ b/src/core/file_sys/title_metadata.h
@@ -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