From a50d6a26969bd001f670cfe7bf19b6e16468887a Mon Sep 17 00:00:00 2001 From: Adubbz Date: Sat, 4 Apr 2020 16:40:46 +1100 Subject: [PATCH] NCM client implementation (#858) * ncm: Implement InstallTaskDataBase and FileInstallTaskData * ncm: minor bugfixes * ncm: Implemented MemoryInstallTaskData * ncm: more std * ncm: begin implementing install task base * ncm: move protected funcs * ncm: fix recursive include * ncm: more install task progress * ncm install task: implement IncrementProgress and update UpdateThroughputMeasurement * ncm: more work * ncm client: more progress * ncm client: more progress * ncm client: finish implementing GetContentMetaInfoList * ncm client: more progress * ncm client: finished InstallTaskBase * ncm client: implement PackageInstallTaskBase * ncm client: fixes * ncm: improve accuracy * ncm client: implement PackageInstallTask * ncm client: implement PackageSystemUpdateTask * ncm client: minor name tweaks * ncm client: implement SubmissionPackageInstallTask * ncm client: add missing this to SubmissionPackageInstallTask * ncm client: add missing nullptr check to SubmissionPackageInstallTask destructor * ncm client: SubmissionPackageInstallTask fixes * ncm: fix forward declarations * ncm client: added simplified funcs * ncm: cleanup client code * ncm: fix bug introduced by cleanup * ncm: fix typo * ncm: implement correct ReadVariationContentMetaInfoList behavior * ncm: correct InstallContentMetaWriter ctor * ncm: correct conversion of content meta header types Co-authored-by: Michael Scire --- .../include/stratosphere/fs/fs_rights_id.hpp | 18 +- .../stratosphere/kvdb/kvdb_auto_buffer.hpp | 2 +- .../include/stratosphere/ncm.hpp | 12 + .../stratosphere/ncm/ncm_auto_buffer.hpp | 2 +- .../stratosphere/ncm/ncm_content_id.hpp | 8 +- .../stratosphere/ncm/ncm_content_id_utils.hpp | 4 + .../ncm/ncm_content_info_data.hpp | 77 + .../ncm/ncm_content_info_utils.hpp | 31 + .../stratosphere/ncm/ncm_content_meta.hpp | 68 +- .../ncm/ncm_content_meta_extended_data.hpp | 384 +++++ .../stratosphere/ncm/ncm_content_meta_key.hpp | 6 +- .../ncm/ncm_content_meta_utils.hpp | 3 + .../ncm/ncm_firmware_variation.hpp | 40 + .../stratosphere/ncm/ncm_install_progress.hpp | 45 + .../ncm/ncm_install_task_base.hpp | 203 +++ .../ncm/ncm_install_task_data.hpp | 149 ++ .../ncm/ncm_install_task_occupied_size.hpp | 30 + .../stratosphere/ncm/ncm_max_count.hpp | 26 + .../ncm/ncm_package_install_task.hpp | 33 + .../ncm/ncm_package_install_task_base.hpp | 47 + .../ncm/ncm_package_system_update_task.hpp | 44 + .../stratosphere/ncm/ncm_placeholder_id.hpp | 8 +- .../stratosphere/ncm/ncm_rights_id.hpp | 11 + .../stratosphere/ncm/ncm_storage_id.hpp | 5 + .../stratosphere/ncm/ncm_storage_utils.hpp | 76 + .../ncm_submission_package_install_task.hpp | 34 + .../ncm/ncm_system_update_task_apply_info.hpp | 27 + .../nim/nim_network_install_manager_api.hpp | 2 +- .../stratosphere/os/os_managed_handle.hpp | 4 +- .../stratosphere/sm/sm_scoped_holder.hpp | 4 +- .../source/lr/lr_location_redirector.cpp | 2 +- .../source/lr/lr_registered_data.hpp | 14 +- .../source/ncm/ncm_content_id_utils.cpp | 20 +- .../source/ncm/ncm_content_info_utils.cpp | 89 ++ .../source/ncm/ncm_content_manager_impl.cpp | 13 +- .../source/ncm/ncm_content_meta.cpp | 242 ++- .../ncm/ncm_content_meta_database_impl.cpp | 2 +- .../ncm/ncm_content_meta_database_impl.hpp | 2 +- .../source/ncm/ncm_content_meta_utils.cpp | 79 + .../source/ncm/ncm_install_task_base.cpp | 1397 +++++++++++++++++ .../source/ncm/ncm_install_task_data.cpp | 397 +++++ .../source/ncm/ncm_package_install_task.cpp | 62 + .../ncm/ncm_package_install_task_base.cpp | 129 ++ .../ncm/ncm_package_system_update_task.cpp | 151 ++ .../source/ncm/ncm_storage_utils.cpp | 127 ++ .../ncm_submission_package_install_task.cpp | 73 + .../nim/nim_network_install_manager_api.cpp | 2 +- .../include/vapours/results/ncm_results.hpp | 15 +- 48 files changed, 4163 insertions(+), 56 deletions(-) create mode 100644 libraries/libstratosphere/include/stratosphere/ncm/ncm_content_info_utils.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_extended_data.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/ncm/ncm_firmware_variation.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/ncm/ncm_install_progress.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_base.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_data.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_occupied_size.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/ncm/ncm_max_count.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/ncm/ncm_package_install_task.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/ncm/ncm_package_install_task_base.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/ncm/ncm_package_system_update_task.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/ncm/ncm_storage_utils.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/ncm/ncm_submission_package_install_task.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/ncm/ncm_system_update_task_apply_info.hpp create mode 100644 libraries/libstratosphere/source/ncm/ncm_content_info_utils.cpp create mode 100644 libraries/libstratosphere/source/ncm/ncm_install_task_base.cpp create mode 100644 libraries/libstratosphere/source/ncm/ncm_install_task_data.cpp create mode 100644 libraries/libstratosphere/source/ncm/ncm_package_install_task.cpp create mode 100644 libraries/libstratosphere/source/ncm/ncm_package_install_task_base.cpp create mode 100644 libraries/libstratosphere/source/ncm/ncm_package_system_update_task.cpp create mode 100644 libraries/libstratosphere/source/ncm/ncm_storage_utils.cpp create mode 100644 libraries/libstratosphere/source/ncm/ncm_submission_package_install_task.cpp diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_rights_id.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_rights_id.hpp index 6c81d83bb..e13bca7bf 100644 --- a/libraries/libstratosphere/include/stratosphere/fs/fs_rights_id.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_rights_id.hpp @@ -19,12 +19,26 @@ namespace ams::fs { union RightsId { - u8 data[0x10]; - u64 data64[2]; + u8 data[0x10]; + u64 data64[2]; }; static_assert(sizeof(RightsId) == 0x10); static_assert(std::is_pod::value); + inline bool operator==(const RightsId &lhs, const RightsId &rhs) { + return std::memcmp(std::addressof(lhs), std::addressof(rhs), sizeof(RightsId)) == 0; + } + + inline bool operator!=(const RightsId &lhs, const RightsId &rhs) { + return !(lhs == rhs); + } + + inline bool operator<(const RightsId &lhs, const RightsId &rhs) { + return std::memcmp(std::addressof(lhs), std::addressof(rhs), sizeof(RightsId)) < 0; + } + + constexpr inline RightsId InvalidRightsId = {}; + /* Rights ID API */ Result GetRightsId(RightsId *out, const char *path); Result GetRightsId(RightsId *out, u8 *out_key_generation, const char *path); diff --git a/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_auto_buffer.hpp b/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_auto_buffer.hpp index f2fdee47a..261fbe28d 100644 --- a/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_auto_buffer.hpp +++ b/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_auto_buffer.hpp @@ -37,7 +37,7 @@ namespace ams::kvdb { rhs.size = 0; } - AutoBuffer& operator=(AutoBuffer &&rhs) { + AutoBuffer &operator=(AutoBuffer &&rhs) { AutoBuffer(std::move(rhs)).Swap(*this); return *this; } diff --git a/libraries/libstratosphere/include/stratosphere/ncm.hpp b/libraries/libstratosphere/include/stratosphere/ncm.hpp index cffff25a4..a43a872fc 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm.hpp @@ -17,13 +17,25 @@ #pragma once #include +#include #include #include #include #include +#include #include +#include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_auto_buffer.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_auto_buffer.hpp index 4b71a3479..095343b5e 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_auto_buffer.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_auto_buffer.hpp @@ -37,7 +37,7 @@ namespace ams::ncm { rhs.size = 0; } - AutoBuffer& operator=(AutoBuffer &&rhs) { + AutoBuffer &operator=(AutoBuffer &&rhs) { AutoBuffer(std::move(rhs)).Swap(*this); return *this; } diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_id.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_id.hpp index 2c727c843..be0ef1e65 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_id.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_id.hpp @@ -21,19 +21,19 @@ namespace ams::ncm { struct alignas(4) ContentId { util::Uuid uuid; - bool operator==(const ContentId& other) const { + bool operator==(const ContentId &other) const { return this->uuid == other.uuid; } - bool operator!=(const ContentId& other) const { + bool operator!=(const ContentId &other) const { return this->uuid != other.uuid; } - bool operator==(const util::Uuid& other) const { + bool operator==(const util::Uuid &other) const { return this->uuid == other; } - bool operator!=(const util::Uuid& other) const { + bool operator!=(const util::Uuid &other) const { return this->uuid != other; } }; diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_id_utils.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_id_utils.hpp index 95bff84ce..32d2e9bf9 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_id_utils.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_id_utils.hpp @@ -32,6 +32,10 @@ namespace ams::ncm { ContentIdString GetContentIdString(ContentId id); void GetStringFromContentId(char *dst, size_t dst_size, ContentId id); + void GetStringFromRightsId(char *dst, size_t dst_size, fs::RightsId id); + + void GetTicketFileStringFromRightsId(char *dst, size_t dst_size, fs::RightsId id); + void GetCertificateFileStringFromRightsId(char *dst, size_t dst_size, fs::RightsId id); std::optional GetContentIdFromString(const char *str, size_t len); diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_info_data.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_info_data.hpp index c29a7de62..4fea848f5 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_info_data.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_info_data.hpp @@ -25,6 +25,13 @@ namespace ams::ncm { u8 data[crypto::Sha256Generator::HashSize]; }; + enum class InstallState : u8 { + NotPrepared, + Prepared, + Installed, + AlreadyExists, + }; + struct PackagedContentInfo { Digest digest; ContentInfo info; @@ -42,4 +49,74 @@ namespace ams::ncm { } }; + struct InstallContentInfo { + Digest digest; + crypto::Sha256Context context; + u8 buffered_data[crypto::Sha256Generator::BlockSize]; + u64 buffered_data_size; + ContentInfo info; + PlaceHolderId placeholder_id; + ContentMetaType meta_type; + InstallState install_state; + bool verify_digest; + StorageId storage_id; + bool is_temporary; + bool is_sha256_calculated; + s64 written; + + constexpr const ContentId &GetId() const { + return this->info.GetId(); + } + + constexpr const u64 GetSize() const { + return this->info.GetSize(); + } + + constexpr const ContentType GetType() const { + return this->info.GetType(); + } + + constexpr const u8 GetIdOffset() const { + return this->info.GetIdOffset(); + } + + constexpr const PlaceHolderId &GetPlaceHolderId() const { + return this->placeholder_id; + } + + constexpr const ContentMetaType GetContentMetaType() const { + return this->meta_type; + } + + constexpr const InstallState GetInstallState() const { + return this->install_state; + } + + constexpr const StorageId GetStorageId() const { + return this->storage_id; + } + + constexpr s64 GetSizeWritten() const { + return this->written; + } + + static constexpr InstallContentInfo Make(const ContentInfo &info, ContentMetaType meta_type) { + return { + .info = info, + .meta_type = meta_type, + }; + } + + static constexpr InstallContentInfo Make(const PackagedContentInfo &info, ContentMetaType meta_type) { + return { + .digest = info.digest, + .info = info.info, + .meta_type = meta_type, + .verify_digest = true, + }; + } + }; + + static_assert(sizeof(InstallContentInfo) == 0xC8); + } diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_info_utils.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_info_utils.hpp new file mode 100644 index 000000000..2f5bb3594 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_info_utils.hpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019-2020 Adubbz, Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +namespace ams::ncm { + + constexpr inline s64 MaxClusterSize = 256_KB; + + s64 CalculateRequiredSize(s64 file_size, s64 cluster_size = MaxClusterSize); + s64 CalculateRequiredSizeForExtension(s64 file_size, s64 cluster_size = MaxClusterSize); + + class ContentMetaDatabase; + + Result EstimateRequiredSize(s64 *out_size, const ContentMetaKey &key, ContentMetaDatabase *db); + +} diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta.hpp index cbb4ab5d6..3f015e80c 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #include namespace ams::ncm { @@ -72,15 +73,16 @@ namespace ams::ncm { u8 attributes; u8 storage_id; ContentInstallType install_type; - u8 reserved_17; + bool committed; u32 required_download_system_version; u8 reserved_1C[4]; }; static_assert(sizeof(PackagedContentMetaHeader) == 0x20); static_assert(OFFSETOF(PackagedContentMetaHeader, reserved_0D) == 0x0D); - static_assert(OFFSETOF(PackagedContentMetaHeader, reserved_17) == 0x17); static_assert(OFFSETOF(PackagedContentMetaHeader, reserved_1C) == 0x1C); + using InstallContentMetaHeader = PackagedContentMetaHeader; + struct ApplicationMetaExtendedHeader { PatchId patch_id; u32 required_system_version; @@ -106,6 +108,10 @@ namespace ams::ncm { u32 padding; }; + struct SystemUpdateMetaExtendedHeader { + u32 extended_data_size; + }; + template class ContentMetaAccessor { public: @@ -194,6 +200,18 @@ namespace ams::ncm { return nullptr; } + s64 CalculateContentRequiredSize() const { + s64 required_size = 0; + for (size_t i = 0; i < this->GetContentCount(); i++) { + required_size += CalculateRequiredSize(this->GetContentInfo(i)->info.GetSize()); + } + return required_size; + } + + void SetStorageId(StorageId storage_id) { + this->GetWritableHeader()->storage_id = static_cast(storage_id); + } + public: const void *GetData() const { return this->data; @@ -203,6 +221,11 @@ namespace ams::ncm { return this->size; } + HeaderType *GetWritableHeader() const { + AMS_ABORT_UNLESS(this->is_header_valid); + return reinterpret_cast(this->data); + } + const HeaderType *GetHeader() const { AMS_ABORT_UNLESS(this->is_header_valid); return static_cast(this->data); @@ -252,9 +275,10 @@ namespace ams::ncm { size_t GetExtendedDataSize() const { switch (this->GetHeader()->type) { - case ContentMetaType::Patch: return this->GetExtendedHeader()->extended_data_size; - case ContentMetaType::Delta: return this->GetExtendedHeader()->extended_data_size; - default: return 0; + case ContentMetaType::Patch: return this->GetExtendedHeader()->extended_data_size; + case ContentMetaType::Delta: return this->GetExtendedHeader()->extended_data_size; + case ContentMetaType::SystemUpdate: return this->GetExtendedHeaderSize() == 0 ? 0 : this->GetExtendedHeader()->extended_data_size; + default: return 0; } } @@ -275,6 +299,10 @@ namespace ams::ncm { return false; } + StorageId GetStorageId() const { + return static_cast(this->GetHeader()->storage_id); + } + std::optional GetApplicationId(const ContentMetaKey &key) const { switch (key.type) { case ContentMetaType::Application: return ApplicationId{ key.id }; @@ -301,10 +329,15 @@ namespace ams::ncm { public: constexpr PackagedContentMetaReader(const void *data, size_t size) : ContentMetaAccessor(data, size) { /* ... */ } - size_t CalculateConvertContentMetaSize() const; + size_t CalculateConvertInstallContentMetaSize() const; + void ConvertToInstallContentMeta(void *dst, size_t size, const InstallContentInfo &meta); + size_t CalculateConvertContentMetaSize() const; void ConvertToContentMeta(void *dst, size_t size, const ContentInfo &meta); + Result CalculateConvertFragmentOnlyInstallContentMetaSize(size_t *out_size, u32 source_version) const; + Result ConvertToFragmentOnlyInstallContentMeta(void *dst, size_t size, const InstallContentInfo &content_info, u32 source_version); + size_t CountDeltaFragments() const; static constexpr size_t CalculateSize(ContentMetaType type, size_t content_count, size_t content_meta_count, size_t extended_data_size) { @@ -312,4 +345,27 @@ namespace ams::ncm { } }; + class InstallContentMetaReader : public ContentMetaAccessor { + public: + constexpr InstallContentMetaReader(const void *data, size_t size) : ContentMetaAccessor(data, size) { /* ... */ } + + using ContentMetaAccessor::CalculateSize; + using ContentMetaAccessor::CalculateContentRequiredSize; + using ContentMetaAccessor::GetStorageId; + + size_t CalculateConvertSize() const; + + void ConvertToContentMeta(void *dst, size_t size) const; + }; + + class InstallContentMetaWriter : public ContentMetaAccessor { + public: + InstallContentMetaWriter(const void *data, size_t size) : ContentMetaAccessor(data, size) { /* ... */ } + + using ContentMetaAccessor::CalculateSize; + using ContentMetaAccessor::CalculateContentRequiredSize; + using ContentMetaAccessor::GetWritableContentInfo; + using ContentMetaAccessor::SetStorageId; + }; + } diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_extended_data.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_extended_data.hpp new file mode 100644 index 000000000..2f997f174 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_extended_data.hpp @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include +#include + +namespace ams::ncm { + + enum class UpdateType : u8 { + ApplyAsDelta = 0, + Overwrite = 1, + Create = 2, + }; + + struct FragmentIndicator { + u16 content_info_index; + u16 fragment_index; + }; + + struct FragmentSet { + ContentId source_content_id; + ContentId destination_content_id; + u32 source_size_low; + u16 source_size_high; + u16 destination_size_high; + u32 destination_size_low; + u16 fragment_count; + ContentType target_content_type; + UpdateType update_type; + u8 reserved[4]; + + constexpr s64 GetSourceSize() const { + return (static_cast(this->source_size_high) << 32) + this->source_size_low; + } + + constexpr s64 GetDestinationSize() const { + return (static_cast(this->destination_size_high) << 32) + this->destination_size_low; + } + + constexpr void SetSourceSize(s64 size) { + this->source_size_low = size & 0xFFFFFFFFll; + this->source_size_high = static_cast(size >> 32); + } + + constexpr void SetDestinationSize(s64 size) { + this->destination_size_low = size & 0xFFFFFFFFll; + this->destination_size_high = static_cast(size >> 32); + } + }; + + struct SystemUpdateMetaExtendedDataHeader { + u32 unk; // Always seems to be set to 2 + u32 firmware_variation_count; + }; + + struct DeltaMetaExtendedDataHeader { + PatchId source_id; + PatchId destination_id; + u32 source_version; + u32 destination_version; + u16 fragment_set_count; + u8 reserved[6]; + }; + + struct PatchMetaExtendedDataHeader { + u32 history_count; + u32 delta_history_count; + u32 delta_count; + u32 fragment_set_count; + u32 history_content_total_count; + u32 delta_content_total_count; + u8 reserved[4]; + }; + + struct PatchHistoryHeader { + ContentMetaKey key; + Digest digest; + u16 content_count; + u8 reserved[2]; + }; + + struct PatchDeltaHistory { + PatchId source_id; + PatchId destination_id; + u32 source_version; + u32 destination_version; + u64 download_size; + u8 reserved[4]; + }; + + struct PatchDeltaHeader { + DeltaMetaExtendedDataHeader delta; + u16 content_count; + u8 reserved[4]; + }; + + template + class PatchMetaExtendedDataReaderWriterBase { + private: + MemberTypePointer data; + const size_t size; + public: + PatchMetaExtendedDataReaderWriterBase(MemberTypePointer d, size_t sz) : data(d), size(sz) { /* ... */ } + protected: + s32 CountFragmentSet(s32 delta_index) const { + auto delta_header = this->GetPatchDeltaHeader(0); + s32 count = 0; + for (s32 i = 0; i < delta_index; i++) { + count += delta_header[i].delta.fragment_set_count; + } + return count; + } + + s32 CountHistoryContent(s32 history_index) const { + auto history_header = this->GetPatchHistoryHeader(0); + s32 count = 0; + for (s32 i = 0; i < history_index; i++) { + count += history_header[i].content_count; + } + return count; + } + + s32 CountDeltaContent(s32 delta_index) const { + auto delta_header = this->GetPatchDeltaHeader(0); + s32 count = 0; + for (s32 i = 0; i < delta_index; i++) { + count += delta_header[i].content_count; + } + return count; + } + + s32 CountFragment(s32 index) const { + auto fragment_set = this->GetFragmentSet(0, 0); + s32 count = 0; + for (s32 i = 0; i < index; i++) { + count += fragment_set[i].fragment_count; + } + return count; + } + + DataTypePointer GetHeaderAddress() const { + return reinterpret_cast(this->data); + } + + DataTypePointer GetPatchHistoryHeaderAddress(s32 index) const { + auto header = this->GetHeader(); + AMS_ABORT_UNLESS(static_cast(index) < header->history_count); + + return this->GetHeaderAddress() + + sizeof(PatchMetaExtendedDataHeader) + + sizeof(PatchHistoryHeader) * index; + } + + DataTypePointer GetPatchDeltaHistoryAddress(s32 index) const { + auto header = this->GetHeader(); + AMS_ABORT_UNLESS(static_cast(index) < header->delta_history_count); + + return this->GetHeaderAddress() + + sizeof(PatchMetaExtendedDataHeader) + + sizeof(PatchHistoryHeader) * header->history_count + + sizeof(PatchDeltaHistory) * index; + } + + DataTypePointer GetPatchDeltaHeaderAddress(s32 index) const { + auto header = this->GetHeader(); + AMS_ABORT_UNLESS(static_cast(index) < header->delta_count); + + return this->GetHeaderAddress() + + sizeof(PatchMetaExtendedDataHeader) + + sizeof(PatchHistoryHeader) * header->history_count + + sizeof(PatchDeltaHistory) * header->delta_history_count + + sizeof(PatchDeltaHeader) * index; + } + + DataTypePointer GetFragmentSetAddress(s32 delta_index, s32 fragment_set_index) const { + auto header = this->GetHeader(); + AMS_ABORT_UNLESS(static_cast(delta_index) < header->delta_count); + + auto delta_header = this->GetPatchDeltaHeader(delta_index); + AMS_ABORT_UNLESS(static_cast(fragment_set_index) < delta_header->delta.fragment_set_count); + + auto previous_fragment_set_count = this->CountFragmentSet(delta_index); + + return this->GetHeaderAddress() + + sizeof(PatchMetaExtendedDataHeader) + + sizeof(PatchHistoryHeader) * header->history_count + + sizeof(PatchDeltaHistory) * header->delta_history_count + + sizeof(PatchDeltaHeader) * header->delta_count + + sizeof(FragmentSet) * (previous_fragment_set_count + fragment_set_index); + } + + DataTypePointer GetPatchHistoryContentInfoAddress(s32 history_index, s32 content_index) const { + auto header = this->GetHeader(); + auto history_header = this->GetPatchHistoryHeader(history_index); + AMS_ABORT_UNLESS(static_cast(content_index) < history_header->content_count); + + auto prev_history_count = this->CountHistoryContent(history_index); + + return this->GetHeaderAddress() + + sizeof(PatchMetaExtendedDataHeader) + + sizeof(PatchHistoryHeader) * header->history_count + + sizeof(PatchDeltaHistory) * header->delta_history_count + + sizeof(PatchDeltaHeader) * header->delta_count + + sizeof(FragmentSet) * header->fragment_set_count + + sizeof(ContentInfo) * (prev_history_count + content_index); + } + + DataTypePointer GetPatchDeltaPackagedContentInfoAddress(s32 delta_index, s32 content_index) const { + auto header = this->GetHeader(); + auto delta_header = this->GetPatchDeltaHeader(delta_index); + AMS_ABORT_UNLESS(static_cast(content_index) < delta_header->content_count); + + auto content_count = this->CountDeltaContent(delta_index); + + return this->GetHeaderAddress() + + sizeof(PatchMetaExtendedDataHeader) + + sizeof(PatchHistoryHeader) * header->history_count + + sizeof(PatchDeltaHistory) * header->delta_history_count + + sizeof(PatchDeltaHeader) * header->delta_count + + sizeof(FragmentSet) * header->fragment_set_count + + sizeof(ContentInfo) * header->history_content_total_count + + sizeof(PackagedContentInfo) * (content_count + content_index); + } + + DataTypePointer GetFragmentIndicatorAddress(s32 delta_index, s32 fragment_set_index, s32 index) const { + auto header = this->GetHeader(); + auto fragment_set = this->GetFragmentSet(delta_index, fragment_set_index); + AMS_ABORT_UNLESS(static_cast(index) < fragment_set->fragment_count); + + auto fragment_set_count = this->CountFragmentSet(delta_index); + auto fragment_count = this->CountFragment(fragment_set_count + fragment_set_index); + + return this->GetHeaderAddress() + + sizeof(PatchMetaExtendedDataHeader) + + sizeof(PatchHistoryHeader) * header->history_count + + sizeof(PatchDeltaHistory) * header->delta_history_count + + sizeof(PatchDeltaHeader) * header->delta_count + + sizeof(FragmentSet) * header->fragment_set_count + + sizeof(ContentInfo) * header->history_content_total_count + + sizeof(PackagedContentInfo) * header->delta_content_total_count + + sizeof(FragmentIndicator) * (fragment_count + index); + } + public: + const PatchMetaExtendedDataHeader *GetHeader() const { + return reinterpret_cast(this->GetHeaderAddress()); + } + + const PatchHistoryHeader *GetPatchHistoryHeader(s32 index) const { + return reinterpret_cast(this->GetPatchHistoryHeaderAddress(index)); + } + + const PatchDeltaHistory *GetPatchDeltaHistory(s32 index) const { + return reinterpret_cast(this->GetPatchDeltaHistoryAddress(index)); + } + + const ContentInfo *GetPatchHistoryContentInfo(s32 history_index, s32 content_index) const { + return reinterpret_cast(this->GetPatchHistoryContentInfoAddress(history_index, content_index)); + } + + const PatchDeltaHeader *GetPatchDeltaHeader(s32 index) const { + return reinterpret_cast(this->GetPatchDeltaHeaderAddress(index)); + } + + const PackagedContentInfo *GetPatchDeltaPackagedContentInfo(s32 delta_index, s32 content_index) const { + return reinterpret_cast(this->GetPatchDeltaPackagedContentInfoAddress(delta_index, content_index)); + } + + const FragmentSet *GetFragmentSet(s32 delta_index, s32 fragment_set_index) const { + return reinterpret_cast(this->GetFragmentSetIndex(delta_index, fragment_set_index)); + } + + const FragmentIndicator *GetFragmentIndicator(s32 delta_index, s32 fragment_set_index, s32 index) const { + return reinterpret_cast(this->GetFragmentIndicatorAddress(delta_index, fragment_set_index, index)); + } + + const FragmentIndicator *FindFragmentIndicator(s32 delta_index, s32 fragment_set_index, s32 fragment_index) const { + auto fragment_set = this->GetFragmentSet(delta_index, fragment_set_index); + auto fragment = this->GetFragmentIndicator(delta_index, fragment_set_index, 0); + for (s32 i = 0; i < fragment_set->fragment_count; i++) { + if (fragment[i].fragment_index == fragment_index) { + return std::addressof(fragment[i]); + } + } + return nullptr; + } + }; + + class PatchMetaExtendedDataReader : public PatchMetaExtendedDataReaderWriterBase { + public: + PatchMetaExtendedDataReader(const void *data, size_t size) : PatchMetaExtendedDataReaderWriterBase(data, size) { /* ... */ } + }; + + class SystemUpdateMetaExtendedDataReaderWriterBase { + private: + void *data; + const size_t size; + bool is_header_valid; + protected: + constexpr SystemUpdateMetaExtendedDataReaderWriterBase(const void *d, size_t sz) : data(const_cast(d)), size(sz), is_header_valid(true) { /* ... */ } + constexpr SystemUpdateMetaExtendedDataReaderWriterBase(void *d, size_t sz) : data(d), size(sz), is_header_valid(false) { /* ... */ } + + uintptr_t GetHeaderAddress() const { + return reinterpret_cast(this->data); + } + + uintptr_t GetFirmwarVariationIdStartAddress() const { + return this->GetHeaderAddress() + sizeof(SystemUpdateMetaExtendedDataHeader); + } + + uintptr_t GetFirmwareVariationIdAddress(size_t i) const { + return this->GetFirmwarVariationIdStartAddress() + i * sizeof(FirmwareVariationId); + } + + uintptr_t GetFirmwareVariationInfoStartAddress() const { + return this->GetFirmwareVariationIdAddress(this->GetFirmwareVariationCount()); + } + + uintptr_t GetFirmwareVariationInfoAddress(size_t i) const { + return this->GetFirmwarVariationIdStartAddress() + i * sizeof(FirmwareVariationInfo); + } + + uintptr_t GetContentMetaInfoStartAddress() const { + return this->GetFirmwareVariationInfoAddress(this->GetFirmwareVariationCount()); + } + + uintptr_t GetContentMetaInfoAddress(size_t i) const { + return this->GetContentMetaInfoStartAddress() + i * sizeof(ContentMetaInfo); + } + public: + const SystemUpdateMetaExtendedDataHeader *GetHeader() const { + AMS_ABORT_UNLESS(this->is_header_valid); + return reinterpret_cast(this->GetHeaderAddress()); + } + + size_t GetFirmwareVariationCount() const { + return this->GetHeader()->firmware_variation_count; + } + + const FirmwareVariationId *GetFirmwareVariationId(size_t i) const { + AMS_ABORT_UNLESS(i < this->GetFirmwareVariationCount()); + + return reinterpret_cast(this->GetFirmwareVariationIdAddress(i)); + } + + const FirmwareVariationInfo *GetFirmwareVariationInfo(size_t i) const { + AMS_ABORT_UNLESS(i < this->GetFirmwareVariationCount()); + + return reinterpret_cast(this->GetFirmwareVariationInfoAddress(i)); + } + + void GetContentMetaInfoList(Span *out_list, size_t i) const { + size_t preceding_content_meta_count = 0; + + /* Count the number of preceding content metas. */ + for (size_t j = 0; j < i; j++) { + preceding_content_meta_count += this->GetFirmwareVariationInfo(j)->content_meta_count; + } + + /* Output the list. */ + *out_list = Span(reinterpret_cast(this->GetContentMetaInfoAddress(preceding_content_meta_count)), this->GetFirmwareVariationInfo(i)->content_meta_count); + } + }; + + class SystemUpdateMetaExtendedDataReader : public SystemUpdateMetaExtendedDataReaderWriterBase { + public: + constexpr SystemUpdateMetaExtendedDataReader(const void *data, size_t size) : SystemUpdateMetaExtendedDataReaderWriterBase(data, size) { /* ... */ } + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_key.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_key.hpp index 2b796d5b0..0c8ed99ef 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_key.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_key.hpp @@ -34,15 +34,15 @@ namespace ams::ncm { ContentInstallType install_type; u8 padding[2]; - bool operator<(const ContentMetaKey& rhs) const { + bool operator<(const ContentMetaKey &rhs) const { return std::tie(this->id, this->version, this->type, this->install_type) < std::tie(rhs.id, rhs.version, rhs.type, rhs.install_type); } - constexpr bool operator==(const ContentMetaKey& rhs) const { + constexpr bool operator==(const ContentMetaKey &rhs) const { return std::tie(this->id, this->version, this->type, this->install_type) == std::tie(rhs.id, rhs.version, rhs.type, rhs.install_type); } - constexpr bool operator!=(const ContentMetaKey& rhs) const { + constexpr bool operator!=(const ContentMetaKey &rhs) const { return !(*this == rhs); } diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_utils.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_utils.hpp index 6673e063d..72ffbfe61 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_utils.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_utils.hpp @@ -19,9 +19,12 @@ #include #include #include +#include namespace ams::ncm { Result ReadContentMetaPath(AutoBuffer *out, const char *path); + Result ReadVariationContentMetaInfoList(s32 *out_count, std::unique_ptr *out_meta_infos, const Path &path, FirmwareVariationId firmware_variation_id); + } diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_firmware_variation.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_firmware_variation.hpp new file mode 100644 index 000000000..d89fa1ea5 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_firmware_variation.hpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::ncm { + + struct FirmwareVariationInfo { + bool refer_to_base; + u8 _0x1[3]; + u32 content_meta_count; + u8 reserved[0x18]; + }; + + struct FirmwareVariationId { + u32 value; + }; + + constexpr inline bool operator==(const FirmwareVariationId &lhs, const FirmwareVariationId &rhs) { + return lhs.value == rhs.value; + } + + constexpr inline bool operator!=(const FirmwareVariationId &lhs, const FirmwareVariationId &rhs) { + return lhs.value != rhs.value; + } + +} diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_progress.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_progress.hpp new file mode 100644 index 000000000..ba70fe1fc --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_progress.hpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019-2020 Adubbz, Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +namespace ams::ncm { + + enum class InstallProgressState : u8 { + NotPrepared = 0, + DataPrepared = 1, + Prepared = 2, + Downloaded = 3, + Committed = 4, + Fatal = 5, + }; + + struct InstallProgress { + InstallProgressState state; + u8 pad[3]; + TYPED_STORAGE(Result) last_result; + s64 installed_size; + s64 total_size; + + Result GetLastResult() const { + return util::GetReference(last_result); + } + + void SetLastResult(Result result) { + *util::GetPointer(last_result) = result; + } + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_base.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_base.hpp new file mode 100644 index 000000000..7f93a7dad --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_base.hpp @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2019-2020 Adubbz, Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +namespace ams::ncm { + + enum class ListContentMetaKeyFilter : u8 { + All = 0, + Committed = 1, + NotCommitted = 2, + }; + + enum InstallConfig { + InstallConfig_None = (0 << 0), + InstallConfig_SystemUpdate = (1 << 2), + InstallConfig_RequiresExFatDriver = (1 << 3), + InstallConfig_IgnoreTicket = (1 << 4), + }; + + struct InstallThroughput { + s64 installed; + TimeSpan elapsed_time; + }; + + struct InstallContentMetaInfo { + ContentId content_id; + s64 content_size; + ContentMetaKey key; + bool verify_digest; + bool has_key; + Digest digest; + + static constexpr InstallContentMetaInfo MakeVerifiable(const ContentId &cid, s64 sz, const ContentMetaKey &ky, const Digest &d) { + return { + .content_id = cid, + .content_size = sz, + .key = ky, + .verify_digest = true, + .has_key = true, + .digest = d, + }; + } + + static constexpr InstallContentMetaInfo MakeUnverifiable(const ContentId &cid, s64 sz, const ContentMetaKey &ky) { + return { + .content_id = cid, + .content_size = sz, + .key = ky, + .verify_digest = false, + .has_key = true, + }; + } + + static constexpr InstallContentMetaInfo MakeUnverifiable(const ContentId &cid, s64 sz) { + return { + .content_id = cid, + .content_size = sz, + .verify_digest = false, + .has_key = false, + }; + } + }; + + static_assert(sizeof(InstallContentMetaInfo) == 0x50); + + class InstallTaskBase { + NON_COPYABLE(InstallTaskBase); + NON_MOVEABLE(InstallTaskBase); + private: + crypto::Sha256Generator sha256_generator; + StorageId install_storage; + InstallTaskDataBase *data; + InstallProgress progress; + os::Mutex progress_mutex; + u32 config; + os::Mutex cancel_mutex; + bool cancel_requested; + InstallThroughput throughput; + TimeSpan throughput_start_time; + os::Mutex throughput_mutex; + FirmwareVariationId firmware_variation_id; + private: + ALWAYS_INLINE Result SetLastResultOnFailure(Result result) { + if (R_FAILED(result)) { + this->SetLastResult(result); + } + return result; + } + public: + InstallTaskBase() : data(), progress(), cancel_requested() { /* ... */ } + virtual ~InstallTaskBase() { /* ... */ }; + public: + virtual void Cancel(); + virtual void ResetCancel(); + + Result Prepare(); + Result GetPreparedPlaceHolderPath(Path *out_path, u64 id, ContentMetaType meta_type, ContentType type); + Result CalculateRequiredSize(s64 *out_size); + Result Cleanup(); + Result ListContentMetaKey(s32 *out_keys_written, StorageContentMetaKey *out_keys, s32 out_keys_count, s32 offset, ListContentMetaKeyFilter filter); + Result ListContentMetaKey(s32 *out_keys_written, StorageContentMetaKey *out_keys, s32 out_keys_count, s32 offset) { return this->ListContentMetaKey(out_keys_written, out_keys, out_keys_count, offset, ListContentMetaKeyFilter::All); } + Result ListApplicationContentMetaKey(s32 *out_keys_written, ApplicationContentMetaKey *out_keys, s32 out_keys_count, s32 offset); + Result Execute(); + Result PrepareAndExecute(); + Result Commit(const StorageContentMetaKey *keys, s32 num_keys); + Result Commit() { return this->Commit(nullptr, 0); } + virtual InstallProgress GetProgress(); + void ResetLastResult(); + Result IncludesExFatDriver(bool *out); + InstallThroughput GetThroughput(); + Result CalculateContentsSize(s64 *out_size, const ContentMetaKey &key, StorageId storage_id); + Result ListOccupiedSize(s32 *out_written, InstallTaskOccupiedSize *out_list, s32 out_list_size, s32 offset); + + Result FindMaxRequiredApplicationVersion(u32 *out); + Result FindMaxRequiredSystemVersion(u32 *out); + protected: + Result Initialize(StorageId install_storage, InstallTaskDataBase *data, u32 config); + + Result PrepareContentMeta(const InstallContentMetaInfo &meta_info, std::optional key, std::optional source_version); + Result PrepareContentMeta(ContentId content_id, s64 size, ContentMetaType meta_type, AutoBuffer *buffer); + Result WritePlaceHolderBuffer(InstallContentInfo *content_info, const void *data, size_t data_size); + void PrepareAgain(); + + Result CountInstallContentMetaData(s32 *out_count); + Result GetInstallContentMetaData(InstallContentMeta *out_content_meta, s32 index); + Result DeleteInstallContentMetaData(const ContentMetaKey *keys, s32 num_keys); + + virtual Result PrepareDependency(); + Result PrepareSystemUpdateDependency(); + Result PrepareContentMetaIfLatest(const ContentMetaKey &key); + u32 GetConfig() const { return this->config; } + Result WriteContentMetaToPlaceHolder(InstallContentInfo *out_install_content_info, ContentStorage *storage, const InstallContentMetaInfo &meta_info, std::optional is_temporary); + + StorageId GetInstallStorage() const { return this->install_storage; } + + virtual Result OnPrepareComplete() { return ResultSuccess(); } + + Result GetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo *out); + + Result CanContinue(); + private: + bool IsCancelRequested(); + Result PrepareImpl(); + Result ExecuteImpl(); + Result CommitImpl(const StorageContentMetaKey *keys, s32 num_keys); + Result CleanupOne(const InstallContentMeta &content_meta); + + Result VerifyAllNotCommitted(const StorageContentMetaKey *keys, s32 num_keys); + + virtual Result PrepareInstallContentMetaData() = 0; + virtual Result GetInstallContentMetaInfo(InstallContentMetaInfo *out_info, const ContentMetaKey &key) = 0; + virtual Result GetLatestVersion(std::optional *out_version, u64 id) { return ncm::ResultContentMetaNotFound(); } + + virtual Result OnExecuteComplete() { return ResultSuccess(); } + + Result WritePlaceHolder(const ContentMetaKey &key, InstallContentInfo *content_info); + virtual Result OnWritePlaceHolder(const ContentMetaKey &key, InstallContentInfo *content_info) = 0; + + bool IsNecessaryInstallTicket(const fs::RightsId &rights_id); + virtual Result InstallTicket(const fs::RightsId &rights_id, ContentMetaType meta_type) = 0; + + Result IsNewerThanInstalled(bool *out, const ContentMetaKey &key); + Result PreparePlaceHolder(); + + void SetProgressState(InstallProgressState state); + void IncrementProgress(s64 size); + void SetTotalSize(s64 size); + void SetLastResult(Result last_result); + void CleanupProgress(); + + void ResetThroughputMeasurement(); + void StartThroughputMeasurement(); + void UpdateThroughputMeasurement(s64 throughput); + + Result GetInstallContentMetaDataFromPath(AutoBuffer *out, const Path &path, const InstallContentInfo &content_info, std::optional source_version); + + InstallContentInfo MakeInstallContentInfoFrom(const InstallContentMetaInfo &info, const PlaceHolderId &placeholder_id, std::optional is_temporary); + + Result ReadContentMetaInfoList(s32 *out_count, std::unique_ptr *out_meta_infos, const ContentMetaKey &key); + Result ListRightsIdsByInstallContentMeta(s32 *out_count, Span out_span, const InstallContentMeta &content_meta, s32 offset); + public: + virtual Result CheckInstallable() { return ResultSuccess(); } + + void SetFirmwareVariationId(FirmwareVariationId id) { this->firmware_variation_id = id; } + Result ListRightsIds(s32 *out_count, Span out_span, const ContentMetaKey &key, s32 offset); + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_data.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_data.hpp new file mode 100644 index 000000000..524f93cb8 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_data.hpp @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2019-2020 Adubbz, Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include + +namespace ams::ncm { + + struct InstallContentMeta { + std::unique_ptr data; + size_t size; + + InstallContentMetaReader GetReader() const { + return InstallContentMetaReader(this->data.get(), this->size); + } + + InstallContentMetaWriter GetWriter() const { + return InstallContentMetaWriter(this->data.get(), this->size); + } + }; + + class InstallTaskDataBase { + public: + Result Get(InstallContentMeta *out, s32 index); + Result Update(const InstallContentMeta &content_meta, s32 index); + Result Has(bool *out, u64 id); + public: + virtual Result GetProgress(InstallProgress *out_progress) = 0; + virtual Result GetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo *out_info) = 0; + virtual Result SetState(InstallProgressState state) = 0; + virtual Result SetLastResult(Result result) = 0; + virtual Result SetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo info) = 0; + virtual Result Push(const void *data, size_t data_size) = 0; + virtual Result Count(s32 *out) = 0; + virtual Result Delete(const ContentMetaKey *keys, s32 num_keys) = 0; + virtual Result Cleanup() = 0; + private: + virtual Result GetSize(size_t *out_size, s32 index) = 0; + virtual Result Get(s32 index, void *out, size_t out_size) = 0; + virtual Result Update(s32 index, const void *data, size_t data_size) = 0; + }; + + class MemoryInstallTaskData : public InstallTaskDataBase { + private: + struct DataHolder : public InstallContentMeta, public util::IntrusiveListBaseNode{}; + using DataList = util::IntrusiveListBaseTraits::ListType; + private: + DataList data_list; + InstallProgressState state; + Result last_result; + SystemUpdateTaskApplyInfo system_update_task_apply_info; + public: + MemoryInstallTaskData() : state(InstallProgressState::NotPrepared), last_result(ResultSuccess()) { /* ... */ }; + ~MemoryInstallTaskData() { + this->Cleanup(); + } + public: + virtual Result GetProgress(InstallProgress *out_progress) override; + virtual Result GetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo *out_info) override; + virtual Result SetState(InstallProgressState state) override; + virtual Result SetLastResult(Result result) override; + virtual Result SetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo info) override; + virtual Result Push(const void *data, size_t data_size) override; + virtual Result Count(s32 *out) override; + virtual Result Delete(const ContentMetaKey *keys, s32 num_keys) override; + virtual Result Cleanup() override; + private: + virtual Result GetSize(size_t *out_size, s32 index) override; + virtual Result Get(s32 index, void *out, size_t out_size) override; + virtual Result Update(s32 index, const void *data, size_t data_size) override; + }; + + class FileInstallTaskData : public InstallTaskDataBase { + private: + struct Header { + u32 max_entries; + u32 count; + s64 last_data_offset; + Result last_result; + InstallProgressState progress_state; + SystemUpdateTaskApplyInfo system_update_task_apply_info; + }; + + static_assert(sizeof(Header) == 0x18); + + struct EntryInfo { + s64 offset; + s64 size; + }; + + static_assert(sizeof(EntryInfo) == 0x10); + private: + Header header; + char path[64]; + private: + static constexpr Header MakeInitialHeader(s32 max_entries) { + return { + .max_entries = static_cast(max_entries), + .count = 0, + .last_data_offset = GetEntryInfoOffset(max_entries), + .last_result = ResultSuccess(), + .progress_state = InstallProgressState::NotPrepared, + .system_update_task_apply_info = SystemUpdateTaskApplyInfo::Unknown, + }; + } + + static constexpr s64 GetEntryInfoOffset(s32 index) { + return index * sizeof(EntryInfo) + sizeof(Header); + } + public: + static Result Create(const char *path, s32 max_entries); + Result Initialize(const char *path); + public: + virtual Result GetProgress(InstallProgress *out_progress) override; + virtual Result GetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo *out_info) override; + virtual Result SetState(InstallProgressState state) override; + virtual Result SetLastResult(Result result) override; + virtual Result SetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo info) override; + virtual Result Push(const void *data, size_t data_size) override; + virtual Result Count(s32 *out) override; + virtual Result Delete(const ContentMetaKey *keys, s32 num_keys) override; + virtual Result Cleanup() override; + private: + virtual Result GetSize(size_t *out_size, s32 index) override; + virtual Result Get(s32 index, void *out, size_t out_size) override; + virtual Result Update(s32 index, const void *data, size_t data_size) override; + + Result GetEntryInfo(EntryInfo *out_entry_info, s32 index); + + Result Write(const void *data, size_t size, s64 offset); + Result Read(void *out, size_t out_size, s64 offset); + Result WriteHeader(); + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_occupied_size.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_occupied_size.hpp new file mode 100644 index 000000000..359a8eabc --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_occupied_size.hpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019-2020 Adubbz, Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::ncm { + + struct InstallTaskOccupiedSize { + ContentMetaKey key; + s64 size; + StorageId storage_id; + u8 reserved[7]; + }; + + static_assert(sizeof(InstallTaskOccupiedSize) == 0x20); + +} \ No newline at end of file diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_max_count.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_max_count.hpp new file mode 100644 index 000000000..5f82715df --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_max_count.hpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019-2020 Adubbz, Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::ncm { + + constexpr inline s32 SystemMaxContentMetaCount = 0x800; + constexpr inline s32 GameCardMaxContentMetaCount = 0x800; + constexpr inline s32 UserMaxContentMetaCount = 0x2000; + constexpr inline s32 SdCardMaxContentMetaCount = 0x2000; + +} diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_install_task.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_install_task.hpp new file mode 100644 index 000000000..062f37a03 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_install_task.hpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019-2020 Adubbz, Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::ncm { + + class PackageInstallTask : public PackageInstallTaskBase { + private: + MemoryInstallTaskData data; + public: + Result Initialize(const char *package_root, StorageId storage_id, void *buffer, size_t buffer_size, bool ignore_ticket); + protected: + bool IsContentMetaContentName(const char *name); + virtual Result PrepareInstallContentMetaData() override; + private: + virtual Result GetInstallContentMetaInfo(InstallContentMetaInfo *out_info, const ContentMetaKey &key) override; + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_install_task_base.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_install_task_base.hpp new file mode 100644 index 000000000..a2f987e87 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_install_task_base.hpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2020 Adubbz, Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +namespace ams::ncm { + + class PackageInstallTaskBase : public InstallTaskBase { + private: + using PackagePath = kvdb::BoundedString<256>; + private: + PackagePath package_root; + void *buffer; + size_t buffer_size; + public: + PackageInstallTaskBase() : package_root() { /* ... */ } + + Result Initialize(const char *package_root_path, void *buffer, size_t buffer_size, StorageId storage_id, InstallTaskDataBase *data, u32 config); + protected: + const char *GetPackageRootPath() { + return this->package_root.Get(); + } + private: + virtual Result OnWritePlaceHolder(const ContentMetaKey &key, InstallContentInfo *content_info) override; + virtual Result InstallTicket(const fs::RightsId &rights_id, ContentMetaType meta_type) override; + + void CreateContentMetaPath(PackagePath *out_path, ContentId content_id); + void CreateContentPath(PackagePath *out_path, ContentId content_id); + void CreateTicketPath(PackagePath *out_path, fs::RightsId id); + void CreateCertificatePath(PackagePath *out_path, fs::RightsId id); + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_system_update_task.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_system_update_task.hpp new file mode 100644 index 000000000..4648d20f6 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_system_update_task.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2020 Adubbz, Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::ncm { + + class PackageSystemUpdateTask : public PackageInstallTaskBase { + private: + using PackagePath = kvdb::BoundedString<0x100>; + private: + PackagePath context_path; + FileInstallTaskData data; + ContentMetaDatabase package_db; + bool gamecard_content_meta_database_active; + public: + ~PackageSystemUpdateTask(); + + void Inactivate(); + Result Initialize(const char *package_root, const char *context_path, void *buffer, size_t buffer_size, bool requires_exfat_driver, FirmwareVariationId firmware_variation_id); + std::optional GetSystemUpdateMetaKey(); + protected: + virtual Result PrepareInstallContentMetaData() override; + private: + virtual Result PrepareDependency() override; + virtual Result GetInstallContentMetaInfo(InstallContentMetaInfo *out, const ContentMetaKey &key) override; + + Result GetContentInfoOfContentMeta(ContentInfo *out, const ContentMetaKey &key); + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_placeholder_id.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_placeholder_id.hpp index 9b41a66d1..2da1eb6ca 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_placeholder_id.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_placeholder_id.hpp @@ -21,19 +21,19 @@ namespace ams::ncm { struct alignas(8) PlaceHolderId { util::Uuid uuid; - bool operator==(const PlaceHolderId& other) const { + bool operator==(const PlaceHolderId &other) const { return this->uuid == other.uuid; } - bool operator!=(const PlaceHolderId& other) const { + bool operator!=(const PlaceHolderId &other) const { return this->uuid != other.uuid; } - bool operator==(const util::Uuid& other) const { + bool operator==(const util::Uuid &other) const { return this->uuid == other; } - bool operator!=(const util::Uuid& other) const { + bool operator!=(const util::Uuid &other) const { return this->uuid != other; } }; diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_rights_id.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_rights_id.hpp index 03b520d94..6806fa7f5 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_rights_id.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_rights_id.hpp @@ -27,4 +27,15 @@ namespace ams::ncm { static_assert(sizeof(RightsId) == 0x18); static_assert(std::is_pod::value); + inline bool operator==(const RightsId &lhs, const RightsId &rhs) { + return std::tie(lhs.id, lhs.key_generation) == std::tie(rhs.id, rhs.key_generation); + } + + inline bool operator!=(const RightsId &lhs, const RightsId &rhs) { + return !(lhs == rhs); + } + + inline bool operator<(const RightsId &lhs, const RightsId &rhs) { + return std::tie(lhs.id, lhs.key_generation) < std::tie(rhs.id, rhs.key_generation); + } } diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_storage_id.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_storage_id.hpp index 532a79430..94e46aa74 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_storage_id.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_storage_id.hpp @@ -26,6 +26,11 @@ namespace ams::ncm { BuiltInUser = 4, SdCard = 5, Any = 6, + + /* Aliases. */ + Card = GameCard, + BuildInSystem = BuiltInSystem, + BuildInUser = BuiltInUser, }; constexpr inline bool IsUniqueStorage(StorageId id) { diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_storage_utils.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_storage_utils.hpp new file mode 100644 index 000000000..371a71eed --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_storage_utils.hpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2019-2020 Adubbz, Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +namespace ams::ncm { + + class StorageList { + public: + static constexpr s32 MaxCount = 10; + private: + StorageId ids[MaxCount]; + s32 count; + public: + constexpr StorageList() : ids(), count() { /* ... */ } + + void Push(StorageId storage_id) { + AMS_ABORT_UNLESS(this->count < MaxCount); + + for (s32 i = 0; i < MaxCount; i++) { + if (this->ids[i] == storage_id) { + return; + } + } + + this->ids[this->count++] = storage_id; + } + + s32 Count() const { + return this->count; + } + + StorageId operator[](s32 i) const { + AMS_ABORT_UNLESS(i < this->count); + return this->ids[i]; + } + }; + + constexpr StorageList GetStorageList(StorageId storage_id) { + StorageList list; + switch (storage_id) { + case StorageId::BuiltInSystem: + case StorageId::BuiltInUser: + case StorageId::SdCard: + list.Push(storage_id); + break; + case StorageId::Any: + list.Push(StorageId::SdCard); + list.Push(StorageId::BuiltInUser); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + return list; + } + + Result SelectDownloadableStorage(StorageId *out_storage_id, StorageId storage_id, s64 required_size); + Result SelectPatchStorage(StorageId *out_storage_id, StorageId storage_id, PatchId patch_id); + const char *GetStorageIdString(StorageId storage_id); + const char *GetStorageIdStringForPlayReport(StorageId storage_id); + +} diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_submission_package_install_task.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_submission_package_install_task.hpp new file mode 100644 index 000000000..91f8b2001 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_submission_package_install_task.hpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019-2020 Adubbz, Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +namespace ams::ncm { + + class SubmissionPackageInstallTask : public PackageInstallTask { + private: + class Impl; + private: + std::unique_ptr impl; + public: + SubmissionPackageInstallTask(); + virtual ~SubmissionPackageInstallTask() override; + + Result Initialize(fs::FileHandle handle, StorageId storage_id, void *buffer, size_t buffer_size, bool ignore_ticket = false); + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_system_update_task_apply_info.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_system_update_task_apply_info.hpp new file mode 100644 index 000000000..b1f7b15e9 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_system_update_task_apply_info.hpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2019-2020 Adubbz, Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::ncm { + + enum class SystemUpdateTaskApplyInfo : u8 { + Unknown = 0, + RequireReboot = 1, + RequireNoReboot = 2, + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/nim/nim_network_install_manager_api.hpp b/libraries/libstratosphere/include/stratosphere/nim/nim_network_install_manager_api.hpp index 6456444f5..c32f932c2 100644 --- a/libraries/libstratosphere/include/stratosphere/nim/nim_network_install_manager_api.hpp +++ b/libraries/libstratosphere/include/stratosphere/nim/nim_network_install_manager_api.hpp @@ -24,7 +24,7 @@ namespace ams::nim { void FinalizeForNetworkInstallManager(); /* Service API. */ - Result DestroySystemUpdateTask(const SystemUpdateTaskId& id); + Result DestroySystemUpdateTask(const SystemUpdateTaskId &id); s32 ListSystemUpdateTask(SystemUpdateTaskId *out_list, size_t out_list_size); diff --git a/libraries/libstratosphere/include/stratosphere/os/os_managed_handle.hpp b/libraries/libstratosphere/include/stratosphere/os/os_managed_handle.hpp index 1219cb6e5..b5e5281d4 100644 --- a/libraries/libstratosphere/include/stratosphere/os/os_managed_handle.hpp +++ b/libraries/libstratosphere/include/stratosphere/os/os_managed_handle.hpp @@ -38,7 +38,7 @@ namespace ams::os { rhs.hnd = INVALID_HANDLE; } - ManagedHandle& operator=(ManagedHandle&& rhs) { + ManagedHandle &operator=(ManagedHandle&& rhs) { rhs.Swap(*this); return *this; } @@ -47,7 +47,7 @@ namespace ams::os { return this->hnd != INVALID_HANDLE; } - void Swap(ManagedHandle& rhs) { + void Swap(ManagedHandle &rhs) { std::swap(this->hnd, rhs.hnd); } diff --git a/libraries/libstratosphere/include/stratosphere/sm/sm_scoped_holder.hpp b/libraries/libstratosphere/include/stratosphere/sm/sm_scoped_holder.hpp index ecc9e1629..952f44b27 100644 --- a/libraries/libstratosphere/include/stratosphere/sm/sm_scoped_holder.hpp +++ b/libraries/libstratosphere/include/stratosphere/sm/sm_scoped_holder.hpp @@ -47,12 +47,12 @@ namespace ams::sm { rhs.has_initialized = false; } - ScopedServiceHolder& operator=(ScopedServiceHolder&& rhs) { + ScopedServiceHolder &operator=(ScopedServiceHolder&& rhs) { rhs.Swap(*this); return *this; } - void Swap(ScopedServiceHolder& rhs) { + void Swap(ScopedServiceHolder &rhs) { std::swap(this->result, rhs.result); std::swap(this->has_initialized, rhs.has_initialized); } diff --git a/libraries/libstratosphere/source/lr/lr_location_redirector.cpp b/libraries/libstratosphere/source/lr/lr_location_redirector.cpp index 9c0445dc5..6414d1503 100644 --- a/libraries/libstratosphere/source/lr/lr_location_redirector.cpp +++ b/libraries/libstratosphere/source/lr/lr_location_redirector.cpp @@ -27,7 +27,7 @@ namespace ams::lr { Path path; u32 flags; public: - Redirection(ncm::ProgramId program_id, ncm::ProgramId owner_id, const Path& path, u32 flags) : + Redirection(ncm::ProgramId program_id, ncm::ProgramId owner_id, const Path &path, u32 flags) : program_id(program_id), owner_id(owner_id), path(path), flags(flags) { /* ... */ } ncm::ProgramId GetProgramId() const { diff --git a/libraries/libstratosphere/source/lr/lr_registered_data.hpp b/libraries/libstratosphere/source/lr/lr_registered_data.hpp index 109baa5f2..741f3d4fe 100644 --- a/libraries/libstratosphere/source/lr/lr_registered_data.hpp +++ b/libraries/libstratosphere/source/lr/lr_registered_data.hpp @@ -47,7 +47,7 @@ namespace ams::lr { inline void RegisterImpl(size_t i, const Key &key, const Value &value, const ncm::ProgramId owner_id) { /* Populate entry. */ - Entry& entry = this->entries[i]; + Entry &entry = this->entries[i]; entry.key = key; entry.value = value; entry.owner_id = owner_id; @@ -61,7 +61,7 @@ namespace ams::lr { bool Register(const Key &key, const Value &value, const ncm::ProgramId owner_id) { /* Try to find an existing value. */ for (size_t i = 0; i < this->GetCapacity(); i++) { - Entry& entry = this->entries[i]; + Entry &entry = this->entries[i]; if (entry.is_valid && entry.key == key) { this->RegisterImpl(i, key, value, owner_id); return true; @@ -70,7 +70,7 @@ namespace ams::lr { /* We didn't find an existing entry, so try to create a new one. */ for (size_t i = 0; i < this->GetCapacity(); i++) { - Entry& entry = this->entries[i]; + Entry &entry = this->entries[i]; if (!entry.is_valid) { this->RegisterImpl(i, key, value, owner_id); return true; @@ -83,7 +83,7 @@ namespace ams::lr { void Unregister(const Key &key) { /* Invalidate entries with a matching key. */ for (size_t i = 0; i < this->GetCapacity(); i++) { - Entry& entry = this->entries[i]; + Entry &entry = this->entries[i]; if (entry.is_valid && entry.key == key) { entry.is_valid = false; } @@ -93,7 +93,7 @@ namespace ams::lr { void UnregisterOwnerProgram(ncm::ProgramId owner_id) { /* Invalidate entries with a matching owner id. */ for (size_t i = 0; i < this->GetCapacity(); i++) { - Entry& entry = this->entries[i]; + Entry &entry = this->entries[i]; if (entry.owner_id == owner_id) { entry.is_valid = false; } @@ -103,7 +103,7 @@ namespace ams::lr { bool Find(Value *out, const Key &key) const { /* Locate a matching entry. */ for (size_t i = 0; i < this->GetCapacity(); i++) { - const Entry& entry = this->entries[i]; + const Entry &entry = this->entries[i]; if (entry.is_valid && entry.key == key) { *out = entry.value; return true; @@ -123,7 +123,7 @@ namespace ams::lr { void ClearExcluding(const ncm::ProgramId *ids, size_t num_ids) { /* Invalidate all entries unless excluded. */ for (size_t i = 0; i < this->GetCapacity(); i++) { - Entry& entry = this->entries[i]; + Entry &entry = this->entries[i]; if (!this->IsExcluded(entry.owner_id, ids, num_ids)) { entry.is_valid = false; diff --git a/libraries/libstratosphere/source/ncm/ncm_content_id_utils.cpp b/libraries/libstratosphere/source/ncm/ncm_content_id_utils.cpp index 4450693c3..a0a27b9d1 100644 --- a/libraries/libstratosphere/source/ncm/ncm_content_id_utils.cpp +++ b/libraries/libstratosphere/source/ncm/ncm_content_id_utils.cpp @@ -48,7 +48,6 @@ namespace ams::ncm { } - ContentIdString GetContentIdString(ContentId id) { ContentIdString str; GetStringFromContentId(str.data, sizeof(str), id); @@ -60,6 +59,25 @@ namespace ams::ncm { GetStringFromBytes(dst, std::addressof(id), sizeof(id)); } + void GetStringFromRightsId(char *dst, size_t dst_size, fs::RightsId id) { + AMS_ABORT_UNLESS(dst_size > RightsIdStringLength); + GetStringFromBytes(dst, std::addressof(id), sizeof(id)); + } + + void GetTicketFileStringFromRightsId(char *dst, size_t dst_size, fs::RightsId id) { + AMS_ABORT_UNLESS(dst_size > TicketFileStringLength); + ContentIdString str; + GetStringFromRightsId(str.data, sizeof(str), id); + std::snprintf(dst, dst_size, "%s.tik", str.data); + } + + void GetCertificateFileStringFromRightsId(char *dst, size_t dst_size, fs::RightsId id) { + AMS_ABORT_UNLESS(dst_size > CertFileStringLength); + ContentIdString str; + GetStringFromRightsId(str.data, sizeof(str), id); + std::snprintf(dst, dst_size, "%s.cert", str.data); + } + std::optional GetContentIdFromString(const char *str, size_t len) { if (len < ContentIdStringLength) { return std::nullopt; diff --git a/libraries/libstratosphere/source/ncm/ncm_content_info_utils.cpp b/libraries/libstratosphere/source/ncm/ncm_content_info_utils.cpp new file mode 100644 index 000000000..f900775d7 --- /dev/null +++ b/libraries/libstratosphere/source/ncm/ncm_content_info_utils.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2019-2020 Adubbz, Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::ncm { + + namespace { + constexpr inline s64 EncryptionMetadataSize = 16_KB; + constexpr inline s64 ConcatenationFileSizeMax = 4_GB; + + constexpr s64 CalculateAdditionalContentSize(s64 file_size, s64 cluster_size) { + /* Account for the encryption header. */ + s64 size = EncryptionMetadataSize; + + /* Account for the file size splitting costs. */ + size += ((file_size / ConcatenationFileSizeMax) + 1) * cluster_size; + + /* Account for various overhead costs. */ + size += cluster_size * 3; + + return size; + } + + template + Result ForEachContentInfo(const ContentMetaKey &key, ncm::ContentMetaDatabase *db, Handler handler) { + constexpr s32 MaxPerIteration = 0x10; + ContentInfo info_list[MaxPerIteration]; + s32 offset = 0; + while (true) { + /* List the content infos. */ + s32 count; + R_TRY(db->ListContentInfo(std::addressof(count), info_list, MaxPerIteration, key, offset)); + + /* Handle all that we listed. */ + for (s32 i = 0; i < count; i++) { + bool done = false; + R_TRY(handler(std::addressof(done), info_list[i])); + if (done) { + break; + } + } + + /* Check if we're done. */ + if (count != MaxPerIteration) { + break; + } + + offset += count; + } + + return ResultSuccess(); + } + + } + + s64 CalculateRequiredSize(s64 file_size, s64 cluster_size) { + return file_size + CalculateAdditionalContentSize(file_size, cluster_size); + } + + s64 CalculateRequiredSizeForExtension(s64 file_size, s64 cluster_size) { + return file_size + ((file_size / ConcatenationFileSizeMax) + 1) * cluster_size; + } + + Result EstimateRequiredSize(s64 *out_size, const ContentMetaKey &key, ncm::ContentMetaDatabase *db) { + s64 size = 0; + R_TRY(ForEachContentInfo(key, db, [&size](bool *out_done, const ContentInfo &info) -> Result { + size += CalculateRequiredSize(info.GetSize(), MaxClusterSize); + *out_done = false; + return ResultSuccess(); + })); + + *out_size = size; + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/source/ncm/ncm_content_manager_impl.cpp b/libraries/libstratosphere/source/ncm/ncm_content_manager_impl.cpp index 3b73df4cb..20bff9295 100644 --- a/libraries/libstratosphere/source/ncm/ncm_content_manager_impl.cpp +++ b/libraries/libstratosphere/source/ncm/ncm_content_manager_impl.cpp @@ -71,11 +71,6 @@ namespace ams::ncm { .space_id = fs::SaveDataSpaceId::SdSystem, }; - constexpr size_t MaxBuiltInSystemContentMetaCount = 0x800; - constexpr size_t MaxBuiltInUserContentMetaCount = 0x2000; - constexpr size_t MaxSdCardContentMetaCount = 0x2000; - constexpr size_t MaxGameCardContentMetaCount = 0x800; - using RootPath = kvdb::BoundedString<32>; inline void ReplaceMountName(char *out_path, const char *mount_name, const char *path) { @@ -342,7 +337,7 @@ namespace ams::ncm { R_TRY(this->ActivateContentStorage(StorageId::BuiltInSystem)); /* Next, the BuiltInSystem content meta entry. */ - R_TRY(this->InitializeContentMetaDatabaseRoot(&this->content_meta_database_roots[this->num_content_meta_entries++], StorageId::BuiltInSystem, BuiltInSystemSystemSaveDataInfo, MaxBuiltInSystemContentMetaCount, std::addressof(g_system_content_meta_memory_resource))); + R_TRY(this->InitializeContentMetaDatabaseRoot(&this->content_meta_database_roots[this->num_content_meta_entries++], StorageId::BuiltInSystem, BuiltInSystemSystemSaveDataInfo, SystemMaxContentMetaCount, std::addressof(g_system_content_meta_memory_resource))); if (R_FAILED(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem))) { R_TRY(this->CreateContentMetaDatabase(StorageId::BuiltInSystem)); @@ -370,18 +365,18 @@ namespace ams::ncm { /* Now for BuiltInUser's content storage and content meta entries. */ R_TRY(this->InitializeContentStorageRoot(&this->content_storage_roots[this->num_content_storage_entries++], StorageId::BuiltInUser, fs::ContentStorageId::User)); - R_TRY(this->InitializeContentMetaDatabaseRoot(&this->content_meta_database_roots[this->num_content_meta_entries++], StorageId::BuiltInUser, BuiltInUserSystemSaveDataInfo, MaxBuiltInUserContentMetaCount, std::addressof(g_sd_and_user_content_meta_memory_resource))); + R_TRY(this->InitializeContentMetaDatabaseRoot(&this->content_meta_database_roots[this->num_content_meta_entries++], StorageId::BuiltInUser, BuiltInUserSystemSaveDataInfo, UserMaxContentMetaCount, std::addressof(g_sd_and_user_content_meta_memory_resource))); /* Beyond this point, N uses hardcoded indices. */ /* Next SdCard's content storage and content meta entries. */ R_TRY(this->InitializeContentStorageRoot(&this->content_storage_roots[2], StorageId::SdCard, fs::ContentStorageId::SdCard)); - R_TRY(this->InitializeContentMetaDatabaseRoot(&this->content_meta_database_roots[2], StorageId::SdCard, SdCardSystemSaveDataInfo, MaxSdCardContentMetaCount, std::addressof(g_sd_and_user_content_meta_memory_resource))); + R_TRY(this->InitializeContentMetaDatabaseRoot(&this->content_meta_database_roots[2], StorageId::SdCard, SdCardSystemSaveDataInfo, SdCardMaxContentMetaCount, std::addressof(g_sd_and_user_content_meta_memory_resource))); /* GameCard's content storage and content meta entries. */ /* N doesn't set a content storage id for game cards, so we'll just use 0 (System). */ R_TRY(this->InitializeGameCardContentStorageRoot(&this->content_storage_roots[3])); - R_TRY(this->InitializeGameCardContentMetaDatabaseRoot(&this->content_meta_database_roots[3], MaxGameCardContentMetaCount, std::addressof(g_gamecard_content_meta_memory_resource))); + R_TRY(this->InitializeGameCardContentMetaDatabaseRoot(&this->content_meta_database_roots[3], GameCardMaxContentMetaCount, std::addressof(g_gamecard_content_meta_memory_resource))); this->initialized = true; return ResultSuccess(); diff --git a/libraries/libstratosphere/source/ncm/ncm_content_meta.cpp b/libraries/libstratosphere/source/ncm/ncm_content_meta.cpp index 6a57acd10..bae3b0b85 100644 --- a/libraries/libstratosphere/source/ncm/ncm_content_meta.cpp +++ b/libraries/libstratosphere/source/ncm/ncm_content_meta.cpp @@ -20,16 +20,76 @@ namespace ams::ncm { namespace { void ConvertPackageContentMetaHeaderToContentMetaHeader(ContentMetaHeader *dst, const PackagedContentMetaHeader &src) { - /* Clear destination. */ - *dst = {}; - - /* Set converted fields. */ - dst->extended_header_size = src.extended_header_size; - dst->content_meta_count = src.content_meta_count; - dst->content_count = src.content_meta_count; - dst->attributes = src.attributes; + /* Set destination. */ + *dst = { + .extended_header_size = src.extended_header_size, + .content_count = src.content_count, + .content_meta_count = src.content_meta_count, + .attributes = src.attributes, + }; } + void ConvertPackageContentMetaHeaderToInstallContentMetaHeader(InstallContentMetaHeader *dst, const PackagedContentMetaHeader &src) { + /* Set destination. */ + static_assert(std::is_same::value); + std::memcpy(dst, std::addressof(src), sizeof(*dst)); + } + + void ConvertInstallContentMetaHeaderToContentMetaHeader(ContentMetaHeader *dst, const InstallContentMetaHeader &src) { + /* Set destination. */ + *dst = { + .extended_header_size = src.extended_header_size, + .content_count = src.content_count, + .content_meta_count = src.content_meta_count, + .attributes = src.attributes, + }; + } + + Result FindDeltaIndex(s32 *out_index, const PatchMetaExtendedDataReader &reader, u32 src_version, u32 dst_version) { + /* Iterate over all deltas. */ + auto header = reader.GetHeader(); + for (s32 i = 0; i < static_cast(header->delta_count); i++) { + /* Check if the current delta matches the versions. */ + auto delta = reader.GetPatchDeltaHeader(i); + if ((src_version == 0 || delta->delta.source_version == src_version) && delta->delta.destination_version == dst_version) { + *out_index = i; + return ResultSuccess(); + } + } + + /* We didn't find the delta. */ + return ncm::ResultDeltaNotFound(); + } + + s32 CountContentExceptForMeta(const PatchMetaExtendedDataReader &reader, s32 delta_index) { + /* Iterate over packaged content infos, checking for those which aren't metas. */ + s32 count = 0; + auto delta = reader.GetPatchDeltaHeader(delta_index); + for (s32 i = 0; i < static_cast(delta->content_count); i++) { + if (reader.GetPatchDeltaPackagedContentInfo(delta_index, i)->GetType() != ContentType::Meta) { + count++; + } + } + + return count; + } + + } + + size_t PackagedContentMetaReader::CalculateConvertInstallContentMetaSize() const { + /* Prepare the header. */ + const auto *header = this->GetHeader(); + + if ((header->type == ContentMetaType::SystemUpdate && this->GetExtendedHeaderSize() > 0) || header->type == ContentMetaType::Delta) { + /* Newer SystemUpdates and Deltas contain extended data. */ + return this->CalculateSizeImpl(header->extended_header_size, header->content_count + 1, header->content_meta_count, this->GetExtendedDataSize(), false); + } else if (header->type == ContentMetaType::Patch) { + /* Subtract the number of delta fragments for patches, include extended data. */ + return this->CalculateSizeImpl(header->extended_header_size, header->content_count - this->CountDeltaFragments() + 1, header->content_meta_count, this->GetExtendedDataSize(), false); + } + + /* No extended data or delta fragments by default. */ + return this->CalculateSizeImpl(header->extended_header_size, header->content_count + 1, header->content_meta_count, 0, false); } size_t PackagedContentMetaReader::CountDeltaFragments() const { @@ -47,6 +107,134 @@ namespace ams::ncm { return this->CalculateSizeImpl(header->extended_header_size, header->content_count + 1, header->content_meta_count, 0, false); } + void PackagedContentMetaReader::ConvertToInstallContentMeta(void *dst, size_t size, const InstallContentInfo &meta) { + /* Ensure we have enough space to convert. */ + AMS_ABORT_UNLESS(size >= this->CalculateConvertInstallContentMetaSize()); + + /* Prepare for conversion. */ + const auto *packaged_header = this->GetHeader(); + uintptr_t dst_addr = reinterpret_cast(dst); + + /* Convert the header. */ + InstallContentMetaHeader header; + ConvertPackageContentMetaHeaderToInstallContentMetaHeader(std::addressof(header), *packaged_header); + header.content_count += 1; + + /* Don't include deltas. */ + if (packaged_header->type == ContentMetaType::Patch) { + header.content_count -= this->CountDeltaFragments(); + } + + /* Copy the header. */ + std::memcpy(reinterpret_cast(dst_addr), std::addressof(header), sizeof(header)); + dst_addr += sizeof(header); + + /* Copy the extended header. */ + std::memcpy(reinterpret_cast(dst_addr), reinterpret_cast(this->GetExtendedHeaderAddress()), packaged_header->extended_header_size); + dst_addr += packaged_header->extended_header_size; + + /* Copy the top level meta. */ + std::memcpy(reinterpret_cast(dst_addr), std::addressof(meta), sizeof(meta)); + dst_addr += sizeof(meta); + + /* Copy content infos. */ + for (size_t i = 0; i < this->GetContentCount(); i++) { + const auto *packaged_content_info = this->GetContentInfo(i); + + /* Don't copy any delta fragments. */ + if (packaged_header->type == ContentMetaType::Patch) { + if (packaged_content_info->GetType() == ContentType::DeltaFragment) { + continue; + } + } + + /* Create the install content info. */ + InstallContentInfo install_content_info = InstallContentInfo::Make(*packaged_content_info, packaged_header->type); + + /* Copy the info. */ + std::memcpy(reinterpret_cast(dst_addr), std::addressof(install_content_info), sizeof(InstallContentInfo)); + dst_addr += sizeof(InstallContentInfo); + } + + /* Copy content meta infos. */ + for (size_t i = 0; i < this->GetContentMetaCount(); i++) { + std::memcpy(reinterpret_cast(dst_addr), this->GetContentMetaInfo(i), sizeof(ContentMetaInfo)); + dst_addr += sizeof(ContentMetaInfo); + } + } + + Result PackagedContentMetaReader::ConvertToFragmentOnlyInstallContentMeta(void *dst, size_t size, const InstallContentInfo &meta, u32 source_version) { + /* Ensure that we have enough space. */ + size_t required_size; + R_TRY(this->CalculateConvertFragmentOnlyInstallContentMetaSize(std::addressof(required_size), source_version)); + AMS_ABORT_UNLESS(size >= required_size); + + /* Find the delta index. */ + PatchMetaExtendedDataReader reader(this->GetExtendedData(), this->GetExtendedDataSize()); + s32 index; + R_TRY(FindDeltaIndex(std::addressof(index), reader, source_version, this->GetKey().version)); + auto delta = reader.GetPatchDeltaHeader(index); + + /* Prepare for conversion. */ + const auto *packaged_header = this->GetHeader(); + uintptr_t dst_addr = reinterpret_cast(dst); + + /* Convert the header. */ + InstallContentMetaHeader header; + ConvertPackageContentMetaHeaderToInstallContentMetaHeader(std::addressof(header), *packaged_header); + header.install_type = ContentInstallType::FragmentOnly; + + /* Set the content count. */ + auto fragment_count = CountContentExceptForMeta(reader, index); + header.content_count = static_cast(fragment_count) + 1; + + /* Copy the header. */ + std::memcpy(reinterpret_cast(dst_addr), std::addressof(header), sizeof(header)); + dst_addr += sizeof(header); + + /* Copy the extended header. */ + std::memcpy(reinterpret_cast(dst_addr), reinterpret_cast(this->GetExtendedHeaderAddress()), packaged_header->extended_header_size); + dst_addr += packaged_header->extended_header_size; + + /* Copy the top level meta. */ + std::memcpy(reinterpret_cast(dst_addr), std::addressof(meta), sizeof(meta)); + dst_addr += sizeof(meta); + + s32 count = 0; + for (s32 i = 0; i < static_cast(delta->content_count); i++) { + auto packaged_content_info = reader.GetPatchDeltaPackagedContentInfo(index, i); + if (packaged_content_info->GetType() != ContentType::Meta) { + /* Create the install content info. */ + InstallContentInfo install_content_info = InstallContentInfo::Make(*packaged_content_info, packaged_header->type); + + /* Copy the info. */ + std::memcpy(reinterpret_cast(dst_addr), std::addressof(install_content_info), sizeof(InstallContentInfo)); + dst_addr += sizeof(InstallContentInfo); + + /* Increment the count. */ + count++; + } + } + + /* Assert that we copied the right number of infos. */ + AMS_ASSERT(count == fragment_count); + + return ResultSuccess(); + } + + Result PackagedContentMetaReader::CalculateConvertFragmentOnlyInstallContentMetaSize(size_t *out_size, u32 source_version) const { + /* Find the delta index. */ + PatchMetaExtendedDataReader reader(this->GetExtendedData(), this->GetExtendedDataSize()); + s32 index; + R_TRY(FindDeltaIndex(std::addressof(index), reader, source_version, this->GetKey().version)); + + /* Get the fragment count. */ + auto fragment_count = CountContentExceptForMeta(reader, index); + + /* Recalculate. */ + return CalculateSizeImpl(this->GetExtendedHeaderSize(), fragment_count + 1, 0, this->GetExtendedDataSize(), false); + } + void PackagedContentMetaReader::ConvertToContentMeta(void *dst, size_t size, const ContentInfo &meta) { /* Ensure we have enough space to convert. */ AMS_ABORT_UNLESS(size >= this->CalculateConvertContentMetaSize()); @@ -98,4 +286,42 @@ namespace ams::ncm { } } + size_t InstallContentMetaReader::CalculateConvertSize() const { + return CalculateSizeImpl(this->GetExtendedHeaderSize(), this->GetContentCount(), this->GetContentMetaCount(), this->GetExtendedDataSize(), false); + } + + void InstallContentMetaReader::ConvertToContentMeta(void *dst, size_t size) const { + /* Ensure we have enough space to convert. */ + AMS_ABORT_UNLESS(size >= this->CalculateConvertSize()); + + /* Prepare for conversion. */ + const auto *install_header = this->GetHeader(); + uintptr_t dst_addr = reinterpret_cast(dst); + + /* Convert the header. */ + ContentMetaHeader header; + ConvertInstallContentMetaHeaderToContentMetaHeader(std::addressof(header), *install_header); + + /* Copy the header. */ + std::memcpy(reinterpret_cast(dst_addr), std::addressof(header), sizeof(header)); + dst_addr += sizeof(header); + + /* Copy the extended header. */ + std::memcpy(reinterpret_cast(dst_addr), reinterpret_cast(this->GetExtendedHeaderAddress()), install_header->extended_header_size); + dst_addr += install_header->extended_header_size; + + /* Copy content infos. */ + for (size_t i = 0; i < this->GetContentCount(); i++) { + /* Copy the current info. */ + std::memcpy(reinterpret_cast(dst_addr), std::addressof(this->GetContentInfo(i)->info), sizeof(ContentInfo)); + dst_addr += sizeof(ContentInfo); + } + + /* Copy content meta infos. */ + for (size_t i = 0; i < this->GetContentMetaCount(); i++) { + std::memcpy(reinterpret_cast(dst_addr), this->GetContentMetaInfo(i), sizeof(ContentMetaInfo)); + dst_addr += sizeof(ContentMetaInfo); + } + } + } diff --git a/libraries/libstratosphere/source/ncm/ncm_content_meta_database_impl.cpp b/libraries/libstratosphere/source/ncm/ncm_content_meta_database_impl.cpp index 19f7c6ffb..9db289db8 100644 --- a/libraries/libstratosphere/source/ncm/ncm_content_meta_database_impl.cpp +++ b/libraries/libstratosphere/source/ncm/ncm_content_meta_database_impl.cpp @@ -18,7 +18,7 @@ namespace ams::ncm { - Result ContentMetaDatabaseImpl::GetContentIdImpl(ContentId *out, const ContentMetaKey& key, ContentType type, std::optional id_offset) const { + Result ContentMetaDatabaseImpl::GetContentIdImpl(ContentId *out, const ContentMetaKey &key, ContentType type, std::optional id_offset) const { R_TRY(this->EnsureEnabled()); /* Find the meta key. */ diff --git a/libraries/libstratosphere/source/ncm/ncm_content_meta_database_impl.hpp b/libraries/libstratosphere/source/ncm/ncm_content_meta_database_impl.hpp index 180c1c481..3ccfaab46 100644 --- a/libraries/libstratosphere/source/ncm/ncm_content_meta_database_impl.hpp +++ b/libraries/libstratosphere/source/ncm/ncm_content_meta_database_impl.hpp @@ -25,7 +25,7 @@ namespace ams::ncm { ContentMetaDatabaseImpl(ContentMetaKeyValueStore *kvs) : ContentMetaDatabaseImplBase(kvs) { /* ... */ } private: /* Helpers. */ - Result GetContentIdImpl(ContentId *out, const ContentMetaKey& key, ContentType type, std::optional id_offset) const; + Result GetContentIdImpl(ContentId *out, const ContentMetaKey &key, ContentType type, std::optional id_offset) const; public: /* Actual commands. */ virtual Result Set(const ContentMetaKey &key, sf::InBuffer value) override; diff --git a/libraries/libstratosphere/source/ncm/ncm_content_meta_utils.cpp b/libraries/libstratosphere/source/ncm/ncm_content_meta_utils.cpp index df9a9e0e8..e4c77ad70 100644 --- a/libraries/libstratosphere/source/ncm/ncm_content_meta_utils.cpp +++ b/libraries/libstratosphere/source/ncm/ncm_content_meta_utils.cpp @@ -77,4 +77,83 @@ namespace ams::ncm { return ncm::ResultContentMetaNotFound(); } + Result ReadVariationContentMetaInfoList(s32 *out_count, std::unique_ptr *out_meta_infos, const Path &path, FirmwareVariationId firmware_variation_id) { + AutoBuffer meta; + { + /* TODO: fs::ScopedAutoAbortDisabler aad; */ + R_TRY(ReadContentMetaPath(std::addressof(meta), path.str)); + } + + /* Create a reader for the content meta. */ + PackagedContentMetaReader reader(meta.Get(), meta.GetSize()); + + /* Define a helper to output the base meta infos. */ + /* TODO: C++20 ALWAYS_INLINE_LAMBDA */ + const auto ReadMetaInfoListFromBase = [&]() -> Result { + /* Output the base content meta info count. */ + *out_count = reader.GetContentMetaCount(); + + /* Create a buffer to hold the infos. NOTE: N does not check for nullptr before accessing. */ + std::unique_ptr buffer(new (std::nothrow) ContentMetaInfo[reader.GetContentMetaCount()]); + AMS_ABORT_UNLESS(buffer != nullptr); + + /* Copy all base meta infos to output */ + for (size_t i = 0; i < reader.GetContentMetaCount(); i++) { + buffer[i] = *reader.GetContentMetaInfo(i); + } + + /* Write out the buffer we've populated. */ + *out_meta_infos = std::move(buffer); + return ResultSuccess(); + }; + + /* If there are no firmware variations to list, read meta infos from base. */ + R_UNLESS(reader.GetExtendedDataSize() != 0, ReadMetaInfoListFromBase()); + + SystemUpdateMetaExtendedDataReader extended_data_reader(reader.GetExtendedData(), reader.GetExtendedDataSize()); + std::optional firmware_variation_index = std::nullopt; + + /* Find the input firmware variation id. */ + for (size_t i = 0; i < extended_data_reader.GetFirmwareVariationCount(); i++) { + if (*extended_data_reader.GetFirmwareVariationId(i) == firmware_variation_id) { + firmware_variation_index = i; + break; + } + } + + /* We couldn't find the input firmware variation id. */ + R_UNLESS(firmware_variation_index, ncm::ResultInvalidFirmwareVariation()); + + /* Obtain the variation info. */ + const FirmwareVariationInfo *variation_info = extended_data_reader.GetFirmwareVariationInfo(*firmware_variation_index); + + /* Refer to base if variation info says we should, or if unk is 1 (unk is usually 2, probably a version). */ + const bool refer_to_base = variation_info->refer_to_base || extended_data_reader.GetHeader()->unk == 1; + R_UNLESS(!refer_to_base, ReadMetaInfoListFromBase()); + + /* Output the content meta count. */ + const u32 content_meta_count = variation_info->content_meta_count; + *out_count = content_meta_count; + + /* We're done if there are no content metas to list. */ + R_SUCCEED_IF(content_meta_count == 0); + + /* Allocate a buffer for the content meta infos. */ + std::unique_ptr buffer(new (std::nothrow) ContentMetaInfo[content_meta_count]); + AMS_ABORT_UNLESS(buffer != nullptr); + + /* Get the content meta infos. */ + Span meta_infos; + extended_data_reader.GetContentMetaInfoList(std::addressof(meta_infos), content_meta_count); + + /* Copy the meta infos to the buffer. */ + for (size_t i = 0; i < content_meta_count; i++) { + buffer[i] = meta_infos[i]; + } + + /* Output the content meta info buffer. */ + *out_meta_infos = std::move(buffer); + return ResultSuccess(); + } + } diff --git a/libraries/libstratosphere/source/ncm/ncm_install_task_base.cpp b/libraries/libstratosphere/source/ncm/ncm_install_task_base.cpp new file mode 100644 index 000000000..5032cf027 --- /dev/null +++ b/libraries/libstratosphere/source/ncm/ncm_install_task_base.cpp @@ -0,0 +1,1397 @@ +/* + * Copyright (c) 2019-2020 Adubbz, Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::ncm { + + namespace { + + bool Contains(const StorageContentMetaKey *keys, s32 num_keys, const ContentMetaKey &key, StorageId storage_id) { + for (s32 i = 0; i < num_keys; i++) { + const StorageContentMetaKey &storage_key = keys[i]; + + /* Check if the key matches the input key and storage id. */ + if (storage_key.key == key && storage_key.storage_id == storage_id) { + return true; + } + } + + return false; + } + + bool Contains(const ContentMetaKey *keys, s32 num_keys, const ContentMetaKey &key) { + for (s32 i = 0; i < num_keys; i++) { + /* Check if the key matches the input key. */ + if (keys[i] == key) { + return true; + } + } + + return false; + } + + bool IsExpectedKey(const ContentMetaKey &expected_key, const ContentMetaKey &actual_key) { + return expected_key.id == actual_key.id && + expected_key.version == actual_key.version && + expected_key.type == actual_key.type; + } + + } + + void InstallTaskBase::Cancel() { + std::scoped_lock lk(this->cancel_mutex); + this->cancel_requested = true; + } + + void InstallTaskBase::ResetCancel() { + std::scoped_lock lk(this->cancel_mutex); + this->cancel_requested = false; + } + + bool InstallTaskBase::IsCancelRequested() { + std::scoped_lock lk(this->cancel_mutex); + return this->cancel_requested; + } + + Result InstallTaskBase::Initialize(StorageId install_storage, InstallTaskDataBase *data, u32 config) { + R_UNLESS(IsInstallableStorage(install_storage), ncm::ResultUnknownStorage()); + + this->install_storage = install_storage; + this->data = data; + this->config = config; + + return data->GetProgress(std::addressof(this->progress)); + } + + Result InstallTaskBase::Prepare() { + R_TRY(this->SetLastResultOnFailure(this->PrepareImpl())); + return ResultSuccess(); + } + + Result InstallTaskBase::GetPreparedPlaceHolderPath(Path *out_path, u64 id, ContentMetaType meta_type, ContentType type) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->CountInstallContentMetaData(std::addressof(count))); + + /* Iterate over content meta. */ + std::optional placeholder_id; + std::optional storage_id; + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->GetInstallContentMetaData(std::addressof(content_meta), i)); + const InstallContentMetaReader reader = content_meta.GetReader(); + + /* Ensure content meta matches the key and meta type. */ + const auto key = reader.GetKey(); + + if (key.id != id || key.type != meta_type) { + continue; + } + + /* Attempt to find a content info for the type. */ + for (size_t j = 0; j < reader.GetContentCount(); j++) { + const auto content_info = reader.GetContentInfo(j); + if (content_info->GetType() == type) { + placeholder_id = content_info->GetPlaceHolderId(); + storage_id = content_info->GetStorageId(); + break; + } + } + } + + R_UNLESS(placeholder_id, ncm::ResultPlaceHolderNotFound()); + R_UNLESS(storage_id, ncm::ResultPlaceHolderNotFound()); + + /* Open the relevant content storage. */ + ContentStorage storage; + R_TRY(OpenContentStorage(std::addressof(storage), *storage_id)); + + /* Get the path. */ + storage.GetPlaceHolderPath(out_path, *placeholder_id); + + return ResultSuccess(); + } + + Result InstallTaskBase::CalculateRequiredSize(s64 *out_size) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->data->Count(std::addressof(count))); + + s64 required_size = 0; + /* Iterate over each entry. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->data->Get(std::addressof(content_meta), i)); + const auto reader = content_meta.GetReader(); + + /* Sum the sizes from the content infos. */ + for (size_t j = 0; j < reader.GetContentCount(); j++) { + const auto *content_info = reader.GetContentInfo(j); + + if (content_info->install_state == InstallState::NotPrepared) { + required_size += ncm::CalculateRequiredSize(content_info->GetSize()); + } + } + } + + *out_size = required_size; + return ResultSuccess(); + } + + Result InstallTaskBase::PrepareImpl() { + /* Reset the throughput. */ + this->ResetThroughputMeasurement(); + + /* Transition from NotPrepared to DataPrepared. */ + if (this->GetProgress().state == InstallProgressState::NotPrepared) { + R_TRY(this->PrepareInstallContentMetaData()); + R_TRY(this->PrepareDependency()); + R_TRY(this->CheckInstallable()); + this->SetProgressState(InstallProgressState::DataPrepared); + } + + /* Transition from DataPrepared to Prepared. */ + if (this->GetProgress().state == InstallProgressState::DataPrepared) { + R_TRY(this->PreparePlaceHolder()); + this->SetProgressState(InstallProgressState::Prepared); + } + + /* Signal prepare is completed. */ + return this->OnPrepareComplete(); + } + + Result InstallTaskBase::Cleanup() { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->data->Count(std::addressof(count))); + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Get the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->data->Get(std::addressof(content_meta), i)); + + /* Cleanup the content meta. */ + /* N doesn't check the result of this. */ + this->CleanupOne(content_meta); + } + + /* Cleanup the data and progress. */ + R_TRY(this->data->Cleanup()); + this->CleanupProgress(); + + return ResultSuccess(); + } + + Result InstallTaskBase::CleanupOne(const InstallContentMeta &content_meta) { + /* Obtain a reader and get the storage id. */ + const auto reader = content_meta.GetReader(); + const auto storage_id = reader.GetStorageId(); + R_SUCCEED_IF(storage_id== StorageId::None); + + /* Open the relevant content storage. */ + ContentStorage content_storage; + R_TRY(ncm::OpenContentStorage(&content_storage, storage_id)); + + /* Iterate over content infos. */ + for (size_t i = 0; i < reader.GetContentCount(); i++) { + auto *content_info = reader.GetContentInfo(i); + + /* Delete placeholders for Prepared or Installed content infos. */ + if (content_info->install_state == InstallState::Prepared || content_info->install_state == InstallState::Installed) { + content_storage.DeletePlaceHolder(content_info->placeholder_id); + } + } + + return ResultSuccess(); + } + + Result InstallTaskBase::ListContentMetaKey(s32 *out_keys_written, StorageContentMetaKey *out_keys, s32 out_keys_count, s32 offset, ListContentMetaKeyFilter filter) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->data->Count(std::addressof(count))); + + /* Offset exceeds keys that can be written. */ + if (count <= offset) { + *out_keys_written = 0; + return ResultSuccess(); + } + + if (filter == ListContentMetaKeyFilter::All) { + const s32 num_keys = std::min(count, offset + out_keys_count); + + /* Iterate over content meta. */ + for (s32 i = offset; i < num_keys; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->data->Get(std::addressof(content_meta), i)); + + /* Write output StorageContentMetaKey. */ + const auto reader = content_meta.GetReader(); + out_keys[i - offset] = { reader.GetKey(), reader.GetStorageId() }; + } + + /* Output the number of keys written. */ + *out_keys_written = num_keys - offset; + } else { + s32 keys_written = 0; + s32 cur_offset = 0; + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->data->Get(std::addressof(content_meta), i)); + + /* Create a reader and check if the content has been committed. */ + const auto reader = content_meta.GetReader(); + const bool committed = reader.GetHeader()->committed; + + /* Apply filter. */ + if ((filter == ListContentMetaKeyFilter::Committed && committed) || (filter == ListContentMetaKeyFilter::NotCommitted && !committed)) { + /* Write output StorageContentMetaKey if at a suitable offset. */ + if (cur_offset >= offset) { + out_keys[keys_written++] = { reader.GetKey(), reader.GetStorageId() }; + } + + /* Increment the current offset. */ + cur_offset++; + + /* We can't write any more output keys. */ + if (keys_written >= out_keys_count) { + break; + } + } + } + + /* Output the number of keys written. */ + *out_keys_written = keys_written; + } + + return ResultSuccess(); + } + + Result InstallTaskBase::ListApplicationContentMetaKey(s32 *out_keys_written, ApplicationContentMetaKey *out_keys, s32 out_keys_count, s32 offset) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->data->Count(std::addressof(count))); + + /* Offset exceeds keys that can be written. */ + if (count <= offset) { + *out_keys_written = 0; + return ResultSuccess(); + } + + /* Iterate over content meta. */ + const s32 max = std::min(count, offset + out_keys_count); + s32 keys_written = 0; + for (s32 i = offset; i < max; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->data->Get(std::addressof(content_meta), i)); + + /* Create a reader. */ + const auto reader = content_meta.GetReader(); + + /* Ensure this key has an application id. */ + const auto app_id = reader.GetApplicationId(); + if (!app_id) { + continue; + } + + /* Write output ApplicationContentMetaKey. */ + out_keys[keys_written++] = { reader.GetKey(), *app_id }; + } + + *out_keys_written = keys_written; + return ResultSuccess(); + } + + Result InstallTaskBase::Execute() { + R_TRY(this->SetLastResultOnFailure(this->ExecuteImpl())); + return ResultSuccess(); + } + + Result InstallTaskBase::ExecuteImpl() { + this->StartThroughputMeasurement(); + + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->data->Count(std::addressof(count))); + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->data->Get(std::addressof(content_meta), i)); + + /* Update the data (and check result) when we are done. */ + const auto DoUpdate = [&]() ALWAYS_INLINE_LAMBDA { return this->data->Update(content_meta, i); }; + { + auto update_guard = SCOPE_GUARD { DoUpdate(); }; + + /* Create a writer. */ + const auto writer = content_meta.GetWriter(); + + /* Iterate over content infos. */ + for (size_t j = 0; j < writer.GetContentCount(); j++) { + auto *content_info = writer.GetWritableContentInfo(j); + + /* Write prepared content infos. */ + if (content_info->install_state == InstallState::Prepared) { + R_TRY(this->WritePlaceHolder(writer.GetKey(), content_info)); + content_info->install_state = InstallState::Installed; + } + } + + /* Cancel so we can check the result of updating. */ + update_guard.Cancel(); + } + R_TRY(DoUpdate()); + } + + /* Execution has finished, signal this and update the state. */ + R_TRY(this->OnExecuteComplete()); + + this->SetProgressState(InstallProgressState::Downloaded); + + return ResultSuccess(); + } + + Result InstallTaskBase::PrepareAndExecute() { + R_TRY(this->SetLastResultOnFailure(this->PrepareImpl())); + R_TRY(this->SetLastResultOnFailure(this->ExecuteImpl())); + return ResultSuccess(); + } + + Result InstallTaskBase::VerifyAllNotCommitted(const StorageContentMetaKey *keys, s32 num_keys) { + /* No keys to check. */ + R_SUCCEED_IF(keys == nullptr); + + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->data->Count(std::addressof(count))); + + s32 num_not_committed = 0; + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->data->Get(std::addressof(content_meta), i)); + + /* Create a reader. */ + const auto reader = content_meta.GetReader(); + + if (Contains(keys, num_keys, reader.GetKey(), reader.GetStorageId())) { + /* Ensure content meta isn't committed. */ + R_UNLESS(!reader.GetHeader()->committed, ncm::ResultListPartiallyNotCommitted()); + num_not_committed++; + } + } + + /* Ensure number of uncommitted keys equals the number of input keys. */ + R_UNLESS(num_not_committed == num_keys, ncm::ResultListPartiallyNotCommitted()); + return ResultSuccess(); + } + + Result InstallTaskBase::CommitImpl(const StorageContentMetaKey *keys, s32 num_keys) { + /* Ensure progress state is Downloaded. */ + R_UNLESS(this->GetProgress().state == InstallProgressState::Downloaded, ncm::ResultInvalidInstallTaskState()); + + /* Ensure keys aren't committed. */ + R_TRY(this->VerifyAllNotCommitted(keys, num_keys)); + + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->data->Count(std::addressof(count))); + + /* List of storages to commit. */ + StorageList commit_list; + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->data->Get(std::addressof(content_meta), i)); + + /* Create a reader. */ + const auto reader = content_meta.GetReader(); + const auto cur_key = reader.GetKey(); + const auto storage_id = reader.GetStorageId(); + const size_t convert_size = reader.CalculateConvertSize(); + + /* Skip content meta not contained in input keys. */ + if (keys != nullptr && !Contains(keys, num_keys, cur_key, storage_id)) { + continue; + } + + /* Skip already committed. This check is primarily for if keys is nullptr. */ + if (reader.GetHeader()->committed) { + continue; + } + + /* Helper for performing an update. */ + const auto DoUpdate = [&]() ALWAYS_INLINE_LAMBDA { return this->data->Update(content_meta, i); }; + + /* Commit the current meta. */ + { + /* Ensure that if something goes wrong during commit, we still try to update. */ + auto update_guard = SCOPE_GUARD { DoUpdate(); }; + + /* Open a writer. */ + const auto writer = content_meta.GetWriter(); + + /* Convert to content meta and store to a buffer. */ + std::unique_ptr content_meta_buffer(new (std::nothrow) char[convert_size]); + R_UNLESS(content_meta_buffer != nullptr, ncm::ResultAllocationFailed()); + reader.ConvertToContentMeta(content_meta_buffer.get(), convert_size); + + /* Open the content storage for this meta. */ + ContentStorage content_storage; + R_TRY(OpenContentStorage(&content_storage, storage_id)); + + /* Open the content meta database for this meta. */ + ContentMetaDatabase meta_db; + R_TRY(OpenContentMetaDatabase(std::addressof(meta_db), storage_id)); + + /* Iterate over content infos. */ + for (size_t j = 0; j < reader.GetContentCount(); j++) { + const auto *content_info = reader.GetContentInfo(j); + + /* Register non-existing content infos. */ + if (content_info->install_state != InstallState::AlreadyExists) { + R_TRY(content_storage.Register(content_info->placeholder_id, content_info->info.content_id)); + } + } + + /* Store the content meta. */ + R_TRY(meta_db.Set(reader.GetKey(), content_meta_buffer.get(), convert_size)); + + /* Mark as committed. */ + writer.GetWritableHeader()->committed = true; + + /* Mark storage id to be committed later. */ + commit_list.Push(reader.GetStorageId()); + + /* We successfully commited this meta, so we want to check for errors when updating. */ + update_guard.Cancel(); + } + + /* Try to update, checking for failure. */ + R_TRY(DoUpdate()); + } + + /* Commit all applicable content meta databases. */ + for (s32 i = 0; i < commit_list.Count(); i++) { + ContentMetaDatabase meta_db; + R_TRY(OpenContentMetaDatabase(std::addressof(meta_db), commit_list[i])); + R_TRY(meta_db.Commit()); + } + + /* Change progress state to committed if keys are nullptr. */ + if (keys == nullptr) { + this->SetProgressState(InstallProgressState::Committed); + } + + return ResultSuccess(); + } + + Result InstallTaskBase::Commit(const StorageContentMetaKey *keys, s32 num_keys) { + auto fatal_guard = SCOPE_GUARD { SetProgressState(InstallProgressState::Fatal); }; + R_TRY(this->SetLastResultOnFailure(this->CommitImpl(keys, num_keys))); + fatal_guard.Cancel(); + return ResultSuccess(); + } + + Result InstallTaskBase::IncludesExFatDriver(bool *out) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->data->Count(std::addressof(count))); + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->data->Get(std::addressof(content_meta), i)); + + /* Check if the attributes are set for including the exfat driver. */ + if (content_meta.GetReader().GetHeader()->attributes & ContentMetaAttribute_IncludesExFatDriver) { + *out = true; + return ResultSuccess(); + } + } + + *out = false; + return ResultSuccess(); + } + + Result InstallTaskBase::WritePlaceHolderBuffer(InstallContentInfo *content_info, const void *data, size_t data_size) { + R_UNLESS(!this->IsCancelRequested(), ncm::ResultWritePlaceHolderCancelled()); + + /* Open the content storage for the content info. */ + ContentStorage content_storage; + R_TRY(OpenContentStorage(&content_storage, content_info->storage_id)); + + /* Write data to the placeholder. */ + R_TRY(content_storage.WritePlaceHolder(content_info->placeholder_id, content_info->written, data, data_size)); + content_info->written += data_size; + + /* Update progress/throughput if content info isn't temporary. */ + if (!content_info->is_temporary) { + this->IncrementProgress(data_size); + this->UpdateThroughputMeasurement(data_size); + } + + /* Update the hash for the new data. */ + this->sha256_generator.Update(data, data_size); + return ResultSuccess(); + } + + Result InstallTaskBase::WritePlaceHolder(const ContentMetaKey &key, InstallContentInfo *content_info) { + if (content_info->is_sha256_calculated) { + /* Update the hash with the buffered data. */ + this->sha256_generator.InitializeWithContext(std::addressof(content_info->context)); + this->sha256_generator.Update(content_info->buffered_data, content_info->buffered_data_size); + } else { + /* Initialize the generator. */ + this->sha256_generator.Initialize(); + } + + { + ON_SCOPE_EXIT { + /* Update this content info's sha256 data. */ + this->sha256_generator.GetContext(std::addressof(content_info->context)); + content_info->buffered_data_size = this->sha256_generator.GetBufferedDataSize(); + this->sha256_generator.GetBufferedData(content_info->buffered_data, this->sha256_generator.GetBufferedDataSize()); + content_info->is_sha256_calculated = true; + }; + + /* Perform the placeholder write. */ + R_TRY(this->OnWritePlaceHolder(key, content_info)); + } + + /* Compare generated hash to expected hash if verification required. */ + if (content_info->verify_digest) { + u8 hash[crypto::Sha256Generator::HashSize]; + this->sha256_generator.GetHash(hash, crypto::Sha256Generator::HashSize); + R_UNLESS(std::memcmp(hash, content_info->digest.data, crypto::Sha256Generator::HashSize) == 0, ncm::ResultInvalidContentHash()); + } + + if (!(this->config & InstallConfig_IgnoreTicket)) { + ncm::RightsId rights_id; + { + /* Open the content storage and obtain the rights id. */ + ncm::ContentStorage storage; + R_TRY(OpenContentStorage(std::addressof(storage), content_info->storage_id)); + R_TRY(storage.GetRightsId(std::addressof(rights_id), content_info->placeholder_id)); + } + + /* Install a ticket if necessary. */ + if (this->IsNecessaryInstallTicket(rights_id.id)) { + R_TRY_CATCH(this->InstallTicket(rights_id.id, content_info->meta_type)) { + R_CATCH(ncm::ResultIgnorableInstallTicketFailure) { /* We can ignore the installation failure. */ } + } R_END_TRY_CATCH; + } + } + + return ResultSuccess(); + } + + bool InstallTaskBase::IsNecessaryInstallTicket(const fs::RightsId &rights_id) { + /* If the title has no rights, there's no ticket to install. */ + fs::RightsId empty_rights_id = {}; + if (std::memcmp(std::addressof(rights_id), std::addressof(empty_rights_id), sizeof(fs::RightsId)) == 0) { + return false; + } + + /* TODO: Support detecting if a title requires rights. */ + /* TODO: How should es be handled without undesired effects? */ + return false; + } + + Result InstallTaskBase::PreparePlaceHolder() { + static os::Mutex placeholder_mutex; + size_t total_size = 0; + + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->data->Count(std::addressof(count))); + + for (s32 i = 0; i < count; i++) { + R_UNLESS(!this->IsCancelRequested(), ncm::ResultCreatePlaceHolderCancelled()); + std::scoped_lock lk(placeholder_mutex); + + InstallContentMeta content_meta; + R_TRY(this->data->Get(std::addressof(content_meta), i)); + + /* Update the data (and check result) when we are done. */ + const auto DoUpdate = [&]() ALWAYS_INLINE_LAMBDA { return this->data->Update(content_meta, i); }; + { + auto update_guard = SCOPE_GUARD { DoUpdate(); }; + + /* Automatically choose a suitable storage id. */ + auto reader = content_meta.GetReader(); + StorageId storage_id; + if (reader.GetStorageId() != StorageId::None) { + storage_id = reader.GetStorageId(); + } else { + StorageId install_storage = this->GetInstallStorage(); + R_TRY(ncm::SelectDownloadableStorage(std::addressof(storage_id), install_storage, reader.CalculateContentRequiredSize())); + } + + /* Open the relevant content storage. */ + ContentStorage content_storage; + R_TRY(ncm::OpenContentStorage(&content_storage, storage_id)); + + /* Update the storage id in the header. */ + auto writer = content_meta.GetWriter(); + writer.SetStorageId(storage_id); + + for (size_t j = 0; j < writer.GetContentCount(); j++) { + R_UNLESS(!this->IsCancelRequested(), ncm::ResultCreatePlaceHolderCancelled()); + auto *content_info = writer.GetWritableContentInfo(j); + + /* Check if we have the content already exists. */ + bool has_content; + R_TRY(content_storage.Has(&has_content, content_info->GetId())); + + if (has_content) { + /* Add the size of installed content infos to the total size. */ + if (content_info->install_state == InstallState::Installed) { + total_size += content_info->GetSize(); + } + + /* Update the install state. */ + content_info->install_state = InstallState::AlreadyExists; + + /* Continue. */ + continue; + } + + if (content_info->install_state == InstallState::NotPrepared) { + /* Generate a placeholder id. */ + const PlaceHolderId placeholder_id = content_storage.GeneratePlaceHolderId(); + + /* Update the placeholder id in the content info. */ + content_info->placeholder_id = placeholder_id; + + /* Create the placeholder. */ + R_TRY(content_storage.CreatePlaceHolder(placeholder_id, content_info->GetId(), content_info->GetSize())); + + /* Update the install state. */ + content_info->install_state = InstallState::Prepared; + } + + /* Update the storage id for the content info. */ + content_info->storage_id = storage_id; + + /* Add the size of this content info to the total size. */ + total_size += content_info->GetSize(); + } + + /* Cancel so that we can check the result of updating. */ + update_guard.Cancel(); + } + R_TRY(DoUpdate()); + } + + this->SetTotalSize(total_size); + return ResultSuccess(); + } + + Result InstallTaskBase::WriteContentMetaToPlaceHolder(InstallContentInfo *out_install_content_info, ContentStorage *storage, const InstallContentMetaInfo &meta_info, std::optional is_temporary) { + /* Generate a placeholder id. */ + auto placeholder_id = storage->GeneratePlaceHolderId(); + + /* Create the placeholder. */ + R_TRY(storage->CreatePlaceHolder(placeholder_id, meta_info.content_id, meta_info.content_size)); + auto placeholder_guard = SCOPE_GUARD { storage->DeletePlaceHolder(placeholder_id); }; + + /* Output install content info. */ + *out_install_content_info = this->MakeInstallContentInfoFrom(meta_info, placeholder_id, is_temporary); + + /* Write install content info. */ + R_TRY(this->WritePlaceHolder(meta_info.key, out_install_content_info)); + + /* Don't delete the placeholder. Set state to installed. */ + placeholder_guard.Cancel(); + out_install_content_info->install_state = InstallState::Installed; + return ResultSuccess(); + } + + Result InstallTaskBase::PrepareContentMeta(const InstallContentMetaInfo &meta_info, std::optional expected_key, std::optional source_version) { + /* Open the BuiltInSystem content storage. */ + ContentStorage content_storage; + R_TRY(OpenContentStorage(&content_storage, StorageId::BuiltInSystem)); + + /* Write content meta to a placeholder. */ + InstallContentInfo content_info; + R_TRY(this->WriteContentMetaToPlaceHolder(std::addressof(content_info), std::addressof(content_storage), meta_info, std::nullopt)); + + /* Get the path of the placeholder. */ + Path path; + content_storage.GetPlaceHolderPath(std::addressof(path), content_info.GetPlaceHolderId()); + + const bool is_temporary = content_info.is_temporary; + auto temporary_guard = SCOPE_GUARD { content_storage.DeletePlaceHolder(content_info.GetPlaceHolderId()); }; + + /* Create a new temporary InstallContentInfo if relevant. */ + if (is_temporary) { + content_info = { + .digest = content_info.digest, + .info = content_info.info, + .placeholder_id = content_info.GetPlaceHolderId(), + .meta_type = content_info.meta_type, + .verify_digest = content_info.verify_digest, + }; + } + + /* Retrieve the install content meta data. */ + AutoBuffer meta; + R_TRY(this->GetInstallContentMetaDataFromPath(std::addressof(meta), path, content_info, source_version)); + + /* Update the storage id if BuiltInSystem. */ + if (this->install_storage == StorageId::BuiltInSystem) { + InstallContentMetaWriter writer(meta.Get(), meta.GetSize()); + writer.SetStorageId(StorageId::BuiltInSystem); + } + + /* Validate the expected key if we have an expectation. */ + if (expected_key) { + InstallContentMetaReader reader(meta.Get(), meta.GetSize()); + R_UNLESS(IsExpectedKey(*expected_key, reader.GetKey()), ncm::ResultUnexpectedContentMetaPrepared()); + } + + /* Push the data. */ + R_TRY(this->data->Push(meta.Get(), meta.GetSize())); + + /* Don't delete the placeholder if not temporary. */ + if (!is_temporary) { + temporary_guard.Cancel(); + } + return ResultSuccess(); + } + + Result InstallTaskBase::PrepareContentMeta(ContentId content_id, s64 size, ContentMetaType meta_type, AutoBuffer *buffer) { + /* Create a reader. */ + PackagedContentMetaReader reader(buffer->Get(), buffer->GetSize()); + + /* Initialize the temporary buffer. */ + AutoBuffer tmp_buffer; + R_TRY(tmp_buffer.Initialize(reader.CalculateConvertInstallContentMetaSize())); + + /* Convert packaged content meta to install content meta. */ + reader.ConvertToInstallContentMeta(tmp_buffer.Get(), tmp_buffer.GetSize(), InstallContentInfo::Make(ContentInfo::Make(content_id, size, ContentType::Meta), meta_type)); + + /* Push the content meta. */ + this->data->Push(tmp_buffer.Get(), tmp_buffer.GetSize()); + return ResultSuccess(); + } + + void InstallTaskBase::PrepareAgain() { + this->SetProgressState(InstallProgressState::NotPrepared); + } + + Result InstallTaskBase::PrepareDependency() { + return ResultSuccess(); + } + + Result InstallTaskBase::PrepareSystemUpdateDependency() { + /* Cleanup on failure. */ + auto guard = SCOPE_GUARD { this->Cleanup(); }; + + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->CountInstallContentMetaData(std::addressof(count))); + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->data->Get(std::addressof(content_meta), i)); + + /* Create a reader. */ + const InstallContentMetaReader reader = content_meta.GetReader(); + + /* Skip non system update content metas. */ + if (reader.GetHeader()->type != ContentMetaType::SystemUpdate) { + continue; + } + + /* List content meta infos. */ + std::unique_ptr content_meta_infos; + s32 num_content_meta_infos; + R_TRY(this->ReadContentMetaInfoList(std::addressof(num_content_meta_infos), std::addressof(content_meta_infos), reader.GetKey())); + + /* Iterate over content meta infos. */ + for (s32 j = 0; j < num_content_meta_infos; j++) { + ContentMetaInfo &content_meta_info = content_meta_infos[j]; + const ContentMetaKey content_meta_info_key = content_meta_info.ToKey(); + + /* If exfat driver is not included or is required, prepare the content meta. */ + if (!(content_meta_info.attributes & ContentMetaAttribute_IncludesExFatDriver) || (this->config & InstallConfig_RequiresExFatDriver)) { + R_TRY(this->PrepareContentMetaIfLatest(content_meta_info_key)); + } + } + } + + guard.Cancel(); + return ResultSuccess(); + } + + Result InstallTaskBase::GetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo *out) { + /* Open the BuiltInSystem content meta database. */ + ContentMetaDatabase meta_db; + R_TRY(OpenContentMetaDatabase(std::addressof(meta_db), StorageId::BuiltInSystem)); + + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->data->Count(std::addressof(count))); + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->GetInstallContentMetaData(std::addressof(content_meta), i)); + + /* Create a reader. */ + const InstallContentMetaReader reader = content_meta.GetReader(); + + /* Skip non system update content metas. */ + if (reader.GetHeader()->type != ContentMetaType::SystemUpdate) { + continue; + } + + /* List content meta infos. */ + std::unique_ptr content_meta_infos; + s32 num_content_meta_infos; + R_TRY(this->ReadContentMetaInfoList(std::addressof(num_content_meta_infos), std::addressof(content_meta_infos), reader.GetKey())); + + /* Iterate over content meta infos. */ + for (s32 j = 0; j < num_content_meta_infos; j++) { + const ContentMetaInfo &content_meta_info = content_meta_infos[j]; + bool found = true; + + /* Get the latest key. */ + ContentMetaKey installed_key; + R_TRY_CATCH(meta_db.GetLatest(std::addressof(installed_key), content_meta_info.id)) { + R_CATCH(ncm::ResultContentMetaNotFound) { + /* Key doesn't exist, this is okay. */ + found = false; + } + } R_END_TRY_CATCH; + + /* Exfat driver included, but not required. */ + if (content_meta_info.attributes & ContentMetaAttribute_IncludesExFatDriver && !(this->config & InstallConfig_RequiresExFatDriver)) { + continue; + } + + /* No reboot is required if we're installing a version below. */ + if (found && content_meta_info.version <= installed_key.version) { + continue; + } + + /* If not rebootless, a reboot is required. */ + if (!(content_meta_info.attributes & ContentMetaAttribute_Rebootless)) { + *out = SystemUpdateTaskApplyInfo::RequireReboot; + return ResultSuccess(); + } + } + } + + *out = SystemUpdateTaskApplyInfo::RequireNoReboot; + return ResultSuccess(); + } + + Result InstallTaskBase::PrepareContentMetaIfLatest(const ContentMetaKey &key) { + /* Check if the key is newer than what is already installed. */ + bool newer_than_installed; + R_TRY(this->IsNewerThanInstalled(std::addressof(newer_than_installed), key)); + + if (newer_than_installed) { + /* Get and prepare install content meta info. */ + InstallContentMetaInfo install_content_meta_info; + R_TRY(this->GetInstallContentMetaInfo(std::addressof(install_content_meta_info), key)); + R_TRY(this->PrepareContentMeta(install_content_meta_info, key, std::nullopt)); + } + + return ResultSuccess(); + } + + Result InstallTaskBase::IsNewerThanInstalled(bool *out, const ContentMetaKey &key) { + /* Obtain a list of suitable storage ids. */ + auto storage_list = GetStorageList(this->install_storage); + + /* Iterate over storage ids. */ + for (s32 i = 0; i < storage_list.Count(); i++) { + /* Open the content meta database. */ + ContentMetaDatabase meta_db; + if (R_FAILED(OpenContentMetaDatabase(std::addressof(meta_db), storage_list[i]))) { + continue; + } + + /* Get the latest key. */ + ContentMetaKey latest_key; + R_TRY_CATCH(meta_db.GetLatest(std::addressof(latest_key), key.id)) { + R_CATCH(ncm::ResultContentMetaNotFound) { + /* Key doesn't exist, this is okay. */ + continue; + } + } R_END_TRY_CATCH; + + /* Check if installed key is newer. */ + if (latest_key.version >= key.version) { + *out = false; + return ResultSuccess(); + } + } + + /* Input key is newer. */ + *out = true; + return ResultSuccess(); + } + + Result InstallTaskBase::CountInstallContentMetaData(s32 *out_count) { + return this->data->Count(out_count); + } + + Result InstallTaskBase::GetInstallContentMetaData(InstallContentMeta *out_content_meta, s32 index) { + return this->data->Get(out_content_meta, index); + } + + Result InstallTaskBase::DeleteInstallContentMetaData(const ContentMetaKey *keys, s32 num_keys) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->CountInstallContentMetaData(std::addressof(count))); + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->GetInstallContentMetaData(std::addressof(content_meta), i)); + + /* Cleanup if the input keys contain this key. */ + if (Contains(keys, num_keys, content_meta.GetReader().GetKey())) { + R_TRY(this->CleanupOne(content_meta)); + } + } + + /* Delete the data if count < 1. */ + return this->data->Delete(keys, num_keys); + } + + Result InstallTaskBase::GetInstallContentMetaDataFromPath(AutoBuffer *out, const Path &path, const InstallContentInfo &content_info, std::optional source_version) { + AutoBuffer meta; + { + /* TODO: fs::ScopedAutoAbortDisabler aad; */ + R_TRY(ReadContentMetaPath(std::addressof(meta), path.str)); + } + + /* Create a reader. */ + PackagedContentMetaReader reader(meta.Get(), meta.GetSize()); + size_t meta_size; + + AutoBuffer install_meta_data; + if (source_version) { + /* Convert to fragment only install content meta. */ + R_TRY(reader.CalculateConvertFragmentOnlyInstallContentMetaSize(std::addressof(meta_size), *source_version)); + R_TRY(install_meta_data.Initialize(meta_size)); + reader.ConvertToFragmentOnlyInstallContentMeta(install_meta_data.Get(), install_meta_data.GetSize(), content_info, *source_version); + } else { + /* Convert to install content meta. */ + meta_size = reader.CalculateConvertInstallContentMetaSize(); + R_TRY(install_meta_data.Initialize(meta_size)); + reader.ConvertToInstallContentMeta(install_meta_data.Get(), install_meta_data.GetSize(), content_info); + } + + /* Set output. */ + *out = std::move(install_meta_data); + + return ResultSuccess(); + } + + InstallContentInfo InstallTaskBase::MakeInstallContentInfoFrom(const InstallContentMetaInfo &info, const PlaceHolderId &placeholder_id, std::optional is_tmp) { + return { + .digest = info.digest, + .info = ContentInfo::Make(info.content_id, info.content_size, ContentType::Meta, 0), + .placeholder_id = placeholder_id, + .meta_type = info.key.type, + .install_state = InstallState::Prepared, + .verify_digest = info.verify_digest, + .storage_id = StorageId::BuiltInSystem, + .is_temporary = is_tmp ? *is_tmp : (this->install_storage != StorageId::BuiltInSystem), + }; + } + + InstallProgress InstallTaskBase::GetProgress() { + std::scoped_lock lk(this->progress_mutex); + return this->progress; + } + + void InstallTaskBase::ResetLastResult() { + this->SetLastResult(ResultSuccess()); + } + + void InstallTaskBase::SetTotalSize(s64 size) { + std::scoped_lock(this->progress_mutex); + this->progress.total_size = size; + } + + void InstallTaskBase::IncrementProgress(s64 size) { + std::scoped_lock lk(this->progress_mutex); + this->progress.installed_size += size; + } + + void InstallTaskBase::SetLastResult(Result last_result) { + std::scoped_lock lk(this->progress_mutex); + this->data->SetLastResult(last_result); + this->progress.SetLastResult(last_result); + } + + void InstallTaskBase::CleanupProgress() { + std::scoped_lock(this->progress_mutex); + this->progress = {}; + } + + InstallThroughput InstallTaskBase::GetThroughput() { + std::scoped_lock lk(this->throughput_mutex); + return this->throughput; + } + + void InstallTaskBase::ResetThroughputMeasurement() { + std::scoped_lock lk(this->throughput_mutex); + this->throughput = { .elapsed_time = TimeSpan() }; + this->throughput_start_time = TimeSpan(); + } + + void InstallTaskBase::StartThroughputMeasurement() { + std::scoped_lock lk(this->throughput_mutex); + this->throughput = { .elapsed_time = TimeSpan() }; + this->throughput_start_time = os::GetSystemTick().ToTimeSpan(); + } + + void InstallTaskBase::UpdateThroughputMeasurement(s64 throughput) { + std::scoped_lock lk(this->throughput_mutex); + + /* Update throughput only if start time has been set. */ + if (this->throughput_start_time.GetNanoSeconds() != 0) { + this->throughput.installed += throughput; + this->throughput.elapsed_time = os::GetSystemTick().ToTimeSpan() - this->throughput_start_time; + } + } + + Result InstallTaskBase::CalculateContentsSize(s64 *out_size, const ContentMetaKey &key, StorageId storage_id) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->data->Count(std::addressof(count))); + + /* Open the content storage. */ + ContentStorage content_storage; + R_TRY(ncm::OpenContentStorage(&content_storage, storage_id)); + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->data->Get(std::addressof(content_meta), i)); + + /* Create a reader. */ + const InstallContentMetaReader reader = content_meta.GetReader(); + + /* Skip this content meta if the key doesn't match. */ + if (reader.GetKey() != key) { + continue; + } + + /* If the storage is unique and doesn't match, continue. */ + if (IsUniqueStorage(storage_id) && reader.GetStorageId() != storage_id) { + continue; + } + + /* Set the out size to 0. */ + *out_size = 0; + + /* Sum the sizes from the content infos. */ + for (size_t j = 0; j < reader.GetContentCount(); j++) { + const auto *content_info = reader.GetContentInfo(j); + + /* If this content info isn't prepared, continue. */ + if (content_info->install_state == InstallState::NotPrepared) { + continue; + } + + /* Check if this content info has a placeholder. */ + bool has_placeholder; + R_TRY(content_storage.HasPlaceHolder(std::addressof(has_placeholder), content_info->GetPlaceHolderId())); + + /* Add the placeholder size to the total. */ + if (has_placeholder) { + *out_size += content_info->GetSize(); + } + } + + /* No need to look for any further keys. */ + return ResultSuccess(); + } + + *out_size = 0; + return ResultSuccess(); + } + + Result InstallTaskBase::ReadContentMetaInfoList(s32 *out_count, std::unique_ptr *out_meta_infos, const ContentMetaKey &key) { + /* Get the install content meta info. */ + InstallContentMetaInfo install_content_meta_info; + R_TRY(this->GetInstallContentMetaInfo(std::addressof(install_content_meta_info), key)); + + /* Open the BuiltInSystem content storage. */ + ContentStorage content_storage; + R_TRY(ncm::OpenContentStorage(&content_storage, StorageId::BuiltInSystem)); + + /* Write content meta to a placeholder. */ + InstallContentInfo content_info; + R_TRY(this->WriteContentMetaToPlaceHolder(std::addressof(content_info), std::addressof(content_storage), install_content_meta_info, true)); + + const PlaceHolderId placeholder_id = content_info.GetPlaceHolderId(); + + /* Get the path of the new placeholder. */ + Path path; + content_storage.GetPlaceHolderPath(std::addressof(path), placeholder_id); + + /* Read the variation list. */ + R_TRY(ReadVariationContentMetaInfoList(out_count, out_meta_infos, path, this->firmware_variation_id)); + + /* Delete the placeholder. */ + content_storage.DeletePlaceHolder(placeholder_id); + return ResultSuccess(); + } + + Result InstallTaskBase::FindMaxRequiredApplicationVersion(u32 *out) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->data->Count(std::addressof(count))); + + u32 max_version = 0; + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->data->Get(std::addressof(content_meta), i)); + + /* Create a reader. */ + const InstallContentMetaReader reader = content_meta.GetReader(); + + /* Check if the meta type is for add on content. */ + if (reader.GetKey().type == ContentMetaType::AddOnContent) { + const auto *extended_header = reader.GetExtendedHeader(); + + /* Set the max version if higher. */ + max_version = std::max(max_version, extended_header->required_application_version); + } + } + + *out = max_version; + return ResultSuccess(); + } + + Result InstallTaskBase::ListOccupiedSize(s32 *out_written, InstallTaskOccupiedSize *out_list, s32 out_list_size, s32 offset) { + AMS_ABORT_UNLESS(offset >= 0); + + /* Count the number of content meta entries. */ + s32 data_count; + R_TRY(this->data->Count(std::addressof(data_count))); + + /* Iterate over content meta. */ + s32 count = 0; + for (s32 i = offset; i < data_count && count < out_list_size; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->data->Get(std::addressof(content_meta), i)); + + /* Create a reader. */ + const InstallContentMetaReader reader = content_meta.GetReader(); + const StorageId storage_id = reader.GetStorageId(); + + s64 total_size = 0; + + for (size_t j = 0; j < reader.GetContentCount(); j++) { + const auto *content_info = reader.GetContentInfo(j); + + /* Skip the content info if not prepared. */ + if (content_info->GetInstallState() == InstallState::NotPrepared) { + continue; + } + + /* Open the relevant content storage. */ + ContentStorage content_storage; + R_TRY_CATCH(ncm::OpenContentStorage(std::addressof(content_storage), storage_id)) { + R_CATCH(ncm::ResultContentStorageNotActive) { break; } + } R_END_TRY_CATCH; + + /* Check if this content info has a placeholder. */ + bool has_placeholder; + R_TRY(content_storage.HasPlaceHolder(std::addressof(has_placeholder), content_info->GetPlaceHolderId())); + + if (has_placeholder) { + total_size += content_info->GetSize(); + } + } + + /* Output this InstallTaskOccupiedSize. */ + out_list[count++] = { + .key = reader.GetKey(), + .size = total_size, + .storage_id = storage_id, + }; + } + + /* Write the out count. */ + *out_written = count; + + return ResultSuccess(); + } + + void InstallTaskBase::SetProgressState(InstallProgressState state) { + std::scoped_lock(this->progress_mutex); + this->data->SetState(state); + this->progress.state = state; + } + + Result InstallTaskBase::FindMaxRequiredSystemVersion(u32 *out) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->data->Count(std::addressof(count))); + + u32 max_version = 0; + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->data->Get(std::addressof(content_meta), i)); + + /* Create a reader. */ + const InstallContentMetaReader reader = content_meta.GetReader(); + + if (reader.GetHeader()->type == ContentMetaType::Application) { + const auto *extended_header = reader.GetExtendedHeader(); + + /* Set the max version if higher. */ + if (extended_header->required_system_version >= max_version) { + max_version = extended_header->required_system_version; + } + } else if (reader.GetHeader()->type == ContentMetaType::Patch) { + const auto *extended_header = reader.GetExtendedHeader(); + + /* Set the max version if higher. */ + if (extended_header->required_system_version >= max_version) { + max_version = extended_header->required_system_version; + } + } + } + + *out = max_version; + return ResultSuccess(); + } + + Result InstallTaskBase::CanContinue() { + switch (this->GetProgress().state) { + case InstallProgressState::NotPrepared: + case InstallProgressState::DataPrepared: + R_UNLESS(!this->IsCancelRequested(), ncm::ResultCreatePlaceHolderCancelled()); + break; + case InstallProgressState::Prepared: + R_UNLESS(!this->IsCancelRequested(), ncm::ResultWritePlaceHolderCancelled()); + break; + default: + break; + } + + return ResultSuccess(); + } + + Result InstallTaskBase::ListRightsIds(s32 *out_count, Span out_span, const ContentMetaKey &key, s32 offset) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->data->Count(std::addressof(count))); + + /* Ensure count is >= 1. */ + R_UNLESS(count >= 1, ncm::ResultContentMetaNotFound()); + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->data->Get(std::addressof(content_meta), i)); + + /* Create a reader. */ + const InstallContentMetaReader reader = content_meta.GetReader(); + + /* List rights ids if the reader's key matches ours. */ + if (reader.GetKey() == key) { + return this->ListRightsIdsByInstallContentMeta(out_count, out_span, content_meta, offset); + } + } + + return ncm::ResultContentMetaNotFound(); + } + + Result InstallTaskBase::ListRightsIdsByInstallContentMeta(s32 *out_count, Span out_span, const InstallContentMeta &content_meta, s32 offset) { + /* If the offset is greater than zero, we can't create a unique span of rights ids. */ + /* Thus, we have nothing to list. */ + if (offset > 0) { + *out_count = 0; + return ResultSuccess(); + } + + /* Create a reader. */ + const InstallContentMetaReader reader = content_meta.GetReader(); + + s32 count = 0; + for (size_t i = 0; i < reader.GetContentCount(); i++) { + const auto *content_info = reader.GetContentInfo(i); + + /* Skip meta content infos and already installed content infos. Also skip if the content meta has already been comitted. */ + if (content_info->GetType() == ContentType::Meta || content_info->GetInstallState() == InstallState::Installed || reader.GetHeader()->committed) { + continue; + } + + /* Open the relevant content storage. */ + ContentStorage content_storage; + R_TRY(ncm::OpenContentStorage(&content_storage, content_info->storage_id)); + + /* Get the rights id. */ + RightsId rights_id; + R_TRY(content_storage.GetRightsId(std::addressof(rights_id), content_info->GetPlaceHolderId())); + + /* Skip empty rights ids. */ + if (rights_id.id == fs::InvalidRightsId) { + continue; + } + + /* Output the rights id. */ + out_span[count++] = rights_id; + } + + /* Sort and remove duplicate ids from the output span. */ + std::sort(out_span.begin(), out_span.end()); + *out_count = std::distance(out_span.begin(), std::unique(out_span.begin(), out_span.end())); + return ResultSuccess(); + } +} diff --git a/libraries/libstratosphere/source/ncm/ncm_install_task_data.cpp b/libraries/libstratosphere/source/ncm/ncm_install_task_data.cpp new file mode 100644 index 000000000..59c246403 --- /dev/null +++ b/libraries/libstratosphere/source/ncm/ncm_install_task_data.cpp @@ -0,0 +1,397 @@ +/* + * Copyright (c) 2019-2020 Adubbz, Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::ncm { + + namespace { + + using BoundedPath = kvdb::BoundedString<64>; + + constexpr inline bool Includes(const ContentMetaKey *keys, s32 count, const ContentMetaKey &key) { + for (s32 i = 0; i < count; i++) { + if (keys[i] == key) { + return true; + } + } + return false; + } + + } + + Result InstallTaskDataBase::Get(InstallContentMeta *out, s32 index) { + /* Determine the data size. */ + size_t data_size; + R_TRY(this->GetSize(std::addressof(data_size), index)); + + /* Create a buffer and read data into it. */ + std::unique_ptr buffer(new (std::nothrow) char[data_size]); + R_UNLESS(buffer != nullptr, ncm::ResultAllocationFailed()); + R_TRY(this->Get(index, buffer.get(), data_size)); + + /* Output the buffer and size. */ + out->data = std::move(buffer); + out->size = data_size; + return ResultSuccess(); + } + + Result InstallTaskDataBase::Update(const InstallContentMeta &content_meta, s32 index) { + return this->Update(index, content_meta.data.get(), content_meta.size); + } + + Result InstallTaskDataBase::Has(bool *out, u64 id) { + s32 count; + R_TRY(this->Count(std::addressof(count))); + + /* Iterate over each entry. */ + for (s32 i = 0; i < count; i++) { + InstallContentMeta content_meta; + R_TRY(this->Get(std::addressof(content_meta), i)); + + /* If the id matches we are successful. */ + if (content_meta.GetReader().GetKey().id == id) { + *out = true; + return ResultSuccess(); + } + } + + /* We didn't find the value. */ + *out = false; + return ResultSuccess(); + } + + Result MemoryInstallTaskData::GetProgress(InstallProgress *out_progress) { + /* Initialize install progress. */ + InstallProgress install_progress = { + .state = this->state, + }; + install_progress.SetLastResult(this->last_result); + + /* Only states after prepared are allowed. */ + if (this->state != InstallProgressState::NotPrepared && this->state != InstallProgressState::DataPrepared) { + for (auto &data_holder : this->data_list) { + const InstallContentMetaReader reader = data_holder.GetReader(); + + /* Sum the sizes from this entry's content infos. */ + for (size_t i = 0; i < reader.GetContentCount(); i++) { + const InstallContentInfo *content_info = reader.GetContentInfo(i); + install_progress.installed_size += content_info->GetSize(); + install_progress.total_size += content_info->GetSizeWritten(); + } + } + } + + *out_progress = install_progress; + return ResultSuccess(); + } + + Result MemoryInstallTaskData::GetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo *out_info) { + *out_info = this->system_update_task_apply_info; + return ResultSuccess(); + } + + Result MemoryInstallTaskData::SetState(InstallProgressState state) { + this->state = state; + return ResultSuccess(); + } + + Result MemoryInstallTaskData::SetLastResult(Result result) { + this->last_result = result; + return ResultSuccess(); + } + + Result MemoryInstallTaskData::SetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo info) { + this->system_update_task_apply_info = info; + return ResultSuccess(); + } + + Result MemoryInstallTaskData::Push(const void *data, size_t size) { + /* Allocate a new data holder. */ + auto holder = std::unique_ptr(new (std::nothrow) DataHolder()); + R_UNLESS(holder != nullptr, ncm::ResultAllocationFailed()); + + /* Allocate memory for the content meta data. */ + holder->data = std::unique_ptr(new (std::nothrow) char[size]); + R_UNLESS(holder->data != nullptr, ncm::ResultAllocationFailed()); + holder->size = size; + + /* Copy data to the data holder. */ + std::memcpy(holder->data.get(), data, size); + + /* Put the data holder into the data list. */ + this->data_list.push_back(*holder); + + /* Relinquish control over the memory allocated to the data holder. */ + holder.release(); + + return ResultSuccess(); + } + + Result MemoryInstallTaskData::Count(s32 *out) { + *out = this->data_list.size(); + return ResultSuccess(); + } + + Result MemoryInstallTaskData::GetSize(size_t *out_size, s32 index) { + /* Find the correct entry in the list. */ + s32 count = 0; + for (auto &data_holder : this->data_list) { + if (index == count++) { + *out_size = data_holder.size; + return ResultSuccess(); + } + } + /* Out of bounds indexing is an unrecoverable error. */ + AMS_ABORT(); + } + + Result MemoryInstallTaskData::Get(s32 index, void *out, size_t out_size) { + /* Find the correct entry in the list. */ + s32 count = 0; + for (auto &data_holder : this->data_list) { + if (index == count++) { + R_UNLESS(out_size >= data_holder.size, ncm::ResultBufferInsufficient()); + std::memcpy(out, data_holder.data.get(), data_holder.size); + return ResultSuccess(); + } + } + /* Out of bounds indexing is an unrecoverable error. */ + AMS_ABORT(); + } + + Result MemoryInstallTaskData::Update(s32 index, const void *data, size_t data_size) { + /* Find the correct entry in the list. */ + s32 count = 0; + for (auto &data_holder : this->data_list) { + if (index == count++) { + R_UNLESS(data_size == data_holder.size, ncm::ResultBufferInsufficient()); + std::memcpy(data_holder.data.get(), data, data_size); + return ResultSuccess(); + } + } + /* Out of bounds indexing is an unrecoverable error. */ + AMS_ABORT(); + } + Result MemoryInstallTaskData::Delete(const ContentMetaKey *keys, s32 num_keys) { + /* Iterate over keys. */ + for (s32 i = 0; i < num_keys; i++) { + const auto &key = keys[i]; + + /* Find and remove matching data from the list. */ + for (auto &data_holder : this->data_list) { + if (key == data_holder.GetReader().GetKey()) { + this->data_list.erase(this->data_list.iterator_to(data_holder)); + delete std::addressof(data_holder); + break; + } + } + } + + return ResultSuccess(); + } + + Result MemoryInstallTaskData::Cleanup() { + while (!this->data_list.empty()) { + auto *data_holder = std::addressof(this->data_list.front()); + this->data_list.pop_front(); + delete data_holder; + } + return ResultSuccess(); + } + + Result FileInstallTaskData::Create(const char *path, s32 max_entries) { + /* Create the file. */ + R_TRY(fs::CreateFile(path, 0)); + + /* Open the file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Write | fs::OpenMode_AllowAppend)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Create an initial header and write it to the file. */ + const Header header = MakeInitialHeader(max_entries); + return fs::WriteFile(file, 0, std::addressof(header), sizeof(Header), fs::WriteOption::Flush); + } + + Result FileInstallTaskData::Initialize(const char *path) { + std::strncpy(this->path, path, sizeof(this->path)); + this->path[sizeof(this->path) - 1] = '\x00'; + return this->Read(std::addressof(this->header), sizeof(Header), 0); + } + + Result FileInstallTaskData::GetProgress(InstallProgress *out_progress) { + /* Initialize install progress. */ + InstallProgress install_progress = { + .state = this->header.progress_state, + }; + install_progress.SetLastResult(this->header.last_result); + + /* Only states after prepared are allowed. */ + if (this->header.progress_state != InstallProgressState::NotPrepared && this->header.progress_state != InstallProgressState::DataPrepared) { + for (size_t i = 0; i < this->header.count; i++) { + /* Obtain the content meta for this entry. */ + InstallContentMeta content_meta; + R_TRY(InstallTaskDataBase::Get(std::addressof(content_meta), i)); + const InstallContentMetaReader reader = content_meta.GetReader(); + + /* Sum the sizes from this entry's content infos. */ + for (size_t j = 0; j < reader.GetContentCount(); j++) { + const InstallContentInfo *content_info = reader.GetContentInfo(j); + install_progress.installed_size += content_info->GetSize(); + install_progress.total_size += content_info->GetSizeWritten(); + } + } + } + + *out_progress = install_progress; + return ResultSuccess(); + } + + Result FileInstallTaskData::GetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo *out_info) { + *out_info = this->header.system_update_task_apply_info; + return ResultSuccess(); + } + + Result FileInstallTaskData::SetState(InstallProgressState state) { + this->header.progress_state = state; + return this->WriteHeader(); + } + + Result FileInstallTaskData::SetLastResult(Result result) { + this->header.last_result = result; + return this->WriteHeader(); + } + + Result FileInstallTaskData::SetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo info) { + this->header.system_update_task_apply_info = info; + return this->WriteHeader(); + } + + Result FileInstallTaskData::Push(const void *data, size_t data_size) { + R_UNLESS(this->header.count < this->header.max_entries, ncm::ResultBufferInsufficient()); + + /* Create a new entry info. Data of the given size will be stored at the end of the file. */ + const EntryInfo entry_info = { this->header.last_data_offset, static_cast(data_size) }; + + /* Write the new entry info. */ + R_TRY(this->Write(std::addressof(entry_info), sizeof(EntryInfo), GetEntryInfoOffset(this->header.count))); + + /* Write the data to the offset in the entry info. */ + R_TRY(this->Write(data, data_size, entry_info.offset)); + + /* Update the header for the new entry. */ + this->header.last_data_offset += data_size; + this->header.count++; + + /* Write the updated header. */ + return this->WriteHeader(); + } + + Result FileInstallTaskData::Count(s32 *out) { + *out = this->header.count; + return ResultSuccess(); + } + + Result FileInstallTaskData::GetSize(size_t *out_size, s32 index) { + EntryInfo entry_info; + R_TRY(this->GetEntryInfo(std::addressof(entry_info), index)); + *out_size = entry_info.size; + return ResultSuccess(); + } + + Result FileInstallTaskData::Get(s32 index, void *out, size_t out_size) { + /* Obtain the entry info. */ + EntryInfo entry_info; + R_TRY(this->GetEntryInfo(std::addressof(entry_info), index)); + + /* Read the entry to the output buffer. */ + R_UNLESS(entry_info.size <= static_cast(out_size), ncm::ResultBufferInsufficient()); + return this->Read(out, out_size, entry_info.offset); + } + + Result FileInstallTaskData::Update(s32 index, const void *data, size_t data_size) { + /* Obtain the entry info. */ + EntryInfo entry_info; + R_TRY(this->GetEntryInfo(std::addressof(entry_info), index)); + + /* Data size must match existing data size. */ + R_UNLESS(entry_info.size == static_cast(data_size), ncm::ResultBufferInsufficient()); + return this->Write(data, data_size, entry_info.offset); + } + + Result FileInstallTaskData::Delete(const ContentMetaKey *keys, s32 num_keys) { + /* Create the path for the temporary data. */ + BoundedPath tmp_path(this->path); + tmp_path.Append(".tmp"); + + /* Create a new temporary install task data. */ + FileInstallTaskData install_task_data; + R_TRY(FileInstallTaskData::Create(tmp_path, this->header.max_entries)); + R_TRY(install_task_data.Initialize(tmp_path)); + + /* Get the number of entries. */ + s32 count; + R_TRY(this->Count(std::addressof(count))); + + /* Copy entries that are not excluded to the new install task data. */ + for (s32 i = 0; i < count; i++) { + InstallContentMeta content_meta; + R_TRY(InstallTaskDataBase::Get(std::addressof(content_meta), i)); + + /* Check if entry is excluded. If not, push it to our new install task data. */ + if (Includes(keys, num_keys, content_meta.GetReader().GetKey())) { + continue; + } + + /* NOTE: Nintendo doesn't check that this operation succeeds. */ + install_task_data.Push(content_meta.data.get(), content_meta.size); + } + + /* Change from our current data to the new data. */ + this->header = install_task_data.header; + R_TRY(fs::DeleteFile(this->path)); + return fs::RenameFile(tmp_path, this->path); + } + + Result FileInstallTaskData::Cleanup() { + this->header = MakeInitialHeader(this->header.max_entries); + return this->WriteHeader(); + } + + Result FileInstallTaskData::GetEntryInfo(EntryInfo *out_entry_info, s32 index) { + AMS_ABORT_UNLESS(static_cast(index) < this->header.count); + return this->Read(out_entry_info, sizeof(EntryInfo), GetEntryInfoOffset(index)); + } + + Result FileInstallTaskData::Write(const void *data, size_t size, s64 offset) { + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), this->path, fs::OpenMode_Write | fs::OpenMode_AllowAppend)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + return fs::WriteFile(file, offset, data, size, fs::WriteOption::Flush); + } + + Result FileInstallTaskData::Read(void *out, size_t out_size, s64 offset) { + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), this->path, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + return fs::ReadFile(file, offset, out, out_size); + } + + Result FileInstallTaskData::WriteHeader() { + return this->Write(std::addressof(this->header), sizeof(Header), 0); + } + +} \ No newline at end of file diff --git a/libraries/libstratosphere/source/ncm/ncm_package_install_task.cpp b/libraries/libstratosphere/source/ncm/ncm_package_install_task.cpp new file mode 100644 index 000000000..688aff3b9 --- /dev/null +++ b/libraries/libstratosphere/source/ncm/ncm_package_install_task.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019-2020 Adubbz, Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "ncm_fs_utils.hpp" + +namespace ams::ncm { + + bool PackageInstallTask::IsContentMetaContentName(const char *name) { + return impl::PathView(name).HasSuffix(".cnmt"); + } + + Result PackageInstallTask::Initialize(const char *package_root, StorageId storage_id, void *buffer, size_t buffer_size, bool ignore_ticket) { + return PackageInstallTaskBase::Initialize(package_root, buffer, buffer_size, storage_id, std::addressof(this->data), ignore_ticket ? InstallConfig_IgnoreTicket : InstallConfig_None); + } + + Result PackageInstallTask::GetInstallContentMetaInfo(InstallContentMetaInfo *out_info, const ContentMetaKey &key) { + return ncm::ResultContentNotFound(); + } + + Result PackageInstallTask::PrepareInstallContentMetaData() { + /* Open the directory. */ + fs::DirectoryHandle dir; + R_TRY(fs::OpenDirectory(std::addressof(dir), this->GetPackageRootPath(), fs::OpenDirectoryMode_File)); + ON_SCOPE_EXIT { fs::CloseDirectory(dir); }; + + while (true) { + /* Read the current entry. */ + s64 count; + fs::DirectoryEntry entry; + R_TRY(fs::ReadDirectory(std::addressof(count), std::addressof(entry), dir, 1)); + + /* No more entries remain, we are done. */ + if (count == 0) { + break; + } + + /* Check if this entry is content meta. */ + if (this->IsContentMetaContentName(entry.name)) { + /* Prepare content meta if id is valid. */ + std::optional id = GetContentIdFromString(entry.name, strnlen(entry.name, fs::EntryNameLengthMax + 1)); + R_UNLESS(id, ncm::ResultInvalidPackageFormat()); + R_TRY(this->PrepareContentMeta(InstallContentMetaInfo::MakeUnverifiable(*id, entry.file_size), std::nullopt, std::nullopt)); + } + } + + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/source/ncm/ncm_package_install_task_base.cpp b/libraries/libstratosphere/source/ncm/ncm_package_install_task_base.cpp new file mode 100644 index 000000000..646841605 --- /dev/null +++ b/libraries/libstratosphere/source/ncm/ncm_package_install_task_base.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2019-2020 Adubbz, Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::ncm { + + Result PackageInstallTaskBase::Initialize(const char *package_root_path, void *buffer, size_t buffer_size, StorageId storage_id, InstallTaskDataBase *data, u32 config) { + R_TRY(InstallTaskBase::Initialize(storage_id, data, config)); + this->package_root.Set(package_root_path); + this->buffer = buffer; + this->buffer_size = buffer_size; + return ResultSuccess(); + } + + Result PackageInstallTaskBase::OnWritePlaceHolder(const ContentMetaKey &key, InstallContentInfo *content_info) { + PackagePath path; + if (content_info->GetType() == ContentType::Meta) { + this->CreateContentMetaPath(std::addressof(path), content_info->GetId()); + } else { + this->CreateContentPath(std::addressof(path), content_info->GetId()); + } + + /* Open the file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Continuously write the file to the placeholder until there is nothing left to write. */ + while (true) { + /* Read as much of the remainder of the file as possible. */ + size_t size_read; + R_TRY(fs::ReadFile(std::addressof(size_read), file, content_info->written, this->buffer, this->buffer_size)); + + /* There is nothing left to read. */ + if (size_read == 0) { + break; + } + + /* Write the placeholder. */ + R_TRY(this->WritePlaceHolderBuffer(content_info, this->buffer, size_read)); + } + + return ResultSuccess(); + } + + Result PackageInstallTaskBase::InstallTicket(const fs::RightsId &rights_id, ContentMetaType meta_type) { + /* Read ticket from file. */ + s64 ticket_size; + std::unique_ptr ticket; + { + fs::FileHandle ticket_file; + { + PackagePath ticket_path; + this->CreateTicketPath(std::addressof(ticket_path), rights_id); + R_TRY(fs::OpenFile(std::addressof(ticket_file), ticket_path, fs::OpenMode_Read)); + } + ON_SCOPE_EXIT { fs::CloseFile(ticket_file); }; + + R_TRY(fs::GetFileSize(std::addressof(ticket_size), ticket_file)); + + ticket.reset(new (std::nothrow) char[static_cast(ticket_size)]); + R_UNLESS(ticket != nullptr, ncm::ResultAllocationFailed()); + R_TRY(fs::ReadFile(ticket_file, 0, ticket.get(), static_cast(ticket_size))); + } + + + /* Read certificate from file. */ + s64 cert_size; + std::unique_ptr cert; + { + fs::FileHandle cert_file; + { + PackagePath cert_path; + this->CreateCertificatePath(std::addressof(cert_path), rights_id); + R_TRY(fs::OpenFile(std::addressof(cert_file), cert_path, fs::OpenMode_Read)); + } + ON_SCOPE_EXIT { fs::CloseFile(cert_file); }; + + R_TRY(fs::GetFileSize(std::addressof(cert_size), cert_file)); + + cert.reset(new (std::nothrow) char[static_cast(cert_size)]); + R_UNLESS(cert != nullptr, ncm::ResultAllocationFailed()); + R_TRY(fs::ReadFile(cert_file, 0, cert.get(), static_cast(cert_size))); + } + + /* TODO: es::ImportTicket() */ + /* TODO: How should es be handled without undesired effects? */ + + return ResultSuccess(); + } + + void PackageInstallTaskBase::CreateContentPath(PackagePath *out_path, ContentId content_id) { + char str[ContentIdStringLength + 1] = {}; + GetStringFromContentId(str, sizeof(str), content_id); + out_path->SetFormat("%s%s%s", this->package_root.Get(), str, ".nca"); + } + + void PackageInstallTaskBase::CreateContentMetaPath(PackagePath *out_path, ContentId content_id) { + char str[ContentIdStringLength + 1] = {}; + GetStringFromContentId(str, sizeof(str), content_id); + out_path->SetFormat("%s%s%s", this->package_root.Get(), str, ".cnmt.nca"); + } + + void PackageInstallTaskBase::CreateTicketPath(PackagePath *out_path, fs::RightsId id) { + char str[RightsIdStringLength + 1] = {}; + GetStringFromRightsId(str, sizeof(str), id); + out_path->SetFormat("%s%s%s", this->package_root.Get(), str, ".tik"); + } + + void PackageInstallTaskBase::CreateCertificatePath(PackagePath *out_path, fs::RightsId id) { + char str[RightsIdStringLength + 1] = {}; + GetStringFromRightsId(str, sizeof(str), id); + out_path->SetFormat("%s%s%s", this->package_root.Get(), str, ".cert"); + } + +} diff --git a/libraries/libstratosphere/source/ncm/ncm_package_system_update_task.cpp b/libraries/libstratosphere/source/ncm/ncm_package_system_update_task.cpp new file mode 100644 index 000000000..881427225 --- /dev/null +++ b/libraries/libstratosphere/source/ncm/ncm_package_system_update_task.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2019-2020 Adubbz, Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::ncm { + + PackageSystemUpdateTask::~PackageSystemUpdateTask() { + if (this->context_path.GetLength() > 0) { + fs::DeleteFile(this->context_path); + } + this->Inactivate(); + } + + void PackageSystemUpdateTask::Inactivate() { + if (this->gamecard_content_meta_database_active) { + InactivateContentMetaDatabase(StorageId::GameCard); + this->gamecard_content_meta_database_active = false; + } + } + + Result PackageSystemUpdateTask::Initialize(const char *package_root, const char *context_path, void *buffer, size_t buffer_size, bool requires_exfat_driver, FirmwareVariationId firmware_variation_id) { + /* Set the firmware variation id. */ + this->SetFirmwareVariationId(firmware_variation_id); + + /* Activate the game card content meta database. */ + R_TRY(ActivateContentMetaDatabase(StorageId::GameCard)); + this->gamecard_content_meta_database_active = true; + auto meta_db_guard = SCOPE_GUARD { this->Inactivate(); }; + + /* Open the game card content meta database. */ + OpenContentMetaDatabase(std::addressof(this->package_db), StorageId::GameCard); + + ContentMetaDatabaseBuilder builder(std::addressof(this->package_db)); + + /* Cleanup and build the content meta database. */ + R_TRY(builder.Cleanup()); + R_TRY(builder.BuildFromPackage(package_root)); + + /* Create a new context file. */ + fs::DeleteFile(context_path); + R_TRY(FileInstallTaskData::Create(context_path, GameCardMaxContentMetaCount)); + auto context_guard = SCOPE_GUARD { fs::DeleteFile(context_path); }; + + /* Initialize data. */ + R_TRY(this->data.Initialize(context_path)); + + /* Initialize PackageInstallTaskBase. */ + u32 config = requires_exfat_driver ? InstallConfig_SystemUpdate : InstallConfig_SystemUpdate | InstallConfig_RequiresExFatDriver; + R_TRY(PackageInstallTaskBase::Initialize(package_root, buffer, buffer_size, StorageId::BuiltInSystem, std::addressof(this->data), config)); + + /* Cancel guards. */ + context_guard.Cancel(); + meta_db_guard.Cancel(); + + /* Set the context path. */ + this->context_path.Set(context_path); + return ResultSuccess(); + } + + std::optional PackageSystemUpdateTask::GetSystemUpdateMetaKey() { + StorageContentMetaKey storage_keys[0x10]; + s32 ofs = 0; + + s32 count = 0; + do { + /* List content meta keys. */ + if (R_FAILED(this->ListContentMetaKey(std::addressof(count), storage_keys, util::size(storage_keys), ofs, ListContentMetaKeyFilter::All))) { + break; + } + + /* Add listed keys to the offset. */ + ofs += count; + + /* Check if any of these keys are for a SystemUpdate. */ + for (s32 i = 0; i < count; i++) { + const ContentMetaKey &key = storage_keys[i].key; + if (key.type == ContentMetaType::SystemUpdate) { + return key; + } + } + } while (count > 0); + + return std::nullopt; + } + + Result PackageSystemUpdateTask::GetInstallContentMetaInfo(InstallContentMetaInfo *out, const ContentMetaKey &key) { + /* Get the content info for the key. */ + ContentInfo info; + R_TRY(this->GetContentInfoOfContentMeta(std::addressof(info), key)); + + /* Create a new install content meta info. */ + *out = InstallContentMetaInfo::MakeUnverifiable(info.GetId(), info.GetSize(), key); + return ResultSuccess(); + } + + Result PackageSystemUpdateTask::PrepareInstallContentMetaData() { + /* Obtain a SystemUpdate key. */ + ContentMetaKey key; + auto list_count = this->package_db.ListContentMeta(std::addressof(key), 1, ContentMetaType::SystemUpdate); + R_UNLESS(list_count.written > 0, ncm::ResultSystemUpdateNotFoundInPackage()); + + /* Get the content info for the key. */ + ContentInfo info; + R_TRY(this->GetContentInfoOfContentMeta(std::addressof(info), key)); + + /* Prepare the content meta. */ + return this->PrepareContentMeta(InstallContentMetaInfo::MakeUnverifiable(info.GetId(), info.GetSize(), key), key, std::nullopt); + } + + Result PackageSystemUpdateTask::PrepareDependency() { + return this->PrepareSystemUpdateDependency(); + } + + Result PackageSystemUpdateTask::GetContentInfoOfContentMeta(ContentInfo *out, const ContentMetaKey &key) { + s32 ofs = 0; + while (true) { + /* List content infos. */ + s32 count; + ContentInfo info; + R_TRY(this->package_db.ListContentInfo(std::addressof(count), std::addressof(info), 1, key, ofs++)); + + /* No content infos left to list. */ + if (count == 0) { + break; + } + + /* Check if the info is for meta content. */ + if (info.GetType() == ContentType::Meta) { + *out = info; + return ResultSuccess(); + } + } + + /* Not found. */ + return ncm::ResultContentInfoNotFound(); + } + +} diff --git a/libraries/libstratosphere/source/ncm/ncm_storage_utils.cpp b/libraries/libstratosphere/source/ncm/ncm_storage_utils.cpp new file mode 100644 index 000000000..d5d337727 --- /dev/null +++ b/libraries/libstratosphere/source/ncm/ncm_storage_utils.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2019-2020 Adubbz, Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::ncm { + + Result SelectDownloadableStorage(StorageId *out_storage_id, StorageId storage_id, s64 required_size) { + auto list = GetStorageList(storage_id); + for (s32 i = 0; i < list.Count(); i++) { + auto candidate = list[i]; + + /* Open the content meta database. NOTE: This is unused. */ + ContentMetaDatabase content_meta_database; + if (R_FAILED(ncm::OpenContentMetaDatabase(std::addressof(content_meta_database), candidate))) { + continue; + } + + /* Open the content storage. */ + ContentStorage content_storage; + if (R_FAILED(ncm::OpenContentStorage(std::addressof(content_storage), candidate))) { + continue; + } + + /* Get the free space on this storage. */ + s64 free_space_size; + R_TRY(content_storage.GetFreeSpaceSize(std::addressof(free_space_size))); + + /* There must be more free space than is required. */ + if (free_space_size <= required_size) { + continue; + } + + /* Output the storage id. */ + *out_storage_id = storage_id; + return ResultSuccess(); + } + + return ncm::ResultNotEnoughInstallSpace(); + } + + Result SelectPatchStorage(StorageId *out_storage_id, StorageId storage_id, PatchId patch_id) { + auto list = GetStorageList(storage_id); + u32 version = 0; + *out_storage_id = storage_id; + + for (s32 i = 0; i < list.Count(); i++) { + auto candidate = list[i]; + + /* Open the content meta database. */ + ContentMetaDatabase content_meta_database; + if (R_FAILED(ncm::OpenContentMetaDatabase(std::addressof(content_meta_database), candidate))) { + continue; + } + + /* Get the latest key. */ + ContentMetaKey key; + R_TRY_CATCH(content_meta_database.GetLatest(std::addressof(key), patch_id.value)) { + R_CATCH(ncm::ResultContentMetaNotFound) { continue; } + } R_END_TRY_CATCH; + + if (key.version > version) { + version = key.version; + *out_storage_id = candidate; + } + } + + return ResultSuccess(); + } + + namespace { + + constexpr const char * const StorageIdStrings[] = { + "None", + "Host", + "GameCard", + "BuiltInSystem", + "BuiltInUser", + "SdCard" + }; + + constexpr const char * const StorageIdStringsForPlayReport[] = { + "None", + "Host", + "Card", + "BuildInSystem", + "BuildInUser", + "SdCard" + }; + + } + + const char *GetStorageIdString(StorageId storage_id) { + switch (storage_id) { + case StorageId::None: return "None"; + case StorageId::Host: return "Host"; + case StorageId::GameCard: return "GameCard"; + case StorageId::BuiltInSystem: return "BuiltInSystem"; + case StorageId::BuiltInUser: return "BuiltInUser"; + default: return "(unknown)"; + } + } + + const char *GetStorageIdStringForPlayReport(StorageId storage_id) { + switch (storage_id) { + case StorageId::None: return "None"; + case StorageId::Host: return "Host"; + case StorageId::Card: return "Card"; + case StorageId::BuildInSystem: return "BuildInSystem"; + case StorageId::BuildInUser: return "BuildInUser"; + default: return "(unknown)"; + } + } + +} diff --git a/libraries/libstratosphere/source/ncm/ncm_submission_package_install_task.cpp b/libraries/libstratosphere/source/ncm/ncm_submission_package_install_task.cpp new file mode 100644 index 000000000..37f702277 --- /dev/null +++ b/libraries/libstratosphere/source/ncm/ncm_submission_package_install_task.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019-2020 Adubbz, Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "ncm_fs_utils.hpp" + +namespace ams::ncm { + + class SubmissionPackageInstallTask::Impl { + private: + fs::FileHandleStorage storage; + std::optional mount_name; + public: + explicit Impl(fs::FileHandle file) : storage(file), mount_name(std::nullopt) { /* ... */ } + + ~Impl() { + if (this->mount_name) { + fs::fsa::Unregister(this->mount_name->str); + } + } + + Result Initialize() { + AMS_ASSERT(!this->mount_name); + + /* Allocate a partition file system. */ + auto partition_file_system = std::make_unique(); + R_UNLESS(partition_file_system != nullptr, ncm::ResultAllocationFailed()); + + /* Create a mount name and register the file system. */ + auto mount_name = impl::CreateUniqueMountName(); + R_TRY(fs::fsa::Register(mount_name.str, std::move(partition_file_system))); + + /* Initialize members. */ + this->mount_name = mount_name; + return ResultSuccess(); + } + + const impl::MountName &GetMountName() const { + return *this->mount_name; + } + }; + + SubmissionPackageInstallTask::SubmissionPackageInstallTask() { /* ... */ } + SubmissionPackageInstallTask::~SubmissionPackageInstallTask() { /* ... */ } + + Result SubmissionPackageInstallTask::Initialize(fs::FileHandle file, StorageId storage_id, void *buffer, size_t buffer_size, bool ignore_ticket) { + AMS_ASSERT(!this->impl); + + /* Allocate impl. */ + this->impl.reset(new (std::nothrow) Impl(file)); + R_UNLESS(this->impl != nullptr, ncm::ResultAllocationFailed()); + + /* Initialize impl. */ + R_TRY(this->impl->Initialize()); + + /* Initialize parent. N doesn't check the result. */ + PackageInstallTask::Initialize(impl::GetRootDirectoryPath(this->impl->GetMountName()).str, storage_id, buffer, buffer_size, ignore_ticket); + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/source/nim/nim_network_install_manager_api.cpp b/libraries/libstratosphere/source/nim/nim_network_install_manager_api.cpp index 4a77452a7..156f924c8 100644 --- a/libraries/libstratosphere/source/nim/nim_network_install_manager_api.cpp +++ b/libraries/libstratosphere/source/nim/nim_network_install_manager_api.cpp @@ -36,7 +36,7 @@ namespace ams::nim { } /* Service API. */ - Result DestroySystemUpdateTask(const SystemUpdateTaskId& id) { + Result DestroySystemUpdateTask(const SystemUpdateTaskId &id) { static_assert(sizeof(SystemUpdateTaskId) == sizeof(::NimSystemUpdateTaskId)); return nimDestroySystemUpdateTask(reinterpret_cast(std::addressof(id))); } diff --git a/libraries/libvapours/include/vapours/results/ncm_results.hpp b/libraries/libvapours/include/vapours/results/ncm_results.hpp index 494d7b4bd..a1885b395 100644 --- a/libraries/libvapours/include/vapours/results/ncm_results.hpp +++ b/libraries/libvapours/include/vapours/results/ncm_results.hpp @@ -32,15 +32,24 @@ namespace ams::ncm { R_DEFINE_ERROR_RESULT(InvalidContentStorage, 100); R_DEFINE_ERROR_RESULT(InvalidContentMetaDatabase, 110); - R_DEFINE_ERROR_RESULT(InvalidPackageFormat, 130); + R_DEFINE_ERROR_RESULT(InvalidContentHash, 140); + R_DEFINE_ERROR_RESULT(InvalidInstallTaskState, 160); R_DEFINE_ERROR_RESULT(InvalidPlaceHolderFile, 170); R_DEFINE_ERROR_RESULT(BufferInsufficient, 180); R_DEFINE_ERROR_RESULT(WriteToReadOnlyContentStorage, 190); + R_DEFINE_ERROR_RESULT(NotEnoughInstallSpace, 200); + R_DEFINE_ERROR_RESULT(SystemUpdateNotFoundInPackage, 210); + R_DEFINE_ERROR_RESULT(ContentInfoNotFound, 220); + R_DEFINE_ERROR_RESULT(DeltaNotFound, 237); R_DEFINE_ERROR_RESULT(InvalidContentMetaKey, 240); + R_DEFINE_ERROR_RESULT(IgnorableInstallTicketFailure, 280); R_DEFINE_ERROR_RESULT(ContentStorageBaseNotFound, 310); + R_DEFINE_ERROR_RESULT(ListPartiallyNotCommitted, 330); + R_DEFINE_ERROR_RESULT(UnexpectedContentMetaPrepared, 360); + R_DEFINE_ERROR_RESULT(InvalidFirmwareVariation, 380); R_DEFINE_ERROR_RANGE(ContentStorageNotActive, 250, 258); R_DEFINE_ERROR_RESULT(GameCardContentStorageNotActive, 251); @@ -56,6 +65,10 @@ namespace ams::ncm { R_DEFINE_ERROR_RESULT(SdCardContentMetaDatabaseNotActive, 264); R_DEFINE_ERROR_RESULT(UnknownContentMetaDatabaseNotActive, 268); + R_DEFINE_ERROR_RANGE(InstallTaskCancelled, 290, 299); + R_DEFINE_ERROR_RESULT(CreatePlaceHolderCancelled, 291); + R_DEFINE_ERROR_RESULT(WritePlaceHolderCancelled, 292); + R_DEFINE_ERROR_RANGE(InvalidArgument, 8181, 8191); R_DEFINE_ERROR_RESULT(InvalidOffset, 8182);