From b3e87d01fb3a330615949a00bc195e05b6ff8ac0 Mon Sep 17 00:00:00 2001 From: shinyquagsire23 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 +#include +#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 header_data(sizeof(Header)); + + // Load the CIA Header + ResultVal 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 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 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 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 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 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& 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& 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& tmd_data, + size_t offset) { + return cia_tmd.Load(tmd_data, offset); +} + +Loader::ResultStatus CIAContainer::LoadMetadata(const std::vector& 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& 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(cia_header.type)); + LOG_DEBUG(Service_FS, "Version: %u\n", static_cast(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 +#include +#include +#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& header_data); + + // Load parts of CIAs (for CIAs streamed in) + Loader::ResultStatus LoadHeader(const std::vector& header_data, size_t offset = 0); + Loader::ResultStatus LoadTitleMetadata(const std::vector& tmd_data, size_t offset = 0); + Loader::ResultStatus LoadMetadata(const std::vector& meta_data, size_t offset = 0); + + const TitleMetadata& GetTitleMetadata() const; + std::array& 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 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 dependencies; + std::array reserved; + u32_le core_version; + std::array 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