From d40d2006e94f676579c1901f189c9ab8cf6b70a1 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Mon, 2 Mar 2020 16:10:03 -0800 Subject: [PATCH] ncm: implement ContentMetaReader --- .../include/stratosphere/ncm.hpp | 1 + .../stratosphere/ncm/ncm_content_meta.hpp | 248 +++++++++++ .../ncm/ncm_i_content_meta_database.hpp | 4 - .../include/stratosphere/ncm/ncm_types.hpp | 69 +++- .../source/ncm_content_meta_database_impl.cpp | 384 ++++++------------ .../source/ncm_content_meta_database_impl.hpp | 4 - .../ncm_content_meta_database_impl_base.hpp | 17 +- ...m_on_memory_content_meta_database_impl.cpp | 4 +- ...m_on_memory_content_meta_database_impl.hpp | 5 +- 9 files changed, 457 insertions(+), 279 deletions(-) create mode 100644 libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta.hpp diff --git a/libraries/libstratosphere/include/stratosphere/ncm.hpp b/libraries/libstratosphere/include/stratosphere/ncm.hpp index b59a88f4c..343073124 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm.hpp @@ -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" diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta.hpp new file mode 100644 index 000000000..836ee56e6 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta.hpp @@ -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 . + */ + +#pragma once +#include + +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 + 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(d)), size(sz), is_header_valid(true) { /* ... */ } + constexpr ContentMetaAccessor(void *d, size_t sz) : data(d), size(sz), is_header_valid(false) { /* ... */ } + + template + 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(GetExtendedHeaderSize(type), content_count, content_meta_count, extended_data_size, has_digest); + } + + uintptr_t GetExtendedHeaderAddress() const { + return reinterpret_cast(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(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(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 + const ExtendedHeaderType *GetExtendedHeader() const { + return reinterpret_cast(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(this->GetContentMetaInfoAddress(i)); + } + + size_t GetExtendedDataSize() const { + switch (this->GetHeader()->type) { + case ContentMetaType::Patch: return this->GetExtendedHeader()->extended_data_size; + case ContentMetaType::Delta: return this->GetExtendedHeader()->extended_data_size; + default: return 0; + } + } + + const void *GetExtendedData() const { + return reinterpret_cast(this->GetExtendedDataAddress()); + } + + const Digest *GetDigest() const { + return reinterpret_cast(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 GetApplicationId(const ContentMetaKey &key) const { + switch (key.type) { + case ContentMetaType::Application: return key.id; + case ContentMetaType::Patch: return this->GetExtendedHeader()->application_id; + case ContentMetaType::AddOnContent: return this->GetExtendedHeader()->application_id; + case ContentMetaType::Delta: return this->GetExtendedHeader()->application_id; + default: return std::nullopt; + } + } + + std::optional GetApplicationId() const { + return this->GetApplicationId(this->GetKey()); + } + }; + + class ContentMetaReader : public ContentMetaAccessor { + public: + constexpr ContentMetaReader(const void *data, size_t size) : ContentMetaAccessor(data, size) { /* ... */ } + + using ContentMetaAccessor::CalculateSize; + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_i_content_meta_database.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_i_content_meta_database.hpp index eb74addec..f09c7a244 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_i_content_meta_database.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_i_content_meta_database.hpp @@ -69,10 +69,6 @@ namespace ams::ncm { virtual Result GetAttributes(sf::Out out_attributes, const ContentMetaKey &key) = 0; virtual Result GetRequiredApplicationVersion(sf::Out out_version, const ContentMetaKey &key) = 0; virtual Result GetContentIdByTypeAndIdOffset(sf::Out 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), diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_types.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_types.hpp index d370c40dd..feb560361 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_types.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_types.hpp @@ -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(this->size_high) << 32) | static_cast(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(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::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]; + }; + } diff --git a/stratosphere/ncm/source/ncm_content_meta_database_impl.cpp b/stratosphere/ncm/source/ncm_content_meta_database_impl.cpp index d3cbbe5cf..0612cafbe 100644 --- a/stratosphere/ncm/source/ncm_content_meta_database_impl.cpp +++ b/stratosphere/ncm/source/ncm_content_meta_database_impl.cpp @@ -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 - inline const ExtendedHeaderType *GetValueExtendedHeader(const ContentMetaHeader *header) { - return reinterpret_cast(header + 1); - } - - inline const ContentInfo *GetValueContentInfos(const ContentMetaHeader *header) { - return reinterpret_cast(reinterpret_cast(GetValueExtendedHeader(header)) + header->extended_header_size); - } - - inline const ContentMetaInfo *GetValueContentMetaInfos(const ContentMetaHeader *header) { - return reinterpret_cast(GetValueContentInfos(header) + header->content_count); - } - - Result GetContentMetaSize(size_t *out, const ContentMetaKey &key, const kvdb::MemoryKeyValueStore *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 *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 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 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::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 out_entries_total, sf::Out out_entries_written, const sf::OutArray &out_info, ContentMetaType type, ProgramId application_program_id, ProgramId program_id_min, ProgramId program_id_max, ContentInstallType install_type) { + Result ContentMetaDatabaseImpl::List(sf::Out out_entries_total, sf::Out out_entries_written, const sf::OutArray &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(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(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 out_key, ProgramId program_id) { + Result ContentMetaDatabaseImpl::GetLatestContentMetaKey(sf::Out out_key, ProgramId program_id) { R_TRY(this->EnsureEnabled()); return this->GetLatestContentMetaKeyImpl(out_key.GetPointer(), program_id); } Result ContentMetaDatabaseImpl::ListApplication(sf::Out out_entries_total, sf::Out out_entries_written, const sf::OutArray &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(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(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 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 out, const sf::InArray &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 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(value)->required_system_version); + out_version.SetValue(reader.GetExtendedHeader()->required_system_version); } else { - out_version.SetValue(GetValueExtendedHeader(value)->required_system_version); + out_version.SetValue(reader.GetExtendedHeader()->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(value)->patch_id); + ContentMetaReader reader(meta, meta_size); + + out_patch_id.SetValue(reader.GetExtendedHeader()->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(entry->GetValuePointer()); - const ContentInfo *content_infos = GetValueContentInfos(header); - auto IsOrphanedContent = [](const sf::InArray &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 &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(std::nullopt); - }; + } + return std::optional(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 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 out_entries_written, const sf::OutArray &out_meta_info, const ContentMetaKey &key, u32 start_index) { - R_UNLESS(start_index <= std::numeric_limits::max(), ncm::ResultInvalidOffset()); + Result ContentMetaDatabaseImpl::ListContentMetaInfo(sf::Out out_entries_written, const sf::OutArray &out_meta_info, const ContentMetaKey &key, u32 offset) { + R_UNLESS(offset <= std::numeric_limits::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 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 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(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()->required_application_version; + } else if (key.type == ContentMetaType::AddOnContent) { + required_version = reader.GetExtendedHeader()->required_application_version; + } else { + return ncm::ResultInvalidContentMetaKey(); } - R_UNLESS(key.type == ContentMetaType::AddOnContent, ncm::ResultInvalidContentMetaKey()); - out_version.SetValue(GetValueExtendedHeader(value)->required_application_version); + out_version.SetValue(required_version); return ResultSuccess(); } Result ContentMetaDatabaseImpl::GetContentIdByTypeAndIdOffset(sf::Out 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)); } } diff --git a/stratosphere/ncm/source/ncm_content_meta_database_impl.hpp b/stratosphere/ncm/source/ncm_content_meta_database_impl.hpp index d57a7e7f2..e3eb9b884 100644 --- a/stratosphere/ncm/source/ncm_content_meta_database_impl.hpp +++ b/stratosphere/ncm/source/ncm_content_meta_database_impl.hpp @@ -50,10 +50,6 @@ namespace ams::ncm { virtual Result GetAttributes(sf::Out out_attributes, const ContentMetaKey &key) override; virtual Result GetRequiredApplicationVersion(sf::Out out_version, const ContentMetaKey &key) override; virtual Result GetContentIdByTypeAndIdOffset(sf::Out 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; }; } diff --git a/stratosphere/ncm/source/ncm_content_meta_database_impl_base.hpp b/stratosphere/ncm/source/ncm_content_meta_database_impl_base.hpp index 2c0007760..951e23f84 100644 --- a/stratosphere/ncm/source/ncm_content_meta_database_impl_base.hpp +++ b/stratosphere/ncm/source/ncm_content_meta_database_impl_base.hpp @@ -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(out_value_ptr), key); + } }; } diff --git a/stratosphere/ncm/source/ncm_on_memory_content_meta_database_impl.cpp b/stratosphere/ncm/source/ncm_on_memory_content_meta_database_impl.cpp index 21d55030e..0bbe14885 100644 --- a/stratosphere/ncm/source/ncm_on_memory_content_meta_database_impl.cpp +++ b/stratosphere/ncm/source/ncm_on_memory_content_meta_database_impl.cpp @@ -22,7 +22,7 @@ namespace ams::ncm { R_TRY(this->EnsureEnabled()); const ContentMetaKey key = ContentMetaKey::Make(program_id, 0, ContentMetaType::Unknown); - + std::optional 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(); } diff --git a/stratosphere/ncm/source/ncm_on_memory_content_meta_database_impl.hpp b/stratosphere/ncm/source/ncm_on_memory_content_meta_database_impl.hpp index 572be6b9f..c7c1dc1df 100644 --- a/stratosphere/ncm/source/ncm_on_memory_content_meta_database_impl.hpp +++ b/stratosphere/ncm/source/ncm_on_memory_content_meta_database_impl.hpp @@ -20,11 +20,10 @@ #include "ncm_content_meta_database_impl.hpp" namespace ams::ncm { - + class OnMemoryContentMetaDatabaseImpl : public ContentMetaDatabaseImpl { public: - OnMemoryContentMetaDatabaseImpl(ams::kvdb::MemoryKeyValueStore *kvs) : ContentMetaDatabaseImpl(kvs) { - } + OnMemoryContentMetaDatabaseImpl(ams::kvdb::MemoryKeyValueStore *kvs) : ContentMetaDatabaseImpl(kvs) { /* ... */ } public: virtual Result GetLatestContentMetaKey(sf::Out out_key, ProgramId id) override; virtual Result LookupOrphanContent(const sf::OutArray &out_orphaned, const sf::InArray &content_ids) override;