mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	file_sys: Add CIA Container
This commit is contained in:
		
							parent
							
								
									79852d3707
								
							
						
					
					
						commit
						b3e87d01fb
					
				
					 3 changed files with 333 additions and 0 deletions
				
			
		|  | @ -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 | ||||
|  |  | |||
							
								
								
									
										228
									
								
								src/core/file_sys/cia_container.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								src/core/file_sys/cia_container.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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)); | ||||
|     } | ||||
| } | ||||
| } | ||||
							
								
								
									
										104
									
								
								src/core/file_sys/cia_container.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/core/file_sys/cia_container.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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
 | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue