mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	Merge pull request #3029 from shinyquagsire23/am-title-install
Services/AM: Add CIA title installation support.
This commit is contained in:
		
						commit
						8ba2de1580
					
				
					 19 changed files with 1160 additions and 83 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 | ||||
|  |  | |||
|  | @ -35,8 +35,7 @@ public: | |||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     ResultVal<size_t> Write(u64 offset, size_t length, bool flush, | ||||
|                             const u8* buffer) const override { | ||||
|     ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) override { | ||||
|         if (offset > size) { | ||||
|             return ERR_WRITE_BEYOND_END; | ||||
|         } else if (offset == size) { | ||||
|  |  | |||
|  | @ -206,7 +206,7 @@ ResultVal<size_t> NCCHFile::Read(const u64 offset, const size_t length, u8* buff | |||
| } | ||||
| 
 | ||||
| ResultVal<size_t> NCCHFile::Write(const u64 offset, const size_t length, const bool flush, | ||||
|                                   const u8* buffer) const { | ||||
|                                   const u8* buffer) { | ||||
|     LOG_ERROR(Service_FS, "Attempted to write to NCCH file"); | ||||
|     // TODO(shinyquagsire23): Find error code
 | ||||
|     return MakeResult<size_t>(0); | ||||
|  |  | |||
|  | @ -54,7 +54,7 @@ public: | |||
|     NCCHFile(std::vector<u8> buffer) : file_buffer(buffer) {} | ||||
| 
 | ||||
|     ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override; | ||||
|     ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; | ||||
|     ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) override; | ||||
|     u64 GetSize() const override; | ||||
|     bool SetSize(u64 size) const override; | ||||
|     bool Close() const override { | ||||
|  |  | |||
|  | @ -51,8 +51,7 @@ public: | |||
|         return MakeResult<size_t>(data->size()); | ||||
|     } | ||||
| 
 | ||||
|     ResultVal<size_t> Write(u64 offset, size_t length, bool flush, | ||||
|                             const u8* buffer) const override { | ||||
|     ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) override { | ||||
|         LOG_ERROR(Service_FS, "The file is read-only!"); | ||||
|         return ERROR_UNSUPPORTED_OPEN_FLAGS; | ||||
|     } | ||||
|  |  | |||
							
								
								
									
										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
 | ||||
|  | @ -25,7 +25,7 @@ ResultVal<size_t> DiskFile::Read(const u64 offset, const size_t length, u8* buff | |||
| } | ||||
| 
 | ||||
| ResultVal<size_t> DiskFile::Write(const u64 offset, const size_t length, const bool flush, | ||||
|                                   const u8* buffer) const { | ||||
|                                   const u8* buffer) { | ||||
|     if (!mode.write_flag) | ||||
|         return ERROR_INVALID_OPEN_FLAGS; | ||||
| 
 | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ public: | |||
|     } | ||||
| 
 | ||||
|     ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override; | ||||
|     ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; | ||||
|     ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) override; | ||||
|     u64 GetSize() const override; | ||||
|     bool SetSize(u64 size) const override; | ||||
|     bool Close() const override; | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ enum { | |||
|     FileAlreadyExists = 180, | ||||
|     DirectoryAlreadyExists = 185, | ||||
|     AlreadyExists = 190, | ||||
|     InsufficientSpace = 210, | ||||
|     InvalidOpenFlags = 230, | ||||
|     DirectoryNotEmpty = 240, | ||||
|     NotAFile = 250, | ||||
|  | @ -74,6 +75,8 @@ constexpr ResultCode ERROR_COMMAND_NOT_ALLOWED(ErrCodes::CommandNotAllowed, Erro | |||
|                                                ErrorSummary::WrongArgument, ErrorLevel::Permanent); | ||||
| constexpr ResultCode ERROR_EXEFS_SECTION_NOT_FOUND(ErrCodes::ExeFSSectionNotFound, ErrorModule::FS, | ||||
|                                                    ErrorSummary::NotFound, ErrorLevel::Status); | ||||
| constexpr ResultCode ERROR_INSUFFICIENT_SPACE(ErrCodes::InsufficientSpace, ErrorModule::FS, | ||||
|                                               ErrorSummary::OutOfResource, ErrorLevel::Status); | ||||
| 
 | ||||
| /// Returned when a function is passed an invalid archive handle.
 | ||||
| constexpr ResultCode ERR_INVALID_ARCHIVE_HANDLE(ErrCodes::ArchiveNotMounted, ErrorModule::FS, | ||||
|  |  | |||
|  | @ -35,8 +35,7 @@ public: | |||
|      * @param buffer Buffer to read data from | ||||
|      * @return Number of bytes written, or error code | ||||
|      */ | ||||
|     virtual ResultVal<size_t> Write(u64 offset, size_t length, bool flush, | ||||
|                                     const u8* buffer) const = 0; | ||||
|     virtual ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Get the size of the file in bytes | ||||
|  |  | |||
|  | @ -94,7 +94,7 @@ ResultVal<size_t> IVFCFile::Read(const u64 offset, const size_t length, u8* buff | |||
| } | ||||
| 
 | ||||
| ResultVal<size_t> IVFCFile::Write(const u64 offset, const size_t length, const bool flush, | ||||
|                                   const u8* buffer) const { | ||||
|                                   const u8* buffer) { | ||||
|     LOG_ERROR(Service_FS, "Attempted to write to IVFC file"); | ||||
|     // TODO(Subv): Find error code
 | ||||
|     return MakeResult<size_t>(0); | ||||
|  |  | |||
|  | @ -56,7 +56,7 @@ public: | |||
|         : romfs_file(file), data_offset(offset), data_size(size) {} | ||||
| 
 | ||||
|     ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override; | ||||
|     ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; | ||||
|     ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) override; | ||||
|     u64 GetSize() const override; | ||||
|     bool SetSize(u64 size) const override; | ||||
|     bool Close() const override { | ||||
|  |  | |||
|  | @ -29,47 +29,70 @@ static u32 GetSignatureSize(u32 signature_type) { | |||
|     case EcdsaSha256: | ||||
|         return 0x3C; | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| Loader::ResultStatus TitleMetadata::Load() { | ||||
|     FileUtil::IOFile file(filepath, "rb"); | ||||
| Loader::ResultStatus TitleMetadata::Load(const std::string& file_path) { | ||||
|     FileUtil::IOFile file(file_path, "rb"); | ||||
|     if (!file.IsOpen()) | ||||
|         return Loader::ResultStatus::Error; | ||||
| 
 | ||||
|     if (!file.ReadBytes(&signature_type, sizeof(u32_be))) | ||||
|     std::vector<u8> file_data(file.GetSize()); | ||||
| 
 | ||||
|     if (!file.ReadBytes(file_data.data(), file.GetSize())) | ||||
|         return Loader::ResultStatus::Error; | ||||
| 
 | ||||
|     Loader::ResultStatus result = Load(file_data); | ||||
|     if (result != Loader::ResultStatus::Success) | ||||
|         LOG_ERROR(Service_FS, "Failed to load TMD from file %s!", file_path.c_str()); | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| Loader::ResultStatus TitleMetadata::Load(const std::vector<u8> file_data, size_t offset) { | ||||
|     size_t total_size = static_cast<size_t>(file_data.size() - offset); | ||||
|     if (total_size < sizeof(u32_be)) | ||||
|         return Loader::ResultStatus::Error; | ||||
| 
 | ||||
|     memcpy(&signature_type, &file_data[offset], sizeof(u32_be)); | ||||
| 
 | ||||
|     // 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); | ||||
|     size_t body_end = body_start + sizeof(Body); | ||||
| 
 | ||||
|     // Read our TMD body, then load the amount of ContentChunks specified
 | ||||
|     if (file.ReadBytes(&tmd_body, sizeof(TitleMetadata::Body)) != sizeof(TitleMetadata::Body)) | ||||
|     if (total_size < body_end) | ||||
|         return Loader::ResultStatus::Error; | ||||
| 
 | ||||
|     // Read signature + TMD body, then load the amount of ContentChunks specified
 | ||||
|     tmd_signature.resize(signature_size); | ||||
|     memcpy(tmd_signature.data(), &file_data[offset + sizeof(u32_be)], signature_size); | ||||
|     memcpy(&tmd_body, &file_data[offset + body_start], sizeof(TitleMetadata::Body)); | ||||
| 
 | ||||
|     size_t expected_size = | ||||
|         body_start + sizeof(Body) + tmd_body.content_count * sizeof(ContentChunk); | ||||
|     if (total_size < expected_size) { | ||||
|         LOG_ERROR(Service_FS, "Malformed TMD, expected size 0x%zx, got 0x%zx!", expected_size, | ||||
|                   total_size); | ||||
|         return Loader::ResultStatus::ErrorInvalidFormat; | ||||
|     } | ||||
| 
 | ||||
|     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; | ||||
|         } | ||||
| 
 | ||||
|         memcpy(&chunk, &file_data[offset + body_end + (i * sizeof(ContentChunk))], | ||||
|                sizeof(ContentChunk)); | ||||
|         tmd_chunks.push_back(chunk); | ||||
|     } | ||||
| 
 | ||||
|     return Loader::ResultStatus::Success; | ||||
| } | ||||
| 
 | ||||
| Loader::ResultStatus TitleMetadata::Save() { | ||||
|     FileUtil::IOFile file(filepath, "wb"); | ||||
| Loader::ResultStatus TitleMetadata::Save(const std::string& file_path) { | ||||
|     FileUtil::IOFile file(file_path, "wb"); | ||||
|     if (!file.IsOpen()) | ||||
|         return Loader::ResultStatus::Error; | ||||
| 
 | ||||
|  | @ -186,8 +209,7 @@ void TitleMetadata::AddContentChunk(const ContentChunk& chunk) { | |||
| } | ||||
| 
 | ||||
| void TitleMetadata::Print() const { | ||||
|     LOG_DEBUG(Service_FS, "%s - %u chunks", filepath.c_str(), | ||||
|               static_cast<u32>(tmd_body.content_count)); | ||||
|     LOG_DEBUG(Service_FS, "%u chunks", static_cast<u32>(tmd_body.content_count)); | ||||
| 
 | ||||
|     // Content info describes ranges of content chunks
 | ||||
|     LOG_DEBUG(Service_FS, "Content info:"); | ||||
|  |  | |||
|  | @ -92,9 +92,9 @@ public: | |||
| 
 | ||||
| #pragma pack(pop) | ||||
| 
 | ||||
|     explicit TitleMetadata(std::string& path) : filepath(std::move(path)) {} | ||||
|     Loader::ResultStatus Load(); | ||||
|     Loader::ResultStatus Save(); | ||||
|     Loader::ResultStatus Load(const std::string& file_path); | ||||
|     Loader::ResultStatus Load(const std::vector<u8> file_data, size_t offset = 0); | ||||
|     Loader::ResultStatus Save(const std::string& file_path); | ||||
| 
 | ||||
|     u64 GetTitleID() const; | ||||
|     u32 GetTitleType() const; | ||||
|  | @ -121,8 +121,6 @@ private: | |||
|     u32_be signature_type; | ||||
|     std::vector<u8> tmd_signature; | ||||
|     std::vector<ContentChunk> tmd_chunks; | ||||
| 
 | ||||
|     std::string filepath; | ||||
| }; | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
|  |  | |||
|  | @ -2,15 +2,27 @@ | |||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <array> | ||||
| #include <cinttypes> | ||||
| #include <cstddef> | ||||
| #include <cstring> | ||||
| #include "common/file_util.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/string_util.h" | ||||
| #include "core/file_sys/cia_container.h" | ||||
| #include "core/file_sys/errors.h" | ||||
| #include "core/file_sys/file_backend.h" | ||||
| #include "core/file_sys/ncch_container.h" | ||||
| #include "core/file_sys/title_metadata.h" | ||||
| #include "core/hle/ipc.h" | ||||
| #include "core/hle/ipc_helpers.h" | ||||
| #include "core/hle/kernel/client_port.h" | ||||
| #include "core/hle/kernel/client_session.h" | ||||
| #include "core/hle/kernel/errors.h" | ||||
| #include "core/hle/kernel/handle_table.h" | ||||
| #include "core/hle/kernel/server_session.h" | ||||
| #include "core/hle/kernel/session.h" | ||||
| #include "core/hle/result.h" | ||||
| #include "core/hle/service/am/am.h" | ||||
| #include "core/hle/service/am/am_app.h" | ||||
|  | @ -20,6 +32,7 @@ | |||
| #include "core/hle/service/fs/archive.h" | ||||
| #include "core/hle/service/service.h" | ||||
| #include "core/loader/loader.h" | ||||
| #include "core/loader/smdh.h" | ||||
| 
 | ||||
| namespace Service { | ||||
| namespace AM { | ||||
|  | @ -27,6 +40,11 @@ namespace AM { | |||
| constexpr u32 TID_HIGH_UPDATE = 0x0004000E; | ||||
| constexpr u32 TID_HIGH_DLC = 0x0004008C; | ||||
| 
 | ||||
| // CIA installation static context variables
 | ||||
| static bool cia_installing = false; | ||||
| static u64 cia_installing_tid; | ||||
| static Service::FS::MediaType cia_installing_media_type; | ||||
| 
 | ||||
| static bool lists_initialized = false; | ||||
| static std::array<std::vector<u64_le>, 3> am_title_list; | ||||
| 
 | ||||
|  | @ -60,7 +78,192 @@ struct TicketInfo { | |||
| 
 | ||||
| static_assert(sizeof(TicketInfo) == 0x18, "Ticket info structure size is wrong"); | ||||
| 
 | ||||
| std::string GetTitleMetadataPath(Service::FS::MediaType media_type, u64 tid) { | ||||
| // A file handled returned for CIAs to be written into and subsequently installed.
 | ||||
| class CIAFile final : public FileSys::FileBackend { | ||||
| public: | ||||
|     explicit CIAFile(Service::FS::MediaType media_type) : media_type(media_type) {} | ||||
| 
 | ||||
|     ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override { | ||||
|         UNIMPLEMENTED(); | ||||
|         return MakeResult<size_t>(length); | ||||
|     } | ||||
| 
 | ||||
|     ResultVal<size_t> WriteTitleMetadata(u64 offset, size_t length, const u8* buffer) { | ||||
|         container.LoadTitleMetadata(data, container.GetTitleMetadataOffset()); | ||||
|         FileSys::TitleMetadata tmd = container.GetTitleMetadata(); | ||||
|         cia_installing_tid = tmd.GetTitleID(); | ||||
|         tmd.Print(); | ||||
| 
 | ||||
|         // If a TMD already exists for this app (ie 00000000.tmd), the incoming TMD
 | ||||
|         // will be the same plus one, (ie 00000001.tmd), both will be kept until
 | ||||
|         // the install is finalized and old contents can be discarded.
 | ||||
|         if (FileUtil::Exists(GetTitleMetadataPath(media_type, tmd.GetTitleID()))) | ||||
|             is_update = true; | ||||
| 
 | ||||
|         std::string tmd_path = GetTitleMetadataPath(media_type, tmd.GetTitleID(), is_update); | ||||
| 
 | ||||
|         // Create content/ folder if it doesn't exist
 | ||||
|         std::string tmd_folder; | ||||
|         Common::SplitPath(tmd_path, &tmd_folder, nullptr, nullptr); | ||||
|         FileUtil::CreateFullPath(tmd_folder); | ||||
| 
 | ||||
|         // Save TMD so that we can start getting new .app paths
 | ||||
|         if (tmd.Save(tmd_path) != Loader::ResultStatus::Success) | ||||
|             return FileSys::ERROR_INSUFFICIENT_SPACE; | ||||
| 
 | ||||
|         // Create any other .app folders which may not exist yet
 | ||||
|         std::string app_folder; | ||||
|         Common::SplitPath(GetTitleContentPath(media_type, tmd.GetTitleID(), | ||||
|                                               FileSys::TMDContentIndex::Main, is_update), | ||||
|                           &app_folder, nullptr, nullptr); | ||||
|         FileUtil::CreateFullPath(app_folder); | ||||
| 
 | ||||
|         content_written.resize(container.GetTitleMetadata().GetContentCount()); | ||||
|         install_state = CIAInstallState::TMDLoaded; | ||||
| 
 | ||||
|         return MakeResult<size_t>(length); | ||||
|     } | ||||
| 
 | ||||
|     ResultVal<size_t> WriteContentData(u64 offset, size_t length, const u8* buffer) { | ||||
|         // Data is not being buffered, so we have to keep track of how much of each <ID>.app
 | ||||
|         // has been written since we might get a written buffer which contains multiple .app
 | ||||
|         // contents or only part of a larger .app's contents.
 | ||||
|         u64 offset_max = offset + length; | ||||
|         for (int i = 0; i < container.GetTitleMetadata().GetContentCount(); i++) { | ||||
|             if (content_written[i] < container.GetContentSize(i)) { | ||||
|                 // The size, minimum unwritten offset, and maximum unwritten offset of this content
 | ||||
|                 u64 size = container.GetContentSize(i); | ||||
|                 u64 range_min = container.GetContentOffset(i) + content_written[i]; | ||||
|                 u64 range_max = container.GetContentOffset(i) + size; | ||||
| 
 | ||||
|                 // The unwritten range for this content is beyond the buffered data we have
 | ||||
|                 // or comes before the buffered data we have, so skip this content ID.
 | ||||
|                 if (range_min > offset_max || range_max < offset) | ||||
|                     continue; | ||||
| 
 | ||||
|                 // Figure out how much of this content ID we have just recieved/can write out
 | ||||
|                 u64 available_to_write = std::min(offset_max, range_max) - range_min; | ||||
| 
 | ||||
|                 // Since the incoming TMD has already been written, we can use GetTitleContentPath
 | ||||
|                 // to get the content paths to write to.
 | ||||
|                 FileSys::TitleMetadata tmd = container.GetTitleMetadata(); | ||||
|                 FileUtil::IOFile file( | ||||
|                     GetTitleContentPath(media_type, tmd.GetTitleID(), i, is_update), | ||||
|                     content_written[i] ? "a" : "w"); | ||||
| 
 | ||||
|                 if (!file.IsOpen()) | ||||
|                     return FileSys::ERROR_INSUFFICIENT_SPACE; | ||||
| 
 | ||||
|                 file.WriteBytes(buffer + (range_min - offset), available_to_write); | ||||
| 
 | ||||
|                 // Keep tabs on how much of this content ID has been written so new range_min
 | ||||
|                 // values can be calculated.
 | ||||
|                 content_written[i] += available_to_write; | ||||
|                 LOG_DEBUG(Service_AM, "Wrote %" PRIx64 " to content %u, total %" PRIx64, | ||||
|                           available_to_write, i, content_written[i]); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return MakeResult<size_t>(length); | ||||
|     } | ||||
| 
 | ||||
|     ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) override { | ||||
|         written += length; | ||||
| 
 | ||||
|         // TODO(shinyquagsire23): Can we assume that things will only be written in sequence?
 | ||||
|         // Does AM send an error if we write to things out of order?
 | ||||
|         // Or does it just ignore offsets and assume a set sequence of incoming data?
 | ||||
| 
 | ||||
|         // The data in CIAs is always stored CIA Header > Cert > Ticket > TMD > Content > Meta.
 | ||||
|         // The CIA Header describes Cert, Ticket, TMD, total content sizes, and TMD is needed for
 | ||||
|         // content sizes so it ends up becoming a problem of keeping track of how much has been
 | ||||
|         // written and what we have been able to pick up.
 | ||||
|         if (install_state == CIAInstallState::InstallStarted) { | ||||
|             size_t buf_copy_size = std::min(length, FileSys::CIA_HEADER_SIZE); | ||||
|             size_t buf_max_size = | ||||
|                 std::min(static_cast<size_t>(offset + length), FileSys::CIA_HEADER_SIZE); | ||||
|             data.resize(buf_max_size); | ||||
|             memcpy(data.data() + offset, buffer, buf_copy_size); | ||||
| 
 | ||||
|             // We have enough data to load a CIA header and parse it.
 | ||||
|             if (written >= FileSys::CIA_HEADER_SIZE) { | ||||
|                 container.LoadHeader(data); | ||||
|                 container.Print(); | ||||
|                 install_state = CIAInstallState::HeaderLoaded; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // If we don't have a header yet, we can't pull offsets of other sections
 | ||||
|         if (install_state == CIAInstallState::InstallStarted) | ||||
|             return MakeResult<size_t>(length); | ||||
| 
 | ||||
|         // If we have been given data before (or including) .app content, pull it into
 | ||||
|         // our buffer, but only pull *up to* the content offset, no further.
 | ||||
|         if (offset < container.GetContentOffset()) { | ||||
|             size_t buf_loaded = data.size(); | ||||
|             size_t copy_offset = std::max(static_cast<size_t>(offset), buf_loaded); | ||||
|             size_t buf_offset = buf_loaded - offset; | ||||
|             size_t buf_copy_size = | ||||
|                 std::min(length, static_cast<size_t>(container.GetContentOffset() - offset)) - | ||||
|                 buf_loaded; | ||||
|             size_t buf_max_size = std::min(offset + length, container.GetContentOffset()); | ||||
|             data.resize(buf_max_size); | ||||
|             memcpy(data.data() + copy_offset, buffer + buf_offset, buf_copy_size); | ||||
|         } | ||||
| 
 | ||||
|         // TODO(shinyquagsire23): Write out .tik files to nand?
 | ||||
| 
 | ||||
|         // The end of our TMD is at the beginning of Content data, so ensure we have that much
 | ||||
|         // buffered before trying to parse.
 | ||||
|         if (written >= container.GetContentOffset() && | ||||
|             install_state != CIAInstallState::TMDLoaded) { | ||||
|             auto result = WriteTitleMetadata(offset, length, buffer); | ||||
|             if (result.Failed()) | ||||
|                 return result; | ||||
|         } | ||||
| 
 | ||||
|         // Content data sizes can only be retrieved from TMD data
 | ||||
|         if (install_state != CIAInstallState::TMDLoaded) | ||||
|             return MakeResult<size_t>(length); | ||||
| 
 | ||||
|         // From this point forward, data will no longer be buffered in data
 | ||||
|         auto result = WriteContentData(offset, length, buffer); | ||||
|         if (result.Failed()) | ||||
|             return result; | ||||
| 
 | ||||
|         return MakeResult<size_t>(length); | ||||
|     } | ||||
| 
 | ||||
|     u64 GetSize() const override { | ||||
|         return written; | ||||
|     } | ||||
| 
 | ||||
|     bool SetSize(u64 size) const override { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     bool Close() const override { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     void Flush() const override {} | ||||
| 
 | ||||
| private: | ||||
|     // Whether it's installing an update, and what step of installation it is at
 | ||||
|     bool is_update = false; | ||||
|     CIAInstallState install_state = CIAInstallState::InstallStarted; | ||||
| 
 | ||||
|     // How much has been written total, CIAContainer for the installing CIA, buffer of all data
 | ||||
|     // prior to content data, how much of each content index has been written, and where the CIA
 | ||||
|     // is being installed to
 | ||||
|     u64 written = 0; | ||||
|     FileSys::CIAContainer container; | ||||
|     std::vector<u8> data; | ||||
|     std::vector<u64> content_written; | ||||
|     Service::FS::MediaType media_type; | ||||
| }; | ||||
| 
 | ||||
| std::string GetTitleMetadataPath(Service::FS::MediaType media_type, u64 tid, bool update) { | ||||
|     std::string content_path = GetTitlePath(media_type, tid) + "content/"; | ||||
| 
 | ||||
|     if (media_type == Service::FS::MediaType::GameCard) { | ||||
|  | @ -69,23 +272,37 @@ std::string GetTitleMetadataPath(Service::FS::MediaType media_type, u64 tid) { | |||
|     } | ||||
| 
 | ||||
|     // The TMD ID is usually held in the title databases, which we don't implement.
 | ||||
|     // For now, just scan for any .tmd files which exist and use the first .tmd
 | ||||
|     // found (there should only really be one unless the directories are meddled with)
 | ||||
|     // For now, just scan for any .tmd files which exist, the smallest will be the
 | ||||
|     // base ID and the largest will be the (currently installing) update ID.
 | ||||
|     constexpr u32 MAX_TMD_ID = 0xFFFFFFFF; | ||||
|     u32 base_id = MAX_TMD_ID; | ||||
|     u32 update_id = 0; | ||||
|     FileUtil::FSTEntry entries; | ||||
|     FileUtil::ScanDirectoryTree(content_path, entries); | ||||
|     for (const FileUtil::FSTEntry& entry : entries.children) { | ||||
|         std::string filename_filename, filename_extension; | ||||
|         Common::SplitPath(entry.virtualName, nullptr, &filename_filename, &filename_extension); | ||||
| 
 | ||||
|         if (filename_extension == ".tmd") | ||||
|             return content_path + entry.virtualName; | ||||
|         if (filename_extension == ".tmd") { | ||||
|             u32 id = std::stoul(filename_filename.c_str(), nullptr, 16); | ||||
|             base_id = std::min(base_id, id); | ||||
|             update_id = std::max(update_id, id); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // If we can't find an existing .tmd, return a path for one to be created.
 | ||||
|     return content_path + "00000000.tmd"; | ||||
|     // If we didn't find anything, default to 00000000.tmd for it to be created.
 | ||||
|     if (base_id == MAX_TMD_ID) | ||||
|         base_id = 0; | ||||
| 
 | ||||
|     // Update ID should be one more than the last, if it hasn't been created yet.
 | ||||
|     if (base_id == update_id) | ||||
|         update_id++; | ||||
| 
 | ||||
|     return content_path + Common::StringFromFormat("%08x.tmd", (update ? update_id : base_id)); | ||||
| } | ||||
| 
 | ||||
| std::string GetTitleContentPath(Service::FS::MediaType media_type, u64 tid, u16 index) { | ||||
| std::string GetTitleContentPath(Service::FS::MediaType media_type, u64 tid, u16 index, | ||||
|                                 bool update) { | ||||
|     std::string content_path = GetTitlePath(media_type, tid) + "content/"; | ||||
| 
 | ||||
|     if (media_type == Service::FS::MediaType::GameCard) { | ||||
|  | @ -95,11 +312,11 @@ std::string GetTitleContentPath(Service::FS::MediaType media_type, u64 tid, u16 | |||
|         return ""; | ||||
|     } | ||||
| 
 | ||||
|     std::string tmd_path = GetTitleMetadataPath(media_type, tid); | ||||
|     std::string tmd_path = GetTitleMetadataPath(media_type, tid, update); | ||||
| 
 | ||||
|     u32 content_id = 0; | ||||
|     FileSys::TitleMetadata tmd(tmd_path); | ||||
|     if (tmd.Load() == Loader::ResultStatus::Success) { | ||||
|     FileSys::TitleMetadata tmd; | ||||
|     if (tmd.Load(tmd_path) == Loader::ResultStatus::Success) { | ||||
|         content_id = tmd.GetContentIDByIndex(index); | ||||
| 
 | ||||
|         // TODO(shinyquagsire23): how does DLC actually get this folder on hardware?
 | ||||
|  | @ -199,8 +416,8 @@ void FindContentInfos(Service::Interface* self) { | |||
|     std::string tmd_path = GetTitleMetadataPath(media_type, title_id); | ||||
| 
 | ||||
|     u32 content_read = 0; | ||||
|     FileSys::TitleMetadata tmd(tmd_path); | ||||
|     if (tmd.Load() == Loader::ResultStatus::Success) { | ||||
|     FileSys::TitleMetadata tmd; | ||||
|     if (tmd.Load(tmd_path) == Loader::ResultStatus::Success) { | ||||
|         // Get info for each content index requested
 | ||||
|         for (size_t i = 0; i < content_count; i++) { | ||||
|             std::shared_ptr<FileUtil::IOFile> romfs_file; | ||||
|  | @ -238,8 +455,8 @@ void ListContentInfos(Service::Interface* self) { | |||
|     std::string tmd_path = GetTitleMetadataPath(media_type, title_id); | ||||
| 
 | ||||
|     u32 copied = 0; | ||||
|     FileSys::TitleMetadata tmd(tmd_path); | ||||
|     if (tmd.Load() == Loader::ResultStatus::Success) { | ||||
|     FileSys::TitleMetadata tmd; | ||||
|     if (tmd.Load(tmd_path) == Loader::ResultStatus::Success) { | ||||
|         copied = std::min(content_count, static_cast<u32>(tmd.GetContentCount())); | ||||
|         for (u32 i = start_index; i < copied; i++) { | ||||
|             std::shared_ptr<FileUtil::IOFile> romfs_file; | ||||
|  | @ -313,8 +530,8 @@ ResultCode GetTitleInfoFromList(const std::vector<u64>& title_id_list, | |||
|         TitleInfo title_info = {}; | ||||
|         title_info.tid = title_id_list[i]; | ||||
| 
 | ||||
|         FileSys::TitleMetadata tmd(tmd_path); | ||||
|         if (tmd.Load() == Loader::ResultStatus::Success) { | ||||
|         FileSys::TitleMetadata tmd; | ||||
|         if (tmd.Load(tmd_path) == Loader::ResultStatus::Success) { | ||||
|             // TODO(shinyquagsire23): This is the total size of all files this process owns,
 | ||||
|             // including savefiles and other content. This comes close but is off.
 | ||||
|             title_info.size = tmd.GetContentSizeByIndex(FileSys::TMDContentIndex::Main); | ||||
|  | @ -462,8 +679,8 @@ void GetNumContentInfos(Service::Interface* self) { | |||
| 
 | ||||
|     std::string tmd_path = GetTitleMetadataPath(media_type, title_id); | ||||
| 
 | ||||
|     FileSys::TitleMetadata tmd(tmd_path); | ||||
|     if (tmd.Load() == Loader::ResultStatus::Success) { | ||||
|     FileSys::TitleMetadata tmd; | ||||
|     if (tmd.Load(tmd_path) == Loader::ResultStatus::Success) { | ||||
|         rb.Push<u32>(tmd.GetContentCount()); | ||||
|     } else { | ||||
|         rb.Push<u32>(1); // Number of content infos plus one
 | ||||
|  | @ -513,7 +730,7 @@ void QueryAvailableTitleDatabase(Service::Interface* self) { | |||
|     rb.Push(RESULT_SUCCESS); // No error
 | ||||
|     rb.Push(true); | ||||
| 
 | ||||
|     LOG_WARNING(Service_APT, "(STUBBED) media_type=%u", media_type); | ||||
|     LOG_WARNING(Service_AM, "(STUBBED) media_type=%u", media_type); | ||||
| } | ||||
| 
 | ||||
| void CheckContentRights(Service::Interface* self) { | ||||
|  | @ -529,7 +746,7 @@ void CheckContentRights(Service::Interface* self) { | |||
|     rb.Push(RESULT_SUCCESS); // No error
 | ||||
|     rb.Push(has_rights); | ||||
| 
 | ||||
|     LOG_WARNING(Service_APT, "(STUBBED) tid=%016" PRIx64 ", content_index=%u", tid, content_index); | ||||
|     LOG_WARNING(Service_AM, "(STUBBED) tid=%016" PRIx64 ", content_index=%u", tid, content_index); | ||||
| } | ||||
| 
 | ||||
| void CheckContentRightsIgnorePlatform(Service::Interface* self) { | ||||
|  | @ -545,7 +762,376 @@ void CheckContentRightsIgnorePlatform(Service::Interface* self) { | |||
|     rb.Push(RESULT_SUCCESS); // No error
 | ||||
|     rb.Push(has_rights); | ||||
| 
 | ||||
|     LOG_WARNING(Service_APT, "(STUBBED) tid=%016" PRIx64 ", content_index=%u", tid, content_index); | ||||
|     LOG_WARNING(Service_AM, "(STUBBED) tid=%016" PRIx64 ", content_index=%u", tid, content_index); | ||||
| } | ||||
| 
 | ||||
| void BeginImportProgram(Service::Interface* self) { | ||||
|     IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0402, 1, 0); // 0x04020040
 | ||||
|     auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>()); | ||||
| 
 | ||||
|     if (cia_installing) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCode(ErrCodes::CIACurrentlyInstalling, ErrorModule::AM, | ||||
|                            ErrorSummary::InvalidState, ErrorLevel::Permanent)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // Create our CIAFile handle for the app to write to, and while the app writes
 | ||||
|     // Citra will store contents out to sdmc/nand
 | ||||
|     const FileSys::Path cia_path = {}; | ||||
|     auto file = | ||||
|         std::make_shared<Service::FS::File>(std::make_unique<CIAFile>(media_type), cia_path); | ||||
|     auto sessions = Kernel::ServerSession::CreateSessionPair(file->GetName()); | ||||
|     file->ClientConnected(std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions)); | ||||
| 
 | ||||
|     cia_installing = true; | ||||
|     cia_installing_media_type = media_type; | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); | ||||
|     rb.Push(RESULT_SUCCESS); // No error
 | ||||
|     rb.PushCopyHandles( | ||||
|         Kernel::g_handle_table.Create(std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions)) | ||||
|             .Unwrap()); | ||||
| 
 | ||||
|     LOG_WARNING(Service_AM, "(STUBBED) media_type=%u", media_type); | ||||
| } | ||||
| 
 | ||||
| void EndImportProgram(Service::Interface* self) { | ||||
|     IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0405, 0, 2); // 0x04050002
 | ||||
|     auto cia_handle = rp.PopHandle(); | ||||
| 
 | ||||
|     // Clean up older content data if we installed newer content on top
 | ||||
|     std::string old_tmd_path = | ||||
|         GetTitleMetadataPath(cia_installing_media_type, cia_installing_tid, false); | ||||
|     std::string new_tmd_path = | ||||
|         GetTitleMetadataPath(cia_installing_media_type, cia_installing_tid, true); | ||||
|     if (FileUtil::Exists(new_tmd_path) && old_tmd_path != new_tmd_path) { | ||||
|         FileSys::TitleMetadata old_tmd; | ||||
|         FileSys::TitleMetadata new_tmd; | ||||
| 
 | ||||
|         old_tmd.Load(old_tmd_path); | ||||
|         new_tmd.Load(new_tmd_path); | ||||
| 
 | ||||
|         // For each content ID in the old TMD, check if there is a matching ID in the new
 | ||||
|         // TMD. If a CIA contains (and wrote to) an identical ID, it should be kept while
 | ||||
|         // IDs which only existed for the old TMD should be deleted.
 | ||||
|         for (u16 old_index = 0; old_index < old_tmd.GetContentCount(); old_index++) { | ||||
|             bool abort = false; | ||||
|             for (u16 new_index = 0; new_index < new_tmd.GetContentCount(); new_index++) { | ||||
|                 if (old_tmd.GetContentIDByIndex(old_index) == | ||||
|                     new_tmd.GetContentIDByIndex(new_index)) { | ||||
|                     abort = true; | ||||
|                 } | ||||
|             } | ||||
|             if (abort) | ||||
|                 break; | ||||
| 
 | ||||
|             FileUtil::Delete( | ||||
|                 GetTitleContentPath(cia_installing_media_type, old_tmd.GetTitleID(), old_index)); | ||||
|         } | ||||
| 
 | ||||
|         FileUtil::Delete(old_tmd_path); | ||||
|     } | ||||
|     ScanForAllTitles(); | ||||
| 
 | ||||
|     cia_installing = false; | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::shared_ptr<Service::FS::File>> GetFileFromHandle(Kernel::Handle handle) { | ||||
|     // Step up the chain from Handle->ClientSession->ServerSession and then
 | ||||
|     // cast to File. For AM on 3DS, invalid handles actually hang the system.
 | ||||
|     auto file_session = Kernel::g_handle_table.Get<Kernel::ClientSession>(handle); | ||||
| 
 | ||||
|     if (file_session == nullptr || file_session->parent == nullptr) { | ||||
|         LOG_WARNING(Service_AM, "Invalid file handle!"); | ||||
|         return Kernel::ERR_INVALID_HANDLE; | ||||
|     } | ||||
| 
 | ||||
|     Kernel::SharedPtr<Kernel::ServerSession> server = file_session->parent->server; | ||||
|     if (server == nullptr) { | ||||
|         LOG_WARNING(Service_AM, "File handle ServerSession disconnected!"); | ||||
|         return Kernel::ERR_SESSION_CLOSED_BY_REMOTE; | ||||
|     } | ||||
| 
 | ||||
|     if (server->hle_handler != nullptr) { | ||||
|         auto file = std::dynamic_pointer_cast<Service::FS::File>(server->hle_handler); | ||||
| 
 | ||||
|         // TODO(shinyquagsire23): This requires RTTI, use service calls directly instead?
 | ||||
|         if (file != nullptr) | ||||
|             return MakeResult<std::shared_ptr<Service::FS::File>>(file); | ||||
| 
 | ||||
|         LOG_ERROR(Service_AM, "Failed to cast handle to FSFile!"); | ||||
|         return Kernel::ERR_INVALID_HANDLE; | ||||
|     } | ||||
| 
 | ||||
|     // Probably the best bet if someone is LLEing the fs service is to just have them LLE AM
 | ||||
|     // while they're at it, so not implemented.
 | ||||
|     LOG_ERROR(Service_AM, "Given file handle does not have an HLE handler!"); | ||||
|     return Kernel::ERR_NOT_IMPLEMENTED; | ||||
| } | ||||
| 
 | ||||
| void GetProgramInfoFromCia(Service::Interface* self) { | ||||
|     IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0408, 1, 2); // 0x04080042
 | ||||
|     auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>()); | ||||
| 
 | ||||
|     // Get a File from our Handle
 | ||||
|     auto file_res = GetFileFromHandle(rp.PopHandle()); | ||||
|     if (!file_res.Succeeded()) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(file_res.Code()); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     auto file = file_res.Unwrap(); | ||||
|     FileSys::CIAContainer container; | ||||
|     if (container.Load(*file->backend) != Loader::ResultStatus::Success) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCode(ErrCodes::InvalidCIAHeader, ErrorModule::AM, | ||||
|                            ErrorSummary::InvalidArgument, ErrorLevel::Permanent)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     FileSys::TitleMetadata tmd = container.GetTitleMetadata(); | ||||
|     TitleInfo title_info = {}; | ||||
|     container.Print(); | ||||
| 
 | ||||
|     // TODO(shinyquagsire23): Sizes allegedly depend on the mediatype, and will double
 | ||||
|     // on some mediatypes. Since this is more of a required install size we'll report
 | ||||
|     // what Citra needs, but it would be good to be more accurate here.
 | ||||
|     title_info.tid = tmd.GetTitleID(); | ||||
|     title_info.size = tmd.GetContentSizeByIndex(FileSys::TMDContentIndex::Main); | ||||
|     title_info.version = tmd.GetTitleVersion(); | ||||
|     title_info.type = tmd.GetTitleType(); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(8, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.PushRaw<TitleInfo>(title_info); | ||||
| } | ||||
| 
 | ||||
| void GetSystemMenuDataFromCia(Service::Interface* self) { | ||||
|     IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0409, 0, 4); // 0x04090004
 | ||||
| 
 | ||||
|     // Get a File from our Handle
 | ||||
|     auto file_res = GetFileFromHandle(rp.PopHandle()); | ||||
|     if (!file_res.Succeeded()) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(file_res.Code()); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     size_t output_buffer_size; | ||||
|     IPC::MappedBufferPermissions output_buffer_perms; | ||||
|     VAddr output_buffer = rp.PopMappedBuffer(&output_buffer_size, &output_buffer_perms); | ||||
|     output_buffer_size = std::min(output_buffer_size, sizeof(Loader::SMDH)); | ||||
| 
 | ||||
|     auto file = file_res.Unwrap(); | ||||
|     FileSys::CIAContainer container; | ||||
|     if (container.Load(*file->backend) != Loader::ResultStatus::Success) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCode(ErrCodes::InvalidCIAHeader, ErrorModule::AM, | ||||
|                            ErrorSummary::InvalidArgument, ErrorLevel::Permanent)); | ||||
|         return; | ||||
|     } | ||||
|     std::vector<u8> temp(output_buffer_size); | ||||
| 
 | ||||
|     //  Read from the Meta offset + 0x400 for the 0x36C0-large SMDH
 | ||||
|     auto read_result = | ||||
|         file->backend->Read(container.GetMetadataOffset() + FileSys::CIA_METADATA_SIZE, | ||||
|                             output_buffer_size, temp.data()); | ||||
|     if (read_result.Failed() || *read_result != output_buffer_size) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCode(ErrCodes::InvalidCIAHeader, ErrorModule::AM, | ||||
|                            ErrorSummary::InvalidArgument, ErrorLevel::Permanent)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Memory::WriteBlock(output_buffer, temp.data(), output_buffer_size); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); | ||||
|     rb.PushMappedBuffer(output_buffer, output_buffer_size, output_buffer_perms); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
| } | ||||
| 
 | ||||
| void GetDependencyListFromCia(Service::Interface* self) { | ||||
|     IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x040A, 0, 2); // 0x040A0002
 | ||||
| 
 | ||||
|     // Get a File from our Handle
 | ||||
|     auto file_res = GetFileFromHandle(rp.PopHandle()); | ||||
|     if (!file_res.Succeeded()) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(file_res.Code()); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     size_t output_buffer_size; | ||||
|     VAddr output_buffer = rp.PeekStaticBuffer(0, &output_buffer_size); | ||||
|     output_buffer_size = std::min(output_buffer_size, FileSys::CIA_DEPENDENCY_SIZE); | ||||
| 
 | ||||
|     auto file = file_res.Unwrap(); | ||||
|     FileSys::CIAContainer container; | ||||
|     if (container.Load(*file->backend) != Loader::ResultStatus::Success) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCode(ErrCodes::InvalidCIAHeader, ErrorModule::AM, | ||||
|                            ErrorSummary::InvalidArgument, ErrorLevel::Permanent)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Memory::WriteBlock(output_buffer, container.GetDependencies().data(), output_buffer_size); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.PushStaticBuffer(output_buffer, output_buffer_size, 0); | ||||
| } | ||||
| 
 | ||||
| void GetTransferSizeFromCia(Service::Interface* self) { | ||||
|     IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x040B, 0, 2); // 0x040B0002
 | ||||
| 
 | ||||
|     // Get a File from our Handle
 | ||||
|     auto file_res = GetFileFromHandle(rp.PopHandle()); | ||||
|     if (!file_res.Succeeded()) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(file_res.Code()); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     auto file = file_res.Unwrap(); | ||||
|     FileSys::CIAContainer container; | ||||
|     if (container.Load(*file->backend) != Loader::ResultStatus::Success) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCode(ErrCodes::InvalidCIAHeader, ErrorModule::AM, | ||||
|                            ErrorSummary::InvalidArgument, ErrorLevel::Permanent)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.Push(container.GetMetadataOffset()); | ||||
| } | ||||
| 
 | ||||
| void GetCoreVersionFromCia(Service::Interface* self) { | ||||
|     IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x040C, 0, 2); // 0x040C0002
 | ||||
| 
 | ||||
|     // Get a File from our Handle
 | ||||
|     auto file_res = GetFileFromHandle(rp.PopHandle()); | ||||
|     if (!file_res.Succeeded()) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(file_res.Code()); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     auto file = file_res.Unwrap(); | ||||
|     FileSys::CIAContainer container; | ||||
|     if (container.Load(*file->backend) != Loader::ResultStatus::Success) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCode(ErrCodes::InvalidCIAHeader, ErrorModule::AM, | ||||
|                            ErrorSummary::InvalidArgument, ErrorLevel::Permanent)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.Push(container.GetCoreVersion()); | ||||
| } | ||||
| 
 | ||||
| void GetRequiredSizeFromCia(Service::Interface* self) { | ||||
|     IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x040D, 1, 2); // 0x040D0042
 | ||||
|     auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>()); | ||||
| 
 | ||||
|     // Get a File from our Handle
 | ||||
|     auto file_res = GetFileFromHandle(rp.PopHandle()); | ||||
|     if (!file_res.Succeeded()) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(file_res.Code()); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     auto file = file_res.Unwrap(); | ||||
|     FileSys::CIAContainer container; | ||||
|     if (container.Load(*file->backend) != Loader::ResultStatus::Success) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCode(ErrCodes::InvalidCIAHeader, ErrorModule::AM, | ||||
|                            ErrorSummary::InvalidArgument, ErrorLevel::Permanent)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // TODO(shinyquagsire23): Sizes allegedly depend on the mediatype, and will double
 | ||||
|     // on some mediatypes. Since this is more of a required install size we'll report
 | ||||
|     // what Citra needs, but it would be good to be more accurate here.
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.Push(container.GetTitleMetadata().GetContentSizeByIndex(FileSys::TMDContentIndex::Main)); | ||||
| } | ||||
| 
 | ||||
| void GetMetaSizeFromCia(Service::Interface* self) { | ||||
|     IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0413, 0, 2); // 0x04130002
 | ||||
| 
 | ||||
|     // Get a File from our Handle
 | ||||
|     auto file_res = GetFileFromHandle(rp.PopHandle()); | ||||
|     if (!file_res.Succeeded()) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(file_res.Code()); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     auto file = file_res.Unwrap(); | ||||
|     FileSys::CIAContainer container; | ||||
|     if (container.Load(*file->backend) != Loader::ResultStatus::Success) { | ||||
| 
 | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCode(ErrCodes::InvalidCIAHeader, ErrorModule::AM, | ||||
|                            ErrorSummary::InvalidArgument, ErrorLevel::Permanent)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.Push(container.GetMetadataSize()); | ||||
| } | ||||
| 
 | ||||
| void GetMetaDataFromCia(Service::Interface* self) { | ||||
|     IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0414, 0, 2); // 0x04140044
 | ||||
| 
 | ||||
|     u32 output_size = rp.Pop<u32>(); | ||||
| 
 | ||||
|     // Get a File from our Handle
 | ||||
|     auto file_res = GetFileFromHandle(rp.PopHandle()); | ||||
|     if (!file_res.Succeeded()) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(file_res.Code()); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     size_t output_buffer_size; | ||||
|     VAddr output_buffer = rp.PeekStaticBuffer(0, &output_buffer_size); | ||||
| 
 | ||||
|     // Don't write beyond the actual static buffer size.
 | ||||
|     output_size = std::min(static_cast<u32>(output_buffer_size), output_size); | ||||
| 
 | ||||
|     auto file = file_res.Unwrap(); | ||||
|     FileSys::CIAContainer container; | ||||
|     if (container.Load(*file->backend) != Loader::ResultStatus::Success) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCode(ErrCodes::InvalidCIAHeader, ErrorModule::AM, | ||||
|                            ErrorSummary::InvalidArgument, ErrorLevel::Permanent)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     //  Read from the Meta offset for the specified size
 | ||||
|     std::vector<u8> temp(output_size); | ||||
|     auto read_result = file->backend->Read(container.GetMetadataOffset(), output_size, temp.data()); | ||||
|     if (read_result.Failed() || *read_result != output_size) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ResultCode(ErrCodes::InvalidCIAHeader, ErrorModule::AM, | ||||
|                            ErrorSummary::InvalidArgument, ErrorLevel::Permanent)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Memory::WriteBlock(output_buffer, temp.data(), output_size); | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.PushStaticBuffer(output_buffer, output_buffer_size, 0); | ||||
| } | ||||
| 
 | ||||
| void Init() { | ||||
|  | @ -557,7 +1143,9 @@ void Init() { | |||
|     ScanForAllTitles(); | ||||
| } | ||||
| 
 | ||||
| void Shutdown() {} | ||||
| void Shutdown() { | ||||
|     cia_installing = false; | ||||
| } | ||||
| 
 | ||||
| } // namespace AM
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -21,26 +21,41 @@ namespace AM { | |||
| 
 | ||||
| namespace ErrCodes { | ||||
| enum { | ||||
|     CIACurrentlyInstalling = 4, | ||||
|     EmptyCIA = 32, | ||||
|     InvalidTIDInList = 60, | ||||
|     InvalidCIAHeader = 104, | ||||
| }; | ||||
| } // namespace ErrCodes
 | ||||
| 
 | ||||
| enum class CIAInstallState : u32 { | ||||
|     InstallStarted, | ||||
|     HeaderLoaded, | ||||
|     CertLoaded, | ||||
|     TicketLoaded, | ||||
|     TMDLoaded, | ||||
|     ContentWritten, | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * Get the .tmd path for a title | ||||
|  * @param media_type the media the title exists on | ||||
|  * @param tid the title ID to get | ||||
|  * @param update set true if the incoming TMD should be used instead of the current TMD | ||||
|  * @returns string path to the .tmd file if it exists, otherwise a path to create one is given. | ||||
|  */ | ||||
| std::string GetTitleMetadataPath(Service::FS::MediaType media_type, u64 tid); | ||||
| std::string GetTitleMetadataPath(Service::FS::MediaType media_type, u64 tid, bool update = false); | ||||
| 
 | ||||
| /**
 | ||||
|  * Get the .app path for a title's installed content index. | ||||
|  * @param media_type the media the title exists on | ||||
|  * @param tid the title ID to get | ||||
|  * @param index the content index to get | ||||
|  * @param update set true if the incoming TMD should be used instead of the current TMD | ||||
|  * @returns string path to the .app file | ||||
|  */ | ||||
| std::string GetTitleContentPath(Service::FS::MediaType media_type, u64 tid, u16 index = 0); | ||||
| std::string GetTitleContentPath(Service::FS::MediaType media_type, u64 tid, u16 index = 0, | ||||
|                                 bool update = false); | ||||
| 
 | ||||
| /**
 | ||||
|  * Get the folder for a title's installed content. | ||||
|  | @ -259,6 +274,127 @@ void CheckContentRights(Service::Interface* self); | |||
|  */ | ||||
| void CheckContentRightsIgnorePlatform(Service::Interface* self); | ||||
| 
 | ||||
| /**
 | ||||
|  * AM::BeginImportProgram service function | ||||
|  * Begin importing from a CTR Installable Archive | ||||
|  *  Inputs: | ||||
|  *      0 : Command header (0x04020040) | ||||
|  *      1 : Media type to install title to | ||||
|  *  Outputs: | ||||
|  *      1 : Result, 0 on success, otherwise error code | ||||
|  *      2-3 : CIAFile handle for application to write to | ||||
|  */ | ||||
| void BeginImportProgram(Service::Interface* self); | ||||
| 
 | ||||
| /**
 | ||||
|  * AM::EndImportProgram service function | ||||
|  * Finish importing from a CTR Installable Archive | ||||
|  *  Inputs: | ||||
|  *      0 : Command header (0x04050002) | ||||
|  *      1-2 : CIAFile handle application wrote to | ||||
|  *  Outputs: | ||||
|  *      1 : Result, 0 on success, otherwise error code | ||||
|  */ | ||||
| void EndImportProgram(Service::Interface* self); | ||||
| 
 | ||||
| /**
 | ||||
|  * AM::GetProgramInfoFromCia service function | ||||
|  * Get TitleInfo from a CIA file handle | ||||
|  *  Inputs: | ||||
|  *      0 : Command header (0x04080042) | ||||
|  *      1 : Media type of the title | ||||
|  *      2-3 : File handle CIA data can be read from | ||||
|  *  Outputs: | ||||
|  *      1 : Result, 0 on success, otherwise error code | ||||
|  *      2-8: TitleInfo structure | ||||
|  */ | ||||
| void GetProgramInfoFromCia(Service::Interface* self); | ||||
| 
 | ||||
| /**
 | ||||
|  * AM::GetSystemMenuDataFromCia service function | ||||
|  * Loads a CIA file's SMDH data into a specified buffer | ||||
|  *  Inputs: | ||||
|  *      0 : Command header (0x04090004) | ||||
|  *      1-2 : File handle CIA data can be read from | ||||
|  *      3-4 : Output buffer | ||||
|  *  Outputs: | ||||
|  *      1 : Result, 0 on success, otherwise error code | ||||
|  */ | ||||
| void GetSystemMenuDataFromCia(Service::Interface* self); | ||||
| 
 | ||||
| /**
 | ||||
|  * AM::GetDependencyListFromCia service function | ||||
|  * Loads a CIA's dependency list into a specified buffer | ||||
|  *  Inputs: | ||||
|  *      0 : Command header (0x040A0002) | ||||
|  *      1-2 : File handle CIA data can be read from | ||||
|  *      64-65 : Output buffer | ||||
|  *  Outputs: | ||||
|  *      1 : Result, 0 on success, otherwise error code | ||||
|  */ | ||||
| void GetDependencyListFromCia(Service::Interface* self); | ||||
| 
 | ||||
| /**
 | ||||
|  * AM::GetTransferSizeFromCia service function | ||||
|  * Returns the total expected transfer size up to the CIA meta offset from a CIA | ||||
|  *  Inputs: | ||||
|  *      0 : Command header (0x040B0002) | ||||
|  *      1-2 : File handle CIA data can be read from | ||||
|  *  Outputs: | ||||
|  *      1 : Result, 0 on success, otherwise error code | ||||
|  *      2-3 : Transfer size | ||||
|  */ | ||||
| void GetTransferSizeFromCia(Service::Interface* self); | ||||
| 
 | ||||
| /**
 | ||||
|  * AM::GetCoreVersionFromCia service function | ||||
|  * Returns the core version from a CIA | ||||
|  *  Inputs: | ||||
|  *      0 : Command header (0x040C0002) | ||||
|  *      1-2 : File handle CIA data can be read from | ||||
|  *  Outputs: | ||||
|  *      1 : Result, 0 on success, otherwise error code | ||||
|  *      2 : Core version | ||||
|  */ | ||||
| void GetCoreVersionFromCia(Service::Interface* self); | ||||
| 
 | ||||
| /**
 | ||||
|  * AM::GetRequiredSizeFromCia service function | ||||
|  * Returns the required amount of free space required to install a given CIA file | ||||
|  *  Inputs: | ||||
|  *      0 : Command header (0x040D0042) | ||||
|  *      1 : Media type to install title to | ||||
|  *      2-3 : File handle CIA data can be read from | ||||
|  *  Outputs: | ||||
|  *      1 : Result, 0 on success, otherwise error code | ||||
|  *      2-3 : Required free space for CIA | ||||
|  */ | ||||
| void GetRequiredSizeFromCia(Service::Interface* self); | ||||
| 
 | ||||
| /**
 | ||||
|  * AM::GetMetaSizeFromCia service function | ||||
|  * Returns the size of a given CIA's meta section | ||||
|  *  Inputs: | ||||
|  *      0 : Command header (0x04130002) | ||||
|  *      1-2 : File handle CIA data can be read from | ||||
|  *  Outputs: | ||||
|  *      1 : Result, 0 on success, otherwise error code | ||||
|  *      2 : Meta section size | ||||
|  */ | ||||
| void GetMetaSizeFromCia(Service::Interface* self); | ||||
| 
 | ||||
| /**
 | ||||
|  * AM::GetMetaDataFromCia service function | ||||
|  * Loads meta section data from a CIA file into a given buffer | ||||
|  *  Inputs: | ||||
|  *      0 : Command header (0x04140044) | ||||
|  *      1-2 : File handle CIA data can be read from | ||||
|  *      3-4 : Output buffer | ||||
|  *  Outputs: | ||||
|  *      1 : Result, 0 on success, otherwise error code | ||||
|  */ | ||||
| void GetMetaDataFromCia(Service::Interface* self); | ||||
| 
 | ||||
| /// Initialize AM service
 | ||||
| void Init(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -55,25 +55,25 @@ const Interface::FunctionInfo FunctionTable[] = { | |||
|     {0x002C0084, nullptr, "GetProgramInfosIgnorePlatform"}, | ||||
|     {0x002D00C0, nullptr, "CheckContentRightsIgnorePlatform"}, | ||||
|     {0x04010080, nullptr, "UpdateFirmwareTo"}, | ||||
|     {0x04020040, nullptr, "BeginImportProgram"}, | ||||
|     {0x04020040, BeginImportProgram, "BeginImportProgram"}, | ||||
|     {0x04030000, nullptr, "BeginImportProgramTemporarily"}, | ||||
|     {0x04040002, nullptr, "CancelImportProgram"}, | ||||
|     {0x04050002, nullptr, "EndImportProgram"}, | ||||
|     {0x04050002, EndImportProgram, "EndImportProgram"}, | ||||
|     {0x04060002, nullptr, "EndImportProgramWithoutCommit"}, | ||||
|     {0x040700C2, nullptr, "CommitImportPrograms"}, | ||||
|     {0x04080042, nullptr, "GetProgramInfoFromCia"}, | ||||
|     {0x04090004, nullptr, "GetSystemMenuDataFromCia"}, | ||||
|     {0x040A0002, nullptr, "GetDependencyListFromCia"}, | ||||
|     {0x040B0002, nullptr, "GetTransferSizeFromCia"}, | ||||
|     {0x040C0002, nullptr, "GetCoreVersionFromCia"}, | ||||
|     {0x040D0042, nullptr, "GetRequiredSizeFromCia"}, | ||||
|     {0x04080042, GetProgramInfoFromCia, "GetProgramInfoFromCia"}, | ||||
|     {0x04090004, GetSystemMenuDataFromCia, "GetSystemMenuDataFromCia"}, | ||||
|     {0x040A0002, GetDependencyListFromCia, "GetDependencyListFromCia"}, | ||||
|     {0x040B0002, GetTransferSizeFromCia, "GetTransferSizeFromCia"}, | ||||
|     {0x040C0002, GetCoreVersionFromCia, "GetCoreVersionFromCia"}, | ||||
|     {0x040D0042, GetRequiredSizeFromCia, "GetRequiredSizeFromCia"}, | ||||
|     {0x040E00C2, nullptr, "CommitImportProgramsAndUpdateFirmwareAuto"}, | ||||
|     {0x040F0000, nullptr, "UpdateFirmwareAuto"}, | ||||
|     {0x041000C0, nullptr, "DeleteProgram"}, | ||||
|     {0x04110044, nullptr, "GetTwlProgramListForReboot"}, | ||||
|     {0x04120000, nullptr, "GetSystemUpdaterMutex"}, | ||||
|     {0x04130002, nullptr, "GetMetaSizeFromCia"}, | ||||
|     {0x04140044, nullptr, "GetMetaDataFromCia"}, | ||||
|     {0x04130002, GetMetaSizeFromCia, "GetMetaSizeFromCia"}, | ||||
|     {0x04140044, GetMetaDataFromCia, "GetMetaDataFromCia"}, | ||||
|     {0x04150080, nullptr, "CheckDemoLaunchRights"}, | ||||
|     {0x041600C0, nullptr, "GetInternalTitleLocationInfo"}, | ||||
|     {0x041700C0, nullptr, "PerpetuateAgbSaveData"}, | ||||
|  |  | |||
|  | @ -55,25 +55,25 @@ const Interface::FunctionInfo FunctionTable[] = { | |||
|     {0x002C0084, nullptr, "GetProgramInfosIgnorePlatform"}, | ||||
|     {0x002D00C0, nullptr, "CheckContentRightsIgnorePlatform"}, | ||||
|     {0x04010080, nullptr, "UpdateFirmwareTo"}, | ||||
|     {0x04020040, nullptr, "BeginImportProgram"}, | ||||
|     {0x04020040, BeginImportProgram, "BeginImportProgram"}, | ||||
|     {0x04030000, nullptr, "BeginImportProgramTemporarily"}, | ||||
|     {0x04040002, nullptr, "CancelImportProgram"}, | ||||
|     {0x04050002, nullptr, "EndImportProgram"}, | ||||
|     {0x04050002, EndImportProgram, "EndImportProgram"}, | ||||
|     {0x04060002, nullptr, "EndImportProgramWithoutCommit"}, | ||||
|     {0x040700C2, nullptr, "CommitImportPrograms"}, | ||||
|     {0x04080042, nullptr, "GetProgramInfoFromCia"}, | ||||
|     {0x04090004, nullptr, "GetSystemMenuDataFromCia"}, | ||||
|     {0x040A0002, nullptr, "GetDependencyListFromCia"}, | ||||
|     {0x040B0002, nullptr, "GetTransferSizeFromCia"}, | ||||
|     {0x040C0002, nullptr, "GetCoreVersionFromCia"}, | ||||
|     {0x040D0042, nullptr, "GetRequiredSizeFromCia"}, | ||||
|     {0x04080042, GetProgramInfoFromCia, "GetProgramInfoFromCia"}, | ||||
|     {0x04090004, GetSystemMenuDataFromCia, "GetSystemMenuDataFromCia"}, | ||||
|     {0x040A0002, GetDependencyListFromCia, "GetDependencyListFromCia"}, | ||||
|     {0x040B0002, GetTransferSizeFromCia, "GetTransferSizeFromCia"}, | ||||
|     {0x040C0002, GetCoreVersionFromCia, "GetCoreVersionFromCia"}, | ||||
|     {0x040D0042, GetRequiredSizeFromCia, "GetRequiredSizeFromCia"}, | ||||
|     {0x040E00C2, nullptr, "CommitImportProgramsAndUpdateFirmwareAuto"}, | ||||
|     {0x040F0000, nullptr, "UpdateFirmwareAuto"}, | ||||
|     {0x041000C0, nullptr, "DeleteProgram"}, | ||||
|     {0x04110044, nullptr, "GetTwlProgramListForReboot"}, | ||||
|     {0x04120000, nullptr, "GetSystemUpdaterMutex"}, | ||||
|     {0x04130002, nullptr, "GetMetaSizeFromCia"}, | ||||
|     {0x04140044, nullptr, "GetMetaDataFromCia"}, | ||||
|     {0x04130002, GetMetaSizeFromCia, "GetMetaSizeFromCia"}, | ||||
|     {0x04140044, GetMetaDataFromCia, "GetMetaDataFromCia"}, | ||||
|     {0x04150080, nullptr, "CheckDemoLaunchRights"}, | ||||
|     {0x041600C0, nullptr, "GetInternalTitleLocationInfo"}, | ||||
|     {0x041700C0, nullptr, "PerpetuateAgbSaveData"}, | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue