mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2025-01-03 11:11:14 +00:00
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 <SciresM@gmail.com>
This commit is contained in:
parent
76d72fa946
commit
a50d6a2696
48 changed files with 4163 additions and 56 deletions
|
@ -25,6 +25,20 @@ namespace ams::fs {
|
|||
static_assert(sizeof(RightsId) == 0x10);
|
||||
static_assert(std::is_pod<RightsId>::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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -17,13 +17,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <stratosphere/ncm/ncm_ids.hpp>
|
||||
#include <stratosphere/ncm/ncm_max_count.hpp>
|
||||
#include <stratosphere/ncm/ncm_program_location.hpp>
|
||||
#include <stratosphere/ncm/ncm_auto_buffer.hpp>
|
||||
#include <stratosphere/ncm/ncm_make_path.hpp>
|
||||
#include <stratosphere/ncm/ncm_content_id_utils.hpp>
|
||||
#include <stratosphere/ncm/ncm_content_info_utils.hpp>
|
||||
#include <stratosphere/ncm/ncm_content_meta.hpp>
|
||||
#include <stratosphere/ncm/ncm_content_meta_extended_data.hpp>
|
||||
#include <stratosphere/ncm/ncm_content_meta_database.hpp>
|
||||
#include <stratosphere/ncm/ncm_content_storage.hpp>
|
||||
#include <stratosphere/ncm/ncm_content_manager_impl.hpp>
|
||||
#include <stratosphere/ncm/ncm_content_meta_utils.hpp>
|
||||
#include <stratosphere/ncm/ncm_firmware_variation.hpp>
|
||||
#include <stratosphere/ncm/ncm_install_task_base.hpp>
|
||||
#include <stratosphere/ncm/ncm_install_task_data.hpp>
|
||||
#include <stratosphere/ncm/ncm_install_task_occupied_size.hpp>
|
||||
#include <stratosphere/ncm/ncm_package_install_task_base.hpp>
|
||||
#include <stratosphere/ncm/ncm_package_install_task.hpp>
|
||||
#include <stratosphere/ncm/ncm_package_system_update_task.hpp>
|
||||
#include <stratosphere/ncm/ncm_submission_package_install_task.hpp>
|
||||
#include <stratosphere/ncm/ncm_storage_utils.hpp>
|
||||
#include <stratosphere/ncm/ncm_api.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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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<ContentId> GetContentIdFromString(const char *str, size_t len);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#include <stratosphere/ncm/ncm_content_meta_key.hpp>
|
||||
|
||||
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);
|
||||
|
||||
}
|
|
@ -18,6 +18,7 @@
|
|||
#include <stratosphere/ncm/ncm_content_meta_key.hpp>
|
||||
#include <stratosphere/ncm/ncm_content_info.hpp>
|
||||
#include <stratosphere/ncm/ncm_content_info_data.hpp>
|
||||
#include <stratosphere/ncm/ncm_firmware_variation.hpp>
|
||||
#include <stratosphere/ncm/ncm_storage_id.hpp>
|
||||
|
||||
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<typename ContentMetaHeaderType, typename ContentInfoType>
|
||||
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<u8>(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<HeaderType *>(this->data);
|
||||
}
|
||||
|
||||
const HeaderType *GetHeader() const {
|
||||
AMS_ABORT_UNLESS(this->is_header_valid);
|
||||
return static_cast<const HeaderType *>(this->data);
|
||||
|
@ -254,6 +277,7 @@ namespace ams::ncm {
|
|||
switch (this->GetHeader()->type) {
|
||||
case ContentMetaType::Patch: return this->GetExtendedHeader<PatchMetaExtendedHeader>()->extended_data_size;
|
||||
case ContentMetaType::Delta: return this->GetExtendedHeader<DeltaMetaExtendedHeader>()->extended_data_size;
|
||||
case ContentMetaType::SystemUpdate: return this->GetExtendedHeaderSize() == 0 ? 0 : this->GetExtendedHeader<SystemUpdateMetaExtendedHeader>()->extended_data_size;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
@ -275,6 +299,10 @@ namespace ams::ncm {
|
|||
return false;
|
||||
}
|
||||
|
||||
StorageId GetStorageId() const {
|
||||
return static_cast<StorageId>(this->GetHeader()->storage_id);
|
||||
}
|
||||
|
||||
std::optional<ApplicationId> 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<InstallContentMetaHeader, InstallContentInfo> {
|
||||
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<InstallContentMetaHeader, InstallContentInfo> {
|
||||
public:
|
||||
InstallContentMetaWriter(const void *data, size_t size) : ContentMetaAccessor(data, size) { /* ... */ }
|
||||
|
||||
using ContentMetaAccessor::CalculateSize;
|
||||
using ContentMetaAccessor::CalculateContentRequiredSize;
|
||||
using ContentMetaAccessor::GetWritableContentInfo;
|
||||
using ContentMetaAccessor::SetStorageId;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere/ncm/ncm_content_info.hpp>
|
||||
#include <stratosphere/ncm/ncm_content_meta_id.hpp>
|
||||
#include <stratosphere/ncm/ncm_content_meta.hpp>
|
||||
#include <stratosphere/ncm/ncm_firmware_variation.hpp>
|
||||
|
||||
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<s64>(this->source_size_high) << 32) + this->source_size_low;
|
||||
}
|
||||
|
||||
constexpr s64 GetDestinationSize() const {
|
||||
return (static_cast<s64>(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<u16>(size >> 32);
|
||||
}
|
||||
|
||||
constexpr void SetDestinationSize(s64 size) {
|
||||
this->destination_size_low = size & 0xFFFFFFFFll;
|
||||
this->destination_size_high = static_cast<u16>(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<typename MemberTypePointer, typename DataTypePointer>
|
||||
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<DataTypePointer>(this->data);
|
||||
}
|
||||
|
||||
DataTypePointer GetPatchHistoryHeaderAddress(s32 index) const {
|
||||
auto header = this->GetHeader();
|
||||
AMS_ABORT_UNLESS(static_cast<u16>(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<u16>(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<u16>(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<u16>(delta_index) < header->delta_count);
|
||||
|
||||
auto delta_header = this->GetPatchDeltaHeader(delta_index);
|
||||
AMS_ABORT_UNLESS(static_cast<u16>(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<u16>(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<u16>(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<u16>(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<const PatchMetaExtendedDataHeader *>(this->GetHeaderAddress());
|
||||
}
|
||||
|
||||
const PatchHistoryHeader *GetPatchHistoryHeader(s32 index) const {
|
||||
return reinterpret_cast<const PatchHistoryHeader *>(this->GetPatchHistoryHeaderAddress(index));
|
||||
}
|
||||
|
||||
const PatchDeltaHistory *GetPatchDeltaHistory(s32 index) const {
|
||||
return reinterpret_cast<const PatchDeltaHistory *>(this->GetPatchDeltaHistoryAddress(index));
|
||||
}
|
||||
|
||||
const ContentInfo *GetPatchHistoryContentInfo(s32 history_index, s32 content_index) const {
|
||||
return reinterpret_cast<const ContentInfo *>(this->GetPatchHistoryContentInfoAddress(history_index, content_index));
|
||||
}
|
||||
|
||||
const PatchDeltaHeader *GetPatchDeltaHeader(s32 index) const {
|
||||
return reinterpret_cast<const PatchDeltaHeader *>(this->GetPatchDeltaHeaderAddress(index));
|
||||
}
|
||||
|
||||
const PackagedContentInfo *GetPatchDeltaPackagedContentInfo(s32 delta_index, s32 content_index) const {
|
||||
return reinterpret_cast<const PackagedContentInfo *>(this->GetPatchDeltaPackagedContentInfoAddress(delta_index, content_index));
|
||||
}
|
||||
|
||||
const FragmentSet *GetFragmentSet(s32 delta_index, s32 fragment_set_index) const {
|
||||
return reinterpret_cast<const FragmentSet *>(this->GetFragmentSetIndex(delta_index, fragment_set_index));
|
||||
}
|
||||
|
||||
const FragmentIndicator *GetFragmentIndicator(s32 delta_index, s32 fragment_set_index, s32 index) const {
|
||||
return reinterpret_cast<const FragmentIndicator *>(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<const void *, const u8 *> {
|
||||
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<void *>(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<uintptr_t>(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<const SystemUpdateMetaExtendedDataHeader *>(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<FirmwareVariationId *>(this->GetFirmwareVariationIdAddress(i));
|
||||
}
|
||||
|
||||
const FirmwareVariationInfo *GetFirmwareVariationInfo(size_t i) const {
|
||||
AMS_ABORT_UNLESS(i < this->GetFirmwareVariationCount());
|
||||
|
||||
return reinterpret_cast<FirmwareVariationInfo *>(this->GetFirmwareVariationInfoAddress(i));
|
||||
}
|
||||
|
||||
void GetContentMetaInfoList(Span<const ContentMetaInfo> *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<const ContentMetaInfo>(reinterpret_cast<const ContentMetaInfo *>(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) { /* ... */ }
|
||||
};
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,9 +19,12 @@
|
|||
#include <stratosphere/ncm/ncm_content_storage.hpp>
|
||||
#include <stratosphere/ncm/ncm_content_meta_key.hpp>
|
||||
#include <stratosphere/ncm/ncm_content_meta_database.hpp>
|
||||
#include <stratosphere/ncm/ncm_firmware_variation.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
Result ReadContentMetaPath(AutoBuffer *out, const char *path);
|
||||
|
||||
Result ReadVariationContentMetaInfoList(s32 *out_count, std::unique_ptr<ContentMetaInfo[]> *out_meta_infos, const Path &path, FirmwareVariationId firmware_variation_id);
|
||||
|
||||
}
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere/ncm/ncm_install_task_data.hpp>
|
||||
#include <stratosphere/ncm/ncm_install_task_occupied_size.hpp>
|
||||
|
||||
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<ContentMetaKey> key, std::optional<u32> 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<bool> 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<u32> *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<u32> source_version);
|
||||
|
||||
InstallContentInfo MakeInstallContentInfoFrom(const InstallContentMetaInfo &info, const PlaceHolderId &placeholder_id, std::optional<bool> is_temporary);
|
||||
|
||||
Result ReadContentMetaInfoList(s32 *out_count, std::unique_ptr<ContentMetaInfo[]> *out_meta_infos, const ContentMetaKey &key);
|
||||
Result ListRightsIdsByInstallContentMeta(s32 *out_count, Span<RightsId> 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<RightsId> out_span, const ContentMetaKey &key, s32 offset);
|
||||
};
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere/ncm/ncm_content_meta.hpp>
|
||||
#include <stratosphere/ncm/ncm_install_progress.hpp>
|
||||
#include <stratosphere/ncm/ncm_system_update_task_apply_info.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
struct InstallContentMeta {
|
||||
std::unique_ptr<char[]> 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<DataHolder>{};
|
||||
using DataList = util::IntrusiveListBaseTraits<DataHolder>::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<u32>(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();
|
||||
};
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere/ncm/ncm_content_meta_key.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
struct InstallTaskOccupiedSize {
|
||||
ContentMetaKey key;
|
||||
s64 size;
|
||||
StorageId storage_id;
|
||||
u8 reserved[7];
|
||||
};
|
||||
|
||||
static_assert(sizeof(InstallTaskOccupiedSize) == 0x20);
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
constexpr inline s32 SystemMaxContentMetaCount = 0x800;
|
||||
constexpr inline s32 GameCardMaxContentMetaCount = 0x800;
|
||||
constexpr inline s32 UserMaxContentMetaCount = 0x2000;
|
||||
constexpr inline s32 SdCardMaxContentMetaCount = 0x2000;
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere/ncm/ncm_package_install_task_base.hpp>
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere/kvdb/kvdb_bounded_string.hpp>
|
||||
#include <stratosphere/ncm/ncm_install_task_base.hpp>
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere/ncm/ncm_package_install_task_base.hpp>
|
||||
|
||||
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<ContentMetaKey> 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);
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -27,4 +27,15 @@ namespace ams::ncm {
|
|||
static_assert(sizeof(RightsId) == 0x18);
|
||||
static_assert(std::is_pod<RightsId>::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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere/ncm/ncm_storage_id.hpp>
|
||||
#include <stratosphere/ncm/ncm_content_meta_id.hpp>
|
||||
|
||||
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);
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere/fs/fs_file_storage.hpp>
|
||||
#include <stratosphere/ncm/ncm_package_install_task.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class SubmissionPackageInstallTask : public PackageInstallTask {
|
||||
private:
|
||||
class Impl;
|
||||
private:
|
||||
std::unique_ptr<Impl> impl;
|
||||
public:
|
||||
SubmissionPackageInstallTask();
|
||||
virtual ~SubmissionPackageInstallTask() override;
|
||||
|
||||
Result Initialize(fs::FileHandle handle, StorageId storage_id, void *buffer, size_t buffer_size, bool ignore_ticket = false);
|
||||
};
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
enum class SystemUpdateTaskApplyInfo : u8 {
|
||||
Unknown = 0,
|
||||
RequireReboot = 1,
|
||||
RequireNoReboot = 2,
|
||||
};
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<ContentId> GetContentIdFromString(const char *str, size_t len) {
|
||||
if (len < ContentIdStringLength) {
|
||||
return std::nullopt;
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
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<typename Handler>
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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<InstallContentMetaHeader, PackagedContentMetaHeader>::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<s32>(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<s32>(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<InstallContentMetaHeader, InstallContentInfo>(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<InstallContentMetaHeader, InstallContentInfo>(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<InstallContentMetaHeader, InstallContentInfo>(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<ContentMetaHeader, ContentInfo>(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<uintptr_t>(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<void *>(dst_addr), std::addressof(header), sizeof(header));
|
||||
dst_addr += sizeof(header);
|
||||
|
||||
/* Copy the extended header. */
|
||||
std::memcpy(reinterpret_cast<void *>(dst_addr), reinterpret_cast<void *>(this->GetExtendedHeaderAddress()), packaged_header->extended_header_size);
|
||||
dst_addr += packaged_header->extended_header_size;
|
||||
|
||||
/* Copy the top level meta. */
|
||||
std::memcpy(reinterpret_cast<void *>(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<void *>(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<void *>(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<uintptr_t>(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<u16>(fragment_count) + 1;
|
||||
|
||||
/* Copy the header. */
|
||||
std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(header), sizeof(header));
|
||||
dst_addr += sizeof(header);
|
||||
|
||||
/* Copy the extended header. */
|
||||
std::memcpy(reinterpret_cast<void *>(dst_addr), reinterpret_cast<void *>(this->GetExtendedHeaderAddress()), packaged_header->extended_header_size);
|
||||
dst_addr += packaged_header->extended_header_size;
|
||||
|
||||
/* Copy the top level meta. */
|
||||
std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(meta), sizeof(meta));
|
||||
dst_addr += sizeof(meta);
|
||||
|
||||
s32 count = 0;
|
||||
for (s32 i = 0; i < static_cast<s32>(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<void *>(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<InstallContentMetaHeader, InstallContentInfo>(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<ContentMetaHeader, ContentInfo>(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<uintptr_t>(dst);
|
||||
|
||||
/* Convert the header. */
|
||||
ContentMetaHeader header;
|
||||
ConvertInstallContentMetaHeaderToContentMetaHeader(std::addressof(header), *install_header);
|
||||
|
||||
/* Copy the header. */
|
||||
std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(header), sizeof(header));
|
||||
dst_addr += sizeof(header);
|
||||
|
||||
/* Copy the extended header. */
|
||||
std::memcpy(reinterpret_cast<void *>(dst_addr), reinterpret_cast<void *>(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<void *>(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<void *>(dst_addr), this->GetContentMetaInfo(i), sizeof(ContentMetaInfo));
|
||||
dst_addr += sizeof(ContentMetaInfo);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
namespace ams::ncm {
|
||||
|
||||
Result ContentMetaDatabaseImpl::GetContentIdImpl(ContentId *out, const ContentMetaKey& key, ContentType type, std::optional<u8> id_offset) const {
|
||||
Result ContentMetaDatabaseImpl::GetContentIdImpl(ContentId *out, const ContentMetaKey &key, ContentType type, std::optional<u8> id_offset) const {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Find the meta key. */
|
||||
|
|
|
@ -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<u8> id_offset) const;
|
||||
Result GetContentIdImpl(ContentId *out, const ContentMetaKey &key, ContentType type, std::optional<u8> id_offset) const;
|
||||
public:
|
||||
/* Actual commands. */
|
||||
virtual Result Set(const ContentMetaKey &key, sf::InBuffer value) override;
|
||||
|
|
|
@ -77,4 +77,83 @@ namespace ams::ncm {
|
|||
return ncm::ResultContentMetaNotFound();
|
||||
}
|
||||
|
||||
Result ReadVariationContentMetaInfoList(s32 *out_count, std::unique_ptr<ContentMetaInfo[]> *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<ContentMetaInfo[]> 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<s32> 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<ContentMetaInfo[]> buffer(new (std::nothrow) ContentMetaInfo[content_meta_count]);
|
||||
AMS_ABORT_UNLESS(buffer != nullptr);
|
||||
|
||||
/* Get the content meta infos. */
|
||||
Span<const ContentMetaInfo> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
1397
libraries/libstratosphere/source/ncm/ncm_install_task_base.cpp
Normal file
1397
libraries/libstratosphere/source/ncm/ncm_install_task_base.cpp
Normal file
File diff suppressed because it is too large
Load diff
397
libraries/libstratosphere/source/ncm/ncm_install_task_data.cpp
Normal file
397
libraries/libstratosphere/source/ncm/ncm_install_task_data.cpp
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
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<char[]> 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<DataHolder>(new (std::nothrow) DataHolder());
|
||||
R_UNLESS(holder != nullptr, ncm::ResultAllocationFailed());
|
||||
|
||||
/* Allocate memory for the content meta data. */
|
||||
holder->data = std::unique_ptr<char[]>(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<s64>(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<s64>(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<s64>(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<u32>(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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#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<ContentId> 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
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<char[]> 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<size_t>(ticket_size)]);
|
||||
R_UNLESS(ticket != nullptr, ncm::ResultAllocationFailed());
|
||||
R_TRY(fs::ReadFile(ticket_file, 0, ticket.get(), static_cast<size_t>(ticket_size)));
|
||||
}
|
||||
|
||||
|
||||
/* Read certificate from file. */
|
||||
s64 cert_size;
|
||||
std::unique_ptr<char[]> 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<size_t>(cert_size)]);
|
||||
R_UNLESS(cert != nullptr, ncm::ResultAllocationFailed());
|
||||
R_TRY(fs::ReadFile(cert_file, 0, cert.get(), static_cast<size_t>(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");
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
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<ContentMetaKey> 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();
|
||||
}
|
||||
|
||||
}
|
127
libraries/libstratosphere/source/ncm/ncm_storage_utils.cpp
Normal file
127
libraries/libstratosphere/source/ncm/ncm_storage_utils.cpp
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
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)";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_fs_utils.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class SubmissionPackageInstallTask::Impl {
|
||||
private:
|
||||
fs::FileHandleStorage storage;
|
||||
std::optional<impl::MountName> 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<fssystem::PartitionFileSystem>();
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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<const ::NimSystemUpdateTaskId *>(std::addressof(id)));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in a new issue