mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2025-04-22 12:34:47 +00:00
ncm: implement ContentMetaReader
This commit is contained in:
parent
13a0e74611
commit
d40d2006e9
9 changed files with 457 additions and 279 deletions
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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];
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue