Implement the NCM sysmodule (closes #91)
* Implement NCM * Modernize ncm_main * Remove unnecessary smExit * Give access to svcCallSecureMonitor * Stack size bump * Fix incorrect setup for NandUser's content storage entry * Fix a potential data abort when flushing the placeholder accessor cache * Fix HasFile and HasDirectory * Use r+b, not w+b * Misc fixes * errno begone * Fixed more stdio error handling * More main fixes * Various command improvements * Make dispatch tables great again * Fix logic inversion * Fixed content path generation * Bump heap size, fix CleanupAllPlaceHolder * Various fixes. Note: This contains debug stuff which will be removed later. I was getting tired of having to cherrypick tiny changes * Fixed placeholder/content deletion * Fixed incorrect content manager destruction * Prevent automatic placeholder creation on open * Fixed List implementation. Also lots of debug logging. * Removed debug code * Added a scope guard for WritePlaceHolder * Manually prevent placeholder/content appending * Revert "Removed debug code" This reverts commitd6ff261fcc. * Always cache placeholder file. Switch to ftell for preventing appending * Universally use EnsureEnabled * Abstract away file writing logic * Misc cleanup * Refactor placeholder cacheing * Remove debug code (again) * Revert "Remove debug code (again)" This reverts commit168447d80e. * Misc changes * Fixed file modes * Fixed ContentId/PlaceHolderId alignment * Improved type safety * Fixed reinitialization * Fixed doubleup on path creation * Remove debug code * Fixed 1.0.0 booting * Correct amount of add on content * Correct main thread stack size * lr: Introducing registered data * Reorder stratosphere Makefile * Move results to libstrat * lr: Cleanup lr_redirection * lr: lr_manager tweaks * lr: Imrpoved path handling and adjust ResolveAddOnContentPath order * lr: Organise types * Add eof newlines * lr: Eliminate unnecessary vars * lr: Unnecessary vars 2 electric boogaloo * lr: Various helpers * lr: RegisteredLocationResolver helpers * ncm: Move ncm_types to libstrat * ncm: Misc cleanup * Implement NCM * Modernize ncm_main * Remove unnecessary smExit * Give access to svcCallSecureMonitor * Stack size bump * Fix incorrect setup for NandUser's content storage entry * Fix a potential data abort when flushing the placeholder accessor cache * Fix HasFile and HasDirectory * Use r+b, not w+b * Misc fixes * errno begone * Fixed more stdio error handling * More main fixes * Various command improvements * Make dispatch tables great again * Fix logic inversion * Fixed content path generation * Bump heap size, fix CleanupAllPlaceHolder * Various fixes. Note: This contains debug stuff which will be removed later. I was getting tired of having to cherrypick tiny changes * Fixed placeholder/content deletion * Fixed incorrect content manager destruction * Prevent automatic placeholder creation on open * Fixed List implementation. Also lots of debug logging. * Removed debug code * Added a scope guard for WritePlaceHolder * Manually prevent placeholder/content appending * Revert "Removed debug code" This reverts commitd6ff261fcc. * Always cache placeholder file. Switch to ftell for preventing appending * Universally use EnsureEnabled * Abstract away file writing logic * Misc cleanup * Refactor placeholder cacheing * Remove debug code (again) * Revert "Remove debug code (again)" This reverts commit168447d80e. * Misc changes * Fixed file modes * Fixed ContentId/PlaceHolderId alignment * Improved type safety * Fixed reinitialization * Fixed doubleup on path creation * Remove debug code * Fixed 1.0.0 booting * Correct amount of add on content * Correct main thread stack size * lr: Introducing registered data * Reorder stratosphere Makefile * Move results to libstrat * lr: Cleanup lr_redirection * lr: lr_manager tweaks * lr: Imrpoved path handling and adjust ResolveAddOnContentPath order * lr: Organise types * Add eof newlines * lr: Eliminate unnecessary vars * lr: Unnecessary vars 2 electric boogaloo * lr: Various helpers * lr: RegisteredLocationResolver helpers * ncm: Move ncm_types to libstrat * ncm: Misc cleanup * Updated AddOnContentLocationResolver and RegisteredLocationResolver to 9.0.0 * Finished updating lr to 9.0.0 * Updated NCM to 9.0.0 * Fix libstrat includes * Fixed application launching * title_id_2 -> owner_tid * Updated to new-ipc * Change to using pure virtuals * Title Id -> Program Id * Fixed compilation against master * std::scoped_lock<> -> std::scoped_lock * Adopted R_UNLESS and R_CONVERT * Prefix namespace to Results * Adopt std::numeric_limits * Fixed incorrect error handling in ReadFile * Adopted AMS_ABORT_UNLESS * Adopt util::GenerateUuid() * Syntax improvements * ncm_types: Address review * Address more review comments * Updated copyrights * Address more feedback * More feedback addressed * More changes * Move dispatch tables out of interface files * Addressed remaining comments * lr: move into libstratosphere * ncm: Fix logic inversion * lr: Add comments * lr: Remove whitespace * ncm: Start addressing feedback * ncm: Cleanup InitializeContentManager * lr: support client-side usage * lr_service -> lr_api * ncm: Begin refactoring content manager * ncm: More content manager improvements * ncm: Content manager mount improvements * ldr: use lr bindings * lr bindings usage: minor fixes * ncm/lr: Pointer placement * ncm: placeholder accessor cleanup * ncm: minor fixes * ncm: refactor rights cache * ncm: content meta database cleanup * ncm: move content meta database impl out of interface file * ncm: Use const ContentMetaKey & * ncm: fix other non-const ContentMetaKey references * ncm: content meta database cleanup * ncm: content storage fixes for 2.0.0 * ncm: add missing end of file newlines * ncm: implement ContentMetaReader * ncm: client-side api * ncm: trim trailing spaces * ncm: FS_MAX_PATH-1 -> fs::EntryNameLengthMax * ncm: Use PathString and Path * fs: implement accessor wrappers for ncm * fs: implement user fs wrappers * fs: add MountSdCard * ncm: move to content manager impl * ncm: fix up main * kvdb: use fs:: * fs: Add wrappers needed for ncm * ncm: use fs bindings, other refactoring * ncm: minor fixes * fsa: fix ReadFile without size output * fs: add substorage, rom path tool * ncm: fix dangling fsdev usage * fs: fix bug in Commit * fs: fixed incorrect mode check * fs: implement Mount(System)Data * ncm: don't delete hos * results: add R_SUCCEED_IF * ams-except-ncm: use R_SUCCEED_IF * ncm: added comments * ncm: fix api definitions * ncm: use R_SUCCEED_IF * pm: think of the savings * ncm: employ kernel strats * ncm: Nintendo has 5 MiB of heap. Give ourselves 4 to be safe, pending analysis * ncm: refactor IDs, split types header into many headers * ams.mitm: use fs bindings instead of stdio * fs: SystemData uses SystemDataId * ncm: improve meta-db accuracy * ncm: inline getlatestkey * fs: improve UnsupportedOperation results * fs: modernize mount utils * ams: misc fixes for merge-errors * fs: improve unsupportedoperation results * git subrepo pull emummc subrepo: subdir: "emummc" merged: "d12dd546" upstream: origin: "https://github.com/m4xw/emuMMC" branch: "develop" commit: "d12dd546" git-subrepo: version: "0.4.1" origin: "???" commit: "???" * util: add boundedmap * ncm: minor style fixes * ncm: don't unmount if mounting fails * lr: bug fixes * ncm: implement ncm.for-initialize + ncm.for-safemode * lr: ncm::ProgramId::Invalid -> ncm::InvalidProgramId * ncm: fix open directory mode on 1.0.0 * ncm: fix fs use, implement more of < 4.0.0 for-initialize/safemode * ncm: implement packagedcontent -> content for building metadb * ncm: fix save data flag management * ncm: address some review suggestions (thanks @leoetlino!) * updater: use fs bindings * fs: implement MountCode * fs: prefer make_unique to operator new * ncm: implement remaining ContentMetaDatabaseBuilder functionality Co-authored-by: Michael Scire <SciresM@gmail.com>
This commit is contained in:
112
libraries/libstratosphere/source/ncm/ncm_api.cpp
Normal file
112
libraries/libstratosphere/source/ncm/ncm_api.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_remote_content_manager_impl.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
namespace {
|
||||
|
||||
std::shared_ptr<IContentManager> g_content_manager;
|
||||
|
||||
}
|
||||
|
||||
void Initialize() {
|
||||
AMS_ASSERT(g_content_manager == nullptr);
|
||||
R_ABORT_UNLESS(ncmInitialize());
|
||||
g_content_manager = std::make_shared<RemoteContentManagerImpl>();
|
||||
}
|
||||
|
||||
void Finalize() {
|
||||
AMS_ASSERT(g_content_manager != nullptr);
|
||||
g_content_manager.reset();
|
||||
ncmExit();
|
||||
}
|
||||
|
||||
void InitializeWithObject(std::shared_ptr<IContentManager> manager_object) {
|
||||
AMS_ASSERT(g_content_manager == nullptr);
|
||||
g_content_manager = manager_object;
|
||||
AMS_ASSERT(g_content_manager != nullptr);
|
||||
}
|
||||
|
||||
/* Service API. */
|
||||
Result CreateContentStorage(StorageId storage_id) {
|
||||
return g_content_manager->CreateContentStorage(storage_id);
|
||||
}
|
||||
|
||||
Result CreateContentMetaDatabase(StorageId storage_id) {
|
||||
return g_content_manager->CreateContentMetaDatabase(storage_id);
|
||||
}
|
||||
|
||||
Result VerifyContentStorage(StorageId storage_id) {
|
||||
return g_content_manager->VerifyContentStorage(storage_id);
|
||||
}
|
||||
|
||||
Result VerifyContentMetaDatabase(StorageId storage_id) {
|
||||
return g_content_manager->VerifyContentMetaDatabase(storage_id);
|
||||
}
|
||||
|
||||
Result OpenContentStorage(ContentStorage *out, StorageId storage_id) {
|
||||
sf::cmif::ServiceObjectHolder object_holder;
|
||||
R_TRY(g_content_manager->OpenContentStorage(std::addressof(object_holder), storage_id));
|
||||
|
||||
*out = ContentStorage(object_holder.GetServiceObject<IContentStorage>());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result OpenContentMetaDatabase(ContentMetaDatabase *out, StorageId storage_id) {
|
||||
sf::cmif::ServiceObjectHolder object_holder;
|
||||
R_TRY(g_content_manager->OpenContentMetaDatabase(std::addressof(object_holder), storage_id));
|
||||
|
||||
*out = ContentMetaDatabase(object_holder.GetServiceObject<IContentMetaDatabase>());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result CleanupContentMetaDatabase(StorageId storage_id) {
|
||||
return g_content_manager->CleanupContentMetaDatabase(storage_id);
|
||||
}
|
||||
|
||||
Result ActivateContentStorage(StorageId storage_id) {
|
||||
return g_content_manager->ActivateContentStorage(storage_id);
|
||||
}
|
||||
|
||||
Result InactivateContentStorage(StorageId storage_id) {
|
||||
return g_content_manager->InactivateContentStorage(storage_id);
|
||||
}
|
||||
|
||||
Result ActivateContentMetaDatabase(StorageId storage_id) {
|
||||
return g_content_manager->ActivateContentMetaDatabase(storage_id);
|
||||
}
|
||||
|
||||
Result InactivateContentMetaDatabase(StorageId storage_id) {
|
||||
return g_content_manager->InactivateContentMetaDatabase(storage_id);
|
||||
}
|
||||
|
||||
Result InvalidateRightsIdCache() {
|
||||
return g_content_manager->InvalidateRightsIdCache();
|
||||
}
|
||||
|
||||
/* Deprecated API. */
|
||||
Result CloseContentStorageForcibly(StorageId storage_id) {
|
||||
AMS_ABORT_UNLESS(hos::GetVersion() == hos::Version_100);
|
||||
return g_content_manager->CloseContentStorageForcibly(storage_id);
|
||||
}
|
||||
|
||||
Result CloseContentMetaDatabaseForcibly(StorageId storage_id) {
|
||||
AMS_ABORT_UNLESS(hos::GetVersion() == hos::Version_100);
|
||||
return g_content_manager->CloseContentMetaDatabaseForcibly(storage_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
namespace {
|
||||
|
||||
void GetStringFromBytes(char *dst, const void *src, size_t count) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
std::snprintf(dst + 2 * i, 3, "%02x", static_cast<const u8 *>(src)[i]);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
char *err = nullptr;
|
||||
reinterpret_cast<u8 *>(dst)[i / 2] = static_cast<u8>(std::strtoul(tmp, std::addressof(err), 16));
|
||||
if (*err != '\x00') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
ContentIdString GetContentIdString(ContentId id) {
|
||||
ContentIdString str;
|
||||
GetStringFromContentId(str.data, sizeof(str), id);
|
||||
return str;
|
||||
}
|
||||
|
||||
void GetStringFromContentId(char *dst, size_t dst_size, ContentId id) {
|
||||
AMS_ABORT_UNLESS(dst_size > ContentIdStringLength);
|
||||
GetStringFromBytes(dst, std::addressof(id), sizeof(id));
|
||||
}
|
||||
|
||||
std::optional<ContentId> GetContentIdFromString(const char *str, size_t len) {
|
||||
if (len < ContentIdStringLength) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ContentId content_id;
|
||||
return GetBytesFromString(std::addressof(content_id), sizeof(content_id), str, ContentIdStringLength) ? std::optional<ContentId>(content_id) : std::nullopt;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_fs_utils.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr inline size_t MaxPackagePathLength = 0x100;
|
||||
|
||||
Result ConvertToFsCommonPath(char *dst, size_t dst_size, const char *package_root_path, const char *entry_path) {
|
||||
char package_path[MaxPackagePathLength];
|
||||
|
||||
const size_t path_len = std::snprintf(package_path, sizeof(package_path), "%s%s", package_root_path, entry_path);
|
||||
AMS_ABORT_UNLESS(path_len < MaxPackagePathLength);
|
||||
|
||||
return fs::ConvertToFsCommonPath(dst, dst_size, package_path);
|
||||
}
|
||||
|
||||
Result LoadContentMeta(ncm::AutoBuffer *out, const char *package_root_path, const fs::DirectoryEntry &entry) {
|
||||
AMS_ABORT_UNLESS(impl::PathView(entry.name).HasSuffix(".cnmt.nca"));
|
||||
|
||||
char path[MaxPackagePathLength];
|
||||
R_TRY(ConvertToFsCommonPath(path, sizeof(path), package_root_path, entry.name));
|
||||
|
||||
return ncm::ReadContentMetaPath(out, path);
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
Result ForEachFileInDirectory(const char *root_path, F f) {
|
||||
/* Open the directory. */
|
||||
fs::DirectoryHandle dir;
|
||||
R_TRY(fs::OpenDirectory(std::addressof(dir), root_path, fs::OpenDirectoryMode_File));
|
||||
ON_SCOPE_EXIT { fs::CloseDirectory(dir); };
|
||||
|
||||
while (true) {
|
||||
/* Read the current entry. */
|
||||
s64 count;
|
||||
fs::DirectoryEntry entry;
|
||||
R_TRY(fs::ReadDirectory(std::addressof(count), std::addressof(entry), dir, 1));
|
||||
if (count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Invoke our handler on the entry. */
|
||||
bool done;
|
||||
R_TRY(f(std::addressof(done), entry));
|
||||
R_SUCCEED_IF(done);
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseBuilder::BuildFromPackageContentMeta(void *buf, size_t size, const ContentInfo &meta_info) {
|
||||
/* Create a reader for the content meta. */
|
||||
ncm::PackagedContentMetaReader package_meta_reader(buf, size);
|
||||
|
||||
/* Allocate space to hold the converted meta. */
|
||||
const size_t meta_size = package_meta_reader.CalculateConvertContentMetaSize();
|
||||
void *meta = std::malloc(meta_size);
|
||||
ON_SCOPE_EXIT { std::free(meta); };
|
||||
|
||||
/* Convert the meta from packaged form to normal form. */
|
||||
package_meta_reader.ConvertToContentMeta(meta, meta_size, meta_info);
|
||||
ncm::ContentMetaReader meta_reader(meta, meta_size);
|
||||
|
||||
/* Insert the new metas into the database. */
|
||||
R_TRY(this->db->Set(package_meta_reader.GetKey(), meta_reader.GetData(), meta_reader.GetSize()));
|
||||
|
||||
/* We're done. */
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseBuilder::BuildFromStorage(ContentStorage *storage) {
|
||||
/* Get the total count of contents. */
|
||||
s32 total_count;
|
||||
R_TRY(storage->GetContentCount(std::addressof(total_count)));
|
||||
|
||||
/* Loop over all contents, looking for a package we can build from. */
|
||||
const size_t MaxContentIds = 64;
|
||||
ContentId content_ids[MaxContentIds];
|
||||
for (s32 offset = 0; offset < total_count; /* ... */) {
|
||||
/* List contents at the current offset. */
|
||||
s32 count;
|
||||
R_TRY(storage->ListContentId(std::addressof(count), content_ids, MaxContentIds, offset));
|
||||
|
||||
/* Loop the contents we listed, looking for a correct one. */
|
||||
for (s32 i = 0; i < count; i++) {
|
||||
/* Get the path for this content id. */
|
||||
auto &content_id = content_ids[i];
|
||||
ncm::Path path;
|
||||
storage->GetPath(std::addressof(path), content_id);
|
||||
|
||||
/* Read the content meta path, and build. */
|
||||
ncm::AutoBuffer package_meta;
|
||||
if (R_SUCCEEDED(ncm::ReadContentMetaPath(std::addressof(package_meta), path.str))) {
|
||||
/* Get the size of the content. */
|
||||
s64 size;
|
||||
R_TRY(storage->GetSize(std::addressof(size), content_id));
|
||||
|
||||
/* Build. */
|
||||
R_TRY(this->BuildFromPackageContentMeta(package_meta.Get(), package_meta.GetSize(), ContentInfo::Make(content_id, size, ContentType::Meta)));
|
||||
}
|
||||
}
|
||||
|
||||
/* Advance. */
|
||||
offset += count;
|
||||
}
|
||||
|
||||
/* Commit our changes. */
|
||||
return this->db->Commit();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseBuilder::BuildFromPackage(const char *package_root_path) {
|
||||
/* Build the database by writing every entry in the package. */
|
||||
R_TRY(ForEachFileInDirectory(package_root_path, [&](bool *done, const fs::DirectoryEntry &entry) -> Result {
|
||||
/* Never early terminate. */
|
||||
*done = false;
|
||||
|
||||
/* We have nothing to list if we're not looking at a meta. */
|
||||
R_SUCCEED_IF(!impl::PathView(entry.name).HasSuffix(".cnmt.nca"));
|
||||
|
||||
/* Read the content meta path, and build. */
|
||||
ncm::AutoBuffer package_meta;
|
||||
R_TRY(LoadContentMeta(std::addressof(package_meta), package_root_path, entry));
|
||||
|
||||
/* Try to parse a content id from the name. */
|
||||
auto content_id = GetContentIdFromString(entry.name, sizeof(entry.name));
|
||||
R_UNLESS(content_id, ncm::ResultInvalidPackageFormat());
|
||||
|
||||
/* Build using the meta. */
|
||||
return this->BuildFromPackageContentMeta(package_meta.Get(), package_meta.GetSize(), ContentInfo::Make(*content_id, entry.file_size, ContentType::Meta));
|
||||
}));
|
||||
|
||||
/* Commit our changes. */
|
||||
return this->db->Commit();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseBuilder::Cleanup() {
|
||||
/* This cleans up the content meta by removing all entries. */
|
||||
while (true) {
|
||||
/* List as many keys as we can. */
|
||||
constexpr s32 MaxKeys = 64;
|
||||
ContentMetaKey keys[MaxKeys];
|
||||
auto list_count = this->db->ListContentMeta(keys, MaxKeys);
|
||||
|
||||
/* Remove the listed keys. */
|
||||
for (auto i = 0; i < list_count.written; i++) {
|
||||
R_TRY(this->db->Remove(keys[i]));
|
||||
}
|
||||
|
||||
/* If there aren't more keys to read, we're done. */
|
||||
if (list_count.written < MaxKeys) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Commit our deletions. */
|
||||
return this->db->Commit();
|
||||
}
|
||||
|
||||
Result ListApplicationPackage(s32 *out_count, ApplicationId *out_ids, size_t max_out_ids, const char *package_root_path) {
|
||||
size_t count = 0;
|
||||
R_TRY(ForEachFileInDirectory(package_root_path, [&](bool *done, const fs::DirectoryEntry &entry) -> Result {
|
||||
/* Never early terminate. */
|
||||
*done = false;
|
||||
|
||||
/* We have nothing to list if we're not looking at a meta. */
|
||||
R_SUCCEED_IF(!impl::PathView(entry.name).HasSuffix(".cnmt.nca"));
|
||||
|
||||
/* Read the content meta path, and build. */
|
||||
ncm::AutoBuffer package_meta;
|
||||
R_TRY(LoadContentMeta(std::addressof(package_meta), package_root_path, entry));
|
||||
|
||||
/* Create a reader for the meta. */
|
||||
ncm::PackagedContentMetaReader package_meta_reader(package_meta.Get(), package_meta.GetSize());
|
||||
|
||||
/* Write the key to output if we're reading an application. */
|
||||
const auto &key = package_meta_reader.GetKey();
|
||||
if (key.type == ContentMetaType::Application) {
|
||||
R_UNLESS(count < max_out_ids, ncm::ResultBufferInsufficient());
|
||||
|
||||
out_ids[count++] = { key.id };
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}));
|
||||
|
||||
*out_count = static_cast<s32>(count);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,663 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_content_storage_impl.hpp"
|
||||
#include "ncm_read_only_content_storage_impl.hpp"
|
||||
#include "ncm_content_meta_database_impl.hpp"
|
||||
#include "ncm_on_memory_content_meta_database_impl.hpp"
|
||||
#include "ncm_fs_utils.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr fs::SystemSaveDataId BuiltInSystemSaveDataId = 0x8000000000000120;
|
||||
constexpr u64 BuiltInSystemSaveDataSize = 0x6c000;
|
||||
constexpr u64 BuiltInSystemSaveDataJournalSize = 0x6c000;
|
||||
constexpr u32 BuiltInSystemSaveDataFlags = fs::SaveDataFlags_KeepAfterResettingSystemSaveData | fs::SaveDataFlags_KeepAfterRefurbishment;
|
||||
|
||||
constexpr SystemSaveDataInfo BuiltInSystemSystemSaveDataInfo = {
|
||||
.id = BuiltInSystemSaveDataId,
|
||||
.size = BuiltInSystemSaveDataSize,
|
||||
.journal_size = BuiltInSystemSaveDataJournalSize,
|
||||
.flags = BuiltInSystemSaveDataFlags,
|
||||
.space_id = fs::SaveDataSpaceId::System
|
||||
};
|
||||
|
||||
constexpr fs::SystemSaveDataId BuiltInUserSaveDataId = 0x8000000000000121;
|
||||
constexpr u64 BuiltInUserSaveDataSize = 0x29e000;
|
||||
constexpr u64 BuiltInUserSaveDataJournalSize = 0x29e000;
|
||||
constexpr u32 BuiltInUserSaveDataFlags = 0;
|
||||
|
||||
constexpr SystemSaveDataInfo BuiltInUserSystemSaveDataInfo = {
|
||||
.id = BuiltInUserSaveDataId,
|
||||
.size = BuiltInUserSaveDataSize,
|
||||
.journal_size = BuiltInUserSaveDataJournalSize,
|
||||
.flags = BuiltInUserSaveDataFlags,
|
||||
.space_id = fs::SaveDataSpaceId::System
|
||||
};
|
||||
|
||||
constexpr fs::SystemSaveDataId SdCardSaveDataId = 0x8000000000000124;
|
||||
constexpr u64 SdCardSaveDataSize = 0xa08000;
|
||||
constexpr u64 SdCardSaveDataJournalSize = 0xa08000;
|
||||
constexpr u32 SdCardSaveDataFlags = 0;
|
||||
|
||||
constexpr SystemSaveDataInfo SdCardSystemSaveDataInfo = {
|
||||
.id = SdCardSaveDataId,
|
||||
.size = SdCardSaveDataSize,
|
||||
.journal_size = SdCardSaveDataJournalSize,
|
||||
.flags = SdCardSaveDataFlags,
|
||||
.space_id = fs::SaveDataSpaceId::SdSystem,
|
||||
};
|
||||
|
||||
constexpr size_t MaxBuiltInSystemContentMetaCount = 0x800;
|
||||
constexpr size_t MaxBuiltInUserContentMetaCount = 0x2000;
|
||||
constexpr size_t MaxSdCardContentMetaCount = 0x2000;
|
||||
constexpr size_t MaxGameCardContentMetaCount = 0x800;
|
||||
|
||||
using RootPath = kvdb::BoundedString<32>;
|
||||
|
||||
inline void ReplaceMountName(char *out_path, const char *mount_name, const char *path) {
|
||||
std::strcpy(out_path, mount_name);
|
||||
std::strcat(out_path, std::strchr(path, ':'));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
ALWAYS_INLINE Result GetContentStorageNotActiveResult(StorageId storage_id) {
|
||||
switch (storage_id) {
|
||||
case StorageId::GameCard: return ResultGameCardContentStorageNotActive();
|
||||
case StorageId::BuiltInSystem: return ResultBuiltInSystemContentStorageNotActive();
|
||||
case StorageId::BuiltInUser: return ResultBuiltInUserContentStorageNotActive();
|
||||
case StorageId::SdCard: return ResultSdCardContentStorageNotActive();
|
||||
default: return ResultUnknownContentStorageNotActive();
|
||||
}
|
||||
}
|
||||
|
||||
ALWAYS_INLINE Result GetContentMetaDatabaseNotActiveResult(StorageId storage_id) {
|
||||
switch (storage_id) {
|
||||
case StorageId::GameCard: return ResultGameCardContentMetaDatabaseNotActive();
|
||||
case StorageId::BuiltInSystem: return ResultBuiltInSystemContentMetaDatabaseNotActive();
|
||||
case StorageId::BuiltInUser: return ResultBuiltInUserContentMetaDatabaseNotActive();
|
||||
case StorageId::SdCard: return ResultSdCardContentMetaDatabaseNotActive();
|
||||
default: return ResultUnknownContentMetaDatabaseNotActive();
|
||||
}
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool IsSignedSystemPartitionOnSdCardValid(const char *bis_mount_name) {
|
||||
/* Signed system partition should never be checked on < 4.0.0, as it did not exist before then. */
|
||||
AMS_ABORT_UNLESS(hos::GetVersion() >= hos::Version_400);
|
||||
|
||||
/* If we're importing from system on SD, make sure that the signed system partition is valid. */
|
||||
const auto version = hos::GetVersion();
|
||||
if (version >= hos::Version_800) {
|
||||
/* On >= 8.0.0, a simpler method was added to check validity. */
|
||||
/* This also works on < 4.0.0 (though the system partition will never be on-sd there), */
|
||||
/* and so this will always return false. */
|
||||
char path[fs::MountNameLengthMax + 2 /* :/ */ + 1];
|
||||
std::snprintf(path, sizeof(path), "%s:/", bis_mount_name);
|
||||
return fs::IsSignedSystemPartitionOnSdCardValid(path);
|
||||
} else {
|
||||
/* On 4.0.0-7.0.1, use the remote command to validate the system partition. */
|
||||
return fs::IsSignedSystemPartitionOnSdCardValidDeprecated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
/* On 1.0.0, not all flags existed. Mask when appropriate. */
|
||||
constexpr u32 SaveDataFlags100Mask = fs::SaveDataFlags_KeepAfterResettingSystemSaveData;
|
||||
const u32 flags = (hos::GetVersion() >= hos::Version_200) ? (info.flags) : (info.flags & SaveDataFlags100Mask);
|
||||
R_TRY(fs::CreateSystemSaveData(info.space_id, info.id, OwnerId, info.size, info.journal_size, flags));
|
||||
R_TRY(fs::MountSystemSaveData(mount_name, info.space_id, info.id));
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
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);
|
||||
return ResultSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
return ncm::ResultUnknownStorage();
|
||||
}
|
||||
|
||||
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);
|
||||
return ResultSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
return ncm::ResultUnknownStorage();
|
||||
}
|
||||
|
||||
|
||||
Result ContentManagerImpl::InitializeContentStorageRoot(ContentStorageRoot *out, StorageId storage_id, fs::ContentStorageId content_storage_id) {
|
||||
out->storage_id = storage_id;
|
||||
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);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::InitializeGameCardContentStorageRoot(ContentStorageRoot *out) {
|
||||
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);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::InitializeContentMetaDatabaseRoot(ContentMetaDatabaseRoot *out, StorageId storage_id, const SystemSaveDataInfo &info, size_t max_content_metas) {
|
||||
out->storage_id = storage_id;
|
||||
out->info = info;
|
||||
out->max_content_metas = max_content_metas;
|
||||
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);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::InitializeGameCardContentMetaDatabaseRoot(ContentMetaDatabaseRoot *out, size_t max_content_metas) {
|
||||
out->storage_id = StorageId::GameCard;
|
||||
out->max_content_metas = max_content_metas;
|
||||
out->content_meta_database = nullptr;
|
||||
out->kvs = std::nullopt;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::ImportContentMetaDatabaseImpl(StorageId storage_id, const char *import_mount_name, const char *path) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Obtain the content meta database root. */
|
||||
ContentMetaDatabaseRoot *root;
|
||||
R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id));
|
||||
|
||||
/* Print the savedata path. */
|
||||
PathString savedata_db_path;
|
||||
savedata_db_path.SetFormat("%s/%s", root->path, "imkvdb.arc");
|
||||
|
||||
/* Print a path for the mounted partition. */
|
||||
PathString bis_db_path;
|
||||
bis_db_path.SetFormat("%s:/%s", import_mount_name, path);
|
||||
|
||||
/* Mount the savedata. */
|
||||
R_TRY(fs::MountSystemSaveData(root->mount_name, root->info.space_id, root->info.id));
|
||||
ON_SCOPE_EXIT { fs::Unmount(root->mount_name); };
|
||||
|
||||
/* Ensure the path exists for us to import to. */
|
||||
R_TRY(impl::EnsureDirectoryRecursively(root->path));
|
||||
|
||||
/* Copy the file from bis to our save. */
|
||||
R_TRY(impl::CopyFile(savedata_db_path, bis_db_path));
|
||||
|
||||
/* Commit the import. */
|
||||
return fs::CommitSaveData(root->mount_name);
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::BuildContentMetaDatabase(StorageId storage_id) {
|
||||
if (hos::GetVersion() <= hos::Version_400) {
|
||||
/* Temporarily activate the database. */
|
||||
R_TRY(this->ActivateContentMetaDatabase(storage_id));
|
||||
ON_SCOPE_EXIT { this->InactivateContentMetaDatabase(storage_id); };
|
||||
|
||||
/* Open the content meta database and storage. */
|
||||
ContentMetaDatabase meta_db;
|
||||
ContentStorage storage;
|
||||
R_TRY(ncm::OpenContentMetaDatabase(std::addressof(meta_db), storage_id));
|
||||
R_TRY(ncm::OpenContentStorage(std::addressof(storage), storage_id));
|
||||
|
||||
/* Create a builder, and build. */
|
||||
ContentMetaDatabaseBuilder builder(std::addressof(meta_db));
|
||||
return builder.BuildFromStorage(std::addressof(storage));
|
||||
} else {
|
||||
/* On 5.0.0+, building just performs an import. */
|
||||
return this->ImportContentMetaDatabase(storage_id, false);
|
||||
}
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::ImportContentMetaDatabase(StorageId storage_id, bool from_signed_partition) {
|
||||
/* Only support importing BuiltInSystem. */
|
||||
AMS_ABORT_UNLESS(storage_id == StorageId::BuiltInSystem);
|
||||
|
||||
/* Get a mount name for the system partition. */
|
||||
auto bis_mount_name = impl::CreateUniqueMountName();
|
||||
|
||||
/* Mount the BIS partition that contains the database we're importing. */
|
||||
R_TRY(fs::MountBis(bis_mount_name.str, fs::BisPartitionId::System));
|
||||
ON_SCOPE_EXIT { fs::Unmount(bis_mount_name.str); };
|
||||
|
||||
/* If we're not importing from a signed partition (or the partition signature is valid), import. */
|
||||
if (!from_signed_partition || IsSignedSystemPartitionOnSdCardValid(bis_mount_name.str)) {
|
||||
R_TRY(this->ImportContentMetaDatabaseImpl(StorageId::BuiltInSystem, bis_mount_name.str, "cnmtdb.arc"));
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::Initialize(const ContentManagerConfig &config) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Check if we've already initialized. */
|
||||
R_SUCCEED_IF(this->initialized);
|
||||
|
||||
/* Clear storage id for all roots. */
|
||||
for (auto &root : this->content_storage_roots) {
|
||||
root.storage_id = StorageId::None;
|
||||
}
|
||||
|
||||
for (auto &root : this->content_meta_database_roots) {
|
||||
root.storage_id = StorageId::None;
|
||||
}
|
||||
|
||||
/* First, setup the BuiltInSystem storage entry. */
|
||||
R_TRY(this->InitializeContentStorageRoot(&this->content_storage_roots[this->num_content_storage_entries++], StorageId::BuiltInSystem, fs::ContentStorageId::System));
|
||||
if (R_FAILED(this->VerifyContentStorage(StorageId::BuiltInSystem))) {
|
||||
R_TRY(this->CreateContentStorage(StorageId::BuiltInSystem));
|
||||
}
|
||||
R_TRY(this->ActivateContentStorage(StorageId::BuiltInSystem));
|
||||
|
||||
/* Next, the BuiltInSystem content meta entry. */
|
||||
R_TRY(this->InitializeContentMetaDatabaseRoot(&this->content_meta_database_roots[this->num_content_meta_entries++], StorageId::BuiltInSystem, BuiltInSystemSystemSaveDataInfo, MaxBuiltInSystemContentMetaCount));
|
||||
|
||||
if (R_FAILED(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem))) {
|
||||
R_TRY(this->CreateContentMetaDatabase(StorageId::BuiltInSystem));
|
||||
|
||||
/* Try to build or import a database, depending on our configuration. */
|
||||
if (config.ShouldBuildDatabase()) {
|
||||
/* If we should build the database, do so. */
|
||||
R_TRY(this->BuildContentMetaDatabase(StorageId::BuiltInSystem));
|
||||
R_TRY(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem));
|
||||
} else if (config.ShouldImportDatabaseFromSignedSystemPartitionOnSd()) {
|
||||
/* Otherwise if we should import the database from the SD, do so. */
|
||||
R_TRY(this->ImportContentMetaDatabase(StorageId::BuiltInSystem, true));
|
||||
R_TRY(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem));
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure correct flags on the BuiltInSystem save data. */
|
||||
/* NOTE: Nintendo does not check this succeeds, and it does on older system versions. */
|
||||
/* We will not check the error, either, even though this kind of defeats the call's purpose. */
|
||||
if (hos::GetVersion() >= hos::Version_200) {
|
||||
EnsureBuiltInSystemSaveDataFlags();
|
||||
}
|
||||
|
||||
R_TRY(this->ActivateContentMetaDatabase(StorageId::BuiltInSystem));
|
||||
|
||||
/* Now for BuiltInUser's content storage and content meta entries. */
|
||||
R_TRY(this->InitializeContentStorageRoot(&this->content_storage_roots[this->num_content_storage_entries++], StorageId::BuiltInUser, fs::ContentStorageId::User));
|
||||
R_TRY(this->InitializeContentMetaDatabaseRoot(&this->content_meta_database_roots[this->num_content_meta_entries++], StorageId::BuiltInUser, BuiltInUserSystemSaveDataInfo, MaxBuiltInUserContentMetaCount));
|
||||
|
||||
/* Beyond this point, N uses hardcoded indices. */
|
||||
|
||||
/* Next SdCard's content storage and content meta entries. */
|
||||
R_TRY(this->InitializeContentStorageRoot(&this->content_storage_roots[2], StorageId::SdCard, fs::ContentStorageId::SdCard));
|
||||
R_TRY(this->InitializeContentMetaDatabaseRoot(&this->content_meta_database_roots[2], StorageId::SdCard, SdCardSystemSaveDataInfo, MaxSdCardContentMetaCount));
|
||||
|
||||
/* GameCard's content storage and content meta entries. */
|
||||
/* N doesn't set a content storage id for game cards, so we'll just use 0 (System). */
|
||||
R_TRY(this->InitializeGameCardContentStorageRoot(&this->content_storage_roots[3]));
|
||||
R_TRY(this->InitializeGameCardContentMetaDatabaseRoot(&this->content_meta_database_roots[3], MaxGameCardContentMetaCount));
|
||||
|
||||
this->initialized = true;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
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. */
|
||||
return ContentStorageImpl::InitializeBase(root->path);
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::CreateContentMetaDatabase(StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
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. */
|
||||
return fs::CommitSaveData(root->mount_name);
|
||||
}
|
||||
|
||||
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();
|
||||
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. */
|
||||
return ContentStorageImpl::VerifyBase(path);
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::VerifyContentMetaDatabase(StorageId storage_id) {
|
||||
/* Game card content meta databases will always be valid. */
|
||||
R_SUCCEED_IF(storage_id == StorageId::GameCard);
|
||||
|
||||
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. */
|
||||
const bool mount = !root->content_meta_database;
|
||||
if (mount) {
|
||||
R_TRY(fs::MountSystemSaveData(root->mount_name, root->info.space_id, root->info.id));
|
||||
}
|
||||
auto mount_guard = SCOPE_GUARD { if (mount) { fs::Unmount(root->mount_name); } };
|
||||
|
||||
/* Ensure the root path exists. */
|
||||
bool has_dir = false;
|
||||
R_TRY(impl::HasDirectory(&has_dir, root->path));
|
||||
R_UNLESS(has_dir, ncm::ResultInvalidContentMetaDatabase());
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
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. */
|
||||
if (!root->content_storage) {
|
||||
R_TRY(this->ActivateContentStorage(storage_id));
|
||||
}
|
||||
}
|
||||
|
||||
out.SetValue(std::shared_ptr<IContentStorage>(root->content_storage));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
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. */
|
||||
if (!root->content_meta_database) {
|
||||
R_TRY(this->ActivateContentMetaDatabase(storage_id));
|
||||
}
|
||||
}
|
||||
|
||||
out.SetValue(std::shared_ptr<IContentMetaDatabase>(root->content_meta_database));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::CloseContentStorageForcibly(StorageId storage_id) {
|
||||
return this->InactivateContentStorage(storage_id);
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::CloseContentMetaDatabaseForcibly(StorageId storage_id) {
|
||||
return this->InactivateContentMetaDatabase(storage_id);
|
||||
}
|
||||
|
||||
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. */
|
||||
return fs::DeleteSaveData(root->info.space_id, root->info.id);
|
||||
}
|
||||
|
||||
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_SUCCEED_IF(root->content_storage != nullptr);
|
||||
|
||||
/* Mount based on the storage type. */
|
||||
if (storage_id == StorageId::GameCard) {
|
||||
fs::GameCardHandle handle;
|
||||
R_TRY(fs::GetGameCardHandle(std::addressof(handle)));
|
||||
R_TRY(fs::MountGameCardPartition(root->mount_name, handle, fs::GameCardPartition::Secure));
|
||||
} else {
|
||||
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)));
|
||||
break;
|
||||
case StorageId::SdCard:
|
||||
R_TRY(content_storage->Initialize(root->path, MakeSha256HierarchicalContentFilePath_ForFat16KCluster, MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster, true, std::addressof(this->rights_id_cache)));
|
||||
break;
|
||||
default:
|
||||
R_TRY(content_storage->Initialize(root->path, MakeSha256HierarchicalContentFilePath_ForFat16KCluster, MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster, false, std::addressof(this->rights_id_cache)));
|
||||
break;
|
||||
}
|
||||
|
||||
root->content_storage = std::move(content_storage);
|
||||
}
|
||||
|
||||
/* Prevent unmounting. */
|
||||
mount_guard.Cancel();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
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();
|
||||
root->content_storage = nullptr;
|
||||
fs::Unmount(root->mount_name);
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
/* Already activated. */
|
||||
R_SUCCEED_IF(root->content_meta_database != nullptr);
|
||||
|
||||
/* Make a new kvs. */
|
||||
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();
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::InvalidateRightsIdCache() {
|
||||
this->rights_id_cache.Invalidate();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
101
libraries/libstratosphere/source/ncm/ncm_content_meta.cpp
Normal file
101
libraries/libstratosphere/source/ncm/ncm_content_meta.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
namespace {
|
||||
|
||||
void ConvertPackageContentMetaHeaderToContentMetaHeader(ContentMetaHeader *dst, const PackagedContentMetaHeader &src) {
|
||||
/* Clear destination. */
|
||||
*dst = {};
|
||||
|
||||
/* Set converted fields. */
|
||||
dst->extended_header_size = src.extended_header_size;
|
||||
dst->content_meta_count = src.content_meta_count;
|
||||
dst->content_count = src.content_meta_count;
|
||||
dst->attributes = src.attributes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
size_t PackagedContentMetaReader::CountDeltaFragments() const {
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i < this->GetContentCount(); i++) {
|
||||
if (this->GetContentInfo(i)->GetType() == ContentType::DeltaFragment) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t PackagedContentMetaReader::CalculateConvertContentMetaSize() const {
|
||||
const auto *header = this->GetHeader();
|
||||
return this->CalculateSizeImpl<ContentMetaHeader, ContentInfo>(header->extended_header_size, header->content_count + 1, header->content_meta_count, 0, false);
|
||||
}
|
||||
|
||||
void PackagedContentMetaReader::ConvertToContentMeta(void *dst, size_t size, const ContentInfo &meta) {
|
||||
/* Ensure we have enough space to convert. */
|
||||
AMS_ABORT_UNLESS(size >= this->CalculateConvertContentMetaSize());
|
||||
|
||||
/* Prepare for conversion. */
|
||||
const auto *packaged_header = this->GetHeader();
|
||||
uintptr_t dst_addr = reinterpret_cast<uintptr_t>(dst);
|
||||
|
||||
/* Convert the header. */
|
||||
ContentMetaHeader header;
|
||||
ConvertPackageContentMetaHeaderToContentMetaHeader(std::addressof(header), *packaged_header);
|
||||
header.content_count += 1;
|
||||
|
||||
/* Don't include deltas. */
|
||||
if (packaged_header->type == ContentMetaType::Patch) {
|
||||
header.content_count -= this->CountDeltaFragments();
|
||||
}
|
||||
|
||||
/* Copy the header. */
|
||||
std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(header), sizeof(header));
|
||||
dst_addr += sizeof(header);
|
||||
|
||||
/* Copy the extended header. */
|
||||
std::memcpy(reinterpret_cast<void *>(dst_addr), reinterpret_cast<void *>(this->GetExtendedHeaderAddress()), packaged_header->extended_header_size);
|
||||
dst_addr += packaged_header->extended_header_size;
|
||||
|
||||
/* Copy the top level meta. */
|
||||
std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(meta), sizeof(meta));
|
||||
dst_addr += sizeof(meta);
|
||||
|
||||
/* Copy content infos. */
|
||||
for (size_t i = 0; i < this->GetContentCount(); i++) {
|
||||
/* Don't copy any delta fragments. */
|
||||
if (packaged_header->type == ContentMetaType::Patch) {
|
||||
if (this->GetContentInfo(i)->GetType() == ContentType::DeltaFragment) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy the current info. */
|
||||
std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(this->GetContentInfo(i)->info), sizeof(ContentInfo));
|
||||
dst_addr += sizeof(ContentInfo);
|
||||
}
|
||||
|
||||
/* Copy content meta infos. */
|
||||
for (size_t i = 0; i < this->GetContentMetaCount(); i++) {
|
||||
std::memcpy(reinterpret_cast<void *>(dst_addr), this->GetContentMetaInfo(i), sizeof(ContentMetaInfo));
|
||||
dst_addr += sizeof(ContentMetaInfo);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,443 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_content_meta_database_impl.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
Result ContentMetaDatabaseImpl::GetContentIdImpl(ContentId *out, const ContentMetaKey& key, ContentType type, std::optional<u8> id_offset) const {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Find the meta key. */
|
||||
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());
|
||||
|
||||
const auto found_key = it->GetKey();
|
||||
|
||||
/* Create a reader for this content meta. */
|
||||
const void *meta;
|
||||
size_t meta_size;
|
||||
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, found_key));
|
||||
|
||||
ContentMetaReader reader(meta, meta_size);
|
||||
|
||||
/* Find the content info. */
|
||||
const ContentInfo *content_info = nullptr;
|
||||
if (id_offset) {
|
||||
content_info = reader.GetContentInfo(type, *id_offset);
|
||||
} else {
|
||||
content_info = reader.GetContentInfo(type);
|
||||
}
|
||||
R_UNLESS(content_info != nullptr, ncm::ResultContentNotFound());
|
||||
|
||||
/* Save output. */
|
||||
*out = content_info->content_id;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::Set(const ContentMetaKey &key, sf::InBuffer value) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
return this->kvs->Set(key, value.GetPointer(), value.GetSize());
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::Get(sf::Out<u64> out_size, const ContentMetaKey &key, sf::OutBuffer out_value) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Get the entry from our key-value store. */
|
||||
size_t size;
|
||||
R_TRY_CATCH(this->kvs->Get(std::addressof(size), out_value.GetPointer(), out_value.GetSize(), key)) {
|
||||
R_CONVERT(kvdb::ResultKeyNotFound, ncm::ResultContentMetaNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
out_size.SetValue(size);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::Remove(const ContentMetaKey &key) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
R_TRY_CATCH(this->kvs->Remove(key)) {
|
||||
R_CONVERT(kvdb::ResultKeyNotFound, ncm::ResultContentMetaNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::GetContentIdByType(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type) {
|
||||
return this->GetContentIdImpl(out_content_id.GetPointer(), key, type, std::nullopt);
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::ListContentInfo(sf::Out<s32> out_count, const sf::OutArray<ContentInfo> &out_info, const ContentMetaKey &key, s32 offset) {
|
||||
R_UNLESS(offset >= 0, 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);
|
||||
}
|
||||
|
||||
out_count.SetValue(count);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::List(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType meta_type, ApplicationId application_id, u64 min, u64 max, ContentInstallType install_type) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
size_t entries_total = 0;
|
||||
size_t entries_written = 0;
|
||||
|
||||
/* Iterate over all entries. */
|
||||
for (auto &entry : *this->kvs) {
|
||||
const ContentMetaKey key = entry.GetKey();
|
||||
|
||||
/* Check if this entry matches the given filters. */
|
||||
if (!((meta_type == ContentMetaType::Unknown || key.type == meta_type) && (min <= key.id && key.id <= max) && (install_type == ContentInstallType::Unknown || key.install_type == install_type))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If application id is present, check if it matches the filter. */
|
||||
if (application_id != InvalidApplicationId) {
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write the entry to the output buffer. */
|
||||
if (entries_written < out_info.GetSize()) {
|
||||
out_info[entries_written++] = key;
|
||||
}
|
||||
entries_total++;
|
||||
}
|
||||
|
||||
out_entries_total.SetValue(entries_total);
|
||||
out_entries_written.SetValue(entries_written);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, u64 id) {
|
||||
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::MakeUnknownType(id, 0)); 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.SetValue(*found_key);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::ListApplication(sf::Out<s32> out_entries_total, sf::Out<s32> 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;
|
||||
|
||||
/* Iterate over all entries. */
|
||||
for (auto &entry : *this->kvs) {
|
||||
const ContentMetaKey key = entry.GetKey();
|
||||
|
||||
/* Check if this entry matches the given filters. */
|
||||
if (!(type == ContentMetaType::Unknown || key.type == type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 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()) {
|
||||
out_keys[entries_written++] = { key, *entry_application_id };
|
||||
}
|
||||
entries_total++;
|
||||
}
|
||||
}
|
||||
|
||||
out_entries_total.SetValue(entries_total);
|
||||
out_entries_written.SetValue(entries_written);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::Has(sf::Out<bool> out, const ContentMetaKey &key) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
*out = false;
|
||||
|
||||
/* Check if key is present. */
|
||||
size_t size;
|
||||
R_TRY_CATCH(this->kvs->GetValueSize(&size, key)) {
|
||||
R_CONVERT(kvdb::ResultKeyNotFound, ResultSuccess());
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
*out = true;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::HasAll(sf::Out<bool> out, const sf::InArray<ContentMetaKey> &keys) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
*out = false;
|
||||
|
||||
/* Check if keys are present. */
|
||||
for (size_t i = 0; i < keys.GetSize(); i++) {
|
||||
/* Check if we have the current key. */
|
||||
bool has;
|
||||
R_TRY(this->Has(std::addressof(has), keys[i]));
|
||||
|
||||
/* If we don't, then we can early return because we don't have all. */
|
||||
R_SUCCEED_IF(!has);
|
||||
}
|
||||
|
||||
*out = true;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
out_size.SetValue(size);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::GetRequiredSystemVersion(sf::Out<u32> out_version, const ContentMetaKey &key) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Only applications and patches have a required system version. */
|
||||
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. */
|
||||
switch (key.type) {
|
||||
case ContentMetaType::Application:
|
||||
out_version.SetValue(reader.GetExtendedHeader<ApplicationMetaExtendedHeader>()->required_system_version);
|
||||
break;
|
||||
case ContentMetaType::Patch:
|
||||
out_version.SetValue(reader.GetExtendedHeader<PatchMetaExtendedHeader>()->required_system_version);
|
||||
break;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::GetPatchId(sf::Out<PatchId> out_patch_id, const ContentMetaKey &key) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Only applications can have patches. */
|
||||
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();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::DisableForcibly() {
|
||||
this->disabled = true;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
R_UNLESS(out_orphaned.GetSize() >= content_ids.GetSize(), ncm::ResultBufferInsufficient());
|
||||
|
||||
/* Default to orphaned for all content ids. */
|
||||
for (size_t i = 0; i < out_orphaned.GetSize(); i++) {
|
||||
out_orphaned[i] = true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: C++20 (gcc 10) fixes ALWAYS_INLINE_LAMBDA in conjunction with trailing return types. */
|
||||
/* This should be changed to std::nullopt once possible. */
|
||||
return std::optional<size_t>(std::nullopt);
|
||||
};
|
||||
|
||||
/* Iterate over all entries. */
|
||||
for (auto &entry : *this->kvs) {
|
||||
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 < reader.GetContentCount(); i++) {
|
||||
if (auto found = IsOrphanedContent(content_ids, reader.GetContentInfo(i)->GetId()); found) {
|
||||
out_orphaned[*found] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/* Optimistically suppose that we will find the content. */
|
||||
out.SetValue(true);
|
||||
|
||||
/* Check if any content infos contain a matching id. */
|
||||
for (size_t i = 0; i < reader.GetContentCount(); i++) {
|
||||
R_SUCCEED_IF(content_id == reader.GetContentInfo(i)->GetId());
|
||||
}
|
||||
|
||||
/* We didn't find a content info. */
|
||||
out.SetValue(false);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::ListContentMetaInfo(sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaInfo> &out_meta_info, const ContentMetaKey &key, s32 offset) {
|
||||
R_UNLESS(offset >= 0, 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);
|
||||
}
|
||||
|
||||
/* Set the ouput value. */
|
||||
out_entries_written.SetValue(count);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/* Set the ouput value. */
|
||||
out_attributes.SetValue(reader.GetHeader()->attributes);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
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. */
|
||||
u32 required_version;
|
||||
switch (key.type) {
|
||||
case ContentMetaType::AddOnContent:
|
||||
required_version = reader.GetExtendedHeader<AddOnContentMetaExtendedHeader>()->required_application_version;
|
||||
break;
|
||||
case ContentMetaType::Application:
|
||||
/* As of 9.0.0, applications can be dependent on a specific base application version. */
|
||||
AMS_ABORT_UNLESS(hos::GetVersion() >= hos::Version_900);
|
||||
required_version = reader.GetExtendedHeader<ApplicationMetaExtendedHeader>()->required_application_version;
|
||||
break;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
|
||||
/* Set the ouput value. */
|
||||
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->GetContentIdImpl(out_content_id.GetPointer(), key, type, std::make_optional(id_offset));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_content_meta_database_impl_base.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class ContentMetaDatabaseImpl : public ContentMetaDatabaseImplBase {
|
||||
public:
|
||||
ContentMetaDatabaseImpl(ContentMetaKeyValueStore *kvs, const char *mount_name) : ContentMetaDatabaseImplBase(kvs, mount_name) { /* ... */ }
|
||||
ContentMetaDatabaseImpl(ContentMetaKeyValueStore *kvs) : ContentMetaDatabaseImplBase(kvs) { /* ... */ }
|
||||
private:
|
||||
/* Helpers. */
|
||||
Result GetContentIdImpl(ContentId *out, const ContentMetaKey& key, ContentType type, std::optional<u8> id_offset) const;
|
||||
public:
|
||||
/* Actual commands. */
|
||||
virtual Result Set(const ContentMetaKey &key, sf::InBuffer value) override;
|
||||
virtual Result Get(sf::Out<u64> out_size, const ContentMetaKey &key, sf::OutBuffer out_value) override;
|
||||
virtual Result Remove(const ContentMetaKey &key) override;
|
||||
virtual Result GetContentIdByType(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type) override;
|
||||
virtual Result ListContentInfo(sf::Out<s32> out_entries_written, const sf::OutArray<ContentInfo> &out_info, const ContentMetaKey &key, s32 offset) override;
|
||||
virtual Result List(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType meta_type, ApplicationId application_id, u64 min, u64 max, ContentInstallType install_type) override;
|
||||
virtual Result GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, u64 id) override;
|
||||
virtual Result ListApplication(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ApplicationContentMetaKey> &out_keys, ContentMetaType meta_type) override;
|
||||
virtual Result Has(sf::Out<bool> out, const ContentMetaKey &key) override;
|
||||
virtual Result HasAll(sf::Out<bool> out, const sf::InArray<ContentMetaKey> &keys) override;
|
||||
virtual Result GetSize(sf::Out<u64> out_size, const ContentMetaKey &key) override;
|
||||
virtual Result GetRequiredSystemVersion(sf::Out<u32> out_version, const ContentMetaKey &key) override;
|
||||
virtual Result GetPatchId(sf::Out<PatchId> out_patch_id, const ContentMetaKey &key) override;
|
||||
virtual Result DisableForcibly() override;
|
||||
virtual Result LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) override;
|
||||
virtual Result Commit() override;
|
||||
virtual Result HasContent(sf::Out<bool> out, const ContentMetaKey &key, const ContentId &content_id) override;
|
||||
virtual Result ListContentMetaInfo(sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaInfo> &out_meta_info, const ContentMetaKey &key, s32 offset) override;
|
||||
virtual Result GetAttributes(sf::Out<u8> 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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class ContentMetaDatabaseImplBase : public IContentMetaDatabase {
|
||||
NON_COPYABLE(ContentMetaDatabaseImplBase);
|
||||
NON_MOVEABLE(ContentMetaDatabaseImplBase);
|
||||
protected:
|
||||
using ContentMetaKeyValueStore = ams::kvdb::MemoryKeyValueStore<ContentMetaKey>;
|
||||
protected:
|
||||
ContentMetaKeyValueStore *kvs;
|
||||
char mount_name[fs::MountNameLengthMax + 1];
|
||||
bool disabled;
|
||||
protected:
|
||||
ContentMetaDatabaseImplBase(ContentMetaKeyValueStore *kvs) : kvs(kvs), disabled(false) { /* ... */ }
|
||||
|
||||
ContentMetaDatabaseImplBase(ContentMetaKeyValueStore *kvs, const char *mount_name) : ContentMetaDatabaseImplBase(kvs) {
|
||||
std::strcpy(this->mount_name, mount_name);
|
||||
}
|
||||
protected:
|
||||
/* Helpers. */
|
||||
Result EnsureEnabled() const {
|
||||
R_UNLESS(!this->disabled, ncm::ResultInvalidContentMetaDatabase());
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
const char *GetContentMetaTypeString(ContentMetaType type) {
|
||||
switch (type) {
|
||||
case ContentMetaType::Application: return "Application";
|
||||
case ContentMetaType::Patch: return "Patch";
|
||||
case ContentMetaType::AddOnContent: return "AddOnContent";
|
||||
default: return "(unknown)";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_fs_utils.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
namespace {
|
||||
|
||||
using FilePathString = kvdb::BoundedString<64>;
|
||||
|
||||
bool IsContentMetaFileName(const char *name) {
|
||||
return impl::PathView(name).HasSuffix(".cnmt");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result ReadContentMetaPath(AutoBuffer *out, const char *path) {
|
||||
/* Mount the content. */
|
||||
auto mount_name = impl::CreateUniqueMountName();
|
||||
R_TRY(fs::MountContent(mount_name.str, path, fs::ContentType_Meta));
|
||||
ON_SCOPE_EXIT { fs::Unmount(mount_name.str); };
|
||||
|
||||
/* Open the root directory. */
|
||||
auto root_path = impl::GetRootDirectoryPath(mount_name);
|
||||
fs::DirectoryHandle dir;
|
||||
R_TRY(fs::OpenDirectory(std::addressof(dir), root_path.str, fs::OpenDirectoryMode_File));
|
||||
ON_SCOPE_EXIT { fs::CloseDirectory(dir); };
|
||||
|
||||
/* Loop directory reading until we find the entry we're looking for. */
|
||||
while (true) {
|
||||
/* Read one entry, and finish when we fail to read. */
|
||||
fs::DirectoryEntry entry;
|
||||
s64 num_read;
|
||||
R_TRY(fs::ReadDirectory(std::addressof(num_read), std::addressof(entry), dir, 1));
|
||||
if (num_read == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* If this is the content meta file, parse it. */
|
||||
if (IsContentMetaFileName(entry.name)) {
|
||||
/* Create the file path. */
|
||||
FilePathString file_path(root_path.str);
|
||||
file_path.Append(entry.name);
|
||||
|
||||
/* Open the content meta file. */
|
||||
fs::FileHandle file;
|
||||
R_TRY(fs::OpenFile(std::addressof(file), file_path, fs::OpenMode_Read));
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
/* Get the meta size. */
|
||||
s64 file_size;
|
||||
R_TRY(fs::GetFileSize(std::addressof(file_size), file));
|
||||
const size_t meta_size = static_cast<size_t>(file_size);
|
||||
|
||||
/* Create a buffer for the meta. */
|
||||
R_TRY(out->Initialize(meta_size));
|
||||
|
||||
/* Read the meta into the buffer. */
|
||||
return fs::ReadFile(file, 0, out->Get(), meta_size);
|
||||
}
|
||||
}
|
||||
|
||||
return ncm::ResultContentMetaNotFound();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,760 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_content_storage_impl.hpp"
|
||||
#include "ncm_fs_utils.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr inline const char * const BaseContentDirectory = "/registered";
|
||||
|
||||
void MakeBaseContentDirectoryPath(PathString *out, const char *root_path) {
|
||||
out->SetFormat("%s%s", root_path, BaseContentDirectory);
|
||||
}
|
||||
|
||||
void MakeContentPath(PathString *out, ContentId id, MakeContentPathFunction func, const char *root_path) {
|
||||
PathString path;
|
||||
MakeBaseContentDirectoryPath(std::addressof(path), root_path);
|
||||
func(out, id, path);
|
||||
}
|
||||
|
||||
Result EnsureContentDirectory(ContentId id, MakeContentPathFunction func, const char *root_path) {
|
||||
PathString path;
|
||||
MakeContentPath(std::addressof(path), id, func, root_path);
|
||||
return impl::EnsureParentDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
Result TraverseDirectory(bool *out_should_continue, const char *root_path, int max_level, F f) {
|
||||
/* If the level is zero, we're done. */
|
||||
R_SUCCEED_IF(max_level <= 0);
|
||||
|
||||
/* On 1.0.0, NotRequireFileSize was not a valid open mode. */
|
||||
const auto open_dir_mode = hos::GetVersion() >= hos::Version_200 ? (fs::OpenDirectoryMode_All | fs::OpenDirectoryMode_NotRequireFileSize) : (fs::OpenDirectoryMode_All);
|
||||
|
||||
/* 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, open_dir_mode));
|
||||
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));
|
||||
|
||||
/* If the provided function wishes to terminate immediately, we should respect it. */
|
||||
if (!should_continue) {
|
||||
*out_should_continue = false;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* Mark for retry. */
|
||||
if (should_retry_dir_read) {
|
||||
retry_dir_read = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/* If the entry is a directory, recurse. */
|
||||
if (entry.type == fs::DirectoryEntryType_Directory) {
|
||||
R_TRY(TraverseDirectory(std::addressof(should_continue), current_path, max_level - 1, f));
|
||||
|
||||
if (!should_continue) {
|
||||
*out_should_continue = false;
|
||||
return ResultSuccess();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
|
||||
template<typename F>
|
||||
Result TraverseDirectory(const char *root_path, int max_level, F f) {
|
||||
bool should_continue = false;
|
||||
return TraverseDirectory(std::addressof(should_continue), root_path, max_level, f);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsPlaceHolderPath(const char *path) {
|
||||
return IsContentPath(path);
|
||||
}
|
||||
|
||||
Result CleanDirectoryRecursively(const PathString &path) {
|
||||
if (hos::GetVersion() >= hos::Version_300) {
|
||||
R_TRY(fs::CleanDirectoryRecursively(path));
|
||||
} else {
|
||||
/* CleanDirectoryRecursively didn't exist on < 3.0.0, so we will polyfill it. */
|
||||
/* We'll delete the directory, then recreate it. */
|
||||
R_TRY(fs::DeleteDirectoryRecursively(path));
|
||||
R_TRY(fs::CreateDirectory(path));
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ContentStorageImpl::~ContentStorageImpl() {
|
||||
this->InvalidateFileCache();
|
||||
}
|
||||
|
||||
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);
|
||||
return impl::EnsureDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
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);
|
||||
return CleanDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
void ContentStorageImpl::InvalidateFileCache() {
|
||||
if (this->cached_content_id != InvalidContentId) {
|
||||
fs::CloseFile(this->cached_file_handle);
|
||||
this->cached_content_id = InvalidContentId;
|
||||
}
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::OpenContentIdFile(ContentId content_id) {
|
||||
/* If the file is the currently cached one, we've nothing to do. */
|
||||
R_SUCCEED_IF(this->cached_content_id == content_id);
|
||||
|
||||
/* 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;
|
||||
|
||||
this->cached_content_id = content_id;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
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);
|
||||
this->rights_id_cache = rights_id_cache;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
out.SetValue({util::GenerateUuid()});
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, s64 size) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
R_TRY(EnsureContentDirectory(content_id, this->make_content_path_func, this->root_path));
|
||||
return this->placeholder_accessor.CreatePlaceHolderFile(placeholder_id, size);
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::DeletePlaceHolder(PlaceHolderId placeholder_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
return this->placeholder_accessor.DeletePlaceHolderFile(placeholder_id);
|
||||
}
|
||||
|
||||
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);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, sf::InBuffer data) {
|
||||
/* Ensure offset is valid. */
|
||||
R_UNLESS(offset >= 0, ncm::ResultInvalidOffset());
|
||||
R_TRY(this->EnsureEnabled());
|
||||
return this->placeholder_accessor.WritePlaceHolderFile(placeholder_id, offset, data.GetPointer(), data.GetSize());
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::Register(PlaceHolderId placeholder_id, ContentId content_id) {
|
||||
this->InvalidateFileCache();
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Create the placeholder path. */
|
||||
PathString placeholder_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())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
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);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
out.SetValue(common_path);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
out.SetValue(common_path);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
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<s32> 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));
|
||||
|
||||
out_buf[entry_count++] = placeholder_id;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}));
|
||||
|
||||
out_count.SetValue(static_cast<s32>(entry_count));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::GetContentCount(sf::Out<s32> 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++;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}));
|
||||
|
||||
out_count.SetValue(static_cast<s32>(count));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::ListContentId(sf::Out<s32> out_count, const sf::OutArray<ContentId> &out_buf, s32 offset) {
|
||||
R_UNLESS(offset >= 0, 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;
|
||||
|
||||
/* We have nothing to do if not working with a file. */
|
||||
if (entry.type != fs::DirectoryEntryType_File) {
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* Skip entries until we reach the start offset. */
|
||||
if (offset > 0) {
|
||||
--offset;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* We don't necessarily expect to be able to completely fill the output buffer. */
|
||||
if (entry_count >= out_buf.GetSize()) {
|
||||
*should_continue = false;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
auto content_id = GetContentIdFromString(entry.name, std::strlen(entry.name));
|
||||
if (content_id) {
|
||||
out_buf[entry_count++] = *content_id;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}));
|
||||
|
||||
out_count.SetValue(static_cast<s32>(entry_count));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::GetSizeFromContentId(sf::Out<s64> 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));
|
||||
|
||||
out_size.SetValue(file_size);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::DisableForcibly() {
|
||||
this->disabled = true;
|
||||
this->InvalidateFileCache();
|
||||
this->placeholder_accessor.InvalidateAll();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
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;
|
||||
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())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
return this->placeholder_accessor.SetPlaceHolderFileSize(placeholder_id, size);
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::ReadContentIdFile(sf::OutBuffer buf, ContentId content_id, s64 offset) {
|
||||
/* Ensure offset is valid. */
|
||||
R_UNLESS(offset >= 0, 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));
|
||||
|
||||
/* 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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::WriteContentForDebug(ContentId content_id, s64 offset, sf::InBuffer data) {
|
||||
/* Ensure offset is valid. */
|
||||
R_UNLESS(offset >= 0, 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. */
|
||||
return fs::WriteFile(file, offset, data.GetPointer(), data.GetSize(), fs::WriteOption::Flush);
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::GetFreeSpaceSize(sf::Out<s64> out_size) {
|
||||
return fs::GetFreeSpaceSize(out_size.GetPointer(), this->root_path);
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::GetTotalSpaceSize(sf::Out<s64> out_size) {
|
||||
return fs::GetTotalSpaceSize(out_size.GetPointer(), this->root_path);
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::FlushPlaceHolder() {
|
||||
this->placeholder_accessor.InvalidateAll();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::GetSizeFromPlaceHolderId(sf::Out<s64> 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);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::RepairInvalidFileAttribute() {
|
||||
/* Callback for TraverseDirectory */
|
||||
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;
|
||||
|
||||
if (entry.type == fs::DirectoryEntryType_Directory) {
|
||||
if (path_checker(current_path)) {
|
||||
if (R_SUCCEEDED(fs::SetConcatenationFileAttribute(current_path))) {
|
||||
*should_retry_dir_read = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
};
|
||||
|
||||
/* Fix content. */
|
||||
{
|
||||
path_checker = IsContentPath;
|
||||
PathString path;
|
||||
MakeBaseContentDirectoryPath(std::addressof(path), this->root_path);
|
||||
|
||||
R_TRY(TraverseDirectory(path, GetHierarchicalContentDirectoryDepth(this->make_content_path_func), fix_file_attributes));
|
||||
}
|
||||
|
||||
/* Fix placeholders. */
|
||||
this->placeholder_accessor.InvalidateAll();
|
||||
{
|
||||
path_checker = IsPlaceHolderPath;
|
||||
PathString path;
|
||||
PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), this->root_path);
|
||||
|
||||
R_TRY(TraverseDirectory(path, GetHierarchicalContentDirectoryDepth(this->make_content_path_func), fix_file_attributes));
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/* Set output. */
|
||||
out_rights_id.SetValue(rights_id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "ncm_content_storage_impl_base.hpp"
|
||||
#include "ncm_placeholder_accessor.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class ContentStorageImpl : public ContentStorageImplBase {
|
||||
protected:
|
||||
PlaceHolderAccessor placeholder_accessor;
|
||||
ContentId cached_content_id;
|
||||
fs::FileHandle cached_file_handle;
|
||||
RightsIdCache *rights_id_cache;
|
||||
public:
|
||||
static Result InitializeBase(const char *root_path);
|
||||
static Result CleanupBase(const char *root_path);
|
||||
static Result VerifyBase(const char *root_path);
|
||||
public:
|
||||
~ContentStorageImpl();
|
||||
|
||||
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, s64 size) override;
|
||||
virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id) override;
|
||||
virtual Result HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) override;
|
||||
virtual Result WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, sf::InBuffer data) override;
|
||||
virtual Result Register(PlaceHolderId placeholder_id, ContentId content_id) override;
|
||||
virtual Result Delete(ContentId content_id) override;
|
||||
virtual Result Has(sf::Out<bool> out, ContentId content_id) override;
|
||||
virtual Result GetPath(sf::Out<Path> out, ContentId content_id) override;
|
||||
virtual Result GetPlaceHolderPath(sf::Out<Path> out, PlaceHolderId placeholder_id) override;
|
||||
virtual Result CleanupAllPlaceHolder() override;
|
||||
virtual Result ListPlaceHolder(sf::Out<s32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) override;
|
||||
virtual Result GetContentCount(sf::Out<s32> out_count) override;
|
||||
virtual Result ListContentId(sf::Out<s32> out_count, const sf::OutArray<ContentId> &out_buf, s32 start_offset) override;
|
||||
virtual Result GetSizeFromContentId(sf::Out<s64> out_size, ContentId content_id) override;
|
||||
virtual Result DisableForcibly() override;
|
||||
virtual Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) override;
|
||||
virtual Result SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size) override;
|
||||
virtual Result ReadContentIdFile(sf::OutBuffer buf, ContentId content_id, s64 offset) override;
|
||||
virtual Result GetRightsIdFromPlaceHolderIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, PlaceHolderId placeholder_id) override;
|
||||
virtual Result GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id) override;
|
||||
virtual Result GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id) override;
|
||||
virtual Result GetRightsIdFromContentId(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id) override;
|
||||
virtual Result WriteContentForDebug(ContentId content_id, s64 offset, sf::InBuffer data) override;
|
||||
virtual Result GetFreeSpaceSize(sf::Out<s64> out_size) override;
|
||||
virtual Result GetTotalSpaceSize(sf::Out<s64> out_size) override;
|
||||
virtual Result FlushPlaceHolder() override;
|
||||
virtual Result GetSizeFromPlaceHolderId(sf::Out<s64> out, PlaceHolderId placeholder_id) override;
|
||||
virtual Result RepairInvalidFileAttribute() override;
|
||||
virtual Result GetRightsIdFromPlaceHolderIdWithCache(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id) override;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class ContentStorageImplBase : public IContentStorage {
|
||||
NON_COPYABLE(ContentStorageImplBase);
|
||||
NON_MOVEABLE(ContentStorageImplBase);
|
||||
protected:
|
||||
PathString root_path;
|
||||
MakeContentPathFunction make_content_path_func;
|
||||
bool disabled;
|
||||
protected:
|
||||
ContentStorageImplBase() { /* ... */ }
|
||||
protected:
|
||||
/* Helpers. */
|
||||
Result EnsureEnabled() const {
|
||||
R_UNLESS(!this->disabled, ncm::ResultInvalidContentStorage());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
static Result GetRightsId(ncm::RightsId *out_rights_id, const Path &path) {
|
||||
if (hos::GetVersion() >= hos::Version_300) {
|
||||
R_TRY(fs::GetRightsId(std::addressof(out_rights_id->id), std::addressof(out_rights_id->key_generation), path.str));
|
||||
} else {
|
||||
R_TRY(fs::GetRightsId(std::addressof(out_rights_id->id), path.str));
|
||||
out_rights_id->key_generation = 0;
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
158
libraries/libstratosphere/source/ncm/ncm_fs_utils.cpp
Normal file
158
libraries/libstratosphere/source/ncm/ncm_fs_utils.cpp
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_fs_utils.hpp"
|
||||
|
||||
namespace ams::ncm::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
Result EnsureDirectory(const char *path) {
|
||||
/* Create the path, and allow it to already exist. */
|
||||
R_TRY_CATCH(fs::CreateDirectory(path)) {
|
||||
R_CONVERT(fs::ResultPathAlreadyExists, ResultSuccess())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result EnsureDirectoryRecursivelyImpl(const char *path, bool create_last) {
|
||||
char work_buf[fs::EntryNameLengthMax];
|
||||
|
||||
/* Ensure the path is not too long. */
|
||||
const size_t len = std::strlen(path);
|
||||
R_UNLESS(len + 1 <= sizeof(work_buf), ResultAllocationFailed());
|
||||
|
||||
/* Copy in the path. */
|
||||
std::strncpy(work_buf, path, sizeof(work_buf));
|
||||
|
||||
/* Create all but the last directory. */
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (i > 0 && fs::PathTool::IsSeparator(work_buf[i]) && !fs::PathTool::IsDriveSeparator(work_buf[i-1])) {
|
||||
work_buf[i] = fs::StringTraits::NullTerminator;
|
||||
R_TRY(EnsureDirectory(work_buf));
|
||||
work_buf[i] = fs::StringTraits::DirectorySeparator;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create the last directory if requested. */
|
||||
if (create_last) {
|
||||
R_TRY(EnsureDirectory(path));
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result HasEntry(bool *out, const char *path, fs::DirectoryEntryType type) {
|
||||
/* Set out to false initially. */
|
||||
*out = false;
|
||||
|
||||
/* Try to get the entry type. */
|
||||
fs::DirectoryEntryType entry_type;
|
||||
R_TRY_CATCH(fs::GetEntryType(std::addressof(entry_type), path)) {
|
||||
/* If the path doesn't exist, nothing has gone wrong. */
|
||||
R_CONVERT(fs::ResultPathNotFound, ResultSuccess());
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* We succeeded. */
|
||||
*out = entry_type == type;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
std::atomic<u32> g_mount_name_count;
|
||||
|
||||
}
|
||||
|
||||
Result HasFile(bool *out, const char *path) {
|
||||
return HasEntry(out, path, fs::DirectoryEntryType_File);
|
||||
}
|
||||
|
||||
Result HasDirectory(bool *out, const char *path) {
|
||||
return HasEntry(out, path, fs::DirectoryEntryType_Directory);
|
||||
}
|
||||
|
||||
Result EnsureDirectoryRecursively(const char *path) {
|
||||
return EnsureDirectoryRecursivelyImpl(path, true);
|
||||
}
|
||||
|
||||
Result EnsureParentDirectoryRecursively(const char *path) {
|
||||
return EnsureDirectoryRecursivelyImpl(path, false);
|
||||
}
|
||||
|
||||
bool PathView::HasPrefix(std::string_view prefix) const {
|
||||
return this->path.compare(0, prefix.length(), prefix) == 0;
|
||||
}
|
||||
|
||||
bool PathView::HasSuffix(std::string_view suffix) const {
|
||||
return this->path.compare(this->path.length() - suffix.length(), suffix.length(), suffix) == 0;
|
||||
}
|
||||
|
||||
std::string_view PathView::GetFileName() const {
|
||||
auto pos = this->path.find_last_of("/");
|
||||
return pos != std::string_view::npos ? this->path.substr(pos + 1) : this->path;
|
||||
}
|
||||
|
||||
MountName CreateUniqueMountName() {
|
||||
MountName name = {};
|
||||
std::snprintf(name.str, sizeof(name.str), "@ncm%08x", g_mount_name_count.fetch_add(1));
|
||||
return name;
|
||||
}
|
||||
|
||||
RootDirectoryPath GetRootDirectoryPath(const MountName &mount_name) {
|
||||
RootDirectoryPath path = {};
|
||||
std::snprintf(path.str, sizeof(path.str), "%s:/", mount_name.str);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
Result CopyFile(const char *dst_path, const char *src_path) {
|
||||
fs::FileHandle src_file, dst_file;
|
||||
|
||||
/* Open the source file and get its size. */
|
||||
R_TRY(fs::OpenFile(std::addressof(src_file), src_path, fs::OpenMode_Read));
|
||||
ON_SCOPE_EXIT { fs::CloseFile(src_file); };
|
||||
|
||||
s64 file_size;
|
||||
R_TRY(fs::GetFileSize(std::addressof(file_size), src_file));
|
||||
|
||||
/* Create the destination file. */
|
||||
R_TRY(fs::CreateFile(dst_path, file_size));
|
||||
|
||||
/* Open the destination file. */
|
||||
R_TRY(fs::OpenFile(std::addressof(dst_file), dst_path, fs::OpenMode_Write));
|
||||
ON_SCOPE_EXIT { fs::CloseFile(dst_file); };
|
||||
|
||||
/* Allocate a buffer with which to copy. */
|
||||
constexpr size_t BufferSize = 4_KB;
|
||||
AutoBuffer buffer;
|
||||
R_TRY(buffer.Initialize(BufferSize));
|
||||
|
||||
/* Repeatedly read until we've copied all the data. */
|
||||
s64 offset = 0;
|
||||
while (offset < file_size) {
|
||||
const size_t read_size = std::min(static_cast<size_t>(file_size - offset), buffer.GetSize());
|
||||
R_TRY(fs::ReadFile(src_file, offset, buffer.Get(), read_size));
|
||||
R_TRY(fs::WriteFile(dst_file, offset, buffer.Get(), read_size, fs::WriteOption::None));
|
||||
offset += read_size;
|
||||
}
|
||||
|
||||
/* Flush the destination file. */
|
||||
R_TRY(fs::FlushFile(dst_file));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
50
libraries/libstratosphere/source/ncm/ncm_fs_utils.hpp
Normal file
50
libraries/libstratosphere/source/ncm/ncm_fs_utils.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ncm::impl {
|
||||
|
||||
Result HasFile(bool *out, const char *path);
|
||||
Result HasDirectory(bool *out, const char *path);
|
||||
|
||||
Result EnsureDirectoryRecursively(const char *path);
|
||||
Result EnsureParentDirectoryRecursively(const char *path);
|
||||
|
||||
Result CopyFile(const char *dst_path, const char *src_path);
|
||||
|
||||
class PathView {
|
||||
private:
|
||||
std::string_view path; /* Nintendo uses util::string_view here. */
|
||||
public:
|
||||
PathView(std::string_view p) : path(p) { /* ...*/ }
|
||||
bool HasPrefix(std::string_view prefix) const;
|
||||
bool HasSuffix(std::string_view suffix) const;
|
||||
std::string_view GetFileName() const;
|
||||
};
|
||||
|
||||
struct MountName {
|
||||
char str[fs::MountNameLengthMax + 1];
|
||||
};
|
||||
|
||||
struct RootDirectoryPath {
|
||||
char str[fs::MountNameLengthMax + 3]; /* mount name + :/ */
|
||||
};
|
||||
|
||||
MountName CreateUniqueMountName();
|
||||
RootDirectoryPath GetRootDirectoryPath(const MountName &mount_name);
|
||||
|
||||
}
|
||||
151
libraries/libstratosphere/source/ncm/ncm_make_path.cpp
Normal file
151
libraries/libstratosphere/source/ncm/ncm_make_path.cpp
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
namespace {
|
||||
|
||||
void MakeContentName(PathString *out, ContentId id) {
|
||||
out->SetFormat("%s.nca", GetContentIdString(id).data);
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
u16 Get16BitSha256HashPrefix(ContentId id) {
|
||||
u8 hash[crypto::Sha256Generator::HashSize];
|
||||
crypto::GenerateSha256Hash(hash, sizeof(hash), std::addressof(id), sizeof(id));
|
||||
return static_cast<u16>(hash[0]) | (static_cast<u16>(hash[1]) << 8);
|
||||
}
|
||||
|
||||
u8 Get8BitSha256HashPrefix(ContentId id) {
|
||||
u8 hash[crypto::Sha256Generator::HashSize];
|
||||
crypto::GenerateSha256Hash(hash, sizeof(hash), std::addressof(id), sizeof(id));
|
||||
return hash[0];
|
||||
}
|
||||
|
||||
u8 Get8BitSha256HashPrefix(PlaceHolderId id) {
|
||||
u8 hash[crypto::Sha256Generator::HashSize];
|
||||
crypto::GenerateSha256Hash(hash, sizeof(hash), std::addressof(id), sizeof(id));
|
||||
return hash[0];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
size_t GetHierarchicalContentDirectoryDepth(MakeContentPathFunction func) {
|
||||
if (func == MakeFlatContentFilePath) {
|
||||
return 1;
|
||||
} else if (func == MakeSha256HierarchicalContentFilePath_ForFat4KCluster) {
|
||||
return 3;
|
||||
} else if (func == MakeSha256HierarchicalContentFilePath_ForFat16KCluster ||
|
||||
func == MakeSha256HierarchicalContentFilePath_ForFat32KCluster) {
|
||||
return 2;
|
||||
} else {
|
||||
AMS_ABORT();
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
size_t GetHierarchicalPlaceHolderDirectoryDepth(MakePlaceHolderPathFunction func) {
|
||||
if (func == MakeFlatPlaceHolderFilePath) {
|
||||
return 1;
|
||||
} else if (func == MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster) {
|
||||
return 2;
|
||||
} else {
|
||||
AMS_ABORT();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_on_memory_content_meta_database_impl.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
Result OnMemoryContentMetaDatabaseImpl::List(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType meta_type, ApplicationId application_id, u64 min, u64 max, ContentInstallType install_type) {
|
||||
/* NOTE: This function is *almost* identical to the ContentMetaDatabaseImpl equivalent. */
|
||||
/* The only difference is that the min max comparison is exclusive for OnMemoryContentMetaDatabaseImpl, */
|
||||
/* but inclusive for ContentMetaDatabaseImpl. This may or may not be a Nintendo bug? */
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
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++) {
|
||||
const ContentMetaKey key = entry->GetKey();
|
||||
|
||||
/* Check if this entry matches the given filters. */
|
||||
if (!((meta_type == ContentMetaType::Unknown || key.type == meta_type) && (min < key.id && key.id < max) && (install_type == ContentInstallType::Unknown || key.install_type == install_type))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If application id is present, check if it matches the filter. */
|
||||
if (application_id != InvalidApplicationId) {
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write the entry to the output buffer. */
|
||||
if (entries_written < out_info.GetSize()) {
|
||||
out_info[entries_written++] = key;
|
||||
}
|
||||
entries_total++;
|
||||
}
|
||||
|
||||
out_entries_total.SetValue(entries_total);
|
||||
out_entries_written.SetValue(entries_written);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result OnMemoryContentMetaDatabaseImpl::GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, u64 id) {
|
||||
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::MakeUnknownType(id, 0)); entry != this->kvs->end(); entry++) {
|
||||
/* No further entries will match the program id, discontinue. */
|
||||
if (entry->GetKey().id != id) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* On memory content database is interested in all keys. */
|
||||
found_key = entry->GetKey();
|
||||
}
|
||||
|
||||
/* Check if the key is absent. */
|
||||
R_UNLESS(found_key, ncm::ResultContentMetaNotFound());
|
||||
|
||||
out_key.SetValue(*found_key);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result OnMemoryContentMetaDatabaseImpl::LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) {
|
||||
return ResultInvalidContentMetaDatabase();
|
||||
}
|
||||
|
||||
Result OnMemoryContentMetaDatabaseImpl::Commit() {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_content_meta_database_impl.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class OnMemoryContentMetaDatabaseImpl : public ContentMetaDatabaseImpl {
|
||||
public:
|
||||
OnMemoryContentMetaDatabaseImpl(ams::kvdb::MemoryKeyValueStore<ContentMetaKey> *kvs) : ContentMetaDatabaseImpl(kvs) { /* ... */ }
|
||||
public:
|
||||
/* Actual commands. */
|
||||
virtual Result List(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType meta_type, ApplicationId application_id, u64 min, u64 max, ContentInstallType install_type) override;
|
||||
virtual Result GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, u64 id) override;
|
||||
virtual Result LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) override;
|
||||
virtual Result Commit() override;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_placeholder_accessor.hpp"
|
||||
#include "ncm_fs_utils.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr inline const char * const BasePlaceHolderDirectory = "/placehld";
|
||||
|
||||
constexpr inline const char * const PlaceHolderExtension = ".nca";
|
||||
constexpr inline size_t PlaceHolderExtensionLength = 4;
|
||||
|
||||
constexpr inline size_t PlaceHolderFileNameLengthWithoutExtension = 2 * sizeof(PlaceHolderId);
|
||||
constexpr inline size_t PlaceHolderFileNameLength = PlaceHolderFileNameLengthWithoutExtension + PlaceHolderExtensionLength;
|
||||
|
||||
void MakeBasePlaceHolderDirectoryPath(PathString *out, const char *root_path) {
|
||||
out->SetFormat("%s%s", root_path, BasePlaceHolderDirectory);
|
||||
}
|
||||
|
||||
void MakePlaceHolderFilePath(PathString *out, PlaceHolderId id, MakePlaceHolderPathFunction func, const char *root_path) {
|
||||
PathString path;
|
||||
MakeBasePlaceHolderDirectoryPath(std::addressof(path), root_path);
|
||||
func(out, id, path);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE Result ConvertNotFoundResult(Result r) {
|
||||
R_TRY_CATCH(r) {
|
||||
R_CONVERT(ams::fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PlaceHolderAccessor::MakePath(PathString *out_placeholder_path, PlaceHolderId placeholder_id) const {
|
||||
MakePlaceHolderFilePath(out_placeholder_path, placeholder_id, this->make_placeholder_path_func, *this->root_path);
|
||||
}
|
||||
|
||||
void PlaceHolderAccessor::MakeBaseDirectoryPath(PathString *out, const char *root_path) {
|
||||
MakeBasePlaceHolderDirectoryPath(out, root_path);
|
||||
}
|
||||
|
||||
Result PlaceHolderAccessor::EnsurePlaceHolderDirectory(PlaceHolderId placeholder_id) {
|
||||
PathString path;
|
||||
this->MakePath(std::addressof(path), placeholder_id);
|
||||
return impl::EnsureParentDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
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];
|
||||
strlcpy(tmp, name + i * 2, sizeof(tmp));
|
||||
|
||||
char *err = nullptr;
|
||||
reinterpret_cast<u8 *>(std::addressof(placeholder_id))[i] = static_cast<u8>(std::strtoul(tmp, std::addressof(err), 16));
|
||||
R_UNLESS(*err == '\x00', ncm::ResultInvalidPlaceHolderFile());
|
||||
}
|
||||
|
||||
*out = placeholder_id;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result PlaceHolderAccessor::Open(fs::FileHandle *out_handle, PlaceHolderId placeholder_id) {
|
||||
/* Try to load from the cache. */
|
||||
R_SUCCEED_IF(this->LoadFromCache(out_handle, placeholder_id));
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
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;
|
||||
entry->counter = this->cur_counter++;
|
||||
}
|
||||
|
||||
void PlaceHolderAccessor::Invalidate(CacheEntry *entry) {
|
||||
/* Flush and close the cached entry's file. */
|
||||
if (entry != nullptr) {
|
||||
fs::FlushFile(entry->handle);
|
||||
fs::CloseFile(entry->handle);
|
||||
entry->id = InvalidPlaceHolderId;
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PlaceHolderAccessor::CacheEntry *PlaceHolderAccessor::GetFreeEntry() {
|
||||
/* Try to find an already free entry. */
|
||||
for (size_t i = 0; i < MaxCacheEntries; i++) {
|
||||
if (this->caches[i].id == InvalidPlaceHolderId) {
|
||||
return std::addressof(this->caches[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the oldest entry. */
|
||||
CacheEntry *entry = std::addressof(this->caches[0]);
|
||||
for (size_t i = 1; i < MaxCacheEntries; i++) {
|
||||
if (entry->counter < this->caches[i].counter) {
|
||||
entry = std::addressof(this->caches[i]);
|
||||
}
|
||||
}
|
||||
this->Invalidate(entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
void PlaceHolderAccessor::GetPath(PathString *placeholder_path, PlaceHolderId placeholder_id) {
|
||||
{
|
||||
std::scoped_lock lock(this->cache_mutex);
|
||||
this->Invalidate(this->FindInCache(placeholder_id));
|
||||
}
|
||||
this->MakePath(placeholder_path, placeholder_id);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
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); };
|
||||
|
||||
/* 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); };
|
||||
|
||||
/* 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;
|
||||
} else {
|
||||
*found_in_cache = false;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
void PlaceHolderAccessor::InvalidateAll() {
|
||||
/* Invalidate all cache entries. */
|
||||
for (auto &entry : this->caches) {
|
||||
if (entry.id != InvalidPlaceHolderId) {
|
||||
this->Invalidate(std::addressof(entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class PlaceHolderAccessor {
|
||||
private:
|
||||
class CacheEntry {
|
||||
public:
|
||||
PlaceHolderId id;
|
||||
fs::FileHandle handle;
|
||||
u64 counter;
|
||||
};
|
||||
|
||||
static constexpr size_t MaxCacheEntries = 0x2;
|
||||
private:
|
||||
std::array<CacheEntry, MaxCacheEntries> caches;
|
||||
PathString *root_path;
|
||||
u64 cur_counter;
|
||||
os::Mutex cache_mutex;
|
||||
MakePlaceHolderPathFunction make_placeholder_path_func;
|
||||
bool delay_flush;
|
||||
private:
|
||||
Result Open(fs::FileHandle *out_handle, PlaceHolderId placeholder_id);
|
||||
bool LoadFromCache(fs::FileHandle *out_handle, PlaceHolderId placeholder_id);
|
||||
void StoreToCache(PlaceHolderId placeholder_id, fs::FileHandle handle);
|
||||
void Invalidate(CacheEntry *entry);
|
||||
CacheEntry *FindInCache(PlaceHolderId placeholder_id);
|
||||
CacheEntry *GetFreeEntry();;
|
||||
public:
|
||||
PlaceHolderAccessor() : cur_counter(0), delay_flush(false) {
|
||||
for (size_t i = 0; i < MaxCacheEntries; i++) {
|
||||
caches[i].id = InvalidPlaceHolderId;
|
||||
}
|
||||
}
|
||||
|
||||
~PlaceHolderAccessor() { this->InvalidateAll(); }
|
||||
|
||||
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;
|
||||
this->delay_flush = delay_flush;
|
||||
}
|
||||
|
||||
Result CreatePlaceHolderFile(PlaceHolderId placeholder_id, s64 size);
|
||||
Result DeletePlaceHolderFile(PlaceHolderId placeholder_id);
|
||||
Result WritePlaceHolderFile(PlaceHolderId placeholder_id, s64 offset, const void *buffer, size_t size);
|
||||
Result SetPlaceHolderFileSize(PlaceHolderId placeholder_id, s64 size);
|
||||
Result TryGetPlaceHolderFileSize(bool *out_found, s64 *out_size, PlaceHolderId placeholder_id);
|
||||
|
||||
void GetPath(PathString *out_placeholder_path, PlaceHolderId placeholder_id);
|
||||
void MakePath(PathString *out_placeholder_path, PlaceHolderId placeholder_id) const;
|
||||
|
||||
void InvalidateAll();
|
||||
|
||||
Result EnsurePlaceHolderDirectory(PlaceHolderId placeholder_id);
|
||||
size_t GetHierarchicalDirectoryDepth() const { return GetHierarchicalPlaceHolderDirectoryDepth(this->make_placeholder_path_func); }
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_read_only_content_storage_impl.hpp"
|
||||
#include "ncm_fs_utils.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
namespace {
|
||||
|
||||
void MakeContentPath(PathString *out, ContentId id, MakeContentPathFunction func, const char *root_path) {
|
||||
return func(out, id, root_path);
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
Result OpenContentIdFileImpl(fs::FileHandle *out, ContentId id, MakeContentPathFunction func, const char *root_path) {
|
||||
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);
|
||||
R_TRY(fs::OpenFile(out, path, fs::OpenMode_Read));
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, s64 size) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::DeletePlaceHolder(PlaceHolderId placeholder_id) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, sf::InBuffer data) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::Register(PlaceHolderId placeholder_id, ContentId content_id) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::Delete(ContentId content_id) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
out.SetValue(has);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetPlaceHolderPath(sf::Out<Path> out, PlaceHolderId placeholder_id) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::CleanupAllPlaceHolder() {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::ListPlaceHolder(sf::Out<s32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetContentCount(sf::Out<s32> out_count) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::ListContentId(sf::Out<s32> out_count, const sf::OutArray<ContentId> &out_buf, s32 offset) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetSizeFromContentId(sf::Out<s64> 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));
|
||||
|
||||
out_size.SetValue(file_size);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::DisableForcibly() {
|
||||
this->disabled = true;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::ReadContentIdFile(sf::OutBuffer buf, ContentId content_id, s64 offset) {
|
||||
/* Ensure offset is valid. */
|
||||
R_UNLESS(offset >= 0, 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();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetRightsIdFromPlaceHolderIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, PlaceHolderId placeholder_id) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::WriteContentForDebug(ContentId content_id, s64 offset, sf::InBuffer data) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetFreeSpaceSize(sf::Out<s64> out_size) {
|
||||
out_size.SetValue(0);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetTotalSpaceSize(sf::Out<s64> out_size) {
|
||||
out_size.SetValue(0);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::FlushPlaceHolder() {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetSizeFromPlaceHolderId(sf::Out<s64> out, PlaceHolderId placeholder_id) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::RepairInvalidFileAttribute() {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetRightsIdFromPlaceHolderIdWithCache(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_content_storage_impl_base.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class ReadOnlyContentStorageImpl : public ContentStorageImplBase {
|
||||
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, s64 size) override;
|
||||
virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id) override;
|
||||
virtual Result HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) override;
|
||||
virtual Result WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, sf::InBuffer data) override;
|
||||
virtual Result Register(PlaceHolderId placeholder_id, ContentId content_id) override;
|
||||
virtual Result Delete(ContentId content_id) override;
|
||||
virtual Result Has(sf::Out<bool> out, ContentId content_id) override;
|
||||
virtual Result GetPath(sf::Out<Path> out, ContentId content_id) override;
|
||||
virtual Result GetPlaceHolderPath(sf::Out<Path> out, PlaceHolderId placeholder_id) override;
|
||||
virtual Result CleanupAllPlaceHolder() override;
|
||||
virtual Result ListPlaceHolder(sf::Out<s32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) override;
|
||||
virtual Result GetContentCount(sf::Out<s32> out_count) override;
|
||||
virtual Result ListContentId(sf::Out<s32> out_count, const sf::OutArray<ContentId> &out_buf, s32 start_offset) override;
|
||||
virtual Result GetSizeFromContentId(sf::Out<s64> out_size, ContentId content_id) override;
|
||||
virtual Result DisableForcibly() override;
|
||||
virtual Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) override;
|
||||
virtual Result SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size) override;
|
||||
virtual Result ReadContentIdFile(sf::OutBuffer buf, ContentId content_id, s64 offset) override;
|
||||
virtual Result GetRightsIdFromPlaceHolderIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, PlaceHolderId placeholder_id) override;
|
||||
virtual Result GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id) override;
|
||||
virtual Result GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id) override;
|
||||
virtual Result GetRightsIdFromContentId(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id) override;
|
||||
virtual Result WriteContentForDebug(ContentId content_id, s64 offset, sf::InBuffer data) override;
|
||||
virtual Result GetFreeSpaceSize(sf::Out<s64> out_size) override;
|
||||
virtual Result GetTotalSpaceSize(sf::Out<s64> out_size) override;
|
||||
virtual Result FlushPlaceHolder() override;
|
||||
virtual Result GetSizeFromPlaceHolderId(sf::Out<s64> out, PlaceHolderId placeholder_id) override;
|
||||
virtual Result RepairInvalidFileAttribute() override;
|
||||
virtual Result GetRightsIdFromPlaceHolderIdWithCache(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id) override;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_remote_content_storage_impl.hpp"
|
||||
#include "ncm_remote_content_meta_database_impl.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class RemoteContentManagerImpl final : public IContentManager {
|
||||
public:
|
||||
RemoteContentManagerImpl() { /* ... */ }
|
||||
|
||||
~RemoteContentManagerImpl() { /* ... */ }
|
||||
public:
|
||||
virtual Result CreateContentStorage(StorageId storage_id) override {
|
||||
return ::ncmCreateContentStorage(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result CreateContentMetaDatabase(StorageId storage_id) override {
|
||||
return ::ncmCreateContentMetaDatabase(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result VerifyContentStorage(StorageId storage_id) override {
|
||||
return ::ncmVerifyContentStorage(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result VerifyContentMetaDatabase(StorageId storage_id) override {
|
||||
return ::ncmVerifyContentMetaDatabase(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result OpenContentStorage(sf::Out<std::shared_ptr<IContentStorage>> out, StorageId storage_id) override {
|
||||
NcmContentStorage cs;
|
||||
R_TRY(::ncmOpenContentStorage(std::addressof(cs), static_cast<NcmStorageId>(storage_id)));
|
||||
|
||||
out.SetValue(std::make_shared<RemoteContentStorageImpl>(cs));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
virtual Result OpenContentMetaDatabase(sf::Out<std::shared_ptr<IContentMetaDatabase>> out, StorageId storage_id) override {
|
||||
NcmContentMetaDatabase db;
|
||||
R_TRY(::ncmOpenContentMetaDatabase(std::addressof(db), static_cast<NcmStorageId>(storage_id)));
|
||||
|
||||
out.SetValue(std::make_shared<RemoteContentMetaDatabaseImpl>(db));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
virtual Result CloseContentStorageForcibly(StorageId storage_id) override {
|
||||
return ::ncmCloseContentStorageForcibly(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result CloseContentMetaDatabaseForcibly(StorageId storage_id) override {
|
||||
return ::ncmCloseContentMetaDatabaseForcibly(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result CleanupContentMetaDatabase(StorageId storage_id) override {
|
||||
return ::ncmCleanupContentMetaDatabase(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result ActivateContentStorage(StorageId storage_id) override {
|
||||
return ::ncmActivateContentStorage(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result InactivateContentStorage(StorageId storage_id) override {
|
||||
return ::ncmInactivateContentStorage(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result ActivateContentMetaDatabase(StorageId storage_id) override {
|
||||
return ::ncmActivateContentMetaDatabase(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result InactivateContentMetaDatabase(StorageId storage_id) override {
|
||||
return ::ncmInactivateContentMetaDatabase(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result InvalidateRightsIdCache() override {
|
||||
return ::ncmInvalidateRightsIdCache();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class RemoteContentMetaDatabaseImpl final : public IContentMetaDatabase {
|
||||
private:
|
||||
::NcmContentMetaDatabase srv;
|
||||
public:
|
||||
RemoteContentMetaDatabaseImpl(::NcmContentMetaDatabase &db) : srv(db) { /* ... */ }
|
||||
|
||||
~RemoteContentMetaDatabaseImpl() { ::ncmContentMetaDatabaseClose(std::addressof(srv)); }
|
||||
private:
|
||||
ALWAYS_INLINE ::NcmContentMetaKey *Convert(ContentMetaKey *k) {
|
||||
static_assert(sizeof(ContentMetaKey) == sizeof(::NcmContentMetaKey));
|
||||
return reinterpret_cast<::NcmContentMetaKey *>(k);
|
||||
}
|
||||
ALWAYS_INLINE const ::NcmContentMetaKey *Convert(const ContentMetaKey *k) {
|
||||
static_assert(sizeof(ContentMetaKey) == sizeof(::NcmContentMetaKey));
|
||||
return reinterpret_cast<const ::NcmContentMetaKey *>(k);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE const ::NcmContentMetaKey *Convert(const ContentMetaKey &k) {
|
||||
static_assert(sizeof(ContentMetaKey) == sizeof(::NcmContentMetaKey));
|
||||
return reinterpret_cast<const ::NcmContentMetaKey *>(std::addressof(k));
|
||||
}
|
||||
|
||||
ALWAYS_INLINE ::NcmApplicationContentMetaKey *Convert(ApplicationContentMetaKey *k) {
|
||||
static_assert(sizeof(ApplicationContentMetaKey) == sizeof(::NcmApplicationContentMetaKey));
|
||||
return reinterpret_cast<::NcmApplicationContentMetaKey *>(k);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE ::NcmContentInfo *Convert(ContentInfo *c) {
|
||||
static_assert(sizeof(ContentInfo) == sizeof(::NcmContentInfo));
|
||||
return reinterpret_cast<::NcmContentInfo *>(c);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE ::NcmContentId *Convert(ContentId *c) {
|
||||
static_assert(sizeof(ContentId) == sizeof(::NcmContentId));
|
||||
return reinterpret_cast<::NcmContentId *>(c);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE ::NcmContentId *Convert(ContentId &c) {
|
||||
static_assert(sizeof(ContentId) == sizeof(::NcmContentId));
|
||||
return reinterpret_cast<::NcmContentId *>(std::addressof(c));
|
||||
}
|
||||
|
||||
ALWAYS_INLINE const ::NcmContentId *Convert(const ContentId *c) {
|
||||
static_assert(sizeof(ContentId) == sizeof(::NcmContentId));
|
||||
return reinterpret_cast<const ::NcmContentId *>(c);
|
||||
}
|
||||
|
||||
|
||||
ALWAYS_INLINE const ::NcmContentId *Convert(const ContentId &c) {
|
||||
static_assert(sizeof(ContentId) == sizeof(::NcmContentId));
|
||||
return reinterpret_cast<const ::NcmContentId *>(std::addressof(c));
|
||||
}
|
||||
public:
|
||||
virtual Result Set(const ContentMetaKey &key, sf::InBuffer value) override {
|
||||
return ncmContentMetaDatabaseSet(std::addressof(this->srv), Convert(key), value.GetPointer(), value.GetSize());
|
||||
}
|
||||
|
||||
virtual Result Get(sf::Out<u64> out_size, const ContentMetaKey &key, sf::OutBuffer out_value) override {
|
||||
return ncmContentMetaDatabaseGet(std::addressof(this->srv), Convert(key), out_size.GetPointer(), out_value.GetPointer(), out_value.GetSize());
|
||||
}
|
||||
|
||||
virtual Result Remove(const ContentMetaKey &key) override {
|
||||
return ncmContentMetaDatabaseRemove(std::addressof(this->srv), Convert(key));
|
||||
}
|
||||
|
||||
virtual Result GetContentIdByType(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type) override {
|
||||
return ncmContentMetaDatabaseGetContentIdByType(std::addressof(this->srv), Convert(out_content_id.GetPointer()), Convert(key), static_cast<::NcmContentType>(type));
|
||||
}
|
||||
|
||||
virtual Result ListContentInfo(sf::Out<s32> out_entries_written, const sf::OutArray<ContentInfo> &out_info, const ContentMetaKey &key, s32 offset) override {
|
||||
return ncmContentMetaDatabaseListContentInfo(std::addressof(this->srv), out_entries_written.GetPointer(), Convert(out_info.GetPointer()), out_info.GetSize(), Convert(key), offset);
|
||||
}
|
||||
|
||||
virtual Result List(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType meta_type, ApplicationId application_id, u64 min, u64 max, ContentInstallType install_type) override {
|
||||
return ncmContentMetaDatabaseList(std::addressof(this->srv), out_entries_total.GetPointer(), out_entries_written.GetPointer(), Convert(out_info.GetPointer()), out_info.GetSize(), static_cast<::NcmContentMetaType>(meta_type), application_id.value, min, max, static_cast<::NcmContentInstallType>(install_type));
|
||||
}
|
||||
|
||||
virtual Result GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, u64 id) override {
|
||||
return ncmContentMetaDatabaseGetLatestContentMetaKey(std::addressof(this->srv), Convert(out_key.GetPointer()), static_cast<u64>(id));
|
||||
}
|
||||
|
||||
virtual Result ListApplication(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ApplicationContentMetaKey> &out_keys, ContentMetaType meta_type) override {
|
||||
return ncmContentMetaDatabaseListApplication(std::addressof(this->srv), out_entries_total.GetPointer(), out_entries_written.GetPointer(), Convert(out_keys.GetPointer()), out_keys.GetSize(), static_cast<::NcmContentMetaType>(meta_type));
|
||||
}
|
||||
|
||||
virtual Result Has(sf::Out<bool> out, const ContentMetaKey &key) override {
|
||||
return ncmContentMetaDatabaseHas(std::addressof(this->srv), out.GetPointer(), Convert(key));
|
||||
}
|
||||
|
||||
virtual Result HasAll(sf::Out<bool> out, const sf::InArray<ContentMetaKey> &keys) override {
|
||||
return ncmContentMetaDatabaseHasAll(std::addressof(this->srv), out.GetPointer(), Convert(keys.GetPointer()), keys.GetSize());
|
||||
}
|
||||
|
||||
virtual Result GetSize(sf::Out<u64> out_size, const ContentMetaKey &key) override {
|
||||
return ncmContentMetaDatabaseGetSize(std::addressof(this->srv), out_size.GetPointer(), Convert(key));
|
||||
}
|
||||
|
||||
virtual Result GetRequiredSystemVersion(sf::Out<u32> out_version, const ContentMetaKey &key) override {
|
||||
return ncmContentMetaDatabaseGetRequiredSystemVersion(std::addressof(this->srv), out_version.GetPointer(), Convert(key));
|
||||
}
|
||||
|
||||
virtual Result GetPatchId(sf::Out<PatchId> out_patch_id, const ContentMetaKey &key) override {
|
||||
return ncmContentMetaDatabaseGetPatchId(std::addressof(this->srv), reinterpret_cast<u64 *>(out_patch_id.GetPointer()), Convert(key));
|
||||
}
|
||||
|
||||
virtual Result DisableForcibly() override {
|
||||
return ncmContentMetaDatabaseDisableForcibly(std::addressof(this->srv));
|
||||
}
|
||||
|
||||
virtual Result LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) override {
|
||||
return ncmContentMetaDatabaseLookupOrphanContent(std::addressof(this->srv), out_orphaned.GetPointer(), Convert(content_ids.GetPointer()), std::min(out_orphaned.GetSize(), content_ids.GetSize()));
|
||||
}
|
||||
|
||||
virtual Result Commit() override {
|
||||
return ncmContentMetaDatabaseCommit(std::addressof(this->srv));
|
||||
}
|
||||
|
||||
virtual Result HasContent(sf::Out<bool> out, const ContentMetaKey &key, const ContentId &content_id) override {
|
||||
return ncmContentMetaDatabaseHasContent(std::addressof(this->srv), out.GetPointer(), Convert(key), Convert(content_id));
|
||||
}
|
||||
|
||||
virtual Result ListContentMetaInfo(sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaInfo> &out_meta_info, const ContentMetaKey &key, s32 offset) override {
|
||||
return ncmContentMetaDatabaseListContentMetaInfo(std::addressof(this->srv), out_entries_written.GetPointer(), out_meta_info.GetPointer(), out_meta_info.GetSize(), Convert(key), offset);
|
||||
}
|
||||
|
||||
virtual Result GetAttributes(sf::Out<u8> out_attributes, const ContentMetaKey &key) override {
|
||||
static_assert(sizeof(ContentMetaAttribute) == sizeof(u8));
|
||||
return ncmContentMetaDatabaseGetAttributes(std::addressof(this->srv), Convert(key), out_attributes.GetPointer());
|
||||
}
|
||||
|
||||
virtual Result GetRequiredApplicationVersion(sf::Out<u32> out_version, const ContentMetaKey &key) override {
|
||||
return ncmContentMetaDatabaseGetRequiredApplicationVersion(std::addressof(this->srv), out_version.GetPointer(), Convert(key));
|
||||
}
|
||||
|
||||
virtual Result GetContentIdByTypeAndIdOffset(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type, u8 id_offset) override {
|
||||
return ncmContentMetaDatabaseGetContentIdByTypeAndIdOffset(std::addressof(this->srv), Convert(out_content_id.GetPointer()), Convert(key), static_cast<::NcmContentType>(type), id_offset);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class RemoteContentStorageImpl final : public IContentStorage {
|
||||
private:
|
||||
::NcmContentStorage srv;
|
||||
public:
|
||||
RemoteContentStorageImpl(::NcmContentStorage &cs) : srv(cs) { /* ... */ }
|
||||
|
||||
~RemoteContentStorageImpl() { ::ncmContentStorageClose(std::addressof(srv)); }
|
||||
private:
|
||||
ALWAYS_INLINE ::NcmPlaceHolderId *Convert(PlaceHolderId *p) {
|
||||
static_assert(sizeof(PlaceHolderId) == sizeof(::NcmPlaceHolderId));
|
||||
return reinterpret_cast<::NcmPlaceHolderId *>(p);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE ::NcmPlaceHolderId *Convert(PlaceHolderId &p) {
|
||||
static_assert(sizeof(PlaceHolderId) == sizeof(::NcmPlaceHolderId));
|
||||
return reinterpret_cast<::NcmPlaceHolderId *>(std::addressof(p));
|
||||
}
|
||||
|
||||
ALWAYS_INLINE ::NcmContentId *Convert(ContentId *c) {
|
||||
static_assert(sizeof(ContentId) == sizeof(::NcmContentId));
|
||||
return reinterpret_cast<::NcmContentId *>(c);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE ::NcmContentId *Convert(ContentId &c) {
|
||||
static_assert(sizeof(ContentId) == sizeof(::NcmContentId));
|
||||
return reinterpret_cast<::NcmContentId *>(std::addressof(c));
|
||||
}
|
||||
public:
|
||||
virtual Result GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) override {
|
||||
return ncmContentStorageGeneratePlaceHolderId(std::addressof(this->srv), Convert(out.GetPointer()));
|
||||
}
|
||||
|
||||
virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, s64 size) override {
|
||||
static_assert(alignof(ContentId) < alignof(PlaceHolderId));
|
||||
return ncmContentStorageCreatePlaceHolder(std::addressof(this->srv), Convert(content_id), Convert(placeholder_id), size);
|
||||
}
|
||||
|
||||
virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id) override {
|
||||
return ncmContentStorageDeletePlaceHolder(std::addressof(this->srv), Convert(placeholder_id));
|
||||
}
|
||||
|
||||
virtual Result HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) override {
|
||||
return ncmContentStorageHasPlaceHolder(std::addressof(this->srv), out.GetPointer(), Convert(placeholder_id));
|
||||
}
|
||||
|
||||
virtual Result WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, sf::InBuffer data) override {
|
||||
return ncmContentStorageWritePlaceHolder(std::addressof(this->srv), Convert(placeholder_id), offset, data.GetPointer(), data.GetSize());
|
||||
}
|
||||
|
||||
virtual Result Register(PlaceHolderId placeholder_id, ContentId content_id) override {
|
||||
static_assert(alignof(ContentId) < alignof(PlaceHolderId));
|
||||
return ncmContentStorageRegister(std::addressof(this->srv), Convert(content_id), Convert(placeholder_id));
|
||||
}
|
||||
|
||||
virtual Result Delete(ContentId content_id) override {
|
||||
return ncmContentStorageDelete(std::addressof(this->srv), Convert(content_id));
|
||||
}
|
||||
|
||||
virtual Result Has(sf::Out<bool> out, ContentId content_id) override {
|
||||
return ncmContentStorageHas(std::addressof(this->srv), out.GetPointer(), Convert(content_id));
|
||||
}
|
||||
|
||||
virtual Result GetPath(sf::Out<Path> out, ContentId content_id) override {
|
||||
return ncmContentStorageGetPath(std::addressof(this->srv), out.GetPointer()->str, sizeof(out.GetPointer()->str), Convert(content_id));
|
||||
}
|
||||
|
||||
virtual Result GetPlaceHolderPath(sf::Out<Path> out, PlaceHolderId placeholder_id) override {
|
||||
return ncmContentStorageGetPlaceHolderPath(std::addressof(this->srv), out.GetPointer()->str, sizeof(out.GetPointer()->str), Convert(placeholder_id));
|
||||
}
|
||||
|
||||
virtual Result CleanupAllPlaceHolder() override {
|
||||
return ncmContentStorageCleanupAllPlaceHolder(std::addressof(this->srv));
|
||||
}
|
||||
|
||||
virtual Result ListPlaceHolder(sf::Out<s32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) override {
|
||||
return ncmContentStorageListPlaceHolder(std::addressof(this->srv), Convert(out_buf.GetPointer()), out_buf.GetSize(), out_count.GetPointer());
|
||||
}
|
||||
|
||||
virtual Result GetContentCount(sf::Out<s32> out_count) override {
|
||||
return ncmContentStorageGetContentCount(std::addressof(this->srv), out_count.GetPointer());
|
||||
}
|
||||
|
||||
virtual Result ListContentId(sf::Out<s32> out_count, const sf::OutArray<ContentId> &out_buf, s32 offset) override {
|
||||
return ncmContentStorageListContentId(std::addressof(this->srv), Convert(out_buf.GetPointer()), out_buf.GetSize(), out_count.GetPointer(), offset);
|
||||
}
|
||||
|
||||
virtual Result GetSizeFromContentId(sf::Out<s64> out_size, ContentId content_id) override {
|
||||
return ncmContentStorageGetSizeFromContentId(std::addressof(this->srv), out_size.GetPointer(), Convert(content_id));
|
||||
}
|
||||
|
||||
virtual Result DisableForcibly() override {
|
||||
return ncmContentStorageDisableForcibly(std::addressof(this->srv));
|
||||
}
|
||||
|
||||
virtual Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) override {
|
||||
return ncmContentStorageRevertToPlaceHolder(std::addressof(this->srv), Convert(placeholder_id), Convert(old_content_id), Convert(new_content_id));
|
||||
}
|
||||
|
||||
virtual Result SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size) override {
|
||||
return ncmContentStorageSetPlaceHolderSize(std::addressof(this->srv), Convert(placeholder_id), size);
|
||||
}
|
||||
|
||||
virtual Result ReadContentIdFile(sf::OutBuffer buf, ContentId content_id, s64 offset) override {
|
||||
return ncmContentStorageReadContentIdFile(std::addressof(this->srv), buf.GetPointer(), buf.GetSize(), Convert(content_id), offset);
|
||||
}
|
||||
|
||||
virtual Result GetRightsIdFromPlaceHolderIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, PlaceHolderId placeholder_id) override {
|
||||
::NcmRightsId rights_id;
|
||||
R_TRY(ncmContentStorageGetRightsIdFromPlaceHolderId(std::addressof(this->srv), std::addressof(rights_id), Convert(placeholder_id)));
|
||||
|
||||
static_assert(sizeof(*out_rights_id.GetPointer()) <= sizeof(rights_id));
|
||||
std::memcpy(out_rights_id.GetPointer(), std::addressof(rights_id), sizeof(*out_rights_id.GetPointer()));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
virtual Result GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id) override {
|
||||
::NcmRightsId rights_id;
|
||||
R_TRY(ncmContentStorageGetRightsIdFromPlaceHolderId(std::addressof(this->srv), std::addressof(rights_id), Convert(placeholder_id)));
|
||||
|
||||
static_assert(sizeof(*out_rights_id.GetPointer()) <= sizeof(rights_id));
|
||||
std::memcpy(out_rights_id.GetPointer(), std::addressof(rights_id), sizeof(*out_rights_id.GetPointer()));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
virtual Result GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id) override {
|
||||
::NcmRightsId rights_id;
|
||||
R_TRY(ncmContentStorageGetRightsIdFromContentId(std::addressof(this->srv), std::addressof(rights_id), Convert(content_id)));
|
||||
|
||||
static_assert(sizeof(*out_rights_id.GetPointer()) <= sizeof(rights_id));
|
||||
std::memcpy(out_rights_id.GetPointer(), std::addressof(rights_id), sizeof(*out_rights_id.GetPointer()));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
virtual Result GetRightsIdFromContentId(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id) override {
|
||||
::NcmRightsId rights_id;
|
||||
R_TRY(ncmContentStorageGetRightsIdFromContentId(std::addressof(this->srv), std::addressof(rights_id), Convert(content_id)));
|
||||
|
||||
static_assert(sizeof(*out_rights_id.GetPointer()) <= sizeof(rights_id));
|
||||
std::memcpy(out_rights_id.GetPointer(), std::addressof(rights_id), sizeof(*out_rights_id.GetPointer()));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
virtual Result WriteContentForDebug(ContentId content_id, s64 offset, sf::InBuffer data) override {
|
||||
return ncmContentStorageWriteContentForDebug(std::addressof(this->srv), Convert(content_id), offset, data.GetPointer(), data.GetSize());
|
||||
}
|
||||
|
||||
virtual Result GetFreeSpaceSize(sf::Out<s64> out_size) override {
|
||||
return ncmContentStorageGetFreeSpaceSize(std::addressof(this->srv), out_size.GetPointer());
|
||||
}
|
||||
|
||||
virtual Result GetTotalSpaceSize(sf::Out<s64> out_size) override {
|
||||
return ncmContentStorageGetTotalSpaceSize(std::addressof(this->srv), out_size.GetPointer());
|
||||
}
|
||||
|
||||
virtual Result FlushPlaceHolder() override {
|
||||
return ncmContentStorageFlushPlaceHolder(std::addressof(this->srv));
|
||||
}
|
||||
|
||||
virtual Result GetSizeFromPlaceHolderId(sf::Out<s64> out_size, PlaceHolderId placeholder_id) override {
|
||||
return ncmContentStorageGetSizeFromPlaceHolderId(std::addressof(this->srv), out_size.GetPointer(), Convert(placeholder_id));
|
||||
}
|
||||
|
||||
virtual Result RepairInvalidFileAttribute() override {
|
||||
return ncmContentStorageRepairInvalidFileAttribute(std::addressof(this->srv));
|
||||
}
|
||||
|
||||
virtual Result GetRightsIdFromPlaceHolderIdWithCache(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id) override {
|
||||
static_assert(sizeof(::NcmRightsId) == sizeof(ncm::RightsId));
|
||||
::NcmRightsId *out = reinterpret_cast<::NcmRightsId *>(out_rights_id.GetPointer());
|
||||
return ncmContentStorageGetRightsIdFromPlaceHolderIdWithCache(std::addressof(this->srv), out, Convert(placeholder_id), Convert(cache_content_id));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user