mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2025-04-22 12:34:47 +00:00
ncm: added comments
This commit is contained in:
parent
06ba2ed8de
commit
8f4cf2deb3
17 changed files with 303 additions and 25 deletions
|
@ -26,10 +26,12 @@ namespace ams::ncm {
|
|||
}
|
||||
|
||||
bool GetBytesFromString(void *dst, size_t dst_size, const char *src, size_t src_size) {
|
||||
/* Each byte is comprised of hex characters. */
|
||||
if (!util::IsAligned(src_size, 2) || (dst_size * 2 < src_size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Convert each character pair to a byte until we reach the end. */
|
||||
for (size_t i = 0; i < src_size; i += 2) {
|
||||
char tmp[3];
|
||||
strlcpy(tmp, src + i, sizeof(tmp));
|
||||
|
|
|
@ -78,7 +78,11 @@ namespace ams::ncm {
|
|||
|
||||
Result EnsureBuiltInSystemSaveDataFlags() {
|
||||
u32 cur_flags = 0;
|
||||
|
||||
/* Obtain the existing flags. */
|
||||
R_TRY(fs::GetSaveDataFlags(std::addressof(cur_flags), BuiltInSystemSaveDataId));
|
||||
|
||||
/* Update the flags if needed. */
|
||||
if (cur_flags != BuiltInSystemSaveDataFlags) {
|
||||
R_TRY(fs::SetSaveDataFlags(BuiltInSystemSaveDataId, fs::SaveDataSpaceId::System, BuiltInSystemSaveDataFlags));
|
||||
}
|
||||
|
@ -109,10 +113,12 @@ namespace ams::ncm {
|
|||
ContentManagerImpl::~ContentManagerImpl() {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Disable and unmount all content storage roots. */
|
||||
for (auto &root : this->content_storage_roots) {
|
||||
this->InactivateContentStorage(root.storage_id);
|
||||
}
|
||||
|
||||
/* Disable and unmount all content meta database roots. */
|
||||
for (auto &root : this->content_meta_database_roots) {
|
||||
this->InactivateContentMetaDatabase(root.storage_id);
|
||||
}
|
||||
|
@ -121,8 +127,10 @@ namespace ams::ncm {
|
|||
Result ContentManagerImpl::EnsureAndMountSystemSaveData(const char *mount_name, const SystemSaveDataInfo &info) const {
|
||||
constexpr u64 OwnerId = 0;
|
||||
|
||||
/* Don't create save if absent - We want to handle this case ourselves. */
|
||||
fs::DisableAutoSaveDataCreation();
|
||||
|
||||
/* Mount existing system save data if present, otherwise create it then mount. */
|
||||
R_TRY_CATCH(fs::MountSystemSaveData(mount_name, info.space_id, info.id)) {
|
||||
R_CATCH(fs::ResultTargetNotFound) {
|
||||
R_TRY(fs::CreateSystemSaveData(info.space_id, info.id, OwnerId, info.size, info.journal_size, info.flags));
|
||||
|
@ -134,8 +142,10 @@ namespace ams::ncm {
|
|||
}
|
||||
|
||||
Result ContentManagerImpl::GetContentStorageRoot(ContentStorageRoot **out, StorageId id) {
|
||||
/* Storage must not be StorageId::Any or StorageId::None. */
|
||||
R_UNLESS(IsUniqueStorage(id), ncm::ResultUnknownStorage());
|
||||
|
||||
/* Find a root with a matching storage id. */
|
||||
for (auto &root : this->content_storage_roots) {
|
||||
if (root.storage_id == id) {
|
||||
*out = std::addressof(root);
|
||||
|
@ -147,8 +157,10 @@ namespace ams::ncm {
|
|||
}
|
||||
|
||||
Result ContentManagerImpl::GetContentMetaDatabaseRoot(ContentMetaDatabaseRoot **out, StorageId id) {
|
||||
/* Storage must not be StorageId::Any or StorageId::None. */
|
||||
R_UNLESS(IsUniqueStorage(id), ncm::ResultUnknownStorage());
|
||||
|
||||
/* Find a root with a matching storage id. */
|
||||
for (auto &root : this->content_meta_database_roots) {
|
||||
if (root.storage_id == id) {
|
||||
*out = std::addressof(root);
|
||||
|
@ -165,6 +177,7 @@ namespace ams::ncm {
|
|||
out->content_storage_id = content_storage_id;
|
||||
out->content_storage = nullptr;
|
||||
|
||||
/* Create a new mount name and copy it to out. */
|
||||
std::strcpy(out->mount_name, impl::CreateUniqueMountName().str);
|
||||
std::snprintf(out->path, sizeof(out->path), "%s:/", out->mount_name);
|
||||
|
||||
|
@ -175,6 +188,7 @@ namespace ams::ncm {
|
|||
out->storage_id = StorageId::GameCard;
|
||||
out->content_storage = nullptr;
|
||||
|
||||
/* Create a new mount name and copy it to out. */
|
||||
std::strcpy(out->mount_name, impl::CreateUniqueMountName().str);
|
||||
std::snprintf(out->path, sizeof(out->path), "%s:/", out->mount_name);
|
||||
|
||||
|
@ -188,6 +202,7 @@ namespace ams::ncm {
|
|||
out->content_meta_database = nullptr;
|
||||
out->kvs = std::nullopt;
|
||||
|
||||
/* Create a new mount name and copy it to out. */
|
||||
std::strcpy(out->mount_name, impl::CreateUniqueMountName().str);
|
||||
out->mount_name[0] = '#';
|
||||
std::snprintf(out->path, sizeof(out->path), "%s:/meta", out->mount_name);
|
||||
|
@ -264,13 +279,18 @@ namespace ams::ncm {
|
|||
Result ContentManagerImpl::CreateContentStorage(StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Obtain the content storage root. */
|
||||
ContentStorageRoot *root;
|
||||
R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id));
|
||||
|
||||
/* Mount the relevant content storage. */
|
||||
R_TRY(fs::MountContentStorage(root->mount_name, root->content_storage_id));
|
||||
ON_SCOPE_EXIT { fs::Unmount(root->mount_name); };
|
||||
|
||||
/* Ensure the content storage root's path exists. */
|
||||
R_TRY(impl::EnsureDirectoryRecursively(root->path));
|
||||
|
||||
/* Initialize content and placeholder directories for the root. */
|
||||
R_TRY(ContentStorageImpl::InitializeBase(root->path));
|
||||
|
||||
return ResultSuccess();
|
||||
|
@ -281,14 +301,18 @@ namespace ams::ncm {
|
|||
|
||||
R_UNLESS(storage_id != StorageId::GameCard, ncm::ResultUnknownStorage());
|
||||
|
||||
/* Obtain the content meta database root. */
|
||||
ContentMetaDatabaseRoot *root;
|
||||
R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id));
|
||||
|
||||
/* Mount (and optionally create) save data for the root. */
|
||||
R_TRY(this->EnsureAndMountSystemSaveData(root->mount_name, root->info));
|
||||
ON_SCOPE_EXIT { fs::Unmount(root->mount_name); };
|
||||
|
||||
/* Ensure the content meta database root's path exists. */
|
||||
R_TRY(impl::EnsureDirectoryRecursively(root->path));
|
||||
|
||||
/* Commit our changes. */
|
||||
R_TRY(fs::CommitSaveData(root->mount_name));
|
||||
|
||||
return ResultSuccess();
|
||||
|
@ -297,29 +321,36 @@ namespace ams::ncm {
|
|||
Result ContentManagerImpl::VerifyContentStorage(StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Obtain the content storage root. */
|
||||
ContentStorageRoot *root;
|
||||
R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id));
|
||||
|
||||
/* Substitute the mount name in the root's path with a unique one. */
|
||||
char path[0x80];
|
||||
auto mount_name = impl::CreateUniqueMountName(); /* should this be fs::? should it be ncm::? ncm::impl? */
|
||||
auto mount_name = impl::CreateUniqueMountName();
|
||||
ReplaceMountName(path, mount_name.str, root->path);
|
||||
|
||||
/* Mount the relevant content storage. */
|
||||
R_TRY(fs::MountContentStorage(mount_name.str, root->content_storage_id));
|
||||
ON_SCOPE_EXIT { fs::Unmount(mount_name.str); };
|
||||
|
||||
/* Ensure the root, content and placeholder directories exist for the storage. */
|
||||
R_TRY(ContentStorageImpl::VerifyBase(path));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::VerifyContentMetaDatabase(StorageId storage_id) {
|
||||
/* Game card content meta databases will always be valid. */
|
||||
R_UNLESS(storage_id != StorageId::GameCard, ResultSuccess());
|
||||
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Obtain the content meta database root. */
|
||||
ContentMetaDatabaseRoot *root;
|
||||
R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id));
|
||||
|
||||
/* Mount save data for non-existing content meta databases. */
|
||||
auto mount_guard = SCOPE_GUARD { fs::Unmount(root->mount_name); };
|
||||
if (!root->content_meta_database) {
|
||||
R_TRY(fs::MountSystemSaveData(root->mount_name, root->info.space_id, root->info.id));
|
||||
|
@ -327,6 +358,7 @@ namespace ams::ncm {
|
|||
mount_guard.Cancel();
|
||||
}
|
||||
|
||||
/* Ensure the root path exists. */
|
||||
bool has_dir = false;
|
||||
R_TRY(impl::HasDirectory(&has_dir, root->path));
|
||||
R_UNLESS(has_dir, ncm::ResultInvalidContentMetaDatabase());
|
||||
|
@ -337,10 +369,12 @@ namespace ams::ncm {
|
|||
Result ContentManagerImpl::OpenContentStorage(sf::Out<std::shared_ptr<IContentStorage>> out, StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Obtain the content storage root. */
|
||||
ContentStorageRoot *root;
|
||||
R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id));
|
||||
|
||||
if (hos::GetVersion() >= hos::Version_200) {
|
||||
/* Obtain the content storage if already active. */
|
||||
R_UNLESS(root->content_storage, GetContentStorageNotActiveResult(storage_id));
|
||||
} else {
|
||||
/* 1.0.0 activates content storages as soon as they are opened. */
|
||||
|
@ -357,11 +391,12 @@ namespace ams::ncm {
|
|||
Result ContentManagerImpl::OpenContentMetaDatabase(sf::Out<std::shared_ptr<IContentMetaDatabase>> out, StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Obtain the content meta database root. */
|
||||
ContentMetaDatabaseRoot *root;
|
||||
R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id));
|
||||
|
||||
|
||||
if (hos::GetVersion() >= hos::Version_200) {
|
||||
/* Obtain the content meta database if already active. */
|
||||
R_UNLESS(root->content_meta_database, GetContentMetaDatabaseNotActiveResult(storage_id));
|
||||
} else {
|
||||
/* 1.0.0 activates content meta databases as soon as they are opened. */
|
||||
|
@ -386,11 +421,14 @@ namespace ams::ncm {
|
|||
Result ContentManagerImpl::CleanupContentMetaDatabase(StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Disable and unmount content meta database root. */
|
||||
R_TRY(this->InactivateContentMetaDatabase(storage_id));
|
||||
|
||||
/* Obtain the content meta database root. */
|
||||
ContentMetaDatabaseRoot *root;
|
||||
R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id));
|
||||
|
||||
/* Delete save data for the content meta database root. */
|
||||
R_TRY(fs::DeleteSaveData(root->info.space_id, root->info.id));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
@ -398,12 +436,14 @@ namespace ams::ncm {
|
|||
Result ContentManagerImpl::ActivateContentStorage(StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Obtain the content storage root. */
|
||||
ContentStorageRoot *root;
|
||||
R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id));
|
||||
|
||||
/* Check if the storage is already activated. */
|
||||
R_UNLESS(root->content_storage == nullptr, ResultSuccess());
|
||||
|
||||
/* Mount based on the storage type. */
|
||||
if (storage_id == StorageId::GameCard) {
|
||||
fs::GameCardHandle handle;
|
||||
R_TRY(fs::GetGameCardHandle(std::addressof(handle)));
|
||||
|
@ -412,15 +452,19 @@ namespace ams::ncm {
|
|||
R_TRY(fs::MountContentStorage(root->mount_name, root->content_storage_id));
|
||||
}
|
||||
|
||||
/* Unmount on failure. */
|
||||
auto mount_guard = SCOPE_GUARD { fs::Unmount(root->mount_name); };
|
||||
|
||||
if (storage_id == StorageId::GameCard) {
|
||||
/* Game card content storage is read only. */
|
||||
auto content_storage = std::make_shared<ReadOnlyContentStorageImpl>();
|
||||
R_TRY(content_storage->Initialize(root->path, MakeFlatContentFilePath));
|
||||
root->content_storage = std::move(content_storage);
|
||||
} else {
|
||||
/* Create a content storage. */
|
||||
auto content_storage = std::make_shared<ContentStorageImpl>();
|
||||
|
||||
/* Initialize content storage with an appropriate path function. */
|
||||
switch (storage_id) {
|
||||
case StorageId::BuiltInSystem:
|
||||
R_TRY(content_storage->Initialize(root->path, MakeFlatContentFilePath, MakeFlatPlaceHolderFilePath, false, std::addressof(this->rights_id_cache)));
|
||||
|
@ -436,6 +480,7 @@ namespace ams::ncm {
|
|||
root->content_storage = std::move(content_storage);
|
||||
}
|
||||
|
||||
/* Prevent unmounting. */
|
||||
mount_guard.Cancel();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
@ -443,9 +488,11 @@ namespace ams::ncm {
|
|||
Result ContentManagerImpl::InactivateContentStorage(StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Obtain the content storage root. */
|
||||
ContentStorageRoot *root;
|
||||
R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id));
|
||||
|
||||
/* Disable and unmount the content storage, if present. */
|
||||
if (root->content_storage) {
|
||||
/* N doesn't bother checking the result of this */
|
||||
root->content_storage->DisableForcibly();
|
||||
|
@ -459,6 +506,7 @@ namespace ams::ncm {
|
|||
Result ContentManagerImpl::ActivateContentMetaDatabase(StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Obtain the content meta database root. */
|
||||
ContentMetaDatabaseRoot *root;
|
||||
R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id));
|
||||
|
||||
|
@ -469,15 +517,23 @@ namespace ams::ncm {
|
|||
root->kvs.emplace();
|
||||
|
||||
if (storage_id == StorageId::GameCard) {
|
||||
/* Initialize the key value store. */
|
||||
R_TRY(root->kvs->Initialize(root->max_content_metas));
|
||||
|
||||
/* Create an on memory content meta database for game cards. */
|
||||
root->content_meta_database = std::make_shared<OnMemoryContentMetaDatabaseImpl>(std::addressof(*root->kvs));
|
||||
} else {
|
||||
/* Mount save data for this root. */
|
||||
R_TRY(fs::MountSystemSaveData(root->mount_name, root->info.space_id, root->info.id));
|
||||
|
||||
/* Unmount on failure. */
|
||||
auto mount_guard = SCOPE_GUARD { fs::Unmount(root->mount_name); };
|
||||
|
||||
/* Initialize and load the key value store from the filesystem. */
|
||||
R_TRY(root->kvs->Initialize(root->path, root->max_content_metas));
|
||||
R_TRY(root->kvs->Load());
|
||||
|
||||
/* Create the content meta database. */
|
||||
root->content_meta_database = std::make_shared<ContentMetaDatabaseImpl>(std::addressof(*root->kvs), root->mount_name);
|
||||
mount_guard.Cancel();
|
||||
}
|
||||
|
@ -488,15 +544,18 @@ namespace ams::ncm {
|
|||
Result ContentManagerImpl::InactivateContentMetaDatabase(StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Obtain the content meta database root. */
|
||||
ContentMetaDatabaseRoot *root;
|
||||
R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id));
|
||||
|
||||
/* Disable the content meta database, if present. */
|
||||
if (root->content_meta_database) {
|
||||
/* N doesn't bother checking the result of this */
|
||||
root->content_meta_database->DisableForcibly();
|
||||
root->content_meta_database = nullptr;
|
||||
root->kvs = std::nullopt;
|
||||
|
||||
/* Also unmount, except in the case of game cards. */
|
||||
if (storage_id != StorageId::GameCard) {
|
||||
fs::Unmount(root->mount_name);
|
||||
}
|
||||
|
|
|
@ -76,6 +76,7 @@ namespace ams::ncm {
|
|||
public:
|
||||
Result Initialize();
|
||||
private:
|
||||
/* Helpers. */
|
||||
Result GetContentStorageRoot(ContentStorageRoot **out, StorageId id);
|
||||
Result GetContentMetaDatabaseRoot(ContentMetaDatabaseRoot **out, StorageId id);
|
||||
|
||||
|
@ -88,6 +89,7 @@ namespace ams::ncm {
|
|||
Result EnsureAndMountSystemSaveData(const char *mount, const SystemSaveDataInfo &info) const;
|
||||
|
||||
public:
|
||||
/* Actual commands. */
|
||||
virtual Result CreateContentStorage(StorageId storage_id) override;
|
||||
virtual Result CreateContentMetaDatabase(StorageId storage_id) override;
|
||||
virtual Result VerifyContentStorage(StorageId storage_id) override;
|
||||
|
|
|
@ -52,15 +52,21 @@ namespace ams::ncm {
|
|||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
std::optional<ContentMetaKey> found_key = std::nullopt;
|
||||
|
||||
/* Find the last key with the desired program id. */
|
||||
for (auto entry = this->kvs->lower_bound(ContentMetaKey::Make(id, 0, ContentMetaType::Unknown)); entry != this->kvs->end(); entry++) {
|
||||
/* No further entries will match the program id, discontinue. */
|
||||
if (entry->GetKey().id != id) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* We are only interested in keys with the Full content install type. */
|
||||
if (entry->GetKey().install_type == ContentInstallType::Full) {
|
||||
found_key = entry->GetKey();
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if the key is absent. */
|
||||
R_UNLESS(found_key, ncm::ResultContentMetaNotFound());
|
||||
|
||||
*out_key = *found_key;
|
||||
|
@ -90,12 +96,15 @@ namespace ams::ncm {
|
|||
R_UNLESS(offset <= std::numeric_limits<s32>::max(), ncm::ResultInvalidOffset());
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Obtain the content meta for the given key. */
|
||||
const void *meta;
|
||||
size_t meta_size;
|
||||
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
|
||||
|
||||
/* Create a reader. */
|
||||
ContentMetaReader reader(meta, meta_size);
|
||||
|
||||
/* Read content infos from the given offset up to the given count. */
|
||||
size_t count;
|
||||
for (count = 0; count < out_info.GetSize() && count + offset < reader.GetContentCount(); count++) {
|
||||
out_info[count] = *reader.GetContentInfo(offset + count);
|
||||
|
@ -111,6 +120,7 @@ namespace ams::ncm {
|
|||
size_t entries_total = 0;
|
||||
size_t entries_written = 0;
|
||||
|
||||
/* Iterate over all entries. */
|
||||
for (auto entry = this->kvs->begin(); entry != this->kvs->end(); entry++) {
|
||||
ContentMetaKey key = entry->GetKey();
|
||||
|
||||
|
@ -121,12 +131,15 @@ namespace ams::ncm {
|
|||
|
||||
/* If application id is present, check if it matches the filter. */
|
||||
if (application_id != InvalidProgramId) {
|
||||
/* Obtain the content meta for the key. */
|
||||
const void *meta;
|
||||
size_t meta_size;
|
||||
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
|
||||
|
||||
/* Create a reader. */
|
||||
ContentMetaReader reader(meta, meta_size);
|
||||
|
||||
/* Ensure application id matches, if present. */
|
||||
if (const auto entry_application_id = reader.GetApplicationId(key); entry_application_id && application_id != *entry_application_id) {
|
||||
continue;
|
||||
}
|
||||
|
@ -155,6 +168,7 @@ namespace ams::ncm {
|
|||
size_t entries_total = 0;
|
||||
size_t entries_written = 0;
|
||||
|
||||
/* Iterate over all entries. */
|
||||
for (auto entry = this->kvs->begin(); entry != this->kvs->end(); entry++) {
|
||||
ContentMetaKey key = entry->GetKey();
|
||||
|
||||
|
@ -185,6 +199,7 @@ namespace ams::ncm {
|
|||
|
||||
*out = false;
|
||||
|
||||
/* Check if key is present. */
|
||||
size_t size;
|
||||
R_TRY_CATCH(this->kvs->GetValueSize(&size, key)) {
|
||||
R_CONVERT(kvdb::ResultKeyNotFound, ResultSuccess());
|
||||
|
@ -198,6 +213,7 @@ namespace ams::ncm {
|
|||
R_TRY(this->EnsureEnabled());
|
||||
*out = false;
|
||||
|
||||
/* Check if keys are present. */
|
||||
for (size_t i = 0; i < keys.GetSize(); i++) {
|
||||
bool has;
|
||||
R_TRY(this->Has(std::addressof(has), keys[i]));
|
||||
|
@ -211,6 +227,7 @@ namespace ams::ncm {
|
|||
Result ContentMetaDatabaseImpl::GetSize(sf::Out<u64> out_size, const ContentMetaKey &key) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Determine the content meta size for the key. */
|
||||
size_t size;
|
||||
R_TRY(this->GetContentMetaSize(&size, key));
|
||||
|
||||
|
@ -222,12 +239,15 @@ namespace ams::ncm {
|
|||
R_TRY(this->EnsureEnabled());
|
||||
R_UNLESS(key.type == ContentMetaType::Application || key.type == ContentMetaType::Patch, ncm::ResultInvalidContentMetaKey());
|
||||
|
||||
/* Obtain the content meta for the key. */
|
||||
const void *meta;
|
||||
size_t meta_size;
|
||||
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
|
||||
|
||||
/* Create a reader. */
|
||||
ContentMetaReader reader(meta, meta_size);
|
||||
|
||||
/* Obtain the required system version. */
|
||||
if (key.type == ContentMetaType::Application) {
|
||||
out_version.SetValue(reader.GetExtendedHeader<ApplicationMetaExtendedHeader>()->required_system_version);
|
||||
} else {
|
||||
|
@ -241,12 +261,15 @@ namespace ams::ncm {
|
|||
R_TRY(this->EnsureEnabled());
|
||||
R_UNLESS(key.type == ContentMetaType::Application, ncm::ResultInvalidContentMetaKey());
|
||||
|
||||
/* Obtain the content meta for the key. */
|
||||
const void *meta;
|
||||
size_t meta_size;
|
||||
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
|
||||
|
||||
/* Create a reader. */
|
||||
ContentMetaReader reader(meta, meta_size);
|
||||
|
||||
/* Obtain the patch id. */
|
||||
out_patch_id.SetValue(reader.GetExtendedHeader<ApplicationMetaExtendedHeader>()->patch_id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
@ -265,9 +288,11 @@ namespace ams::ncm {
|
|||
out_orphaned[i] = true;
|
||||
}
|
||||
|
||||
/* If key value store is empty, all content is orphaned. */
|
||||
R_UNLESS(this->kvs->GetCount() != 0, ResultSuccess());
|
||||
|
||||
auto IsOrphanedContent = [](const sf::InArray<ContentId> &list, const ncm::ContentId &id) ALWAYS_INLINE_LAMBDA {
|
||||
/* Check if any input content ids match our found content id. */
|
||||
for (size_t i = 0; i < list.GetSize(); i++) {
|
||||
if (list[i] == id) {
|
||||
return std::make_optional(i);
|
||||
|
@ -276,6 +301,7 @@ namespace ams::ncm {
|
|||
return std::optional<size_t>(std::nullopt);
|
||||
};
|
||||
|
||||
/* Iterate over all entries. */
|
||||
for (auto entry = this->kvs->begin(); entry != this->kvs->end(); entry++) {
|
||||
ContentMetaReader reader(entry->GetValuePointer(), entry->GetValueSize());
|
||||
|
||||
|
@ -293,16 +319,22 @@ namespace ams::ncm {
|
|||
|
||||
Result ContentMetaDatabaseImpl::Commit() {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Save and commit. */
|
||||
R_TRY(this->kvs->Save());
|
||||
return fs::CommitSaveData(this->mount_name);
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::HasContent(sf::Out<bool> out, const ContentMetaKey &key, const ContentId &content_id) {
|
||||
/* Obtain the content meta for the key. */
|
||||
const void *meta;
|
||||
size_t meta_size;
|
||||
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
|
||||
|
||||
/* Create a reader. */
|
||||
ContentMetaReader reader(meta, meta_size);
|
||||
|
||||
/* Check if any content infos contain a matching id. */
|
||||
for (size_t i = 0; i < reader.GetContentCount(); i++) {
|
||||
if (content_id == reader.GetContentInfo(i)->GetId()) {
|
||||
out.SetValue(true);
|
||||
|
@ -318,12 +350,15 @@ namespace ams::ncm {
|
|||
R_UNLESS(offset <= std::numeric_limits<s32>::max(), ncm::ResultInvalidOffset());
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Obtain the content meta for the key. */
|
||||
const void *meta;
|
||||
size_t meta_size;
|
||||
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
|
||||
|
||||
/* Create a reader. */
|
||||
ContentMetaReader reader(meta, meta_size);
|
||||
|
||||
/* Read content meta infos from the given offset up to the given count. */
|
||||
size_t count;
|
||||
for (count = 0; count < out_meta_info.GetSize() && count + offset <= reader.GetContentMetaCount(); count++) {
|
||||
out_meta_info[count] = *reader.GetContentMetaInfo(count + offset);
|
||||
|
@ -336,10 +371,12 @@ namespace ams::ncm {
|
|||
Result ContentMetaDatabaseImpl::GetAttributes(sf::Out<u8> out_attributes, const ContentMetaKey &key) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Obtain the content meta for the key. */
|
||||
const void *meta;
|
||||
size_t meta_size;
|
||||
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
|
||||
|
||||
/* Create a reader. */
|
||||
ContentMetaReader reader(meta, meta_size);
|
||||
|
||||
out_attributes.SetValue(reader.GetHeader()->attributes);
|
||||
|
@ -349,10 +386,12 @@ namespace ams::ncm {
|
|||
Result ContentMetaDatabaseImpl::GetRequiredApplicationVersion(sf::Out<u32> out_version, const ContentMetaKey &key) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Obtain the content meta for the key. */
|
||||
const void *meta;
|
||||
size_t meta_size;
|
||||
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
|
||||
|
||||
/* Create a reader. */
|
||||
ContentMetaReader reader(meta, meta_size);
|
||||
|
||||
/* Get the required version. */
|
||||
|
|
|
@ -26,9 +26,11 @@ namespace ams::ncm {
|
|||
ContentMetaDatabaseImpl(ContentMetaKeyValueStore *kvs, const char *mount_name) : ContentMetaDatabaseImplBase(kvs, mount_name) { /* ... */ }
|
||||
ContentMetaDatabaseImpl(ContentMetaKeyValueStore *kvs) : ContentMetaDatabaseImplBase(kvs) { /* ... */ }
|
||||
private:
|
||||
/* Helpers. */
|
||||
Result GetContentIdByTypeImpl(ContentId *out, const ContentMetaKey& key, ContentType type, std::optional<u8> id_offset);
|
||||
Result GetLatestContentMetaKeyImpl(ContentMetaKey *out_key, ProgramId id);
|
||||
public:
|
||||
/* Actual commands. */
|
||||
virtual Result Set(const ContentMetaKey &key, sf::InBuffer value) override;
|
||||
virtual Result Get(sf::Out<u64> out_size, const ContentMetaKey &key, sf::OutBuffer out_value) override;
|
||||
virtual Result Remove(const ContentMetaKey &key) override;
|
||||
|
|
|
@ -35,6 +35,7 @@ namespace ams::ncm {
|
|||
std::strcpy(this->mount_name, mount_name);
|
||||
}
|
||||
protected:
|
||||
/* Helpers. */
|
||||
Result EnsureEnabled() const {
|
||||
R_UNLESS(!this->disabled, ncm::ResultInvalidContentMetaDatabase());
|
||||
return ResultSuccess();
|
||||
|
|
|
@ -42,9 +42,11 @@ namespace ams::ncm {
|
|||
}
|
||||
|
||||
Result DeleteContentFile(ContentId id, MakeContentPathFunction func, const char *root_path) {
|
||||
/* Create the content path. */
|
||||
PathString path;
|
||||
MakeContentPath(std::addressof(path), id, func, root_path);
|
||||
|
||||
/* Delete the content. */
|
||||
R_TRY_CATCH(fs::DeleteFile(path)) {
|
||||
R_CONVERT(fs::ResultPathNotFound, ncm::ResultContentNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
|
@ -56,25 +58,32 @@ namespace ams::ncm {
|
|||
Result TraverseDirectory(bool *out_should_continue, const char *root_path, int max_level, F f) {
|
||||
R_UNLESS(max_level > 0, ResultSuccess());
|
||||
|
||||
/* Retry traversal upon request. */
|
||||
bool retry_dir_read = true;
|
||||
while (retry_dir_read) {
|
||||
retry_dir_read = false;
|
||||
|
||||
/* Open the directory at the given path. All entry types are allowed. */
|
||||
fs::DirectoryHandle dir;
|
||||
R_TRY(fs::OpenDirectory(std::addressof(dir), root_path, fs::OpenDirectoryMode_All | fs::OpenDirectoryMode_NotRequireFileSize));
|
||||
ON_SCOPE_EXIT { fs::CloseDirectory(dir); };
|
||||
|
||||
while (true) {
|
||||
/* Read a single directory entry. */
|
||||
fs::DirectoryEntry entry;
|
||||
s64 entry_count;
|
||||
R_TRY(fs::ReadDirectory(std::addressof(entry_count), std::addressof(entry), dir, 1));
|
||||
|
||||
/* Directory has no entries to process. */
|
||||
if (entry_count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Path of the current entry. */
|
||||
PathString current_path;
|
||||
current_path.SetFormat("%s/%s", root_path, entry.name);
|
||||
|
||||
/* Call the process function. */
|
||||
bool should_continue = true;
|
||||
bool should_retry_dir_read = false;
|
||||
R_TRY(f(&should_continue, &should_retry_dir_read, current_path, entry));
|
||||
|
@ -85,6 +94,7 @@ namespace ams::ncm {
|
|||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* Mark for retry. */
|
||||
if (should_retry_dir_read) {
|
||||
retry_dir_read = true;
|
||||
break;
|
||||
|
@ -114,15 +124,19 @@ namespace ams::ncm {
|
|||
|
||||
bool IsContentPath(const char *path) {
|
||||
impl::PathView view(path);
|
||||
|
||||
/* Ensure nca suffix. */
|
||||
if (!view.HasSuffix(".nca")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* File name should be the size of a content id plus the nca file extension. */
|
||||
auto file_name = view.GetFileName();
|
||||
if (file_name.length() != ContentIdStringLength + 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Ensure file name is comprised of hex characters. */
|
||||
for (size_t i = 0; i < ContentIdStringLength; i++) {
|
||||
if (!std::isxdigit(static_cast<unsigned char>(file_name[i]))) {
|
||||
return false;
|
||||
|
@ -157,9 +171,11 @@ namespace ams::ncm {
|
|||
Result ContentStorageImpl::InitializeBase(const char *root_path) {
|
||||
PathString path;
|
||||
|
||||
/* Create the content directory. */
|
||||
MakeBaseContentDirectoryPath(std::addressof(path), root_path);
|
||||
R_TRY(impl::EnsureDirectoryRecursively(path));
|
||||
|
||||
/* Create the placeholder directory. */
|
||||
PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), root_path);
|
||||
R_TRY(impl::EnsureDirectoryRecursively(path));
|
||||
|
||||
|
@ -169,9 +185,11 @@ namespace ams::ncm {
|
|||
Result ContentStorageImpl::CleanupBase(const char *root_path) {
|
||||
PathString path;
|
||||
|
||||
/* Create the content directory. */
|
||||
MakeBaseContentDirectoryPath(std::addressof(path), root_path);
|
||||
R_TRY(CleanDirectoryRecursively(path));
|
||||
|
||||
/* Create the placeholder directory. */
|
||||
PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), root_path);
|
||||
R_TRY(CleanDirectoryRecursively(path));
|
||||
|
||||
|
@ -181,18 +199,22 @@ namespace ams::ncm {
|
|||
Result ContentStorageImpl::VerifyBase(const char *root_path) {
|
||||
PathString path;
|
||||
|
||||
/* Check if root directory exists. */
|
||||
bool has_dir;
|
||||
R_TRY(impl::HasDirectory(std::addressof(has_dir), root_path));
|
||||
R_UNLESS(has_dir, ncm::ResultContentStorageBaseNotFound());
|
||||
|
||||
/* Check if content directory exists. */
|
||||
bool has_registered;
|
||||
MakeBaseContentDirectoryPath(std::addressof(path), root_path);
|
||||
R_TRY(impl::HasDirectory(std::addressof(has_registered), path));
|
||||
|
||||
/* Check if placeholder directory exists. */
|
||||
bool has_placeholder;
|
||||
PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), root_path);
|
||||
R_TRY(impl::HasDirectory(std::addressof(has_placeholder), path));
|
||||
|
||||
/* Convert findings to results. */
|
||||
R_UNLESS(has_registered || has_placeholder, ncm::ResultContentStorageBaseNotFound());
|
||||
R_UNLESS(has_registered, ncm::ResultInvalidContentStorageBase());
|
||||
R_UNLESS(has_placeholder, ncm::ResultInvalidContentStorageBase());
|
||||
|
@ -210,11 +232,14 @@ namespace ams::ncm {
|
|||
Result ContentStorageImpl::OpenContentIdFile(ContentId content_id) {
|
||||
R_UNLESS(this->cached_content_id != content_id, ResultSuccess());
|
||||
|
||||
/* Close any cached file. */
|
||||
this->InvalidateFileCache();
|
||||
|
||||
/* Create the content path. */
|
||||
PathString path;
|
||||
MakeContentPath(std::addressof(path), content_id, this->make_content_path_func, this->root_path);
|
||||
|
||||
/* Open the content file and store to the cache. */
|
||||
R_TRY_CATCH(fs::OpenFile(&this->cached_file_handle, path, fs::OpenMode_Read)) {
|
||||
R_CONVERT(ams::fs::ResultPathNotFound, ncm::ResultContentNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
|
@ -226,8 +251,10 @@ namespace ams::ncm {
|
|||
Result ContentStorageImpl::Initialize(const char *path, MakeContentPathFunction content_path_func, MakePlaceHolderPathFunction placeholder_path_func, bool delay_flush, RightsIdCache *rights_id_cache) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Check paths exists for this content storage. */
|
||||
R_TRY(VerifyBase(path));
|
||||
|
||||
/* Initialize members. */
|
||||
this->root_path = PathString(path);
|
||||
this->make_content_path_func = content_path_func;
|
||||
this->placeholder_accessor.Initialize(std::addressof(this->root_path), placeholder_path_func, delay_flush);
|
||||
|
@ -259,9 +286,11 @@ namespace ams::ncm {
|
|||
Result ContentStorageImpl::HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Create the placeholder path. */
|
||||
PathString placeholder_path;
|
||||
this->placeholder_accessor.MakePath(std::addressof(placeholder_path), placeholder_id);
|
||||
|
||||
/* Check if placeholder file exists. */
|
||||
bool has = false;
|
||||
R_TRY(impl::HasFile(&has, placeholder_path));
|
||||
out.SetValue(has);
|
||||
|
@ -274,18 +303,21 @@ namespace ams::ncm {
|
|||
R_UNLESS(offset <= std::numeric_limits<s64>::max(), ncm::ResultInvalidOffset());
|
||||
R_TRY(this->EnsureEnabled());
|
||||
return this->placeholder_accessor.WritePlaceHolderFile(placeholder_id, offset, data.GetPointer(), data.GetSize());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::Register(PlaceHolderId placeholder_id, ContentId content_id) {
|
||||
this->InvalidateFileCache();
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Create the placeholder path. */
|
||||
PathString placeholder_path;
|
||||
PathString content_path;
|
||||
this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id);
|
||||
|
||||
/* Create the content path. */
|
||||
PathString content_path;
|
||||
MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
|
||||
|
||||
/* Move the placeholder to the content path. */
|
||||
R_TRY_CATCH(fs::RenameFile(placeholder_path, content_path)) {
|
||||
R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound())
|
||||
R_CONVERT(fs::ResultPathAlreadyExists, ncm::ResultContentAlreadyExists())
|
||||
|
@ -296,18 +328,18 @@ namespace ams::ncm {
|
|||
|
||||
Result ContentStorageImpl::Delete(ContentId content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
this->InvalidateFileCache();
|
||||
|
||||
return DeleteContentFile(content_id, this->make_content_path_func, this->root_path);
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::Has(sf::Out<bool> out, ContentId content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Create the content path. */
|
||||
PathString content_path;
|
||||
MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
|
||||
|
||||
/* Check if the content file exists. */
|
||||
bool has = false;
|
||||
R_TRY(impl::HasFile(&has, content_path));
|
||||
out.SetValue(has);
|
||||
|
@ -318,9 +350,11 @@ namespace ams::ncm {
|
|||
Result ContentStorageImpl::GetPath(sf::Out<Path> out, ContentId content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Create the content path. */
|
||||
PathString content_path;
|
||||
MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
|
||||
|
||||
/* Substitute our mount name for the common mount name. */
|
||||
Path common_path;
|
||||
R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), content_path));
|
||||
|
||||
|
@ -331,9 +365,11 @@ namespace ams::ncm {
|
|||
Result ContentStorageImpl::GetPlaceHolderPath(sf::Out<Path> out, PlaceHolderId placeholder_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Obtain the placeholder path. */
|
||||
PathString placeholder_path;
|
||||
this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id);
|
||||
|
||||
/* Substitute our mount name for the common mount name. */
|
||||
Path common_path;
|
||||
R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), placeholder_path));
|
||||
|
||||
|
@ -344,31 +380,38 @@ namespace ams::ncm {
|
|||
Result ContentStorageImpl::CleanupAllPlaceHolder() {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Clear the cache. */
|
||||
this->placeholder_accessor.InvalidateAll();
|
||||
|
||||
/* Obtain the placeholder base directory path. */
|
||||
PathString placeholder_dir;
|
||||
PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(placeholder_dir), this->root_path);
|
||||
|
||||
/* Cleanup the placeholder base directory. */
|
||||
CleanDirectoryRecursively(placeholder_dir);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::ListPlaceHolder(sf::Out<u32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Obtain the placeholder base directory path. */
|
||||
PathString placeholder_dir;
|
||||
PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(placeholder_dir), this->root_path);
|
||||
|
||||
const size_t max_entries = out_buf.GetSize();
|
||||
size_t entry_count = 0;
|
||||
|
||||
/* Traverse the placeholder base directory finding valid placeholder files. */
|
||||
R_TRY(TraverseDirectory(placeholder_dir, placeholder_accessor.GetHierarchicalDirectoryDepth(), [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, const fs::DirectoryEntry &entry) -> Result {
|
||||
*should_continue = true;
|
||||
*should_retry_dir_read = false;
|
||||
|
||||
/* We are only looking for files. */
|
||||
if (entry.type == fs::DirectoryEntryType_File) {
|
||||
R_UNLESS(entry_count <= max_entries, ncm::ResultBufferInsufficient());
|
||||
|
||||
/* Get the placeholder id from the filename. */
|
||||
PlaceHolderId placeholder_id;
|
||||
R_TRY(PlaceHolderAccessor::GetPlaceHolderIdFromFileName(std::addressof(placeholder_id), entry.name));
|
||||
|
||||
|
@ -385,15 +428,19 @@ namespace ams::ncm {
|
|||
Result ContentStorageImpl::GetContentCount(sf::Out<u32> out_count) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Obtain the content base directory path. */
|
||||
PathString path;
|
||||
MakeBaseContentDirectoryPath(std::addressof(path), this->root_path);
|
||||
|
||||
const auto depth = GetHierarchicalContentDirectoryDepth(this->make_content_path_func);
|
||||
size_t count = 0;
|
||||
|
||||
/* Traverse the content base directory finding all files. */
|
||||
R_TRY(TraverseDirectory(path, depth, [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, const fs::DirectoryEntry &entry) -> Result {
|
||||
*should_continue = true;
|
||||
*should_retry_dir_read = false;
|
||||
|
||||
/* Increment the count for each file found. */
|
||||
if (entry.type == fs::DirectoryEntryType_File) {
|
||||
count++;
|
||||
}
|
||||
|
@ -409,11 +456,14 @@ namespace ams::ncm {
|
|||
R_UNLESS(offset <= std::numeric_limits<s32>::max(), ncm::ResultInvalidOffset());
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Obtain the content base directory path. */
|
||||
PathString path;
|
||||
MakeBaseContentDirectoryPath(std::addressof(path), this->root_path);
|
||||
|
||||
const auto depth = GetHierarchicalContentDirectoryDepth(this->make_content_path_func);
|
||||
size_t entry_count = 0;
|
||||
|
||||
/* Traverse the content base directory finding all valid content. */
|
||||
R_TRY(TraverseDirectory(path, depth, [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, const fs::DirectoryEntry &entry) {
|
||||
*should_retry_dir_read = false;
|
||||
*should_continue = true;
|
||||
|
@ -450,13 +500,16 @@ namespace ams::ncm {
|
|||
Result ContentStorageImpl::GetSizeFromContentId(sf::Out<u64> out_size, ContentId content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Create the content path. */
|
||||
PathString content_path;
|
||||
MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
|
||||
|
||||
/* Open the content file. */
|
||||
fs::FileHandle file;
|
||||
R_TRY(fs::OpenFile(std::addressof(file), content_path, fs::OpenMode_Read));
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
/* Obtain the size of the content. */
|
||||
s64 file_size;
|
||||
R_TRY(fs::GetFileSize(std::addressof(file_size), file));
|
||||
|
||||
|
@ -474,16 +527,24 @@ namespace ams::ncm {
|
|||
Result ContentStorageImpl::RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Close any cached file. */
|
||||
this->InvalidateFileCache();
|
||||
|
||||
/* Ensure the future content directory exists. */
|
||||
R_TRY(EnsureContentDirectory(new_content_id, this->make_content_path_func, this->root_path));
|
||||
|
||||
/* Ensure the destination placeholder directory exists. */
|
||||
R_TRY(this->placeholder_accessor.EnsurePlaceHolderDirectory(placeholder_id));
|
||||
|
||||
/* Obtain the placeholder path. */
|
||||
PathString placeholder_path;
|
||||
PathString content_path;
|
||||
this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id);
|
||||
|
||||
/* Make the old content path. */
|
||||
PathString content_path;
|
||||
MakeContentPath(std::addressof(content_path), old_content_id, this->make_content_path_func, this->root_path);
|
||||
|
||||
/* Move the content to the placeholder path. */
|
||||
R_TRY_CATCH(fs::RenameFile(content_path, placeholder_path)) {
|
||||
R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound())
|
||||
R_CONVERT(fs::ResultPathAlreadyExists, ncm::ResultContentAlreadyExists())
|
||||
|
@ -494,8 +555,7 @@ namespace ams::ncm {
|
|||
|
||||
Result ContentStorageImpl::SetPlaceHolderSize(PlaceHolderId placeholder_id, u64 size) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
R_TRY(this->placeholder_accessor.SetPlaceHolderFileSize(placeholder_id, size));
|
||||
return ResultSuccess();
|
||||
return this->placeholder_accessor.SetPlaceHolderFileSize(placeholder_id, size);
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::ReadContentIdFile(sf::OutBuffer buf, ContentId content_id, u64 offset) {
|
||||
|
@ -503,19 +563,23 @@ namespace ams::ncm {
|
|||
R_UNLESS(offset <= std::numeric_limits<s64>::max(), ncm::ResultInvalidOffset());
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Create the content path. */
|
||||
PathString content_path;
|
||||
MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
|
||||
|
||||
/* Open the content file. */
|
||||
R_TRY(this->OpenContentIdFile(content_id));
|
||||
|
||||
R_TRY(fs::ReadFile(this->cached_file_handle, offset, buf.GetPointer(), buf.GetSize()));
|
||||
|
||||
return ResultSuccess();
|
||||
/* Read from the requested offset up to the requested size. */
|
||||
return fs::ReadFile(this->cached_file_handle, offset, buf.GetPointer(), buf.GetSize());
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::GetRightsIdFromPlaceHolderIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, PlaceHolderId placeholder_id) {
|
||||
/* Obtain the regular rights id for the placeholder id. */
|
||||
ncm::RightsId rights_id;
|
||||
R_TRY(this->GetRightsIdFromPlaceHolderId(&rights_id, placeholder_id));
|
||||
|
||||
/* Output the fs rights id. */
|
||||
out_rights_id.SetValue(rights_id.id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
@ -523,15 +587,20 @@ namespace ams::ncm {
|
|||
Result ContentStorageImpl::GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Get the placeholder path. */
|
||||
Path path;
|
||||
R_TRY(this->GetPlaceHolderPath(std::addressof(path), placeholder_id));
|
||||
|
||||
/* Get the rights id for the placeholder id. */
|
||||
return GetRightsId(out_rights_id.GetPointer(), path);
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id) {
|
||||
/* Obtain the regular rights id for the content id. */
|
||||
ncm::RightsId rights_id;
|
||||
R_TRY(this->GetRightsIdFromContentId(&rights_id, content_id));
|
||||
|
||||
/* Output the fs rights id. */
|
||||
out_rights_id.SetValue(rights_id.id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
@ -539,15 +608,20 @@ namespace ams::ncm {
|
|||
Result ContentStorageImpl::GetRightsIdFromContentId(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Attempt to obtain the rights id from the cache. */
|
||||
if (this->rights_id_cache->Find(out_rights_id.GetPointer(), content_id)) {
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* Get the path of the content. */
|
||||
Path path;
|
||||
R_TRY(this->GetPath(std::addressof(path), content_id));
|
||||
|
||||
/* Obtain the rights id for the content. */
|
||||
ncm::RightsId rights_id;
|
||||
R_TRY(GetRightsId(std::addressof(rights_id), path));
|
||||
|
||||
/* Store the rights id to the cache. */
|
||||
this->rights_id_cache->Store(content_id, rights_id);
|
||||
|
||||
out_rights_id.SetValue(rights_id);
|
||||
|
@ -559,17 +633,22 @@ namespace ams::ncm {
|
|||
R_UNLESS(offset <= std::numeric_limits<s64>::max(), ncm::ResultInvalidOffset());
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* This command is for development hardware only. */
|
||||
AMS_ABORT_UNLESS(spl::IsDevelopmentHardware());
|
||||
|
||||
/* Close any cached file. */
|
||||
this->InvalidateFileCache();
|
||||
|
||||
/* Make the content path. */
|
||||
PathString path;
|
||||
MakeContentPath(std::addressof(path), content_id, this->make_content_path_func, this->root_path);
|
||||
|
||||
/* Open the content file. */
|
||||
fs::FileHandle file;
|
||||
R_TRY(fs::OpenFile(std::addressof(file), path.Get(), fs::OpenMode_Write));
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
/* Write the provided data to the file. */
|
||||
R_TRY(fs::WriteFile(file, offset, data.GetPointer(), data.GetSize(), fs::WriteOption::Flush));
|
||||
|
||||
return ResultSuccess();
|
||||
|
@ -597,21 +676,27 @@ namespace ams::ncm {
|
|||
Result ContentStorageImpl::GetSizeFromPlaceHolderId(sf::Out<u64> out_size, PlaceHolderId placeholder_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Attempt to get the placeholder file size. */
|
||||
bool found = false;
|
||||
s64 file_size = 0;
|
||||
R_TRY(this->placeholder_accessor.TryGetPlaceHolderFileSize(std::addressof(found), std::addressof(file_size), placeholder_id));
|
||||
|
||||
/* Set the output if placeholder file is found. */
|
||||
if (found) {
|
||||
out_size.SetValue(file_size);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* Get the path of the placeholder. */
|
||||
PathString placeholder_path;
|
||||
this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id);
|
||||
|
||||
/* Open the placeholder file. */
|
||||
fs::FileHandle file;
|
||||
R_TRY(fs::OpenFile(std::addressof(file), placeholder_path, fs::OpenMode_Read));
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
/* Get the size of the placeholder file. */
|
||||
R_TRY(fs::GetFileSize(std::addressof(file_size), file));
|
||||
|
||||
out_size.SetValue(file_size);
|
||||
|
@ -623,6 +708,7 @@ namespace ams::ncm {
|
|||
using PathChecker = bool (*)(const char *);
|
||||
PathChecker path_checker = nullptr;
|
||||
|
||||
/* Set the archive bit appropriately for content/placeholders. */
|
||||
auto fix_file_attributes = [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, const fs::DirectoryEntry &entry) {
|
||||
*should_retry_dir_read = false;
|
||||
*should_continue = true;
|
||||
|
@ -638,7 +724,7 @@ namespace ams::ncm {
|
|||
return ResultSuccess();
|
||||
};
|
||||
|
||||
/* Fix Content */
|
||||
/* Fix content. */
|
||||
{
|
||||
path_checker = IsContentPath;
|
||||
PathString path;
|
||||
|
@ -647,7 +733,7 @@ namespace ams::ncm {
|
|||
R_TRY(TraverseDirectory(path, GetHierarchicalContentDirectoryDepth(this->make_content_path_func), fix_file_attributes));
|
||||
}
|
||||
|
||||
/* Fix placeholder. */
|
||||
/* Fix placeholders. */
|
||||
this->placeholder_accessor.InvalidateAll();
|
||||
{
|
||||
path_checker = IsPlaceHolderPath;
|
||||
|
@ -663,16 +749,20 @@ namespace ams::ncm {
|
|||
Result ContentStorageImpl::GetRightsIdFromPlaceHolderIdWithCache(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Attempt to find the rights id in the cache. */
|
||||
if (this->rights_id_cache->Find(out_rights_id.GetPointer(), cache_content_id)) {
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* Get the placeholder path. */
|
||||
PathString placeholder_path;
|
||||
this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id);
|
||||
|
||||
/* Substitute mount name with the common mount name. */
|
||||
Path common_path;
|
||||
R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), placeholder_path));
|
||||
|
||||
/* Get the rights id. */
|
||||
ncm::RightsId rights_id;
|
||||
R_TRY(GetRightsId(&rights_id, common_path));
|
||||
this->rights_id_cache->Store(cache_content_id, rights_id);
|
||||
|
|
|
@ -40,9 +40,11 @@ namespace ams::ncm {
|
|||
|
||||
Result Initialize(const char *root_path, MakeContentPathFunction content_path_func, MakePlaceHolderPathFunction placeholder_path_func, bool delay_flush, RightsIdCache *rights_id_cache);
|
||||
private:
|
||||
/* Helpers. */
|
||||
Result OpenContentIdFile(ContentId content_id);
|
||||
void InvalidateFileCache();
|
||||
public:
|
||||
/* Actual commands. */
|
||||
virtual Result GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) override;
|
||||
virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) override;
|
||||
virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id) override;
|
||||
|
|
|
@ -29,6 +29,7 @@ namespace ams::ncm {
|
|||
protected:
|
||||
ContentStorageImplBase() { /* ... */ }
|
||||
protected:
|
||||
/* Helpers. */
|
||||
Result EnsureEnabled() const {
|
||||
R_UNLESS(!this->disabled, ncm::ResultInvalidContentStorage());
|
||||
return ResultSuccess();
|
||||
|
|
|
@ -193,17 +193,22 @@ namespace {
|
|||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
/* Create and initialize the content manager. */
|
||||
auto content_manager = GetSharedPointerToContentManager();
|
||||
R_ABORT_UNLESS(content_manager->Initialize());
|
||||
|
||||
/* Initialize ncm's server and start threads. */
|
||||
R_ABORT_UNLESS(g_ncm_server_manager.Initialize(content_manager));
|
||||
R_ABORT_UNLESS(g_ncm_server_manager.StartThreads());
|
||||
|
||||
/* Initialize ncm api. */
|
||||
ncm::InitializeWithObject(content_manager);
|
||||
|
||||
/* Initialize lr's server and start threads. */
|
||||
R_ABORT_UNLESS(g_lr_server_manager.Initialize());
|
||||
R_ABORT_UNLESS(g_lr_server_manager.StartThreads());
|
||||
|
||||
/* Wait indefinitely. */
|
||||
g_ncm_server_manager.Wait();
|
||||
g_lr_server_manager.Wait();
|
||||
|
||||
|
|
|
@ -28,10 +28,14 @@ namespace ams::ncm {
|
|||
void MakePlaceHolderName(PathString *out, PlaceHolderId id) {
|
||||
auto &bytes = id.uuid.data;
|
||||
char tmp[3];
|
||||
|
||||
/* Create a hex string from bytes. */
|
||||
for (size_t i = 0; i < sizeof(bytes); i++) {
|
||||
std::snprintf(tmp, util::size(tmp), "%02x", bytes[i]);
|
||||
out->Append(tmp);
|
||||
}
|
||||
|
||||
/* Append file extension. */
|
||||
out->Append(".nca");
|
||||
}
|
||||
|
||||
|
@ -56,34 +60,49 @@ namespace ams::ncm {
|
|||
}
|
||||
|
||||
void MakeFlatContentFilePath(PathString *out, ContentId content_id, const char *root_path) {
|
||||
/* Create the content name from the content id. */
|
||||
PathString content_name;
|
||||
MakeContentName(std::addressof(content_name), content_id);
|
||||
|
||||
/* Format the output path. */
|
||||
out->SetFormat("%s/%s", root_path, content_name.Get());
|
||||
}
|
||||
|
||||
void MakeSha256HierarchicalContentFilePath_ForFat4KCluster(PathString *out, ContentId content_id, const char *root_path) {
|
||||
/* Hash the content id. */
|
||||
const u16 hash = Get16BitSha256HashPrefix(content_id);
|
||||
const u32 hash_upper = (hash >> 10) & 0x3f;
|
||||
const u32 hash_lower = (hash >> 4) & 0x3f;
|
||||
|
||||
/* Create the content name from the content id. */
|
||||
PathString content_name;
|
||||
MakeContentName(std::addressof(content_name), content_id);
|
||||
|
||||
/* Format the output path. */
|
||||
out->SetFormat("%s/%08X/%08X/%s", root_path, hash_upper, hash_lower, content_name.Get());
|
||||
}
|
||||
|
||||
void MakeSha256HierarchicalContentFilePath_ForFat32KCluster(PathString *out, ContentId content_id, const char *root_path) {
|
||||
/* Hash the content id. */
|
||||
const u32 hash = (Get16BitSha256HashPrefix(content_id) >> 6) & 0x3FF;
|
||||
|
||||
/* Create the content name from the content id. */
|
||||
PathString content_name;
|
||||
MakeContentName(std::addressof(content_name), content_id);
|
||||
|
||||
/* Format the output path. */
|
||||
out->SetFormat("%s/%08X/%s", root_path, hash, content_name.Get());
|
||||
}
|
||||
|
||||
void MakeSha256HierarchicalContentFilePath_ForFat16KCluster(PathString *out, ContentId content_id, const char *root_path) {
|
||||
/* Hash the content id. */
|
||||
const u32 hash_byte = static_cast<u32>(Get8BitSha256HashPrefix(content_id));
|
||||
|
||||
/* Create the content name from the content id. */
|
||||
PathString content_name;
|
||||
MakeContentName(std::addressof(content_name), content_id);
|
||||
|
||||
/* Format the output path. */
|
||||
out->SetFormat("%s/%08X/%s", root_path, hash_byte, content_name.Get());
|
||||
}
|
||||
|
||||
|
@ -101,16 +120,23 @@ namespace ams::ncm {
|
|||
}
|
||||
|
||||
void MakeFlatPlaceHolderFilePath(PathString *out, PlaceHolderId placeholder_id, const char *root_path) {
|
||||
/* Create the placeholder name from the placeholder id. */
|
||||
PathString placeholder_name;
|
||||
MakePlaceHolderName(std::addressof(placeholder_name), placeholder_id);
|
||||
|
||||
/* Format the output path. */
|
||||
out->SetFormat("%s/%s", root_path, placeholder_name.Get());
|
||||
}
|
||||
|
||||
void MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster(PathString *out, PlaceHolderId placeholder_id, const char *root_path) {
|
||||
/* Hash the placeholder id. */
|
||||
const u32 hash_byte = static_cast<u32>(Get8BitSha256HashPrefix(placeholder_id));
|
||||
|
||||
/* Create the placeholder name from the placeholder id. */
|
||||
PathString placeholder_name;
|
||||
MakePlaceHolderName(std::addressof(placeholder_name), placeholder_id);
|
||||
|
||||
/* Format the output path. */
|
||||
out->SetFormat("%s/%08X/%s", root_path, hash_byte, placeholder_name.Get());
|
||||
}
|
||||
|
||||
|
|
|
@ -21,10 +21,11 @@ namespace ams::ncm {
|
|||
Result OnMemoryContentMetaDatabaseImpl::GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, ProgramId program_id) {
|
||||
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++) {
|
||||
|
||||
/* Find the last key with the desired program id. */
|
||||
for (auto entry = this->kvs->lower_bound(ContentMetaKey::Make(program_id, 0, ContentMetaType::Unknown)); entry != this->kvs->end(); entry++) {
|
||||
/* No further entries will match the program id, discontinue. */
|
||||
if (entry->GetKey().id != program_id) {
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ namespace ams::ncm {
|
|||
public:
|
||||
OnMemoryContentMetaDatabaseImpl(ams::kvdb::MemoryKeyValueStore<ContentMetaKey> *kvs) : ContentMetaDatabaseImpl(kvs) { /* ... */ }
|
||||
public:
|
||||
/* Actual commands. */
|
||||
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;
|
||||
virtual Result Commit() override;
|
||||
|
|
|
@ -63,9 +63,11 @@ namespace ams::ncm {
|
|||
}
|
||||
|
||||
Result PlaceHolderAccessor::GetPlaceHolderIdFromFileName(PlaceHolderId *out, const char *name) {
|
||||
/* Ensure placeholder name is valid. */
|
||||
R_UNLESS(strnlen(name, PlaceHolderFileNameLength) == PlaceHolderFileNameLength, ncm::ResultInvalidPlaceHolderFile());
|
||||
R_UNLESS(strncmp(name + PlaceHolderFileNameLengthWithoutExtension, PlaceHolderExtension, PlaceHolderExtensionLength) == 0, ncm::ResultInvalidPlaceHolderFile());
|
||||
|
||||
/* Convert each character pair to a byte until we reach the end. */
|
||||
PlaceHolderId placeholder_id = {};
|
||||
for (size_t i = 0; i < sizeof(placeholder_id); i++) {
|
||||
char tmp[3];
|
||||
|
@ -84,20 +86,24 @@ namespace ams::ncm {
|
|||
/* Try to load from the cache. */
|
||||
R_UNLESS(!this->LoadFromCache(out_handle, placeholder_id), ResultSuccess());
|
||||
|
||||
/* Make the path of the placeholder. */
|
||||
PathString placeholder_path;
|
||||
this->MakePath(std::addressof(placeholder_path), placeholder_id);
|
||||
|
||||
/* Open the placeholder file. */
|
||||
return fs::OpenFile(out_handle, placeholder_path, fs::OpenMode_Write);
|
||||
}
|
||||
|
||||
bool PlaceHolderAccessor::LoadFromCache(fs::FileHandle *out_handle, PlaceHolderId placeholder_id) {
|
||||
std::scoped_lock lk(this->cache_mutex);
|
||||
|
||||
/* attempt to find an entry in the cache. */
|
||||
CacheEntry *entry = this->FindInCache(placeholder_id);
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* No cached entry found. */
|
||||
entry->id = InvalidPlaceHolderId;
|
||||
*out_handle = entry->handle;
|
||||
return true;
|
||||
|
@ -106,6 +112,7 @@ namespace ams::ncm {
|
|||
void PlaceHolderAccessor::StoreToCache(PlaceHolderId placeholder_id, fs::FileHandle handle) {
|
||||
std::scoped_lock lk(this->cache_mutex);
|
||||
|
||||
/* Store placeholder id and file handle to a free entry. */
|
||||
CacheEntry *entry = this->GetFreeEntry();
|
||||
entry->id = placeholder_id;
|
||||
entry->handle = handle;
|
||||
|
@ -113,6 +120,7 @@ namespace ams::ncm {
|
|||
}
|
||||
|
||||
void PlaceHolderAccessor::Invalidate(CacheEntry *entry) {
|
||||
/* Flush and close the cached entry's file. */
|
||||
if (entry != nullptr) {
|
||||
fs::FlushFile(entry->handle);
|
||||
fs::CloseFile(entry->handle);
|
||||
|
@ -121,10 +129,12 @@ namespace ams::ncm {
|
|||
}
|
||||
|
||||
PlaceHolderAccessor::CacheEntry *PlaceHolderAccessor::FindInCache(PlaceHolderId placeholder_id) {
|
||||
/* Ensure placeholder id is valid. */
|
||||
if (placeholder_id == InvalidPlaceHolderId) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Attempt to find a cache entry with the same placeholder id. */
|
||||
for (size_t i = 0; i < MaxCacheEntries; i++) {
|
||||
if (placeholder_id == this->caches[i].id) {
|
||||
return &this->caches[i];
|
||||
|
@ -161,11 +171,14 @@ namespace ams::ncm {
|
|||
}
|
||||
|
||||
Result PlaceHolderAccessor::CreatePlaceHolderFile(PlaceHolderId placeholder_id, s64 size) {
|
||||
/* Ensure the destination directory exists. */
|
||||
R_TRY(this->EnsurePlaceHolderDirectory(placeholder_id));
|
||||
|
||||
/* Get the placeholder path. */
|
||||
PathString placeholder_path;
|
||||
this->GetPath(std::addressof(placeholder_path), placeholder_id);
|
||||
|
||||
/* Create the placeholder file. */
|
||||
R_TRY_CATCH(fs::CreateFile(placeholder_path, size, fs::CreateOption_BigFile)) {
|
||||
R_CONVERT(fs::ResultPathAlreadyExists, ncm::ResultPlaceHolderAlreadyExists())
|
||||
} R_END_TRY_CATCH;
|
||||
|
@ -174,9 +187,11 @@ namespace ams::ncm {
|
|||
}
|
||||
|
||||
Result PlaceHolderAccessor::DeletePlaceHolderFile(PlaceHolderId placeholder_id) {
|
||||
/* Get the placeholder path. */
|
||||
PathString placeholder_path;
|
||||
this->GetPath(std::addressof(placeholder_path), placeholder_id);
|
||||
|
||||
/* Delete the placeholder file. */
|
||||
R_TRY_CATCH(fs::DeleteFile(placeholder_path)) {
|
||||
R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
|
@ -185,32 +200,40 @@ namespace ams::ncm {
|
|||
}
|
||||
|
||||
Result PlaceHolderAccessor::WritePlaceHolderFile(PlaceHolderId placeholder_id, s64 offset, const void *buffer, size_t size) {
|
||||
/* Open the placeholder file. */
|
||||
fs::FileHandle file;
|
||||
R_TRY_CATCH(this->Open(std::addressof(file), placeholder_id)) {
|
||||
R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* Store opened files to the cache regardless of write failures. */
|
||||
ON_SCOPE_EXIT { this->StoreToCache(placeholder_id, file); };
|
||||
|
||||
R_TRY(fs::WriteFile(file, offset, buffer, size, this->delay_flush ? fs::WriteOption::Flush : fs::WriteOption::None));
|
||||
return ResultSuccess();
|
||||
/* Write data to the placeholder file. */
|
||||
return fs::WriteFile(file, offset, buffer, size, this->delay_flush ? fs::WriteOption::Flush : fs::WriteOption::None);
|
||||
}
|
||||
|
||||
Result PlaceHolderAccessor::SetPlaceHolderFileSize(PlaceHolderId placeholder_id, s64 size) {
|
||||
/* Open the placeholder file. */
|
||||
fs::FileHandle file;
|
||||
R_TRY_CATCH(this->Open(std::addressof(file), placeholder_id)) {
|
||||
R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* Close the file on exit. */
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
R_TRY(fs::SetFileSize(file, size));
|
||||
|
||||
return ResultSuccess();
|
||||
/* Set the size of the placeholder file. */
|
||||
return fs::SetFileSize(file, size);
|
||||
}
|
||||
|
||||
Result PlaceHolderAccessor::TryGetPlaceHolderFileSize(bool *found_in_cache, s64 *out_size, PlaceHolderId placeholder_id) {
|
||||
/* Attempt to find the placeholder in the cache. */
|
||||
fs::FileHandle handle;
|
||||
auto found = this->LoadFromCache(std::addressof(handle), placeholder_id);
|
||||
|
||||
if (found) {
|
||||
/* Renew the entry in the cache. */
|
||||
this->StoreToCache(placeholder_id, handle);
|
||||
R_TRY(fs::GetFileSize(out_size, handle));
|
||||
*found_in_cache = true;
|
||||
|
@ -222,6 +245,7 @@ namespace ams::ncm {
|
|||
}
|
||||
|
||||
void PlaceHolderAccessor::InvalidateAll() {
|
||||
/* Invalidate all cache entries. */
|
||||
for (auto &entry : this->caches) {
|
||||
if (entry.id != InvalidPlaceHolderId) {
|
||||
this->Invalidate(std::addressof(entry));
|
||||
|
|
|
@ -59,6 +59,7 @@ namespace ams::ncm {
|
|||
static void MakeBaseDirectoryPath(PathString *out, const char *root_path);
|
||||
static Result GetPlaceHolderIdFromFileName(PlaceHolderId *out, const char *name);
|
||||
public:
|
||||
/* API. */
|
||||
void Initialize(PathString *root, MakePlaceHolderPathFunction path_func, bool delay_flush) {
|
||||
this->root_path = root;
|
||||
this->make_placeholder_path_func = path_func;
|
||||
|
|
|
@ -26,8 +26,11 @@ namespace ams::ncm {
|
|||
}
|
||||
|
||||
void MakeGameCardContentMetaPath(PathString *out, ContentId id, MakeContentPathFunction func, const char *root_path) {
|
||||
/* Determine the content path. */
|
||||
PathString path;
|
||||
func(std::addressof(path), id, root_path);
|
||||
|
||||
/* Substitute the .nca extension with .cmnt.nca. */
|
||||
*out = path.GetSubstring(0, path.GetLength() - 4);
|
||||
out->Append(".cnmt.nca");
|
||||
}
|
||||
|
@ -36,6 +39,8 @@ namespace ams::ncm {
|
|||
PathString path;
|
||||
MakeContentPath(std::addressof(path), id, func, root_path);
|
||||
|
||||
/* Open the content file. */
|
||||
/* If absent, make the path for game card content meta and open again. */
|
||||
R_TRY_CATCH(fs::OpenFile(out, path, fs::OpenMode_Read)) {
|
||||
R_CATCH(fs::ResultPathNotFound) {
|
||||
MakeGameCardContentMetaPath(std::addressof(path), id, func, root_path);
|
||||
|
@ -50,7 +55,6 @@ namespace ams::ncm {
|
|||
|
||||
Result ReadOnlyContentStorageImpl::Initialize(const char *path, MakeContentPathFunction content_path_func) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
this->root_path.Set(path);
|
||||
this->make_content_path_func = content_path_func;
|
||||
return ResultSuccess();
|
||||
|
@ -87,12 +91,15 @@ namespace ams::ncm {
|
|||
Result ReadOnlyContentStorageImpl::Has(sf::Out<bool> out, ContentId content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Make the content path. */
|
||||
PathString content_path;
|
||||
MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
|
||||
|
||||
/* Check if the file exists. */
|
||||
bool has;
|
||||
R_TRY(impl::HasFile(std::addressof(has), content_path));
|
||||
|
||||
/* If the file is absent, make the path for game card content meta and check presence again. */
|
||||
if (!has) {
|
||||
MakeGameCardContentMetaPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
|
||||
R_TRY(impl::HasFile(std::addressof(has), content_path));
|
||||
|
@ -105,19 +112,24 @@ namespace ams::ncm {
|
|||
Result ReadOnlyContentStorageImpl::GetPath(sf::Out<Path> out, ContentId content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Make the path for game card content meta. */
|
||||
PathString content_path;
|
||||
MakeGameCardContentMetaPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
|
||||
|
||||
/* Check if the file exists. */
|
||||
bool has_file;
|
||||
R_TRY(impl::HasFile(std::addressof(has_file), content_path));
|
||||
|
||||
/* If the file is absent, make the path for regular content. */
|
||||
if (!has_file) {
|
||||
MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
|
||||
}
|
||||
|
||||
/* Substitute mount name with the common mount name. */
|
||||
Path common_path;
|
||||
R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), content_path));
|
||||
|
||||
out.SetValue(common_path);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
|
@ -144,10 +156,12 @@ namespace ams::ncm {
|
|||
Result ReadOnlyContentStorageImpl::GetSizeFromContentId(sf::Out<u64> out_size, ContentId content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Open the file for the content id. */
|
||||
fs::FileHandle file;
|
||||
R_TRY(OpenContentIdFileImpl(std::addressof(file), content_id, this->make_content_path_func, this->root_path));
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
/* Determine the file size. */
|
||||
s64 file_size;
|
||||
R_TRY(fs::GetFileSize(std::addressof(file_size), file));
|
||||
|
||||
|
@ -173,10 +187,12 @@ namespace ams::ncm {
|
|||
R_UNLESS(offset <= std::numeric_limits<s64>::max(), ncm::ResultInvalidOffset());
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Open the file for the content id. */
|
||||
fs::FileHandle file;
|
||||
R_TRY(OpenContentIdFileImpl(std::addressof(file), content_id, this->make_content_path_func, this->root_path));
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
/* Read from the given offset up to the given size. */
|
||||
R_TRY(fs::ReadFile(file, offset, buf.GetPointer(), buf.GetSize()));
|
||||
|
||||
return ResultSuccess();
|
||||
|
@ -191,8 +207,11 @@ namespace ams::ncm {
|
|||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id) {
|
||||
/* Obtain the regular rights id for the content id. */
|
||||
ncm::RightsId rights_id;
|
||||
R_TRY(this->GetRightsIdFromContentId(&rights_id, content_id));
|
||||
|
||||
/* Output the fs rights id. */
|
||||
out_rights_id.SetValue(rights_id.id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
@ -200,9 +219,11 @@ namespace ams::ncm {
|
|||
Result ReadOnlyContentStorageImpl::GetRightsIdFromContentId(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Get the content path. */
|
||||
Path path;
|
||||
R_TRY(this->GetPath(std::addressof(path), content_id));
|
||||
|
||||
/* Get the rights id. */
|
||||
ncm::RightsId rights_id;
|
||||
R_TRY(GetRightsId(&rights_id, path));
|
||||
out_rights_id.SetValue(rights_id);
|
||||
|
|
|
@ -25,6 +25,7 @@ namespace ams::ncm {
|
|||
public:
|
||||
Result Initialize(const char *root_path, MakeContentPathFunction content_path_func);
|
||||
public:
|
||||
/* Actual commands. */
|
||||
virtual Result GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) override;
|
||||
virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) override;
|
||||
virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id) override;
|
||||
|
|
Loading…
Add table
Reference in a new issue