ncm: implement ContentMetaReader

This commit is contained in:
Michael Scire 2020-03-02 16:10:03 -08:00
parent 13a0e74611
commit d40d2006e9
9 changed files with 457 additions and 279 deletions

View file

@ -17,5 +17,6 @@
#pragma once
#include "ncm/ncm_types.hpp"
#include "ncm/ncm_content_meta.hpp"
#include "ncm/ncm_i_content_meta_database.hpp"
#include "ncm/ncm_i_content_storage.hpp"

View file

@ -0,0 +1,248 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere/ncm/ncm_types.hpp>
namespace ams::ncm {
struct ContentMetaHeader {
u16 extended_header_size;
u16 content_count;
u16 content_meta_count;
ContentMetaAttribute attributes;
StorageId storage_id;
};
static_assert(sizeof(ContentMetaHeader) == 0x8, "ContentMetaHeader definition!");
struct ApplicationMetaExtendedHeader {
ProgramId patch_id;
u32 required_system_version;
u32 required_application_version;
};
struct PatchMetaExtendedHeader {
ProgramId application_id;
u32 required_system_version;
u32 extended_data_size;
u8 reserved[0x8];
};
struct AddOnContentMetaExtendedHeader {
ProgramId application_id;
u32 required_application_version;
u32 padding;
};
struct DeltaMetaExtendedHeader {
ProgramId application_id;
u32 extended_data_size;
u32 padding;
};
template<typename ContentMetaHeaderType, typename ContentInfoType>
class ContentMetaAccessor {
public:
using HeaderType = ContentMetaHeaderType;
using InfoType = ContentInfoType;
private:
void *data;
const size_t size;
bool is_header_valid;
private:
static size_t GetExtendedHeaderSize(ContentMetaType type) {
switch (type) {
case ContentMetaType::Application: return sizeof(ApplicationMetaExtendedHeader);
case ContentMetaType::Patch: return sizeof(PatchMetaExtendedHeader);
case ContentMetaType::AddOnContent: return sizeof(AddOnContentMetaExtendedHeader);
case ContentMetaType::Delta: return sizeof(DeltaMetaExtendedHeader);
default: return 0;
}
}
protected:
constexpr ContentMetaAccessor(const void *d, size_t sz) : data(const_cast<void *>(d)), size(sz), is_header_valid(true) { /* ... */ }
constexpr ContentMetaAccessor(void *d, size_t sz) : data(d), size(sz), is_header_valid(false) { /* ... */ }
template<class NewHeaderType, class NewInfoType>
static constexpr size_t CalculateSizeImpl(size_t ext_header_size, size_t content_count, size_t content_meta_count, size_t extended_data_size, bool has_digest) {
return sizeof(NewHeaderType) + ext_header_size + content_count * sizeof(NewInfoType) + content_meta_count * sizeof(ContentMetaInfo) + extended_data_size + (has_digest ? sizeof(Digest) : 0);
}
static constexpr size_t CalculateSize(ContentMetaType type, size_t content_count, size_t content_meta_count, size_t extended_data_size, bool has_digest = false) {
return CalculateSizeImpl<ContentMetaHeaderType, ContentInfoType>(GetExtendedHeaderSize(type), content_count, content_meta_count, extended_data_size, has_digest);
}
uintptr_t GetExtendedHeaderAddress() const {
return reinterpret_cast<uintptr_t>(this->data) + sizeof(HeaderType);
}
uintptr_t GetContentInfoStartAddress() const {
return this->GetExtendedHeaderAddress() + this->GetExtendedHeaderSize();
}
uintptr_t GetContentInfoAddress(size_t i) const {
return this->GetContentInfoStartAddress() + i * sizeof(InfoType);
}
uintptr_t GetContentMetaInfoStartAddress() const {
return this->GetContentInfoAddress(this->GetContentCount());
}
uintptr_t GetContentMetaInfoAddress(size_t i) const {
return this->GetContentMetaInfoStartAddress() + i * sizeof(ContentMetaInfo);
}
uintptr_t GetExtendedDataAddress() const {
return this->GetContentMetaInfoAddress(this->GetContentMetaCount());
}
uintptr_t GetDigestAddress() const {
return this->GetExtendedDataAddress() + this->GetExtendedDataSize();
}
InfoType *GetWritableContentInfo(size_t i) const {
AMS_ABORT_UNLESS(i < this->GetContentCount());
return reinterpret_cast<InfoType *>(this->GetContentInfoAddress(i));
}
InfoType *GetWritableContentInfo(ContentType type) const {
InfoType *found = nullptr;
for (size_t i = 0; i < this->GetContentCount(); i++) {
/* We want to find the info with the lowest id offset and the correct type. */
InfoType *info = this->GetWritableContentInfo(i);
if (info->GetType() == type && (found == nullptr || info->GetIdOffset() < found->GetIdOffset())) {
found = info;
}
}
return found;
}
InfoType *GetWritableContentInfo(ContentType type, u8 id_ofs) const {
for (size_t i = 0; i < this->GetContentCount(); i++) {
/* We want to find the info with the correct id offset and the correct type. */
if (InfoType *info = this->GetWritableContentInfo(i); info->GetType() == type && info->GetIdOffset() == id_ofs) {
return info;
}
}
return nullptr;
}
public:
const void *GetData() const {
return this->data;
}
size_t GetSize() const {
return this->size;
}
const HeaderType *GetHeader() const {
AMS_ABORT_UNLESS(this->is_header_valid);
return static_cast<const HeaderType *>(this->data);
}
ContentMetaKey GetKey() const {
auto header = this->GetHeader();
return ContentMetaKey::Make(header->id, header->version, header->type, header->install_type);
}
size_t GetExtendedHeaderSize() const {
return this->GetHeader()->extended_header_size;
}
template<typename ExtendedHeaderType>
const ExtendedHeaderType *GetExtendedHeader() const {
return reinterpret_cast<const ExtendedHeaderType *>(this->GetExtendedHeaderAddress());
}
size_t GetContentCount() const {
return this->GetHeader()->content_count;
}
const InfoType *GetContentInfo(size_t i) const {
AMS_ABORT_UNLESS(i < this->GetContentCount());
return this->GetWritableContentInfo(i);
}
const InfoType *GetContentInfo(ContentType type) const {
return this->GetWritableContentInfo(type);
}
const InfoType *GetContentInfo(ContentType type, u8 id_ofs) const {
return this->GetWritableContentInfo(type, id_ofs);
}
size_t GetContentMetaCount() const {
return this->GetHeader()->content_meta_count;
}
const ContentMetaInfo *GetContentMetaInfo(size_t i) const {
AMS_ABORT_UNLESS(i < this->GetContentMetaCount());
return reinterpret_cast<const ContentMetaInfo *>(this->GetContentMetaInfoAddress(i));
}
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;
}
}
const void *GetExtendedData() const {
return reinterpret_cast<const void *>(this->GetExtendedDataAddress());
}
const Digest *GetDigest() const {
return reinterpret_cast<Digest *>(this->GetDigestAddress());
}
bool HasContent(const ContentId &id) const {
for (size_t i = 0; i < this->GetContentCount(); i++) {
if (id == this->GetContentInfo(i)->GetId()) {
return true;
}
}
return false;
}
std::optional<ProgramId> GetApplicationId(const ContentMetaKey &key) const {
switch (key.type) {
case ContentMetaType::Application: return key.id;
case ContentMetaType::Patch: return this->GetExtendedHeader<PatchMetaExtendedHeader>()->application_id;
case ContentMetaType::AddOnContent: return this->GetExtendedHeader<AddOnContentMetaExtendedHeader>()->application_id;
case ContentMetaType::Delta: return this->GetExtendedHeader<DeltaMetaExtendedHeader>()->application_id;
default: return std::nullopt;
}
}
std::optional<ProgramId> GetApplicationId() const {
return this->GetApplicationId(this->GetKey());
}
};
class ContentMetaReader : public ContentMetaAccessor<ContentMetaHeader, ContentInfo> {
public:
constexpr ContentMetaReader(const void *data, size_t size) : ContentMetaAccessor(data, size) { /* ... */ }
using ContentMetaAccessor::CalculateSize;
};
}

View file

@ -69,10 +69,6 @@ namespace ams::ncm {
virtual Result GetAttributes(sf::Out<ContentMetaAttribute> out_attributes, const ContentMetaKey &key) = 0;
virtual Result GetRequiredApplicationVersion(sf::Out<u32> out_version, const ContentMetaKey &key) = 0;
virtual Result GetContentIdByTypeAndIdOffset(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type, u8 id_offset) = 0;
/* APIs. */
virtual Result GetLatestProgram(ContentId *out_content_id, ProgramId program_id) = 0;
virtual Result GetLatestData(ContentId *out_content_id, ProgramId program_id) = 0;
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MAKE_SERVICE_COMMAND_META(Set),

View file

@ -108,12 +108,42 @@ namespace ams::ncm {
struct ContentInfo {
ContentId content_id;
u8 size[6];
u32 size_low;
u16 size_high;
ContentType content_type;
u8 id_offset;
constexpr const ContentId &GetId() const {
return this->content_id;
}
constexpr u64 GetSize() const {
return (static_cast<u64>(this->size_high) << 32) | static_cast<u64>(this->size_low);
}
constexpr ContentType GetType() const {
return this->content_type;
}
constexpr u8 GetIdOffset() const {
return this->id_offset;
}
static constexpr ContentInfo Make(ContentId id, u64 size, ContentType type, u8 id_ofs) {
const u32 size_low = size & 0xFFFFFFFFu;
const u16 size_high = static_cast<u16>(size >> 32);
return {
.content_id = id,
.size_low = size_low,
.size_high = size_high,
.content_type = type,
.id_offset = id_ofs,
};
}
};
static_assert(sizeof(ContentInfo) == 0x18, "ContentInfo definition!");
static_assert(sizeof(std::is_pod<ContentInfo>::value));
static_assert(sizeof(ContentInfo) == 0x18);
using MakeContentPathFunc = void (*)(char *out, ContentId content_id, const char *root);
using MakePlaceHolderPathFunc = void (*)(char *out, PlaceHolderId placeholder_id, const char *root);
@ -564,19 +594,19 @@ namespace ams::ncm {
} else if (this->id != other.id) {
return false;
}
if (this->version < other.version) {
return true;
} else if (this->version != other.version) {
return false;
}
if (this->type < other.type) {
return true;
} else if (this->type != other.type) {
return false;
}
return this->install_type < other.install_type;
}
@ -602,8 +632,29 @@ namespace ams::ncm {
static_assert(sizeof(ContentMetaKey) == 0x10, "ContentMetaKey definition!");
/* Used by system updates. They share the exact same struct as ContentMetaKey */
using ContentMetaInfo = ContentMetaKey;
/* Used by system updates. */
struct ContentMetaInfo {
ProgramId id;
u32 version;
ContentMetaType type;
u8 attributes;
u8 padding[2];
static constexpr ContentMetaInfo Make(ProgramId program_id, u32 version, ContentMetaType type, u8 attributes) {
return {
.id = program_id,
.version = version,
.type = type,
.attributes = attributes,
};
}
constexpr ContentMetaKey ToKey() {
return ContentMetaKey::Make(this->id, this->version, this->type);
}
};
static_assert(sizeof(ContentMetaInfo) == 0x10);
struct ApplicationContentMetaKey {
ContentMetaKey key;
@ -612,4 +663,8 @@ namespace ams::ncm {
static_assert(sizeof(ApplicationContentMetaKey) == 0x18, "ApplicationContentMetaKey definition!");
struct Digest {
u8 data[crypto::Sha256Generator::HashSize];
};
}

View file

@ -19,131 +19,52 @@
namespace ams::ncm {
namespace {
struct ContentMetaHeader {
u16 extended_header_size;
u16 content_count;
u16 content_meta_count;
ContentMetaAttribute attributes;
StorageId storage_id;
};
static_assert(sizeof(ContentMetaHeader) == 0x8, "ContentMetaHeader definition!");
struct ApplicationMetaExtendedHeader {
ProgramId patch_id;
u32 required_system_version;
u32 required_application_version;
};
struct PatchMetaExtendedHeader {
ProgramId application_id;
u32 required_system_version;
u32 extended_data_size;
u8 reserved[0x8];
};
struct AddOnContentMetaExtendedHeader {
ProgramId application_id;
u32 required_application_version;
u32 padding;
};
struct SystemUpdateMetaExtendedHeader {
u32 extended_data_size;
};
template<typename ExtendedHeaderType>
inline const ExtendedHeaderType *GetValueExtendedHeader(const ContentMetaHeader *header) {
return reinterpret_cast<const ExtendedHeaderType *>(header + 1);
}
inline const ContentInfo *GetValueContentInfos(const ContentMetaHeader *header) {
return reinterpret_cast<const ContentInfo*>(reinterpret_cast<const u8*>(GetValueExtendedHeader<void>(header)) + header->extended_header_size);
}
inline const ContentMetaInfo *GetValueContentMetaInfos(const ContentMetaHeader *header) {
return reinterpret_cast<const ContentMetaInfo*>(GetValueContentInfos(header) + header->content_count);
}
Result GetContentMetaSize(size_t *out, const ContentMetaKey &key, const kvdb::MemoryKeyValueStore<ContentMetaKey> *kvs) {
R_TRY_CATCH(kvs->GetValueSize(out, key)) {
R_CONVERT(kvdb::ResultKeyNotFound, ncm::ResultContentMetaNotFound())
} R_END_TRY_CATCH;
return ResultSuccess();
}
Result GetContentMetaValuePointer(const ContentMetaHeader **out_value_ptr, size_t *out_size, const ContentMetaKey &key, const kvdb::MemoryKeyValueStore<ContentMetaKey> *kvs) {
R_TRY(GetContentMetaSize(out_size, key, kvs));
return kvs->GetValuePointer(out_value_ptr, key);
}
}
Result ContentMetaDatabaseImpl::GetContentIdByTypeImpl(ContentId *out, const ContentMetaKey& key, ContentType type, std::optional<u8> id_offset) {
R_TRY(this->EnsureEnabled());
/* Find the meta key. */
ContentMetaKeyValueStore::Entry *entry = nullptr;
R_TRY(this->FindContentMetaKeyValue(std::addressof(entry), key));
const auto stored_key = entry->GetKey();
const ContentMetaHeader *header = nullptr;
size_t value_size = 0;
R_TRY(GetContentMetaValuePointer(&header, &value_size, stored_key, this->kvs));
/* Create a reader for this content meta. */
const void *meta;
size_t meta_size;
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, stored_key));
R_UNLESS(header->content_count != 0, ncm::ResultContentNotFound());
const ContentInfo *content_infos = GetValueContentInfos(header);
const ContentInfo *found_content_info = nullptr;
ContentMetaReader reader(meta, meta_size);
/* Find the content info. */
const ContentInfo *content_info = nullptr;
if (id_offset) {
for (size_t i = 0; i < header->content_count; i++) {
const ContentInfo *content_info = &content_infos[i];
if (content_info->content_type == type && content_info->id_offset == *id_offset) {
found_content_info = content_info;
break;
}
}
content_info = reader.GetContentInfo(type, *id_offset);
} else {
const ContentInfo *lowest_id_offset_info = nullptr;
for (size_t i = 0; i < header->content_count; i++) {
const ContentInfo *content_info = &content_infos[i];
if (content_info->content_type == type && (!lowest_id_offset_info || lowest_id_offset_info->id_offset > content_info->id_offset)) {
lowest_id_offset_info = content_info;
}
}
found_content_info = lowest_id_offset_info;
content_info = reader.GetContentInfo(type);
}
R_UNLESS(content_info != nullptr, ncm::ResultContentNotFound());
R_UNLESS(found_content_info, ncm::ResultContentNotFound());
*out = found_content_info->content_id;
/* Save output. */
*out = content_info->content_id;
return ResultSuccess();
}
Result ContentMetaDatabaseImpl::GetLatestContentMetaKeyImpl(ContentMetaKey *out_key, ProgramId id) {
R_TRY(this->EnsureEnabled());
ContentMetaKey key = {0};
key.id = id;
for (auto entry = this->kvs->lower_bound(key); entry != this->kvs->end(); entry++) {
if (entry->GetKey().id != key.id) {
std::optional<ContentMetaKey> found_key = std::nullopt;
for (auto entry = this->kvs->lower_bound(ContentMetaKey::Make(id, 0, ContentMetaType::Unknown)); entry != this->kvs->end(); entry++) {
if (entry->GetKey().id != id) {
break;
}
if (entry->GetKey().install_type == ContentInstallType::Full) {
*out_key = entry->GetKey();
return ResultSuccess();
found_key = entry->GetKey();
}
}
R_UNLESS(found_key, ncm::ResultContentMetaNotFound());
return ncm::ResultContentMetaNotFound();
*out_key = *found_key;
return ResultSuccess();
}
Result ContentMetaDatabaseImpl::Set(const ContentMetaKey &key, sf::InBuffer value) {
@ -169,62 +90,45 @@ namespace ams::ncm {
R_UNLESS(offset <= std::numeric_limits<s32>::max(), ncm::ResultInvalidOffset());
R_TRY(this->EnsureEnabled());
const ContentMetaHeader *header = nullptr;
size_t value_size = 0;
R_TRY(GetContentMetaValuePointer(&header, &value_size, key, this->kvs));
const auto content_infos = GetValueContentInfos(header);
const void *meta;
size_t meta_size;
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
ContentMetaReader reader(meta, meta_size);
size_t count;
for (count = 0; offset + count < header->content_count && count < out_info.GetSize(); count++) {
out_info[count] = content_infos[offset + count];
for (count = 0; count < out_info.GetSize() && count + offset < reader.GetContentCount(); count++) {
out_info[count] = *reader.GetContentInfo(offset + count);
}
out_count.SetValue(count);
return ResultSuccess();
}
Result ContentMetaDatabaseImpl::List(sf::Out<u32> out_entries_total, sf::Out<u32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType type, ProgramId application_program_id, ProgramId program_id_min, ProgramId program_id_max, ContentInstallType install_type) {
Result ContentMetaDatabaseImpl::List(sf::Out<u32> out_entries_total, sf::Out<u32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType type, ProgramId application_id, ProgramId program_id_min, ProgramId program_id_max, ContentInstallType install_type) {
R_TRY(this->EnsureEnabled());
size_t entries_total = 0;
size_t entries_written = 0;
/* If there are no entries then we've already successfully listed them all. */
if (this->kvs->GetCount() == 0) {
out_entries_total.SetValue(entries_total);
out_entries_written.SetValue(entries_written);
return ResultSuccess();
}
for (auto entry = this->kvs->begin(); entry != this->kvs->end(); entry++) {
ContentMetaKey key = entry->GetKey();
/* Check if this entry matches the given filters. */
if (!((type == ContentMetaType::Unknown || key.type == type) && (program_id_min <= key.id && key.id <= program_id_max) && (key.install_type == ContentInstallType::Unknown || key.install_type == install_type))) {
if (!((type == ContentMetaType::Unknown || key.type == type) && (program_id_min <= key.id && key.id <= program_id_max) && (install_type == ContentInstallType::Unknown || key.install_type == install_type))) {
continue;
}
if (static_cast<u64>(application_program_id) != 0) {
size_t meta_size = 0;
const ContentMetaHeader *meta = nullptr;
R_TRY(GetContentMetaValuePointer(&meta, &meta_size, key, this->kvs));
/* If application id is present, check if it matches the filter. */
if (application_id != InvalidProgramId) {
const void *meta;
size_t meta_size;
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
/* Each of these types are owned by an application. We need to check if their owner application matches the filter. */
if (key.type == ContentMetaType::Application || key.type == ContentMetaType::Patch || key.type == ContentMetaType::AddOnContent || key.type == ContentMetaType::Delta) {
ProgramId entry_application_id = key.id;
switch (key.type) {
case ContentMetaType::Application:
break;
default:
/* The first u64 of all non-application extended headers is the application program id. */
entry_application_id = *GetValueExtendedHeader<ProgramId>(meta);
}
ContentMetaReader reader(meta, meta_size);
/* Application id doesn't match filter, skip this entry. */
if (entry_application_id != application_program_id) {
continue;
}
if (const auto entry_application_id = reader.GetApplicationId(key); entry_application_id && application_id != *entry_application_id) {
continue;
}
}
@ -232,7 +136,6 @@ namespace ams::ncm {
if (entries_written < out_info.GetSize()) {
out_info[entries_written++] = key;
}
entries_total++;
}
@ -241,54 +144,33 @@ namespace ams::ncm {
return ResultSuccess();
}
Result ContentMetaDatabaseImpl::GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, ProgramId program_id) {
Result ContentMetaDatabaseImpl::GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, ProgramId program_id) {
R_TRY(this->EnsureEnabled());
return this->GetLatestContentMetaKeyImpl(out_key.GetPointer(), program_id);
}
Result ContentMetaDatabaseImpl::ListApplication(sf::Out<u32> out_entries_total, sf::Out<u32> out_entries_written, const sf::OutArray<ApplicationContentMetaKey> &out_keys, ContentMetaType type) {
R_TRY(this->EnsureEnabled());
size_t entries_total = 0;
size_t entries_written = 0;
/* If there are no entries then we've already successfully listed them all. */
if (this->kvs->GetCount() == 0) {
out_entries_total.SetValue(entries_total);
out_entries_written.SetValue(entries_written);
return ResultSuccess();
}
for (auto entry = this->kvs->begin(); entry != this->kvs->end(); entry++) {
ContentMetaKey key = entry->GetKey();
/* Check if this entry matches the given filters. */
if (!((static_cast<u8>(type) == 0 || key.type == type))) {
if (!((type == ContentMetaType::Unknown || key.type == type))) {
continue;
}
const ContentMetaHeader *value = nullptr;
size_t value_size = 0;
R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs));
if (key.type == ContentMetaType::Application || key.type == ContentMetaType::Patch || key.type == ContentMetaType::AddOnContent || key.type == ContentMetaType::Delta) {
ProgramId application_id = key.id;
switch (key.type) {
case ContentMetaType::Application:
break;
default:
/* The first u64 of all non-application extended headers is the application program id. */
application_id = *GetValueExtendedHeader<ProgramId>(value);
}
/* Check if the entry has an application id. */
ContentMetaReader reader(entry->GetValuePointer(), entry->GetValueSize());
if (const auto entry_application_id = reader.GetApplicationId(key); entry_application_id) {
/* Write the entry to the output buffer. */
if (entries_written < out_keys.GetSize()) {
ApplicationContentMetaKey *out_app_key = &out_keys[entries_written++];
out_app_key->application_program_id = application_id;
out_app_key->key = key;
out_keys[entries_written++] = { key, *entry_application_id };
}
entries_total++;
}
}
@ -301,23 +183,21 @@ namespace ams::ncm {
Result ContentMetaDatabaseImpl::Has(sf::Out<bool> out, const ContentMetaKey &key) {
R_TRY(this->EnsureEnabled());
if (this->kvs->GetCount() == 0) {
out.SetValue(false);
return ResultSuccess();
}
*out = false;
bool has = false;
const auto it = this->kvs->lower_bound(key);
if (it != this->kvs->end()) {
has = it->GetKey() == key;
}
size_t size;
R_TRY_CATCH(this->kvs->GetValueSize(&size, key)) {
R_CONVERT(kvdb::ResultKeyNotFound, ResultSuccess());
} R_END_TRY_CATCH;
out.SetValue(has);
*out = true;
return ResultSuccess();
}
Result ContentMetaDatabaseImpl::HasAll(sf::Out<bool> out, const sf::InArray<ContentMetaKey> &keys) {
R_TRY(this->EnsureEnabled());
*out = false;
for (size_t i = 0; i < keys.GetSize(); i++) {
bool has;
R_TRY(this->Has(std::addressof(has), keys[i]));
@ -330,13 +210,11 @@ namespace ams::ncm {
Result ContentMetaDatabaseImpl::GetSize(sf::Out<u64> out_size, const ContentMetaKey &key) {
R_TRY(this->EnsureEnabled());
R_UNLESS(this->kvs->GetCount() != 0, ncm::ResultContentMetaNotFound());
ContentMetaKeyValueStore::Entry *entry = nullptr;
R_TRY(this->FindContentMetaKeyValue(std::addressof(entry), key));
out_size.SetValue(entry->GetValueSize());
size_t size;
R_TRY(this->GetContentMetaSize(&size, key));
out_size.SetValue(size);
return ResultSuccess();
}
@ -344,14 +222,16 @@ namespace ams::ncm {
R_TRY(this->EnsureEnabled());
R_UNLESS(key.type == ContentMetaType::Application || key.type == ContentMetaType::Patch, ncm::ResultInvalidContentMetaKey());
const ContentMetaHeader *value = nullptr;
size_t value_size = 0;
R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs));
const void *meta;
size_t meta_size;
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
ContentMetaReader reader(meta, meta_size);
if (key.type == ContentMetaType::Application) {
out_version.SetValue(GetValueExtendedHeader<ApplicationMetaExtendedHeader>(value)->required_system_version);
out_version.SetValue(reader.GetExtendedHeader<ApplicationMetaExtendedHeader>()->required_system_version);
} else {
out_version.SetValue(GetValueExtendedHeader<PatchMetaExtendedHeader>(value)->required_system_version);
out_version.SetValue(reader.GetExtendedHeader<PatchMetaExtendedHeader>()->required_system_version);
}
return ResultSuccess();
@ -361,11 +241,13 @@ namespace ams::ncm {
R_TRY(this->EnsureEnabled());
R_UNLESS(key.type == ContentMetaType::Application, ncm::ResultInvalidContentMetaKey());
const ContentMetaHeader *value = nullptr;
size_t value_size = 0;
R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs));
const void *meta;
size_t meta_size;
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
out_patch_id.SetValue(GetValueExtendedHeader<ApplicationMetaExtendedHeader>(value)->patch_id);
ContentMetaReader reader(meta, meta_size);
out_patch_id.SetValue(reader.GetExtendedHeader<ApplicationMetaExtendedHeader>()->patch_id);
return ResultSuccess();
}
@ -382,25 +264,25 @@ namespace ams::ncm {
for (size_t i = 0; i < out_orphaned.GetSize(); i++) {
out_orphaned[i] = true;
}
R_UNLESS(this->kvs->GetCount() != 0, ResultSuccess());
for (auto entry = this->kvs->begin(); entry != this->kvs->end(); entry++) {
const auto header = reinterpret_cast<const ContentMetaHeader *>(entry->GetValuePointer());
const ContentInfo *content_infos = GetValueContentInfos(header);
auto IsOrphanedContent = [](const sf::InArray<ContentId> &list, const ncm::ContentId &id) ALWAYS_INLINE_LAMBDA {
for (size_t i = 0; i < list.GetSize(); i++) {
if (list[i] == id) {
return std::make_optional(i);
}
auto IsOrphanedContent = [](const sf::InArray<ContentId> &list, const ncm::ContentId &id) ALWAYS_INLINE_LAMBDA {
for (size_t i = 0; i < list.GetSize(); i++) {
if (list[i] == id) {
return std::make_optional(i);
}
return std::optional<size_t>(std::nullopt);
};
}
return std::optional<size_t>(std::nullopt);
};
for (auto entry = this->kvs->begin(); entry != this->kvs->end(); entry++) {
ContentMetaReader reader(entry->GetValuePointer(), entry->GetValueSize());
/* Check if any of this entry's content infos matches one of the content ids for lookup. */
/* If they do, then the content id isn't orphaned. */
for (size_t i = 0; i < header->content_count; i++) {
if (auto found = IsOrphanedContent(content_ids, content_infos[i].content_id); found) {
for (size_t i = 0; i < reader.GetContentCount(); i++) {
if (auto found = IsOrphanedContent(content_ids, reader.GetContentInfo(i)->GetId()); found) {
out_orphaned[*found] = false;
}
}
@ -416,17 +298,15 @@ namespace ams::ncm {
}
Result ContentMetaDatabaseImpl::HasContent(sf::Out<bool> out, const ContentMetaKey &key, ContentId content_id) {
const ContentMetaHeader *header = nullptr;
size_t value_size = 0;
R_TRY(GetContentMetaValuePointer(&header, &value_size, key, this->kvs));
const ContentInfo *content_infos = GetValueContentInfos(header);
const void *meta;
size_t meta_size;
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
if (header->content_count > 0) {
for (size_t i = 0; i < header->content_count; i++) {
if (content_id == content_infos[i].content_id) {
out.SetValue(true);
return ResultSuccess();
}
ContentMetaReader reader(meta, meta_size);
for (size_t i = 0; i < reader.GetContentCount(); i++) {
if (content_id == reader.GetContentInfo(i)->GetId()) {
out.SetValue(true);
return ResultSuccess();
}
}
@ -434,74 +314,64 @@ namespace ams::ncm {
return ResultSuccess();
}
Result ContentMetaDatabaseImpl::ListContentMetaInfo(sf::Out<u32> out_entries_written, const sf::OutArray<ContentMetaInfo> &out_meta_info, const ContentMetaKey &key, u32 start_index) {
R_UNLESS(start_index <= std::numeric_limits<s32>::max(), ncm::ResultInvalidOffset());
Result ContentMetaDatabaseImpl::ListContentMetaInfo(sf::Out<u32> out_entries_written, const sf::OutArray<ContentMetaInfo> &out_meta_info, const ContentMetaKey &key, u32 offset) {
R_UNLESS(offset <= std::numeric_limits<s32>::max(), ncm::ResultInvalidOffset());
R_TRY(this->EnsureEnabled());
const ContentMetaHeader *header = nullptr;
size_t value_size = 0;
R_TRY(GetContentMetaValuePointer(&header, &value_size, key, this->kvs));
const auto content_meta_infos = GetValueContentMetaInfos(header);
size_t entries_written = 0;
for (size_t i = start_index; i < out_meta_info.GetSize(); i++) {
/* We have no more entries we can read out. */
if (header->content_meta_count <= start_index + i) {
break;
}
const void *meta;
size_t meta_size;
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
out_meta_info[i] = content_meta_infos[i];
entries_written = i + 1;
ContentMetaReader reader(meta, meta_size);
size_t count;
for (count = 0; count < out_meta_info.GetSize() && count + offset <= reader.GetContentMetaCount(); count++) {
out_meta_info[count] = *reader.GetContentMetaInfo(count + offset);
}
out_entries_written.SetValue(entries_written);
out_entries_written.SetValue(count);
return ResultSuccess();
}
Result ContentMetaDatabaseImpl::GetAttributes(sf::Out<ContentMetaAttribute> out_attributes, const ContentMetaKey &key) {
R_TRY(this->EnsureEnabled());
const ContentMetaHeader *header = nullptr;
size_t value_size = 0;
R_TRY(GetContentMetaValuePointer(&header, &value_size, key, this->kvs));
out_attributes.SetValue(header->attributes);
const void *meta;
size_t meta_size;
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
ContentMetaReader reader(meta, meta_size);
out_attributes.SetValue(reader.GetHeader()->attributes);
return ResultSuccess();
}
Result ContentMetaDatabaseImpl::GetRequiredApplicationVersion(sf::Out<u32> out_version, const ContentMetaKey &key) {
R_TRY(this->EnsureEnabled());
const ContentMetaHeader *value = nullptr;
size_t value_size = 0;
R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs));
const void *meta;
size_t meta_size;
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
/* As of 9.0.0, applications can be dependent on a specific base application version. */
if (hos::GetVersion() >= hos::Version_900 && key.type == ContentMetaType::Application) {
out_version.SetValue(GetValueExtendedHeader<ApplicationMetaExtendedHeader>(value)->required_application_version);
return ResultSuccess();
ContentMetaReader reader(meta, meta_size);
/* Get the required version. */
u32 required_version;
if (key.type == ContentMetaType::Application && hos::GetVersion() >= hos::Version_900) {
/* As of 9.0.0, applications can be dependent on a specific base application version. */
required_version = reader.GetExtendedHeader<ApplicationMetaExtendedHeader>()->required_application_version;
} else if (key.type == ContentMetaType::AddOnContent) {
required_version = reader.GetExtendedHeader<AddOnContentMetaExtendedHeader>()->required_application_version;
} else {
return ncm::ResultInvalidContentMetaKey();
}
R_UNLESS(key.type == ContentMetaType::AddOnContent, ncm::ResultInvalidContentMetaKey());
out_version.SetValue(GetValueExtendedHeader<AddOnContentMetaExtendedHeader>(value)->required_application_version);
out_version.SetValue(required_version);
return ResultSuccess();
}
Result ContentMetaDatabaseImpl::GetContentIdByTypeAndIdOffset(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type, u8 id_offset) {
return this->GetContentIdByTypeImpl(out_content_id.GetPointer(), key, type, std::optional(id_offset));
}
Result ContentMetaDatabaseImpl::GetLatestProgram(ContentId *out_content_id, ProgramId program_id) {
ContentMetaKey key;
R_TRY(this->GetLatestContentMetaKey(&key, program_id));
return this->GetContentIdByType(out_content_id, key, ContentType::Program);
}
Result ContentMetaDatabaseImpl::GetLatestData(ContentId *out_content_id, ProgramId program_id) {
ContentMetaKey key;
R_TRY(this->GetLatestContentMetaKey(&key, program_id));
return this->GetContentIdByType(out_content_id, key, ContentType::Data);
return this->GetContentIdByTypeImpl(out_content_id.GetPointer(), key, type, std::make_optional(id_offset));
}
}

View file

@ -50,10 +50,6 @@ namespace ams::ncm {
virtual Result GetAttributes(sf::Out<ContentMetaAttribute> out_attributes, const ContentMetaKey &key) override;
virtual Result GetRequiredApplicationVersion(sf::Out<u32> out_version, const ContentMetaKey &key) override;
virtual Result GetContentIdByTypeAndIdOffset(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type, u8 id_offset) override;
/* APIs. */
virtual Result GetLatestProgram(ContentId *out_content_id, ProgramId program_id) override;
virtual Result GetLatestData(ContentId *out_content_id, ProgramId program_id) override;
};
}

View file

@ -35,18 +35,31 @@ namespace ams::ncm {
std::strcpy(this->mount_name, mount_name);
}
protected:
Result EnsureEnabled() {
Result EnsureEnabled() const {
R_UNLESS(!this->disabled, ncm::ResultInvalidContentMetaDatabase());
return ResultSuccess();
}
Result FindContentMetaKeyValue(ContentMetaKeyValueStore::Entry **out_entry, const ContentMetaKey &key) {
Result FindContentMetaKeyValue(ContentMetaKeyValueStore::Entry **out_entry, const ContentMetaKey &key) const {
const auto it = this->kvs->lower_bound(key);
R_UNLESS(it != this->kvs->end(), ncm::ResultContentMetaNotFound());
R_UNLESS(it->GetKey().id == key.id, ncm::ResultContentMetaNotFound());
*out_entry = it;
return ResultSuccess();
}
Result GetContentMetaSize(size_t *out, const ContentMetaKey &key) const {
R_TRY_CATCH(this->kvs->GetValueSize(out, key)) {
R_CONVERT(kvdb::ResultKeyNotFound, ncm::ResultContentMetaNotFound())
} R_END_TRY_CATCH;
return ResultSuccess();
}
Result GetContentMetaPointer(const void **out_value_ptr, size_t *out_size, const ContentMetaKey &key) const {
R_TRY(this->GetContentMetaSize(out_size, key));
return this->kvs->GetValuePointer(reinterpret_cast<const ContentMetaHeader **>(out_value_ptr), key);
}
};
}

View file

@ -22,7 +22,7 @@ namespace ams::ncm {
R_TRY(this->EnsureEnabled());
const ContentMetaKey key = ContentMetaKey::Make(program_id, 0, ContentMetaType::Unknown);
std::optional<ContentMetaKey> found_key;
for (auto entry = this->kvs->lower_bound(key); entry != this->kvs->end(); entry++) {
if (entry->GetKey().id != program_id) {
@ -31,8 +31,8 @@ namespace ams::ncm {
found_key = entry->GetKey();
}
R_UNLESS(found_key, ncm::ResultContentMetaNotFound());
*out_key = *found_key;
return ResultSuccess();
}

View file

@ -20,11 +20,10 @@
#include "ncm_content_meta_database_impl.hpp"
namespace ams::ncm {
class OnMemoryContentMetaDatabaseImpl : public ContentMetaDatabaseImpl {
public:
OnMemoryContentMetaDatabaseImpl(ams::kvdb::MemoryKeyValueStore<ContentMetaKey> *kvs) : ContentMetaDatabaseImpl(kvs) {
}
OnMemoryContentMetaDatabaseImpl(ams::kvdb::MemoryKeyValueStore<ContentMetaKey> *kvs) : ContentMetaDatabaseImpl(kvs) { /* ... */ }
public:
virtual Result GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, ProgramId id) override;
virtual Result LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) override;