ncm: begin implementing install task base

This commit is contained in:
Adubbz 2020-03-14 19:30:05 +11:00 committed by Michael Scire
parent d31afcb99d
commit ad9d61c0de
10 changed files with 630 additions and 0 deletions

View file

@ -21,10 +21,13 @@
#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_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_install_task.hpp>
#include <stratosphere/ncm/ncm_install_task_data.hpp>
#include <stratosphere/ncm/ncm_storage_id_utils.hpp>
#include <stratosphere/ncm/ncm_api.hpp>

View file

@ -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 s64 MaxClusterSize = 256_KB;
s64 CalculateRequiredSize(s64 file_size, s64 cluster_size = MaxClusterSize);
s64 CalculateRequiredSizeForExtension(s64 file_size, s64 cluster_size = MaxClusterSize);
}

View file

@ -206,6 +206,10 @@ namespace ams::ncm {
return this->size;
}
HeaderType *GetWritableHeader() const {
return reinterpret_cast<HeaderType *>(this->data);
}
const HeaderType *GetHeader() const {
AMS_ABORT_UNLESS(this->is_header_valid);
return static_cast<const HeaderType *>(this->data);
@ -291,6 +295,19 @@ namespace ams::ncm {
std::optional<ApplicationId> GetApplicationId() const {
return this->GetApplicationId(this->GetKey());
}
protected:
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);
}
};
class ContentMetaReader : public ContentMetaAccessor<ContentMetaHeader, ContentInfo> {
@ -320,4 +337,14 @@ namespace ams::ncm {
constexpr InstallContentMetaReader(const void *data, size_t size) : ContentMetaAccessor(data, size) { /* ... */ }
};
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;
};
}

View file

@ -0,0 +1,79 @@
/*
* 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>
namespace ams::ncm {
/* protected:
PrepareContentMeta (both), WritePlaceHolderBuffer, PrepareAgain, Get/Delete InstallContentMetaData, PrepareDependency, PrepareSystemDependency, PrepareContentMetaIfLatest, GetConfig, WriteContentMetaToPlaceHolder, GetInstallStorage, GetSystemUpdateTaskApplyInfo, CanContinue
*/
struct InstallThroughput {
s64 installed;
TimeSpan elapsed_time;
};
class 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;
/* ... */
public:
virtual ~InstallTaskBase() { /* TODO */ };
private:
Result PrepareImpl();
protected:
Result Initialize(StorageId install_storage, InstallTaskDataBase *data, u32 config);
Result CountInstallContentMetaData(s32 *out_count);
Result GetInstallContentMetaData(InstallContentMeta *out_content_meta, s32 index);
public:
/* TODO: Fix access types. */
bool IsCancelRequested();
Result Prepare();
void SetLastResult(Result last_result);
Result GetPreparedPlaceHolderPath(Path *out_path, u64 id, ContentMetaType meta_type, ContentType type);
Result CalculateRequiredSize(size_t *out_size);
void ResetThroughputMeasurement();
void SetProgressState(InstallProgressState state);
void SetTotalSize(s64 size);
Result PreparePlaceHolder();
protected:
virtual Result OnPrepareComplete();
virtual Result PrepareDependency();
public:
/* TODO: Fix access types. */
virtual void Cancel();
virtual void ResetCancel();
virtual InstallProgress GetProgress();
virtual Result PrepareInstallContentMetaData() = 0;
void *GetInstallContentMetaInfo;
virtual Result GetLatestVersion(std::optional<u32> *out_version, u64 id);
virtual Result CheckInstallable();
virtual Result OnExecuteComplete();
void *OnWritePlaceHolder;
void *InstallTicket;
};
}

View file

@ -27,6 +27,10 @@ namespace ams::ncm {
InstallContentMetaReader GetReader() const {
return InstallContentMetaReader(this->data.get(), this->size);
}
InstallContentMetaWriter GetWriter() const {
return InstallContentMetaWriter(this->data.get(), this->size);
}
};
class InstallTaskDataBase {

View file

@ -0,0 +1,74 @@
/*
* 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_utils.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, size_t required_size);
const char *GetStorageIdString(StorageId storage_id);
const char *GetStorageIdStringForPlayReport(StorageId storage_id);
}

View file

@ -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/>.
*/
#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;
}
}
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;
}
}

View file

