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 commit d6ff261fcc.

* 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 commit 168447d80e.

* 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 commit d6ff261fcc.

* 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 commit 168447d80e.

* 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:
Adubbz
2020-03-08 19:06:23 +11:00
committed by GitHub
parent f9403201f0
commit c7026b9094
254 changed files with 16876 additions and 1274 deletions

View 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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View 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);
}
}
}

View File

@@ -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));
}
}

View File

@@ -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;
};
}

View File

@@ -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);
}
};
}

View File

@@ -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)";
}
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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;
};
}

View File

@@ -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();
}
};
}

View 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();
}
}

View 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);
}

View 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();
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
};
}

View File

@@ -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));
}
}
}
}

View File

@@ -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); }
};
}

View File

@@ -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();
}
}

View File

@@ -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;
};
}

View File

@@ -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();
}
};
}

View File

@@ -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);
}
};
}

View File

@@ -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));
}
};
}