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:
Adubbz
2020-04-04 16:40:46 +11:00
committed by GitHub
parent 76d72fa946
commit a50d6a2696
48 changed files with 4163 additions and 56 deletions

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -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();

View File

@@ -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);
}
}
}

View File

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

View File

@@ -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;

View File

@@ -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();
}
}

File diff suppressed because it is too large Load Diff

View 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);
}
}

View File

@@ -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();
}
}

View File

@@ -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");
}
}

View File

@@ -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();
}
}

View 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)";
}
}
}

View File

@@ -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();
}
}

View File

@@ -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)));
}