diff --git a/libraries/libstratosphere/include/stratosphere/ncm.hpp b/libraries/libstratosphere/include/stratosphere/ncm.hpp index 9870f0746..f5b79d400 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm.hpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include 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 1c05a6da8..07ace78cf 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_info_data.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_info_data.hpp @@ -32,6 +32,23 @@ namespace ams::ncm { AlreadyExists, }; + struct PackagedContentInfo { + Digest digest; + ContentInfo info; + + constexpr const ContentId &GetId() const { + return this->info.GetId(); + } + + constexpr const ContentType GetType() const { + return this->info.GetType(); + } + + constexpr const u8 GetIdOffset() const { + return this->info.GetIdOffset(); + } + }; + struct InstallContentInfo { Digest digest; crypto::Sha256Context context; @@ -41,7 +58,7 @@ namespace ams::ncm { PlaceHolderId placeholder_id; ContentMetaType meta_type; InstallState install_state; - bool verify_hash; + bool verify_digest; StorageId storage_id; bool is_temporary; bool is_sha256_calculated; @@ -83,25 +100,17 @@ namespace ams::ncm { constexpr s64 GetSizeWritten() const { return this->written; } + + 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); - struct PackagedContentInfo { - Digest digest; - ContentInfo info; - - constexpr const ContentId &GetId() const { - return this->info.GetId(); - } - - constexpr const ContentType GetType() const { - return this->info.GetType(); - } - - constexpr const u8 GetIdOffset() const { - return this->info.GetIdOffset(); - } - }; - } diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta.hpp index 3241d1a5f..58299cba6 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 { @@ -107,6 +108,15 @@ namespace ams::ncm { u32 padding; }; + struct SystemUpdateMetaExtendedHeader { + u32 extended_data_size; + }; + + struct SystemUpdateMetaExtendedDataHeader { + u32 unk; // Always seems to be set to 2 + u32 firmware_variation_count; + }; + template class ContentMetaAccessor { public: @@ -269,9 +279,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->GetExtendedHeader()->extended_data_size; + default: return 0; } } @@ -322,9 +333,11 @@ namespace ams::ncm { public: constexpr PackagedContentMetaReader(const void *data, size_t size) : ContentMetaAccessor(data, size) { /* ... */ } + size_t CalculateConvertInstallContentMetaSize() const; size_t CalculateConvertContentMetaSize() const; void ConvertToContentMeta(void *dst, size_t size, const ContentInfo &meta); + void ConvertToInstallContentMeta(void *dst, size_t size, const InstallContentInfo &meta); size_t CountDeltaFragments() const; @@ -352,4 +365,68 @@ namespace ams::ncm { using ContentMetaAccessor::SetStorageId; }; + 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; + } + + FirmwareVariationId *GetFirmwareVariationId(size_t i) const { + AMS_ABORT_UNLESS(i < this->GetFirmwareVariationCount()); + + return reinterpret_cast(this->GetFirmwareVariationIdAddress(i)); + } + + FirmwareVariationInfo *GetFirmwareVariationInfo(size_t i) const { + AMS_ABORT_UNLESS(i < this->GetFirmwareVariationCount()); + + return reinterpret_cast(this->GetFirmwareVariationInfoAddress(i)); + } + + void GetContentMetaInfoList() const { + /* TODO */ + } + }; + } 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..054824abb --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_firmware_variation.hpp @@ -0,0 +1,32 @@ +/* + * 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; + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task.hpp index 48b6ab7fa..a0bd12fb4 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task.hpp @@ -21,11 +21,6 @@ namespace ams::ncm { /* protected: PrepareContentMeta (both), WritePlaceHolderBuffer, Get/Delete InstallContentMetaData, PrepareDependency, PrepareSystemDependency, PrepareContentMetaIfLatest, GetConfig, WriteContentMetaToPlaceHolder, GetInstallStorage, GetSystemUpdateTaskApplyInfo, CanContinue */ - struct InstallThroughput { - s64 installed; - TimeSpan elapsed_time; - }; - enum class ListContentMetaKeyFilter : u8 { All = 0, Committed = 1, @@ -38,6 +33,40 @@ PrepareContentMeta (both), WritePlaceHolderBuffer, Get/Delete InstallContentMeta InstallConfig_IgnoreTicket = (1 << 4), }; + struct InstallThroughput { + s64 installed; + TimeSpan elapsed_time; + }; + + struct InstallContentMetaInfo { + ContentId content_id; + s64 content_size; + ContentMetaKey key; + bool verify_digest; + 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, + .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, + }; + } + }; + + static_assert(sizeof(InstallContentMetaInfo) == 0x50); + class InstallTaskBase { private: crypto::Sha256Generator sha256_generator; @@ -51,9 +80,8 @@ PrepareContentMeta (both), WritePlaceHolderBuffer, Get/Delete InstallContentMeta InstallThroughput throughput; TimeSpan throughput_start_time; os::Mutex throughput_mutex; - /* ... */ public: - virtual ~InstallTaskBase() { /* TODO */ }; + virtual ~InstallTaskBase() { /* ... */ }; private: ALWAYS_INLINE Result SetLastResultOnFailure(Result result) { if (R_FAILED(result)) { @@ -97,7 +125,13 @@ PrepareContentMeta (both), WritePlaceHolderBuffer, Get/Delete InstallContentMeta Result PrepareAndExecute(); Result VerifyAllNotCommitted(const StorageContentMetaKey *keys, s32 num_keys); Result Commit(const StorageContentMetaKey *keys, s32 num_keys); + Result IncludesExFatDriver(bool *out); + Result WritePlaceHolderBuffer(InstallContentInfo *content_info, const void *data, size_t data_size); + Result WriteContentMetaToPlaceHolder(InstallContentInfo *install_content_info, ContentStorage *storage, const InstallContentMetaInfo &meta_info, std::optional is_temporary); + InstallContentInfo MakeInstallContentInfoFrom(const InstallContentMetaInfo &info, const PlaceHolderId &placeholder_id, std::optional is_temporary); + Result IsNewerThanInstalled(bool *out, const ContentMetaKey &key); + Result DeleteInstallContentMetaData(const ContentMetaKey *keys, s32 num_keys); void ResetLastResult(); s64 GetThroughput(); protected: diff --git a/libraries/libstratosphere/source/ncm/ncm_content_meta.cpp b/libraries/libstratosphere/source/ncm/ncm_content_meta.cpp index 39ac6c728..df24a2b0a 100644 --- a/libraries/libstratosphere/source/ncm/ncm_content_meta.cpp +++ b/libraries/libstratosphere/source/ncm/ncm_content_meta.cpp @@ -30,6 +30,24 @@ namespace ams::ncm { dst->attributes = src.attributes; } + void ConvertPackageContentMetaHeaderToInstallContentMetaHeader(InstallContentMetaHeader *dst, const PackagedContentMetaHeader &src) { + /* Clear destination. */ + *dst = {}; + + /* Set converted fields. */ + dst->id = src.id; + dst->version = src.version; + dst->type = src.type; + dst->extended_header_size = src.extended_header_size; + dst->content_count = src.content_count; + dst->content_meta_count = src.content_meta_count; + dst->attributes = src.attributes; + dst->storage_id = src.storage_id; + dst->install_type = src.install_type; + dst->committed = src.committed; + dst->required_download_system_version = src.required_download_system_version; + } + void ConvertInstallContentMetaHeaderToContentMetaHeader(ContentMetaHeader *dst, const InstallContentMetaHeader &src) { /* Clear destination. */ *dst = {}; @@ -43,6 +61,22 @@ namespace ams::ncm { } + 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 { size_t count = 0; for (size_t i = 0; i < this->GetContentCount(); i++) { @@ -58,6 +92,62 @@ 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); + } + } + void PackagedContentMetaReader::ConvertToContentMeta(void *dst, size_t size, const ContentInfo &meta) { /* Ensure we have enough space to convert. */ AMS_ABORT_UNLESS(size >= this->CalculateConvertContentMetaSize()); diff --git a/libraries/libstratosphere/source/ncm/ncm_install_task.cpp b/libraries/libstratosphere/source/ncm/ncm_install_task.cpp index 010fd30d6..e1841d14e 100644 --- a/libraries/libstratosphere/source/ncm/ncm_install_task.cpp +++ b/libraries/libstratosphere/source/ncm/ncm_install_task.cpp @@ -32,6 +32,17 @@ namespace ams::ncm { 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; + } + } Result InstallTaskBase::OnPrepareComplete() { @@ -498,7 +509,7 @@ namespace ams::ncm { } /* Compare generated hash to expected hash if verification required. */ - if (content_info->verify_hash) { + 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()); @@ -670,6 +681,83 @@ namespace ams::ncm { 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(&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. */ + 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::WriteContentMetaToPlaceHolder(InstallContentInfo *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. */ + *install_content_info = this->MakeInstallContentInfoFrom(meta_info, placeholder_id, is_temporary); + + /* Write install content info. */ + R_TRY(this->WritePlaceHolder(meta_info.key, install_content_info)); + + /* Don't delete the placeholder. Set state to installed. */ + placeholder_guard.Cancel(); + install_content_info->install_state = InstallState::Installed; + 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), + }; + } + /* ... */ void InstallTaskBase::IncrementProgress(s64 size) { @@ -716,6 +804,59 @@ namespace ams::ncm { /* ... */ + 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; + R_TRY(OpenContentMetaDatabase(std::addressof(meta_db), storage_list[i])); + + /* Get the latest key. */ + ContentMetaKey installed_key; + R_TRY_CATCH(meta_db.GetLatest(std::addressof(installed_key), key.id)) { + R_CATCH(ncm::ResultContentMetaNotFound) { /* Key doesn't exist, this is okay. */ } + } R_END_TRY_CATCH; + + /* Check if installed key is newer. */ + if (installed_key.version >= key.version) { + *out = false; + return ResultSuccess(); + } + } + + /* Input key is newer. */ + *out = true; + return ResultSuccess(); + } + + Result InstallTaskBase::DeleteInstallContentMetaData(const ContentMetaKey *keys, s32 num_keys) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->data->Count(std::addressof(count))); + + /* Delete the data if count < 1. */ + if (count < 1) { + return this->data->Delete(keys, num_keys); + } + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->data->Get(&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)); + } + } + + return ResultSuccess(); + } + InstallProgress InstallTaskBase::GetProgress() { std::scoped_lock lk(this->progress_mutex); return this->progress;