ncm client: more progress

This commit is contained in:
Adubbz 2020-03-21 22:40:53 +11:00
parent 8fbb10a83c
commit 12670e1a01
7 changed files with 413 additions and 29 deletions

View file

@ -27,6 +27,7 @@
#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.hpp>
#include <stratosphere/ncm/ncm_install_task_data.hpp>
#include <stratosphere/ncm/ncm_storage_id_utils.hpp>

View file

@ -32,6 +32,23 @@ namespace ams::ncm {
AlreadyExists,
};
struct PackagedContentInfo {
Digest digest;
ContentInfo info;
constexpr const ContentId &GetId() const {
return this->info.GetId();
}
constexpr const ContentType GetType() const {
return this->info.GetType();
}
constexpr const u8 GetIdOffset() const {
return this->info.GetIdOffset();
}
};
struct InstallContentInfo {
Digest digest;
crypto::Sha256Context context;
@ -41,7 +58,7 @@ namespace ams::ncm {
PlaceHolderId placeholder_id;
ContentMetaType meta_type;
InstallState install_state;
bool verify_hash;
bool verify_digest;
StorageId storage_id;
bool is_temporary;
bool is_sha256_calculated;
@ -83,25 +100,17 @@ namespace ams::ncm {
constexpr s64 GetSizeWritten() const {
return this->written;
}
static constexpr InstallContentInfo Make(const PackagedContentInfo &info, ContentMetaType meta_type) {
return {
.digest = info.digest,
.info = info.info,
.meta_type = meta_type,
.verify_digest = true,
};
}
};
static_assert(sizeof(InstallContentInfo) == 0xC8);
struct PackagedContentInfo {
Digest digest;
ContentInfo info;
constexpr const ContentId &GetId() const {
return this->info.GetId();
}
constexpr const ContentType GetType() const {
return this->info.GetType();
}
constexpr const u8 GetIdOffset() const {
return this->info.GetIdOffset();
}
};
}

View file

@ -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 {
@ -107,6 +108,15 @@ namespace ams::ncm {
u32 padding;
};
struct SystemUpdateMetaExtendedHeader {
u32 extended_data_size;
};
struct SystemUpdateMetaExtendedDataHeader {
u32 unk; // Always seems to be set to 2
u32 firmware_variation_count;
};
template<typename ContentMetaHeaderType, typename ContentInfoType>
class ContentMetaAccessor {
public:
@ -269,9 +279,10 @@ namespace ams::ncm {
size_t GetExtendedDataSize() const {
switch (this->GetHeader()->type) {
case ContentMetaType::Patch: return this->GetExtendedHeader<PatchMetaExtendedHeader>()->extended_data_size;
case ContentMetaType::Delta: return this->GetExtendedHeader<DeltaMetaExtendedHeader>()->extended_data_size;
default: return 0;
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->GetExtendedHeader<SystemUpdateMetaExtendedHeader>()->extended_data_size;
default: return 0;
}
}
@ -322,9 +333,11 @@ namespace ams::ncm {
public:
constexpr PackagedContentMetaReader(const void *data, size_t size) : ContentMetaAccessor(data, size) { /* ... */ }
size_t CalculateConvertInstallContentMetaSize() const;
size_t CalculateConvertContentMetaSize() const;
void ConvertToContentMeta(void *dst, size_t size, const ContentInfo &meta);
void ConvertToInstallContentMeta(void *dst, size_t size, const InstallContentInfo &meta);
size_t CountDeltaFragments() const;
@ -352,4 +365,68 @@ namespace ams::ncm {
using ContentMetaAccessor::SetStorageId;
};
class SystemUpdateMetaExtendedDataReaderWriterBase {
private:
void *data;
const size_t size;
bool is_header_valid;
protected:
constexpr SystemUpdateMetaExtendedDataReaderWriterBase(const void *d, size_t sz) : data(const_cast<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;
}
FirmwareVariationId *GetFirmwareVariationId(size_t i) const {
AMS_ABORT_UNLESS(i < this->GetFirmwareVariationCount());
return reinterpret_cast<FirmwareVariationId *>(this->GetFirmwareVariationIdAddress(i));
}
FirmwareVariationInfo *GetFirmwareVariationInfo(size_t i) const {
AMS_ABORT_UNLESS(i < this->GetFirmwareVariationCount());
return reinterpret_cast<FirmwareVariationInfo *>(this->GetFirmwareVariationInfoAddress(i));
}
void GetContentMetaInfoList() const {
/* TODO */
}
};
}

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <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;
};
}

View file

@ -21,11 +21,6 @@ namespace ams::ncm {
/* protected:
PrepareContentMeta (both), WritePlaceHolderBuffer, Get/Delete InstallContentMetaData, PrepareDependency, PrepareSystemDependency, PrepareContentMetaIfLatest, GetConfig, WriteContentMetaToPlaceHolder, GetInstallStorage, GetSystemUpdateTaskApplyInfo, CanContinue
*/
struct InstallThroughput {
s64 installed;
TimeSpan elapsed_time;
};
enum class ListContentMetaKeyFilter : u8 {
All = 0,
Committed = 1,
@ -38,6 +33,40 @@ PrepareContentMeta (both), WritePlaceHolderBuffer, Get/Delete InstallContentMeta
InstallConfig_IgnoreTicket = (1 << 4),
};
struct InstallThroughput {
s64 installed;
TimeSpan elapsed_time;
};
struct InstallContentMetaInfo {
ContentId content_id;
s64 content_size;
ContentMetaKey key;
bool verify_digest;
Digest digest;
static constexpr InstallContentMetaInfo MakeVerifiable(const ContentId &cid, s64 sz, const ContentMetaKey &ky, const Digest &d) {
return {
.content_id = cid,
.content_size = sz,
.key = ky,
.verify_digest = true,
.digest = d,
};
}
static constexpr InstallContentMetaInfo MakeUnverifiable(const ContentId &cid, s64 sz, const ContentMetaKey &ky) {
return {
.content_id = cid,
.content_size = sz,
.key = ky,
.verify_digest = false,
};
}
};
static_assert(sizeof(InstallContentMetaInfo) == 0x50);
class InstallTaskBase {
private:
crypto::Sha256Generator sha256_generator;
@ -51,9 +80,8 @@ PrepareContentMeta (both), WritePlaceHolderBuffer, Get/Delete InstallContentMeta
InstallThroughput throughput;
TimeSpan throughput_start_time;
os::Mutex throughput_mutex;
/* ... */
public:
virtual ~InstallTaskBase() { /* TODO */ };
virtual ~InstallTaskBase() { /* ... */ };
private:
ALWAYS_INLINE Result SetLastResultOnFailure(Result result) {
if (R_FAILED(result)) {
@ -97,7 +125,13 @@ PrepareContentMeta (both), WritePlaceHolderBuffer, Get/Delete InstallContentMeta
Result PrepareAndExecute();
Result VerifyAllNotCommitted(const StorageContentMetaKey *keys, s32 num_keys);
Result Commit(const StorageContentMetaKey *keys, s32 num_keys);
Result IncludesExFatDriver(bool *out);
Result WritePlaceHolderBuffer(InstallContentInfo *content_info, const void *data, size_t data_size);
Result WriteContentMetaToPlaceHolder(InstallContentInfo *install_content_info, ContentStorage *storage, const InstallContentMetaInfo &meta_info, std::optional<bool> is_temporary);
InstallContentInfo MakeInstallContentInfoFrom(const InstallContentMetaInfo &info, const PlaceHolderId &placeholder_id, std::optional<bool> is_temporary);
Result IsNewerThanInstalled(bool *out, const ContentMetaKey &key);
Result DeleteInstallContentMetaData(const ContentMetaKey *keys, s32 num_keys);
void ResetLastResult();
s64 GetThroughput();
protected:

View file

@ -30,6 +30,24 @@ namespace ams::ncm {
dst->attributes = src.attributes;
}
void ConvertPackageContentMetaHeaderToInstallContentMetaHeader(InstallContentMetaHeader *dst, const PackagedContentMetaHeader &src) {
/* Clear destination. */
*dst = {};
/* Set converted fields. */
dst->id = src.id;
dst->version = src.version;
dst->type = src.type;
dst->extended_header_size = src.extended_header_size;
dst->content_count = src.content_count;
dst->content_meta_count = src.content_meta_count;
dst->attributes = src.attributes;
dst->storage_id = src.storage_id;
dst->install_type = src.install_type;
dst->committed = src.committed;
dst->required_download_system_version = src.required_download_system_version;
}
void ConvertInstallContentMetaHeaderToContentMetaHeader(ContentMetaHeader *dst, const InstallContentMetaHeader &src) {
/* Clear destination. */
*dst = {};
@ -43,6 +61,22 @@ namespace ams::ncm {
}
size_t PackagedContentMetaReader::CalculateConvertInstallContentMetaSize() const {
/* Prepare the header. */
const auto *header = this->GetHeader();
if ((header->type == ContentMetaType::SystemUpdate && this->GetExtendedHeaderSize() > 0) || header->type == ContentMetaType::Delta) {
/* Newer SystemUpdates and Deltas contain extended data. */
return this->CalculateSizeImpl<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 {
size_t count = 0;
for (size_t i = 0; i < this->GetContentCount(); i++) {
@ -58,6 +92,62 @@ 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);
}
}
void PackagedContentMetaReader::ConvertToContentMeta(void *dst, size_t size, const ContentInfo &meta) {
/* Ensure we have enough space to convert. */
AMS_ABORT_UNLESS(size >= this->CalculateConvertContentMetaSize());

View file

@ -32,6 +32,17 @@ namespace ams::ncm {
return false;
}
bool Contains(const ContentMetaKey *keys, s32 num_keys, const ContentMetaKey &key) {
for (s32 i = 0; i < num_keys; i++) {
/* Check if the key matches the input key. */
if (keys[i] == key) {
return true;
}
}
return false;
}
}
Result InstallTaskBase::OnPrepareComplete() {
@ -498,7 +509,7 @@ namespace ams::ncm {
}
/* Compare generated hash to expected hash if verification required. */
if (content_info->verify_hash) {
if (content_info->verify_digest) {
u8 hash[crypto::Sha256Generator::HashSize];
this->sha256_generator.GetHash(hash, crypto::Sha256Generator::HashSize);
R_UNLESS(std::memcmp(hash, content_info->digest.data, crypto::Sha256Generator::HashSize) == 0, ncm::ResultInvalidContentHash());
@ -670,6 +681,83 @@ namespace ams::ncm {
return ResultSuccess();
}
Result InstallTaskBase::IncludesExFatDriver(bool *out) {
/* Count the number of content meta entries. */
s32 count;
R_TRY(this->data->Count(std::addressof(count)));
/* Iterate over content meta. */
for (s32 i = 0; i < count; i++) {
/* Obtain the content meta. */
InstallContentMeta content_meta;
R_TRY(this->data->Get(&content_meta, i));
/* Check if the attributes are set for including the exfat driver. */
if (content_meta.GetReader().GetHeader()->attributes & ContentMetaAttribute_IncludesExFatDriver) {
*out = true;
return ResultSuccess();
}
}
*out = false;
return ResultSuccess();
}
Result InstallTaskBase::WritePlaceHolderBuffer(InstallContentInfo *content_info, const void *data, size_t data_size) {
R_UNLESS(!this->IsCancelRequested(), ncm::ResultWritePlaceHolderCancelled());
/* Open the content storage for the content info. */
ContentStorage content_storage;
R_TRY(OpenContentStorage(&content_storage, content_info->storage_id));
/* Write data to the placeholder. */
content_storage.WritePlaceHolder(content_info->placeholder_id, content_info->written, data, data_size);
content_info->written += data_size;
/* Update progress/throughput if content info isn't temporary. */
if (!content_info->is_temporary) {
this->IncrementProgress(data_size);
this->UpdateThroughputMeasurement(data_size);
}
/* Update the hash for the new data. */
this->sha256_generator.Update(data, data_size);
return ResultSuccess();
}
Result InstallTaskBase::WriteContentMetaToPlaceHolder(InstallContentInfo *install_content_info, ContentStorage *storage, const InstallContentMetaInfo &meta_info, std::optional<bool> is_temporary) {
/* Generate a placeholder id. */
auto placeholder_id = storage->GeneratePlaceHolderId();
/* Create the placeholder. */
R_TRY(storage->CreatePlaceHolder(placeholder_id, meta_info.content_id, meta_info.content_size));
auto placeholder_guard = SCOPE_GUARD { storage->DeletePlaceHolder(placeholder_id); };
/* Output install content info. */
*install_content_info = this->MakeInstallContentInfoFrom(meta_info, placeholder_id, is_temporary);
/* Write install content info. */
R_TRY(this->WritePlaceHolder(meta_info.key, install_content_info));
/* Don't delete the placeholder. Set state to installed. */
placeholder_guard.Cancel();
install_content_info->install_state = InstallState::Installed;
return ResultSuccess();
}
InstallContentInfo InstallTaskBase::MakeInstallContentInfoFrom(const InstallContentMetaInfo &info, const PlaceHolderId &placeholder_id, std::optional<bool> is_tmp) {
return {
.digest = info.digest,
.info = ContentInfo::Make(info.content_id, info.content_size, ContentType::Meta, 0),
.placeholder_id = placeholder_id,
.meta_type = info.key.type,
.install_state = InstallState::Prepared,
.verify_digest = info.verify_digest,
.storage_id = StorageId::BuiltInSystem,
.is_temporary = is_tmp ? *is_tmp : (this->install_storage != StorageId::BuiltInSystem),
};
}
/* ... */
void InstallTaskBase::IncrementProgress(s64 size) {
@ -716,6 +804,59 @@ namespace ams::ncm {
/* ... */
Result InstallTaskBase::IsNewerThanInstalled(bool *out, const ContentMetaKey &key) {
/* Obtain a list of suitable storage ids. */
auto storage_list = GetStorageList(this->install_storage);
/* Iterate over storage ids. */
for (s32 i = 0; i < storage_list.Count(); i++) {
/* Open the content meta database. */
ContentMetaDatabase meta_db;
R_TRY(OpenContentMetaDatabase(std::addressof(meta_db), storage_list[i]));
/* Get the latest key. */
ContentMetaKey installed_key;
R_TRY_CATCH(meta_db.GetLatest(std::addressof(installed_key), key.id)) {
R_CATCH(ncm::ResultContentMetaNotFound) { /* Key doesn't exist, this is okay. */ }
} R_END_TRY_CATCH;
/* Check if installed key is newer. */
if (installed_key.version >= key.version) {
*out = false;
return ResultSuccess();
}
}
/* Input key is newer. */
*out = true;
return ResultSuccess();
}
Result InstallTaskBase::DeleteInstallContentMetaData(const ContentMetaKey *keys, s32 num_keys) {
/* Count the number of content meta entries. */
s32 count;
R_TRY(this->data->Count(std::addressof(count)));
/* Delete the data if count < 1. */
if (count < 1) {
return this->data->Delete(keys, num_keys);
}
/* Iterate over content meta. */
for (s32 i = 0; i < count; i++) {
/* Obtain the content meta. */
InstallContentMeta content_meta;
R_TRY(this->data->Get(&content_meta, i));
/* Cleanup if the input keys contain this key. */
if (Contains(keys, num_keys, content_meta.GetReader().GetKey())) {
R_TRY(this->CleanupOne(content_meta));
}
}
return ResultSuccess();
}
InstallProgress InstallTaskBase::GetProgress() {
std::scoped_lock lk(this->progress_mutex);
return this->progress;