@ -0,0 +1,279 @@
/*
* 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 InstallTaskBase::OnPrepareComplete() {
return ResultSuccess();
}
Result InstallTaskBase::GetLatestVersion(std::optional<u32> *out_version, u64 id) {
return ncm::ResultContentMetaNotFound();
}
Result InstallTaskBase::CheckInstallable() {
return ResultSuccess();
}
Result InstallTaskBase::OnExecuteComplete() {
return ResultSuccess();
}
void InstallTaskBase::Cancel() {
std::scoped_lock lk(this->cancel_mutex);
this->cancel_requested = true;
}
void InstallTaskBase::ResetCancel() {
std::scoped_lock lk(this->cancel_mutex);
this->cancel_requested = false;
}
bool InstallTaskBase::IsCancelRequested() {
std::scoped_lock lk(this->cancel_mutex);
return this->cancel_requested;
}
Result InstallTaskBase::Initialize(StorageId install_storage, InstallTaskDataBase *data, u32 config) {
R_UNLESS(IsInstallableStorage(install_storage), ncm::ResultUnknownStorage());
this->install_storage = install_storage;
this->data = data;
this->config = config;
return data->GetProgress(std::addressof(this->progress));
}
Result InstallTaskBase::Prepare() {
/* Call the implementation. */
Result result = this->PrepareImpl();
/* Update the last result. */
this->SetLastResult(result);
return result;
}
Result InstallTaskBase::PrepareImpl() {
/* Reset the throughput. */
this->ResetThroughputMeasurement();
/* Get the current progress. */
InstallProgress progress = this->GetProgress();
/* Transition from NotPrepared to DataPrepared. */
if (progress.state == InstallProgressState::NotPrepared) {
R_TRY(this->PrepareInstallContentMetaData());
R_TRY(this->PrepareDependency());
R_TRY(this->CheckInstallable());
this->SetProgressState(InstallProgressState::DataPrepared);
}
/* Get the current progress. */
progress = this->GetProgress();
/* Transition from DataPrepared to Prepared. */
if (progress.state == InstallProgressState::DataPrepared) {
R_TRY(this->PreparePlaceHolder());
this->SetProgressState(InstallProgressState::Prepared);
}
/* Signal prepare is completed. */
return this->OnPrepareComplete();
}
void InstallTaskBase::SetLastResult(Result last_result) {
std::scoped_lock lk(this->progress_mutex);
this->data->SetLastResult(last_result);
this->progress.SetLastResult(last_result);
}
Result InstallTaskBase::GetPreparedPlaceHolderPath(Path *out_path, u64 id, ContentMetaType meta_type, ContentType type) {
/* Count the number of content meta entries. */
s32 count;
R_TRY(this->data->Count(std::addressof(count)));
R_UNLESS(count > 0, ncm::ResultPlaceHolderNotFound());
/* Iterate over content meta. */
for (s32 i = 0; i < count; i++) {
/* Obtain the content meta. */
InstallContentMeta content_meta;
R_TRY(this->data->Get(std::addressof(content_meta), i));
const InstallContentMetaReader reader = content_meta.GetReader();
/* Ensure content meta matches the key and meta type. */
if (reader.GetKey().id != id || reader.GetKey().type != meta_type) {
continue;
}
/* Attempt to obtain a content info for the content type. */
if (const auto content_info = reader.GetContentInfo(type); content_info != nullptr) {
/* Open the relevant content storage. */
ContentStorage content_storage;
R_TRY(ncm::OpenContentStorage(&content_storage, content_info->storage_id));
/* Get the placeholder path. */
/* N doesn't bother checking the result. */
content_storage.GetPlaceHolderPath(out_path, content_info->placeholder_id);
return ResultSuccess();
}
}
return ncm::ResultPlaceHolderNotFound();
}
Result InstallTaskBase::CountInstallContentMetaData(s32 *out_count) {
return this->data->Count(out_count);
}
Result InstallTaskBase::GetInstallContentMetaData(InstallContentMeta *out_content_meta, s32 index) {
return this->data->Get(out_content_meta, index);
}
Result InstallTaskBase::CalculateRequiredSize(size_t *out_size) {
/* Count the number of content meta entries. */
s32 count;
R_TRY(this->data->Count(std::addressof(count)));
size_t required_size = 0;
/* Iterate over each entry. */
for (s32 i = 0; i < count; i++) {
/* Obtain the content meta. */
InstallContentMeta content_meta;
R_TRY(this->data->Get(std::addressof(content_meta), i));
const auto reader = content_meta.GetReader();
/* Sum the sizes from the content infos. */
for (size_t j = 0; j < reader.GetContentCount(); j++) {
const auto *content_info = reader.GetContentInfo(j);
if (content_info->install_state == InstallState::NotPrepared) {
required_size += ncm::CalculateRequiredSize(content_info->GetSize());
}
}
}
*out_size = required_size;
return ResultSuccess();
}
void InstallTaskBase::ResetThroughputMeasurement() {
std::scoped_lock lk(this->throughput_mutex);
this->throughput.elapsed_time = TimeSpan();
this->throughput_start_time = TimeSpan();
this->throughput.installed = 0;
}
void InstallTaskBase::SetProgressState(InstallProgressState state) {
std::scoped_lock(this->progress_mutex);
this->data->SetState(state);
this->progress.state = state;
}
Result InstallTaskBase::PreparePlaceHolder() {
static os::Mutex placeholder_mutex;
size_t total_size = 0;
/* Count the number of content meta entries. */
s32 count;
R_TRY(this->data->Count(std::addressof(count)));
for (s32 i = 0; i < count; i++) {
R_UNLESS(!this->IsCancelRequested(), ncm::ResultCreatePlaceHolderCancelled());
std::scoped_lock lk(placeholder_mutex);
InstallContentMeta content_meta;
if (R_SUCCEEDED(this->data->Get(&content_meta, i))) {
auto writer = content_meta.GetWriter();
StorageId storage_id = static_cast<StorageId>(writer.GetHeader()->storage_id);
/* Automatically choose a suitable storage id. */
if (storage_id == StorageId::None) {
R_TRY(ncm::SelectDownloadableStorage(std::addressof(storage_id), storage_id, writer.CalculateContentRequiredSize()));
}
/* Update the data when we are done. */
ON_SCOPE_EXIT { this->data->Update(content_meta, i); };
/* Open the relevant content storage. */
ContentStorage content_storage;
R_TRY(ncm::OpenContentStorage(&content_storage, storage_id));
/* Update the storage id in the header. */
writer.SetStorageId(storage_id);
for (size_t j = 0; j < writer.GetContentCount(); j++) {
R_UNLESS(!this->IsCancelRequested(), ncm::ResultCreatePlaceHolderCancelled());
auto *content_info = writer.GetWritableContentInfo(j);
bool has_content;
R_TRY(content_storage.Has(&has_content, content_info->GetId()));
if (has_content) {
/* Add the size of installed content infos to the total size. */
if (content_info->install_state == InstallState::Installed) {
total_size += content_info->GetSize();
}
/* Update the install state. */
content_info->install_state = InstallState::AlreadyExists;
} else {
if (content_info->install_state == InstallState::NotPrepared) {
/* Generate a placeholder id. */
const PlaceHolderId placeholder_id = content_storage.GeneratePlaceHolderId();
/* Update the placeholder id in the content info. */
content_info->placeholder_id = placeholder_id;
/* Create the placeholder. */
R_TRY(content_storage.CreatePlaceHolder(placeholder_id, content_info->GetId(), content_info->GetSize()));
/* Update the install state. */
content_info->install_state = InstallState::Prepared;
}
/* Update the storage id for the content info. */
content_info->storage_id = storage_id;
/* Add the size of this content info to the total size. */
total_size += content_info->GetSize();
}
}
}
}
this->SetTotalSize(total_size);
return ResultSuccess();
}
/* ... */
void InstallTaskBase::SetTotalSize(s64 size) {
std::scoped_lock(this->progress_mutex);
this->progress.total_size = size;
}
/* ... */
Result InstallTaskBase::PrepareDependency() {
return ResultSuccess();
}
/* ... */
InstallProgress InstallTaskBase::GetProgress() {
std::scoped_lock lk(this->progress_mutex);
return this->progress;
}
}

View file

@ -0,0 +1,86 @@
/*
* 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 const char *StorageIdStrings[] = {
"None",
"Host",
"GameCard",
"BuiltInSystem",
"BuiltInUser",
"SdCard"
};
constexpr const char *StorageIdStringsForPlayReport[] = {
"None",
"Host",
"Card",
"BuildInSystem",
"BuildInUser",
"SdCard"
};
}
Result SelectDownloadableStorage(StorageId *out_storage_id, StorageId storage_id, size_t 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();
}
const char *GetStorageIdString(StorageId storage_id) {
u8 id = static_cast<u8>(storage_id);
return id > 5 ? "(unknown)" : StorageIdStrings[id];
}
const char *GetStorageIdStringForPlayReport(StorageId storage_id) {
u8 id = static_cast<u8>(storage_id);
return id > 5 ? "(unknown)" : StorageIdStringsForPlayReport[id];
}
}

View file

@ -38,6 +38,7 @@ namespace ams::ncm {
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(InvalidContentMetaKey, 240);
R_DEFINE_ERROR_RESULT(ContentStorageBaseNotFound, 310);
@ -56,6 +57,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);