From 21595dd51e7aae6046747a77d1e959fdd173cb22 Mon Sep 17 00:00:00 2001 From: Adubbz Date: Wed, 25 Mar 2020 19:13:42 +1100 Subject: [PATCH] ncm client: implement PackageSystemUpdateTask --- .../include/stratosphere/ncm.hpp | 1 + .../ncm/ncm_install_task_base.hpp | 12 ++ .../ncm/ncm_package_system_update_task.hpp | 43 +++++ .../source/ncm/ncm_package_install_task.cpp | 2 +- .../ncm/ncm_package_system_update_task.cpp | 158 ++++++++++++++++++ .../include/vapours/results/ncm_results.hpp | 2 + 6 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 libraries/libstratosphere/include/stratosphere/ncm/ncm_package_system_update_task.hpp create mode 100644 libraries/libstratosphere/source/ncm/ncm_package_system_update_task.cpp diff --git a/libraries/libstratosphere/include/stratosphere/ncm.hpp b/libraries/libstratosphere/include/stratosphere/ncm.hpp index cb9ee8c3e..ed395444b 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm.hpp @@ -33,5 +33,6 @@ #include #include #include +#include #include #include diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_base.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_base.hpp index 3beb56f87..3731b6472 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_base.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_base.hpp @@ -42,6 +42,7 @@ namespace ams::ncm { 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) { @@ -50,6 +51,7 @@ namespace ams::ncm { .content_size = sz, .key = ky, .verify_digest = true, + .has_key = true, .digest = d, }; } @@ -60,6 +62,16 @@ namespace ams::ncm { .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, }; } }; 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..ca9c6204c --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_system_update_task.hpp @@ -0,0 +1,43 @@ +/* + * 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: + Result GetContentInfoOfContentMeta(ContentInfo *out, const ContentMetaKey &key); + virtual Result PrepareDependency() override; + virtual Result GetInstallContentMetaInfo(InstallContentMetaInfo *out, const ContentMetaKey &key) override; + }; + +} diff --git a/libraries/libstratosphere/source/ncm/ncm_package_install_task.cpp b/libraries/libstratosphere/source/ncm/ncm_package_install_task.cpp index a59ad9d8c..aa3ccb530 100644 --- a/libraries/libstratosphere/source/ncm/ncm_package_install_task.cpp +++ b/libraries/libstratosphere/source/ncm/ncm_package_install_task.cpp @@ -52,7 +52,7 @@ namespace ams::ncm { /* 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, ContentMetaKey::MakeUnknownType(0, 0)), std::nullopt, std::nullopt)); + R_TRY(this->PrepareContentMeta(InstallContentMetaInfo::MakeUnverifiable(*id, entry.file_size), std::nullopt, std::nullopt)); } } 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..eef0cdacf --- /dev/null +++ b/libraries/libstratosphere/source/ncm/ncm_package_system_update_task.cpp @@ -0,0 +1,158 @@ +/* + * 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; + + /* Open the game card content meta database. */ + OpenContentMetaDatabase(std::addressof(this->package_db), StorageId::GameCard); + auto meta_db_guard = SCOPE_GUARD { + if (this->gamecard_content_meta_database_active) { + InactivateContentMetaDatabase(StorageId::GameCard); + this->gamecard_content_meta_database_active = false; + } + }; + + 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, 0x800)); + 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::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(); + } + + 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(); + } + +} diff --git a/libraries/libvapours/include/vapours/results/ncm_results.hpp b/libraries/libvapours/include/vapours/results/ncm_results.hpp index 65094b9df..2bd841658 100644 --- a/libraries/libvapours/include/vapours/results/ncm_results.hpp +++ b/libraries/libvapours/include/vapours/results/ncm_results.hpp @@ -40,6 +40,8 @@ namespace ams::ncm { 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(InvalidContentMetaKey, 240); R_DEFINE_ERROR_RESULT(IgnorableInstallTicketFailure, 280);