From b3e87d01fb3a330615949a00bc195e05b6ff8ac0 Mon Sep 17 00:00:00 2001
From: shinyquagsire23 <mtinc2@gmail.com>
Date: Sat, 21 Oct 2017 16:38:48 -0600
Subject: [PATCH] file_sys: Add CIA Container

---
 src/core/CMakeLists.txt             |   1 +
 src/core/file_sys/cia_container.cpp | 228 ++++++++++++++++++++++++++++
 src/core/file_sys/cia_container.h   | 104 +++++++++++++
 3 files changed, 333 insertions(+)
 create mode 100644 src/core/file_sys/cia_container.cpp
 create mode 100644 src/core/file_sys/cia_container.h

diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 2618da18c..7a10b448e 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -24,6 +24,7 @@ set(SRCS
             file_sys/archive_selfncch.cpp
             file_sys/archive_source_sd_savedata.cpp
             file_sys/archive_systemsavedata.cpp
+            file_sys/cia_container.cpp
             file_sys/disk_archive.cpp
             file_sys/ivfc_archive.cpp
             file_sys/ncch_container.cpp
diff --git a/src/core/file_sys/cia_container.cpp b/src/core/file_sys/cia_container.cpp
new file mode 100644
index 000000000..3ec9b4a33
--- /dev/null
+++ b/src/core/file_sys/cia_container.cpp
@@ -0,0 +1,228 @@
+// 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/cia_container.h"
+#include "core/file_sys/file_backend.h"
+#include "core/loader/loader.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// FileSys namespace
+
+namespace FileSys {
+
+constexpr u32 CIA_SECTION_ALIGNMENT = 0x40;
+
+Loader::ResultStatus CIAContainer::Load(const FileBackend& backend) {
+    std::vector<u8> header_data(sizeof(Header));
+
+    // Load the CIA Header
+    ResultVal<size_t> read_result = backend.Read(0, sizeof(Header), header_data.data());
+    if (read_result.Failed() || *read_result != sizeof(Header))
+        return Loader::ResultStatus::Error;
+
+    Loader::ResultStatus result = LoadHeader(header_data);
+    if (result != Loader::ResultStatus::Success)
+        return result;
+
+    // Load Title Metadata
+    std::vector<u8> tmd_data(cia_header.tmd_size);
+    read_result = backend.Read(GetTitleMetadataOffset(), cia_header.tmd_size, tmd_data.data());
+    if (read_result.Failed() || *read_result != cia_header.tmd_size)
+        return Loader::ResultStatus::Error;
+
+    result = LoadTitleMetadata(tmd_data);
+    if (result != Loader::ResultStatus::Success)
+        return result;
+
+    // Load CIA Metadata
+    if (cia_header.meta_size) {
+        std::vector<u8> meta_data(sizeof(Metadata));
+        read_result = backend.Read(GetMetadataOffset(), sizeof(Metadata), meta_data.data());
+        if (read_result.Failed() || *read_result != sizeof(Metadata))
+            return Loader::ResultStatus::Error;
+
+        result = LoadMetadata(meta_data);
+        if (result != Loader::ResultStatus::Success)
+            return result;
+    }
+
+    return Loader::ResultStatus::Success;
+}
+
+Loader::ResultStatus CIAContainer::Load(const std::string& filepath) {
+    FileUtil::IOFile file(filepath, "rb");
+    if (!file.IsOpen())
+        return Loader::ResultStatus::Error;
+
+    // Load CIA Header
+    std::vector<u8> header_data(sizeof(Header));
+    if (file.ReadBytes(header_data.data(), sizeof(Header)) != sizeof(Header))
+        return Loader::ResultStatus::Error;
+
+    Loader::ResultStatus result = LoadHeader(header_data);
+    if (result != Loader::ResultStatus::Success)
+        return result;
+
+    // Load Title Metadata
+    std::vector<u8> tmd_data(cia_header.tmd_size);
+    file.Seek(GetTitleMetadataOffset(), SEEK_SET);
+    if (!file.ReadBytes(tmd_data.data(), cia_header.tmd_size) != cia_header.tmd_size)
+        return Loader::ResultStatus::Error;
+
+    result = LoadTitleMetadata(tmd_data);
+    if (result != Loader::ResultStatus::Success)
+        return result;
+
+    // Load CIA Metadata
+    if (cia_header.meta_size) {
+        std::vector<u8> meta_data(sizeof(Metadata));
+        file.Seek(GetMetadataOffset(), SEEK_SET);
+        if (file.ReadBytes(meta_data.data(), sizeof(Metadata)) != sizeof(Metadata))
+            return Loader::ResultStatus::Error;
+
+        result = LoadMetadata(meta_data);
+        if (result != Loader::ResultStatus::Success)
+            return result;
+    }
+
+    return Loader::ResultStatus::Success;
+}
+
+Loader::ResultStatus CIAContainer::Load(const std::vector<u8>& file_data) {
+    Loader::ResultStatus result = LoadHeader(file_data);
+    if (result != Loader::ResultStatus::Success)
+        return result;
+
+    // Load Title Metadata
+    result = LoadTitleMetadata(file_data, GetTitleMetadataOffset());
+    if (result != Loader::ResultStatus::Success)
+        return result;
+
+    // Load CIA Metadata
+    if (cia_header.meta_size) {
+        result = LoadMetadata(file_data, GetMetadataOffset());
+        if (result != Loader::ResultStatus::Success)
+            return result;
+    }
+
+    return Loader::ResultStatus::Success;
+}
+
+Loader::ResultStatus CIAContainer::LoadHeader(const std::vector<u8>& header_data, size_t offset) {
+    if (header_data.size() - offset < sizeof(Header))
+        return Loader::ResultStatus::Error;
+
+    std::memcpy(&cia_header, header_data.data(), sizeof(Header));
+
+    return Loader::ResultStatus::Success;
+}
+
+Loader::ResultStatus CIAContainer::LoadTitleMetadata(const std::vector<u8>& tmd_data,
+                                                     size_t offset) {
+    return cia_tmd.Load(tmd_data, offset);
+}
+
+Loader::ResultStatus CIAContainer::LoadMetadata(const std::vector<u8>& meta_data, size_t offset) {
+    if (meta_data.size() - offset < sizeof(Metadata))
+        return Loader::ResultStatus::Error;
+
+    std::memcpy(&cia_metadata, meta_data.data(), sizeof(Metadata));
+
+    return Loader::ResultStatus::Success;
+}
+
+const TitleMetadata& CIAContainer::GetTitleMetadata() const {
+    return cia_tmd;
+}
+
+std::array<u64, 0x30>& CIAContainer::GetDependencies() {
+    return cia_metadata.dependencies;
+}
+
+u32 CIAContainer::GetCoreVersion() const {
+    return cia_metadata.core_version;
+}
+
+u64 CIAContainer::GetCertificateOffset() const {
+    return Common::AlignUp(cia_header.header_size, CIA_SECTION_ALIGNMENT);
+}
+
+u64 CIAContainer::GetTicketOffset() const {
+    return Common::AlignUp(GetCertificateOffset() + cia_header.cert_size, CIA_SECTION_ALIGNMENT);
+}
+
+u64 CIAContainer::GetTitleMetadataOffset() const {
+    return Common::AlignUp(GetTicketOffset() + cia_header.tik_size, CIA_SECTION_ALIGNMENT);
+}
+
+u64 CIAContainer::GetMetadataOffset() const {
+    u64 tmd_end_offset = GetContentOffset();
+
+    // Meta exists after all content in the CIA
+    u64 offset = Common::AlignUp(tmd_end_offset + cia_header.content_size, CIA_SECTION_ALIGNMENT);
+
+    return offset;
+}
+
+u64 CIAContainer::GetContentOffset(u16 index) const {
+    u64 offset =
+        Common::AlignUp(GetTitleMetadataOffset() + cia_header.tmd_size, CIA_SECTION_ALIGNMENT);
+    for (u16 i = 0; i < index; i++) {
+        offset += GetContentSize(i);
+    }
+    return offset;
+}
+
+u32 CIAContainer::GetCertificateSize() const {
+    return cia_header.cert_size;
+}
+
+u32 CIAContainer::GetTicketSize() const {
+    return cia_header.tik_size;
+}
+
+u32 CIAContainer::GetTitleMetadataSize() const {
+    return cia_header.tmd_size;
+}
+
+u32 CIAContainer::GetMetadataSize() const {
+    return cia_header.meta_size;
+}
+
+u64 CIAContainer::GetTotalContentSize() const {
+    return cia_header.content_size;
+}
+
+u64 CIAContainer::GetContentSize(u16 index) const {
+    // If the content doesn't exist in the CIA, it doesn't have a size.
+    if (!cia_header.isContentPresent(index))
+        return 0;
+
+    return cia_tmd.GetContentSizeByIndex(index);
+}
+
+void CIAContainer::Print() const {
+    LOG_DEBUG(Service_FS, "Type:               %u", static_cast<u32>(cia_header.type));
+    LOG_DEBUG(Service_FS, "Version:            %u\n", static_cast<u32>(cia_header.version));
+
+    LOG_DEBUG(Service_FS, "Certificate Size: 0x%08x bytes", GetCertificateSize());
+    LOG_DEBUG(Service_FS, "Ticket Size:      0x%08x bytes", GetTicketSize());
+    LOG_DEBUG(Service_FS, "TMD Size:         0x%08x bytes", GetTitleMetadataSize());
+    LOG_DEBUG(Service_FS, "Meta Size:        0x%08x bytes", GetMetadataSize());
+    LOG_DEBUG(Service_FS, "Content Size:     0x%08x bytes\n", GetTotalContentSize());
+
+    LOG_DEBUG(Service_FS, "Certificate Offset: 0x%08" PRIx64 " bytes", GetCertificateOffset());
+    LOG_DEBUG(Service_FS, "Ticket Offset:      0x%08" PRIx64 " bytes", GetTicketOffset());
+    LOG_DEBUG(Service_FS, "TMD Offset:         0x%08" PRIx64 " bytes", GetTitleMetadataOffset());
+    LOG_DEBUG(Service_FS, "Meta Offset:        0x%08" PRIx64 " bytes", GetMetadataOffset());
+    for (u16 i = 0; i < cia_tmd.GetContentCount(); i++) {
+        LOG_DEBUG(Service_FS, "Content %x Offset:   0x%08" PRIx64 " bytes", i, GetContentOffset(i));
+    }
+}
+}
diff --git a/src/core/file_sys/cia_container.h b/src/core/file_sys/cia_container.h
new file mode 100644
index 000000000..8cdca0848
--- /dev/null
+++ b/src/core/file_sys/cia_container.h
@@ -0,0 +1,104 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <vector>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/file_sys/title_metadata.h"
+
+namespace Loader {
+enum class ResultStatus;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// FileSys namespace
+
+namespace FileSys {
+
+class FileBackend;
+
+constexpr size_t CIA_CONTENT_MAX_COUNT = 0x10000;
+constexpr size_t CIA_CONTENT_BITS_SIZE = (CIA_CONTENT_MAX_COUNT / 8);
+constexpr size_t CIA_HEADER_SIZE = 0x2020;
+constexpr size_t CIA_DEPENDENCY_SIZE = 0x300;
+constexpr size_t CIA_METADATA_SIZE = 0x400;
+
+/**
+ * Helper which implements an interface to read and write CTR Installable Archive (CIA) files.
+ * Data can either be loaded from a FileBackend, a string path, or from a data array. Data can
+ * also be partially loaded for CIAs which are downloading/streamed in and need some metadata
+ * read out.
+ */
+class CIAContainer {
+public:
+    // Load whole CIAs outright
+    Loader::ResultStatus Load(const FileBackend& backend);
+    Loader::ResultStatus Load(const std::string& filepath);
+    Loader::ResultStatus Load(const std::vector<u8>& header_data);
+
+    // Load parts of CIAs (for CIAs streamed in)
+    Loader::ResultStatus LoadHeader(const std::vector<u8>& header_data, size_t offset = 0);
+    Loader::ResultStatus LoadTitleMetadata(const std::vector<u8>& tmd_data, size_t offset = 0);
+    Loader::ResultStatus LoadMetadata(const std::vector<u8>& meta_data, size_t offset = 0);
+
+    const TitleMetadata& GetTitleMetadata() const;
+    std::array<u64, 0x30>& GetDependencies();
+    u32 GetCoreVersion() const;
+
+    u64 GetCertificateOffset() const;
+    u64 GetTicketOffset() const;
+    u64 GetTitleMetadataOffset() const;
+    u64 GetMetadataOffset() const;
+    u64 GetContentOffset(u16 index = 0) const;
+
+    u32 GetCertificateSize() const;
+    u32 GetTicketSize() const;
+    u32 GetTitleMetadataSize() const;
+    u32 GetMetadataSize() const;
+    u64 GetTotalContentSize() const;
+    u64 GetContentSize(u16 index = 0) const;
+
+    void Print() const;
+
+private:
+    struct Header {
+        u32_le header_size;
+        u16_le type;
+        u16_le version;
+        u32_le cert_size;
+        u32_le tik_size;
+        u32_le tmd_size;
+        u32_le meta_size;
+        u64_le content_size;
+        std::array<u8, CIA_CONTENT_BITS_SIZE> content_present;
+
+        bool isContentPresent(u16 index) const {
+            // The content_present is a bit array which defines which content in the TMD
+            // is included in the CIA, so check the bit for this index and add if set.
+            // The bits in the content index are arranged w/ index 0 as the MSB, 7 as the LSB, etc.
+            return (content_present[index >> 3] & (0x80 >> (index & 7)));
+        }
+    };
+
+    static_assert(sizeof(Header) == CIA_HEADER_SIZE, "CIA Header structure size is wrong");
+
+    struct Metadata {
+        std::array<u64_le, 0x30> dependencies;
+        std::array<u8, 0x180> reserved;
+        u32_le core_version;
+        std::array<u8, 0xfc> reserved_2;
+    };
+
+    static_assert(sizeof(Metadata) == CIA_METADATA_SIZE, "CIA Metadata structure size is wrong");
+
+    Header cia_header;
+    Metadata cia_metadata;
+    TitleMetadata cia_tmd;
+};
+
+} // namespace FileSys