mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-11-15 17:39:28 +00:00
337fcf07a4
Static save files do not require an entry in the save data indexer to mount. Prior to 17.0.0, save data files were considered static if userid was 0. In 17.0.0+, only 8000000000000000 is static. However, some users using cfw do not have an entry for 8000000000000120 in the indexer, for various reasons (but mostly manual nand-restore, I think). Thus, on boot of 17.0.0+, FS will say 8000000000000120 is not present (not in indexer), and NCM will create it anew. The 8000000000000120 save will then be empty, and then the firmware can't boot. To workaround this, logic has been re-enabled on 17.0.0+ for building the content meta database. Thus, if the user encounters this error, the 8000000000000120 save will be emptied, but then it will be automatically reconstructed, fixing the problem.
1169 lines
54 KiB
C++
1169 lines
54 KiB
C++
/*
|
|
* Copyright (c) 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_host_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 {
|
|
|
|
alignas(os::MemoryPageSize) u8 g_system_content_meta_database_heap[512_KB];
|
|
alignas(os::MemoryPageSize) u8 g_gamecard_content_meta_database_heap[512_KB];
|
|
alignas(os::MemoryPageSize) u8 g_sd_and_user_content_meta_database_heap[2_MB + 512_KB];
|
|
|
|
ContentMetaMemoryResource g_system_content_meta_memory_resource(g_system_content_meta_database_heap, sizeof(g_system_content_meta_database_heap));
|
|
ContentMetaMemoryResource g_gamecard_content_meta_memory_resource(g_gamecard_content_meta_database_heap, sizeof(g_gamecard_content_meta_database_heap));
|
|
ContentMetaMemoryResource g_sd_and_user_content_meta_memory_resource(g_sd_and_user_content_meta_database_heap, sizeof(g_sd_and_user_content_meta_database_heap));
|
|
|
|
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,
|
|
};
|
|
|
|
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));
|
|
}
|
|
R_SUCCEED();
|
|
}
|
|
|
|
ALWAYS_INLINE Result GetContentStorageNotActiveResult(StorageId storage_id) {
|
|
switch (storage_id) {
|
|
case StorageId::GameCard: R_THROW(ncm::ResultGameCardContentStorageNotActive());
|
|
case StorageId::BuiltInSystem: R_THROW(ncm::ResultBuiltInSystemContentStorageNotActive());
|
|
case StorageId::BuiltInUser: R_THROW(ncm::ResultBuiltInUserContentStorageNotActive());
|
|
case StorageId::SdCard: R_THROW(ncm::ResultSdCardContentStorageNotActive());
|
|
default: R_THROW(ncm::ResultUnknownContentStorageNotActive());
|
|
}
|
|
}
|
|
|
|
ALWAYS_INLINE Result GetContentMetaDatabaseNotActiveResult(StorageId storage_id) {
|
|
switch (storage_id) {
|
|
case StorageId::GameCard: R_THROW(ncm::ResultGameCardContentMetaDatabaseNotActive());
|
|
case StorageId::BuiltInSystem: R_THROW(ncm::ResultBuiltInSystemContentMetaDatabaseNotActive());
|
|
case StorageId::BuiltInUser: R_THROW(ncm::ResultBuiltInUserContentMetaDatabaseNotActive());
|
|
case StorageId::SdCard: R_THROW(ncm::ResultSdCardContentMetaDatabaseNotActive());
|
|
default: R_THROW(ncm::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_4_0_0);
|
|
|
|
/* 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_8_0_0) {
|
|
/* 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];
|
|
util::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();
|
|
}
|
|
}
|
|
|
|
Result EnsureAndMountSystemSaveData(const char *mount_name, const SystemSaveDataInfo &info) {
|
|
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_2_0_0) ? (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;
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
}
|
|
|
|
Result ContentManagerImpl::IntegratedContentStorageRoot::Create() {
|
|
/* Create all storages. */
|
|
for (auto i = 0; i < m_num_roots; ++i) {
|
|
/* Get the current root. */
|
|
const auto &root = m_roots[i];
|
|
|
|
/* If we should, skip. */
|
|
if (!root.config.has_value() || root.config->skip_verify_and_create) {
|
|
continue;
|
|
}
|
|
|
|
/* Mount the relevant content storage. */
|
|
R_TRY(fs::MountContentStorage(root.mount_name, root.config->content_storage_id));
|
|
ON_SCOPE_EXIT { fs::Unmount(root.mount_name); };
|
|
|
|
/* Ensure the content storage root's path exists. */
|
|
R_TRY(fs::EnsureDirectory(root.path));
|
|
|
|
/* Initialize content and placeholder directories for the root. */
|
|
R_TRY(ContentStorageImpl::InitializeBase(root.path));
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::IntegratedContentStorageRoot::Verify() {
|
|
/* Verify all storages. */
|
|
for (auto i = 0; i < m_num_roots; ++i) {
|
|
/* Get the current root. */
|
|
const auto &root = m_roots[i];
|
|
|
|
/* If we should, skip. */
|
|
if (!root.config.has_value() || root.config->skip_verify_and_create) {
|
|
continue;
|
|
}
|
|
|
|
/* 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.config->content_storage_id));
|
|
ON_SCOPE_EXIT { fs::Unmount(mount_name.str); };
|
|
|
|
/* Ensure the root, content and placeholder directories exist for the storage. */
|
|
R_TRY(ContentStorageImpl::VerifyBase(path));
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::IntegratedContentStorageRoot::Open(sf::Out<sf::SharedPointer<IContentStorage>> out, RightsIdCache &rights_id_cache, RegisteredHostContent ®istered_host_content) {
|
|
/* Get the interface. */
|
|
const bool has_intf = m_config->is_integrated ? m_integrated_content_storage != nullptr : m_roots->content_storage != nullptr;
|
|
|
|
if (hos::GetVersion() >= hos::Version_2_0_0) {
|
|
/* Obtain the content storage if already active. */
|
|
R_UNLESS(has_intf, GetContentStorageNotActiveResult(m_config->storage_id));
|
|
} else {
|
|
/* 1.0.0 activates content storages as soon as they are opened. */
|
|
if (!has_intf) {
|
|
R_TRY(this->Activate(rights_id_cache, registered_host_content));
|
|
}
|
|
}
|
|
|
|
*out = m_config->is_integrated ? m_integrated_content_storage : m_roots->content_storage;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::IntegratedContentStorageRoot::Activate(RightsIdCache &rights_id_cache, RegisteredHostContent ®istered_host_content) {
|
|
/* If necessary, create the integrated storage. */
|
|
if (m_config->is_integrated && !m_integrated_content_storage) {
|
|
m_integrated_content_storage = sf::CreateSharedObjectEmplaced<IContentStorage, IntegratedContentStorageImpl>();
|
|
}
|
|
|
|
/* Activate all storages. */
|
|
for (auto i = 0; i < m_num_roots; ++i) {
|
|
/* Get the current root. */
|
|
auto &root = m_roots[i];
|
|
|
|
/* If we should, skip. */
|
|
if (root.config.has_value() && root.config->skip_activate) {
|
|
continue;
|
|
}
|
|
|
|
/* Activate. */
|
|
R_TRY(this->Activate(root, rights_id_cache, registered_host_content));
|
|
|
|
/* If we're integrated, add the storage. */
|
|
if (m_config->is_integrated) {
|
|
/* Determine the index of the storage. */
|
|
int index;
|
|
for (index = 0; index < m_config->num_content_storage_ids; ++index) {
|
|
if (m_config->content_storage_ids[index] == root.config->content_storage_id) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Add the storage. */
|
|
m_integrated_content_storage.GetImpl().Add(root.content_storage, index + 1);
|
|
}
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::IntegratedContentStorageRoot::Inactivate(RegisteredHostContent ®istered_host_content) {
|
|
/* If we have an integrated storage, disable it. */
|
|
if (m_integrated_content_storage != nullptr) {
|
|
R_TRY(m_integrated_content_storage->DisableForcibly());
|
|
m_integrated_content_storage.Reset();
|
|
}
|
|
|
|
/* Disable and unmount all storages. */
|
|
for (auto i = 0; i < m_num_roots; ++i) {
|
|
/* Get the current root. */
|
|
auto &root = m_roots[i];
|
|
|
|
if (root.content_storage != nullptr) {
|
|
/* N doesn't bother checking the result of this. */
|
|
root.content_storage->DisableForcibly();
|
|
root.content_storage = nullptr;
|
|
|
|
if (root.storage_id == StorageId::Host) {
|
|
registered_host_content.ClearPaths();
|
|
} else {
|
|
fs::Unmount(root.mount_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::IntegratedContentStorageRoot::Activate(RightsIdCache &rights_id_cache, RegisteredHostContent ®istered_host_content, fs::ContentStorageId content_storage_id) {
|
|
/* Check that we have the desired storage id in some root. */
|
|
ContentStorageRoot *root = this->GetRoot(content_storage_id);
|
|
R_UNLESS(root != nullptr, ncm::ResultUnknownStorage());
|
|
|
|
/* If necessary, create the integrated storage. */
|
|
if (m_config->is_integrated && !m_integrated_content_storage) {
|
|
m_integrated_content_storage = sf::CreateSharedObjectEmplaced<IContentStorage, IntegratedContentStorageImpl>();
|
|
}
|
|
|
|
/* Activate. */
|
|
R_TRY(this->Activate(*root, rights_id_cache, registered_host_content));
|
|
|
|
/* If we're integrated, add the storage. */
|
|
if (m_config->is_integrated) {
|
|
/* Determine the index of the storage. */
|
|
int index;
|
|
for (index = 0; index < m_config->num_content_storage_ids; ++index) {
|
|
if (m_config->content_storage_ids[index] == root->config->content_storage_id) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Add the storage. */
|
|
m_integrated_content_storage.GetImpl().Add(root->content_storage, index + 1);
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::IntegratedContentStorageRoot::Activate(ContentStorageRoot &root, RightsIdCache &rights_id_cache, RegisteredHostContent ®istered_host_content) {
|
|
/* Check if the storage is already activated. */
|
|
R_SUCCEED_IF(root.content_storage != nullptr);
|
|
|
|
/* Handle based on whether or not we have a content storage config. */
|
|
if (root.config.has_value()) {
|
|
/* Mount the content storage. */
|
|
R_TRY(fs::MountContentStorage(root.mount_name, root.config->content_storage_id));
|
|
ON_RESULT_FAILURE { fs::Unmount(root.mount_name); };
|
|
|
|
/* Create a content storage. */
|
|
auto content_storage = sf::CreateSharedObjectEmplaced<IContentStorage, ContentStorageImpl>();
|
|
|
|
/* Initialize content storage with an appropriate path function. */
|
|
switch (root.storage_id) {
|
|
case StorageId::BuiltInSystem:
|
|
R_TRY(content_storage.GetImpl().Initialize(root.path, MakeFlatContentFilePath, MakeFlatPlaceHolderFilePath, false, std::addressof(rights_id_cache)));
|
|
break;
|
|
case StorageId::SdCard:
|
|
R_TRY(content_storage.GetImpl().Initialize(root.path, MakeSha256HierarchicalContentFilePath_ForFat16KCluster, MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster, true, std::addressof(rights_id_cache)));
|
|
break;
|
|
default:
|
|
R_TRY(content_storage.GetImpl().Initialize(root.path, MakeSha256HierarchicalContentFilePath_ForFat16KCluster, MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster, false, std::addressof(rights_id_cache)));
|
|
break;
|
|
}
|
|
|
|
root.content_storage = std::move(content_storage);
|
|
} else {
|
|
switch (root.storage_id) {
|
|
case ncm::StorageId::Host:
|
|
root.content_storage = sf::CreateSharedObjectEmplaced<IContentStorage, HostContentStorageImpl>(std::addressof(registered_host_content));
|
|
break;
|
|
case ncm::StorageId::GameCard:
|
|
{
|
|
/* Get the gamecard handle. */
|
|
fs::GameCardHandle handle{};
|
|
R_TRY(fs::GetGameCardHandle(std::addressof(handle)));
|
|
|
|
/* Mount the secure gamecard partition. */
|
|
R_TRY(fs::MountGameCardPartition(root.mount_name, handle, fs::GameCardPartition::Secure));
|
|
ON_RESULT_FAILURE { fs::Unmount(root.mount_name); };
|
|
|
|
/* Create the content storage. */
|
|
auto content_storage = sf::CreateSharedObjectEmplaced<IContentStorage, ReadOnlyContentStorageImpl>();
|
|
R_TRY(content_storage.GetImpl().Initialize(root.path, MakeFlatContentFilePath));
|
|
root.content_storage = std::move(content_storage);
|
|
}
|
|
break;
|
|
AMS_UNREACHABLE_DEFAULT_CASE();
|
|
}
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::IntegratedContentMetaDatabaseRoot::Create() {
|
|
/* Create all content meta databases. */
|
|
for (auto i = 0; i < m_num_roots; ++i) {
|
|
/* Get the current root. */
|
|
const auto &root = m_roots[i];
|
|
|
|
/* If we should, skip. */
|
|
if (!root.save_data_info.has_value()) {
|
|
continue;
|
|
}
|
|
|
|
/* Mount (and optionally create) save data for the root. */
|
|
R_TRY(EnsureAndMountSystemSaveData(root.mount_name, *root.save_data_info));
|
|
ON_SCOPE_EXIT { fs::Unmount(root.mount_name); };
|
|
|
|
/* Ensure the content meta database root's path exists. */
|
|
R_TRY(fs::EnsureDirectory(root.path));
|
|
|
|
/* Commit our changes. */
|
|
R_TRY(fs::CommitSaveData(root.mount_name));
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::IntegratedContentMetaDatabaseRoot::Verify() {
|
|
/* Verify all content meta databases. */
|
|
for (auto i = 0; i < m_num_roots; ++i) {
|
|
/* Get the current root. */
|
|
const auto &root = m_roots[i];
|
|
|
|
/* If we should, skip. */
|
|
if (!root.save_data_info.has_value()) {
|
|
continue;
|
|
}
|
|
|
|
/* 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.save_data_info->space_id, root.save_data_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(fs::HasDirectory(std::addressof(has_dir), root.path));
|
|
R_UNLESS(has_dir, ncm::ResultInvalidContentMetaDatabase());
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::IntegratedContentMetaDatabaseRoot::Open(sf::Out<sf::SharedPointer<IContentMetaDatabase>> out) {
|
|
/* Get the interface. */
|
|
const bool has_intf = m_config->is_integrated ? m_integrated_content_meta_database != nullptr : m_roots->content_meta_database != nullptr;
|
|
|
|
if (hos::GetVersion() >= hos::Version_2_0_0) {
|
|
/* Obtain the content meta database if already active. */
|
|
R_UNLESS(has_intf, GetContentMetaDatabaseNotActiveResult(m_config->storage_id));
|
|
} else {
|
|
/* 1.0.0 activates content meta database as soon as they are opened. */
|
|
if (!has_intf) {
|
|
R_TRY(this->Activate());
|
|
}
|
|
}
|
|
|
|
*out = m_config->is_integrated ? m_integrated_content_meta_database : m_roots->content_meta_database;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::IntegratedContentMetaDatabaseRoot::Cleanup() {
|
|
/* Cleanup all content meta databases. */
|
|
for (auto i = 0; i < m_num_roots; ++i) {
|
|
/* Get the current root. */
|
|
const auto &root = m_roots[i];
|
|
|
|
/* If we should, skip. */
|
|
if (!root.save_data_info.has_value()) {
|
|
continue;
|
|
}
|
|
|
|
/* Delete save data for the content meta database. */
|
|
R_TRY(fs::DeleteSaveData(root.save_data_info->space_id, root.save_data_info->id));
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::IntegratedContentMetaDatabaseRoot::Activate() {
|
|
/* If necessary, create the integrated meta database. */
|
|
if (m_config->is_integrated && !m_integrated_content_meta_database) {
|
|
m_integrated_content_meta_database = sf::CreateSharedObjectEmplaced<IContentMetaDatabase, IntegratedContentMetaDatabaseImpl>();
|
|
}
|
|
|
|
/* Activate all meta databases. */
|
|
for (auto i = 0; i < m_num_roots; ++i) {
|
|
/* Get the current root. */
|
|
auto &root = m_roots[i];
|
|
|
|
/* If we should, skip. */
|
|
if (root.storage_config.has_value() && root.storage_config->skip_activate) {
|
|
continue;
|
|
}
|
|
|
|
/* Activate. */
|
|
R_TRY(this->Activate(root));
|
|
|
|
/* If we're integrated, add the meta database. */
|
|
if (m_config->is_integrated) {
|
|
/* Determine the index of the meta database. */
|
|
int index;
|
|
for (index = 0; index < m_config->num_content_storage_ids; ++index) {
|
|
if (m_config->content_storage_ids[index] == root.storage_config->content_storage_id) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Add the meta database. */
|
|
m_integrated_content_meta_database.GetImpl().Add(root.content_meta_database, index + 1);
|
|
}
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::IntegratedContentMetaDatabaseRoot::Inactivate() {
|
|
/* If we have an integrated meta database, disable it. */
|
|
/* NOTE: Nintendo does not reset the integrated DB, presumably they just forgot... */
|
|
/* ...this breaks this, hard, so we'll do the correct thing. */
|
|
if (m_integrated_content_meta_database != nullptr) {
|
|
R_TRY(m_integrated_content_meta_database->DisableForcibly());
|
|
m_integrated_content_meta_database.Reset();
|
|
}
|
|
|
|
/* Disable and unmount all storages. */
|
|
for (auto i = 0; i < m_num_roots; ++i) {
|
|
/* Get the current root. */
|
|
auto &root = m_roots[i];
|
|
|
|
if (root.content_meta_database != nullptr) {
|
|
/* N doesn't bother checking the result of this. */
|
|
root.content_meta_database->DisableForcibly();
|
|
root.content_meta_database = nullptr;
|
|
root.kvs = util::nullopt;
|
|
|
|
/* Additionally, if we have savedata, unmount. */
|
|
if (root.save_data_info.has_value()) {
|
|
fs::Unmount(root.mount_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::IntegratedContentMetaDatabaseRoot::Activate(ContentMetaDatabaseRoot &root) {
|
|
/* If the root is already activated, there's nothing to do. */
|
|
R_SUCCEED_IF(root.content_meta_database != nullptr);
|
|
|
|
/* Make a new kvs. */
|
|
root.kvs.emplace();
|
|
|
|
/* If we should, mount save data for this route. */
|
|
if (root.save_data_info.has_value()) {
|
|
/* Mount save data for this root. */
|
|
R_TRY(fs::MountSystemSaveData(root.mount_name, root.save_data_info->space_id, root.save_data_info->id));
|
|
ON_RESULT_FAILURE { 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, root.memory_resource));
|
|
R_TRY(root.kvs->Load());
|
|
|
|
/* Create the content meta database. */
|
|
root.content_meta_database = sf::CreateSharedObjectEmplaced<IContentMetaDatabase, ContentMetaDatabaseImpl>(std::addressof(*root.kvs), root.mount_name);
|
|
} else {
|
|
if (root.storage_id == StorageId::BuiltInSystem) {
|
|
/* Create a temporary mount name, and mount the partition. */
|
|
auto tmp_mount_name = impl::CreateUniqueMountName();
|
|
|
|
switch (root.storage_config->content_storage_id) {
|
|
case fs::ContentStorageId::System:
|
|
R_TRY(fs::MountBis(tmp_mount_name.str, fs::BisPartitionId::System));
|
|
break;
|
|
case fs::ContentStorageId::System0:
|
|
R_TRY(fs::MountBis(tmp_mount_name.str, fs::BisPartitionId::System0));
|
|
break;
|
|
AMS_UNREACHABLE_DEFAULT_CASE();
|
|
}
|
|
ON_SCOPE_EXIT { fs::Unmount(tmp_mount_name.str); };
|
|
|
|
/* Initialize and load the key value store from the filesystem. */
|
|
char path[fs::EntryNameLengthMax];
|
|
util::SNPrintf(path, sizeof(path), "%s:/cnmtdb.arc", tmp_mount_name.str);
|
|
R_TRY(root.kvs->InitializeForReadOnlyArchiveFile(path, root.max_content_metas, root.memory_resource));
|
|
R_TRY(root.kvs->Load());
|
|
|
|
/* Create an on memory content meta database. */
|
|
root.content_meta_database = sf::CreateSharedObjectEmplaced<IContentMetaDatabase, OnMemoryContentMetaDatabaseImpl>(std::addressof(*root.kvs));
|
|
} else {
|
|
/* Initialize the key value store. */
|
|
R_TRY(root.kvs->Initialize(root.max_content_metas, root.memory_resource));
|
|
|
|
/* Create an on memory content meta database. */
|
|
root.content_meta_database = sf::CreateSharedObjectEmplaced<IContentMetaDatabase, OnMemoryContentMetaDatabaseImpl>(std::addressof(*root.kvs));
|
|
}
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::IntegratedContentMetaDatabaseRoot::Activate(fs::ContentStorageId content_storage_id) {
|
|
/* Check that we have the desired storage id in some root. */
|
|
ContentMetaDatabaseRoot *root = this->GetRoot(content_storage_id);
|
|
R_UNLESS(root != nullptr, ncm::ResultUnknownStorage());
|
|
|
|
/* If necessary, create the integrated meta database. */
|
|
if (m_config->is_integrated && !m_integrated_content_meta_database) {
|
|
m_integrated_content_meta_database = sf::CreateSharedObjectEmplaced<IContentMetaDatabase, IntegratedContentMetaDatabaseImpl>();
|
|
}
|
|
|
|
/* Activate. */
|
|
R_TRY(this->Activate(*root));
|
|
|
|
/* If we're integrated, add the storage. */
|
|
if (m_config->is_integrated) {
|
|
/* Determine the index of the meta database. */
|
|
int index;
|
|
for (index = 0; index < m_config->num_content_storage_ids; ++index) {
|
|
if (m_config->content_storage_ids[index] == root->storage_config->content_storage_id) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Add the meta database. */
|
|
m_integrated_content_meta_database.GetImpl().Add(root->content_meta_database, index + 1);
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
ContentManagerImpl::~ContentManagerImpl() {
|
|
std::scoped_lock lk(m_mutex);
|
|
|
|
/* Disable and unmount all content storage roots. */
|
|
for (size_t i = 0; i < m_num_integrated_content_storage_entries; ++i) {
|
|
this->InactivateContentStorage(m_integrated_content_storage_roots[i].m_config->storage_id);
|
|
}
|
|
|
|
/* Disable and unmount all content meta database roots. */
|
|
for (size_t i = 0; i < m_num_integrated_content_meta_entries; ++i) {
|
|
this->InactivateContentMetaDatabase(m_integrated_content_meta_database_roots[i].m_config->storage_id);
|
|
}
|
|
}
|
|
|
|
Result ContentManagerImpl::GetIntegratedContentStorageConfig(IntegratedContentStorageConfig **out, fs::ContentStorageId content_storage_id) {
|
|
for (size_t i = 0; i < m_num_integrated_configs; ++i) {
|
|
auto &integrated_config = m_integrated_configs[i];
|
|
for (auto n = 0; n < integrated_config.num_content_storage_ids; n++) {
|
|
if (integrated_config.content_storage_ids[n] == content_storage_id) {
|
|
*out = std::addressof(integrated_config);
|
|
R_SUCCEED();
|
|
}
|
|
}
|
|
}
|
|
|
|
R_THROW(ncm::ResultUnknownStorage());
|
|
}
|
|
|
|
|
|
Result ContentManagerImpl::GetIntegratedContentStorageRoot(IntegratedContentStorageRoot **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 (size_t i = 0; i < m_num_integrated_content_storage_entries; ++i) {
|
|
if (auto &root = m_integrated_content_storage_roots[i]; root.m_config->storage_id == id) {
|
|
*out = std::addressof(root);
|
|
R_SUCCEED();
|
|
}
|
|
}
|
|
|
|
R_THROW(ncm::ResultUnknownStorage());
|
|
}
|
|
|
|
Result ContentManagerImpl::GetIntegratedContentMetaDatabaseRoot(IntegratedContentMetaDatabaseRoot **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 (size_t i = 0; i < m_num_integrated_content_meta_entries; ++i) {
|
|
if (auto &root = m_integrated_content_meta_database_roots[i]; root.m_config->storage_id == id) {
|
|
*out = std::addressof(root);
|
|
R_SUCCEED();
|
|
}
|
|
}
|
|
|
|
R_THROW(ncm::ResultUnknownStorage());
|
|
}
|
|
|
|
|
|
Result ContentManagerImpl::InitializeContentStorageRoot(ContentStorageRoot *out, StorageId storage_id, util::optional<ContentStorageConfig> config) {
|
|
out->storage_id = storage_id;
|
|
out->config = config;
|
|
out->content_storage = nullptr;
|
|
|
|
/* Create a new mount name and copy it to out. */
|
|
std::strcpy(out->mount_name, impl::CreateUniqueMountName().str);
|
|
util::SNPrintf(out->path, sizeof(out->path), "%s:/", out->mount_name);
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::InitializeContentMetaDatabaseRoot(ContentMetaDatabaseRoot *out, StorageId storage_id, util::optional<ContentStorageConfig> storage_config) {
|
|
out->storage_id = storage_id;
|
|
out->storage_config = storage_config;
|
|
|
|
/* Set the storage-specific info. */
|
|
switch (storage_id) {
|
|
case ncm::StorageId::Host:
|
|
out->save_data_info = util::nullopt;
|
|
out->max_content_metas = HostMaxContentMetaCount;
|
|
out->memory_resource = std::addressof(g_sd_and_user_content_meta_memory_resource);
|
|
break;
|
|
case ncm::StorageId::GameCard:
|
|
out->save_data_info = util::nullopt;
|
|
out->max_content_metas = GameCardMaxContentMetaCount;
|
|
out->memory_resource = std::addressof(g_gamecard_content_meta_memory_resource);
|
|
break;
|
|
case ncm::StorageId::BuiltInSystem:
|
|
/* If we should, skip save data info. */
|
|
if (storage_config.has_value() && (storage_config->content_storage_id != fs::ContentStorageId::System || storage_config->skip_verify_and_create)) {
|
|
out->save_data_info = util::nullopt;
|
|
} else {
|
|
/* Otherwise, use normal save data info. */
|
|
out->save_data_info = BuiltInSystemSystemSaveDataInfo;
|
|
}
|
|
|
|
out->max_content_metas = SystemMaxContentMetaCount;
|
|
out->memory_resource = std::addressof(g_system_content_meta_memory_resource);
|
|
break;
|
|
case ncm::StorageId::BuiltInUser:
|
|
out->save_data_info = BuiltInUserSystemSaveDataInfo;
|
|
out->max_content_metas = UserMaxContentMetaCount;
|
|
out->memory_resource = std::addressof(g_sd_and_user_content_meta_memory_resource);
|
|
break;
|
|
case ncm::StorageId::SdCard:
|
|
out->save_data_info = SdCardSystemSaveDataInfo;
|
|
out->max_content_metas = SdCardMaxContentMetaCount;
|
|
out->memory_resource = std::addressof(g_sd_and_user_content_meta_memory_resource);
|
|
break;
|
|
AMS_UNREACHABLE_DEFAULT_CASE();
|
|
}
|
|
|
|
/* Clear the kvs. */
|
|
out->kvs = util::nullopt;
|
|
|
|
/* Create a new mount name and copy it to out. */
|
|
std::strcpy(out->mount_name, impl::CreateUniqueMountName().str);
|
|
out->mount_name[0] = '#';
|
|
util::SNPrintf(out->path, sizeof(out->path), "%s:/meta", out->mount_name);
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::InitializeIntegratedContentStorageRoot(IntegratedContentStorageRoot *out, const IntegratedContentStorageConfig *config, size_t root_idx, size_t root_count) {
|
|
/* Set config and roots. */
|
|
out->m_config = config;
|
|
out->m_roots = std::addressof(m_content_storage_roots[root_idx]);
|
|
out->m_num_roots = root_count;
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::InitializeIntegratedContentMetaDatabaseRoot(IntegratedContentMetaDatabaseRoot *out, const IntegratedContentStorageConfig *config, size_t root_idx, size_t root_count) {
|
|
/* Set config and roots. */
|
|
out->m_config = config;
|
|
out->m_roots = std::addressof(m_content_meta_database_roots[root_idx]);
|
|
out->m_num_roots = root_count;
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::ImportContentMetaDatabaseImpl(ContentMetaDatabaseRoot *root, const char *import_mount_name) {
|
|
/* Check that the root is system. */
|
|
AMS_ABORT_UNLESS(root->storage_id == ncm::StorageId::BuiltInSystem);
|
|
|
|
std::scoped_lock lk(m_mutex);
|
|
|
|
/* Print the savedata path. */
|
|
PathString savedata_db_path;
|
|
savedata_db_path.AssignFormat("%s/%s", root->path, "imkvdb.arc");
|
|
|
|
/* Print a path for the mounted partition. */
|
|
PathString bis_db_path;
|
|
bis_db_path.AssignFormat("%s:/%s", import_mount_name, "cnmtdb.arc");
|
|
|
|
/* Mount the savedata. */
|
|
R_TRY(fs::MountSystemSaveData(root->mount_name, root->save_data_info->space_id, root->save_data_info->id));
|
|
ON_SCOPE_EXIT { fs::Unmount(root->mount_name); };
|
|
|
|
/* Ensure the path exists for us to import to. */
|
|
R_TRY(fs::EnsureDirectory(root->path));
|
|
|
|
/* Copy the file from bis to our save. */
|
|
R_TRY(impl::CopyFile(savedata_db_path, bis_db_path));
|
|
|
|
/* Commit the import. */
|
|
R_RETURN(fs::CommitSaveData(root->mount_name));
|
|
}
|
|
|
|
Result ContentManagerImpl::BuildContentMetaDatabase(StorageId storage_id) {
|
|
/* NOTE: we build on 17.0.0+, to work around a change in Nintendo save handling behavior. */
|
|
if (hos::GetVersion() < hos::Version_5_0_0 || hos::GetVersion() >= hos::Version_17_0_0) {
|
|
/* 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));
|
|
R_RETURN(builder.BuildFromStorage(std::addressof(storage)));
|
|
} else {
|
|
/* On 5.0.0+, building just performs an import. */
|
|
R_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);
|
|
|
|
/* Obtain the integrated content meta database root. */
|
|
IntegratedContentMetaDatabaseRoot *integrated_root;
|
|
R_TRY(this->GetIntegratedContentMetaDatabaseRoot(std::addressof(integrated_root), storage_id));
|
|
|
|
/* Obtain the root. */
|
|
ContentMetaDatabaseRoot *root = integrated_root->GetRoot(fs::ContentStorageId::System);
|
|
R_UNLESS(root != nullptr, ncm::ResultUnknownStorage());
|
|
|
|
/* 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(root, bis_mount_name.str));
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::Initialize(const ContentManagerConfig &config) {
|
|
/* Initialize based on whether integrated content is enabled. */
|
|
if (config.IsIntegratedSystemContentEnabled()) {
|
|
constexpr const IntegratedContentStorageConfig IntegratedConfigsForIntegratedSystemContent[] = {
|
|
{ ncm::StorageId::BuiltInSystem, { fs::ContentStorageId::System, fs::ContentStorageId::System0 }, 2, true },
|
|
{ ncm::StorageId::BuiltInUser, { fs::ContentStorageId::User }, 1, false },
|
|
{ ncm::StorageId::SdCard, { fs::ContentStorageId::SdCard }, 1, false },
|
|
{ ncm::StorageId::GameCard, { }, 0, false },
|
|
{ ncm::StorageId::Host, { }, 0, false },
|
|
};
|
|
constexpr const ContentStorageConfig ContentStorageConfigsForIntegratedSystemContent[] = {
|
|
{ .content_storage_id = fs::ContentStorageId::System, .skip_verify_and_create = true, .skip_activate = true, },
|
|
{ .content_storage_id = fs::ContentStorageId::System0, .skip_verify_and_create = true, .skip_activate = false, },
|
|
{ .content_storage_id = fs::ContentStorageId::User, .skip_verify_and_create = false, .skip_activate = false, },
|
|
{ .content_storage_id = fs::ContentStorageId::SdCard, .skip_verify_and_create = false, .skip_activate = false, },
|
|
};
|
|
constexpr const ncm::StorageId ActivatedStoragesForIntegratedSystemContent[] = {
|
|
ncm::StorageId::BuiltInSystem,
|
|
};
|
|
|
|
R_RETURN(this->Initialize(config, IntegratedConfigsForIntegratedSystemContent, util::size(IntegratedConfigsForIntegratedSystemContent), ContentStorageConfigsForIntegratedSystemContent, util::size(ContentStorageConfigsForIntegratedSystemContent), ActivatedStoragesForIntegratedSystemContent, util::size(ActivatedStoragesForIntegratedSystemContent)));
|
|
} else {
|
|
constexpr const IntegratedContentStorageConfig IntegratedConfigs[] = {
|
|
{ ncm::StorageId::BuiltInSystem, { fs::ContentStorageId::System }, 1, false },
|
|
{ ncm::StorageId::BuiltInUser, { fs::ContentStorageId::User }, 1, false },
|
|
{ ncm::StorageId::SdCard, { fs::ContentStorageId::SdCard }, 1, false },
|
|
{ ncm::StorageId::GameCard, { }, 0, false },
|
|
{ ncm::StorageId::Host, { }, 0, false },
|
|
};
|
|
constexpr const ContentStorageConfig ContentStorageConfigs[] = {
|
|
{ .content_storage_id = fs::ContentStorageId::System, .skip_verify_and_create = false, .skip_activate = false, },
|
|
{ .content_storage_id = fs::ContentStorageId::User, .skip_verify_and_create = false, .skip_activate = false, },
|
|
{ .content_storage_id = fs::ContentStorageId::SdCard, .skip_verify_and_create = false, .skip_activate = false, },
|
|
};
|
|
constexpr const ncm::StorageId ActivatedStorages[] = {
|
|
ncm::StorageId::BuiltInSystem,
|
|
};
|
|
|
|
R_RETURN(this->Initialize(config, IntegratedConfigs, util::size(IntegratedConfigs), ContentStorageConfigs, util::size(ContentStorageConfigs), ActivatedStorages, util::size(ActivatedStorages)));
|
|
}
|
|
}
|
|
|
|
Result ContentManagerImpl::Initialize(const ContentManagerConfig &manager_config, const IntegratedContentStorageConfig *integrated_configs, size_t num_integrated_configs, const ContentStorageConfig *configs, size_t num_configs, const ncm::StorageId *activated_storages, size_t num_activated_storages) {
|
|
std::scoped_lock lk(m_mutex);
|
|
|
|
/* Check if we've already initialized. */
|
|
R_SUCCEED_IF(m_initialized);
|
|
|
|
/* Set our configs. */
|
|
for (size_t i = 0; i < num_integrated_configs; ++i) {
|
|
m_integrated_configs[i] = integrated_configs[i];
|
|
}
|
|
m_num_integrated_configs = num_integrated_configs;
|
|
|
|
for (size_t i = 0; i < num_configs; ++i) {
|
|
m_configs[i] = configs[i];
|
|
}
|
|
m_num_configs = num_configs;
|
|
|
|
/* Setup roots. */
|
|
m_num_integrated_content_storage_entries = 0;
|
|
m_num_content_storage_entries = 0;
|
|
m_num_integrated_content_meta_entries = 0;
|
|
m_num_content_meta_entries = 0;
|
|
for (size_t i = 0; i < m_num_integrated_configs; ++i) {
|
|
/* Get the integrated config. */
|
|
const auto &integrated_config = m_integrated_configs[i];
|
|
|
|
/* Set up storage and meta db roots. */
|
|
size_t content_storage_root_idx = m_num_content_storage_entries;
|
|
size_t content_meta_root_idx = m_num_content_meta_entries;
|
|
if (integrated_config.num_content_storage_ids > 0) {
|
|
/* If we have content storage ids, set up storage/meta db roots for each. */
|
|
for (auto n = 0; n < integrated_config.num_content_storage_ids; n++) {
|
|
/* Get the config. */
|
|
const auto &config = this->GetContentStorageConfig(integrated_config.content_storage_ids[n]);
|
|
R_TRY(this->InitializeContentStorageRoot(std::addressof(m_content_storage_roots[m_num_content_storage_entries++]), integrated_config.storage_id, config));
|
|
R_TRY(this->InitializeContentMetaDatabaseRoot(std::addressof(m_content_meta_database_roots[m_num_content_meta_entries++]), integrated_config.storage_id, config));
|
|
}
|
|
} else {
|
|
/* If we have no content storage ids, set up a single storage/meta db root. */
|
|
R_TRY(this->InitializeContentStorageRoot(std::addressof(m_content_storage_roots[m_num_content_storage_entries++]), integrated_config.storage_id, util::nullopt));
|
|
R_TRY(this->InitializeContentMetaDatabaseRoot(std::addressof(m_content_meta_database_roots[m_num_content_meta_entries++]), integrated_config.storage_id, util::nullopt));
|
|
}
|
|
|
|
R_TRY(this->InitializeIntegratedContentStorageRoot(std::addressof(m_integrated_content_storage_roots[m_num_integrated_content_storage_entries++]), std::addressof(integrated_config), content_storage_root_idx, m_num_content_storage_entries - content_storage_root_idx));
|
|
R_TRY(this->InitializeIntegratedContentMetaDatabaseRoot(std::addressof(m_integrated_content_meta_database_roots[m_num_integrated_content_meta_entries++]), std::addressof(integrated_config), content_meta_root_idx, m_num_content_meta_entries - content_meta_root_idx));
|
|
}
|
|
|
|
/* Activate storages. */
|
|
for (size_t i = 0; i < num_activated_storages; i++) {
|
|
const auto storage_id = activated_storages[i];
|
|
if (storage_id == ncm::StorageId::BuiltInSystem) {
|
|
R_TRY(this->InitializeStorageBuiltInSystem(manager_config));
|
|
} else {
|
|
R_TRY(this->InitializeStorage(storage_id));
|
|
}
|
|
}
|
|
|
|
m_initialized = true;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::InitializeStorageBuiltInSystem(const ContentManagerConfig &manager_config) {
|
|
/* Setup and activate the storage for BuiltInSystem. */
|
|
if (R_FAILED(this->VerifyContentStorage(StorageId::BuiltInSystem))) {
|
|
R_TRY(this->CreateContentStorage(StorageId::BuiltInSystem));
|
|
}
|
|
R_TRY(this->ActivateContentStorage(StorageId::BuiltInSystem));
|
|
|
|
/* Setup the content meta database for system. */
|
|
if (R_FAILED(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem))) {
|
|
R_TRY(this->CreateContentMetaDatabase(StorageId::BuiltInSystem));
|
|
|
|
/* Try to build or import a database, depending on our configuration. */
|
|
/* NOTE: To work around a change in save management behavior in 17.0.0+, we build the database if needed. */
|
|
if (manager_config.ShouldBuildDatabase() || hos::GetVersion() >= hos::Version_17_0_0) {
|
|
/* If we should build the database, do so. */
|
|
R_TRY(this->BuildContentMetaDatabase(StorageId::BuiltInSystem));
|
|
R_TRY(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem));
|
|
} else if (manager_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_2_0_0) {
|
|
EnsureBuiltInSystemSaveDataFlags();
|
|
}
|
|
|
|
/* Activate the content meta database. */
|
|
R_TRY(this->ActivateContentMetaDatabase(StorageId::BuiltInSystem));
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::InitializeStorage(ncm::StorageId storage_id) {
|
|
/* Setup and activate the storage. */
|
|
if (R_FAILED(this->VerifyContentStorage(storage_id))) {
|
|
R_TRY(this->CreateContentStorage(storage_id));
|
|
}
|
|
R_TRY(this->ActivateContentStorage(StorageId::BuiltInSystem));
|
|
|
|
/* Setup the content meta database for system. */
|
|
if (R_FAILED(this->VerifyContentMetaDatabase(storage_id))) {
|
|
R_TRY(this->CreateContentMetaDatabase(storage_id));
|
|
}
|
|
R_TRY(this->ActivateContentMetaDatabase(storage_id));
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::CreateContentStorage(StorageId storage_id) {
|
|
std::scoped_lock lk(m_mutex);
|
|
|
|
/* Obtain the integrated content storage root. */
|
|
IntegratedContentStorageRoot *root;
|
|
R_TRY(this->GetIntegratedContentStorageRoot(std::addressof(root), storage_id));
|
|
|
|
R_RETURN(root->Create());
|
|
}
|
|
|
|
Result ContentManagerImpl::CreateContentMetaDatabase(StorageId storage_id) {
|
|
std::scoped_lock lk(m_mutex);
|
|
|
|
/* Obtain the integrated content meta database root. */
|
|
IntegratedContentMetaDatabaseRoot *root;
|
|
R_TRY(this->GetIntegratedContentMetaDatabaseRoot(std::addressof(root), storage_id));
|
|
|
|
R_RETURN(root->Create());
|
|
}
|
|
|
|
Result ContentManagerImpl::VerifyContentStorage(StorageId storage_id) {
|
|
std::scoped_lock lk(m_mutex);
|
|
|
|
/* Obtain the integrated content storage root. */
|
|
IntegratedContentStorageRoot *root;
|
|
R_TRY(this->GetIntegratedContentStorageRoot(std::addressof(root), storage_id));
|
|
|
|
R_RETURN(root->Verify());
|
|
}
|
|
|
|
Result ContentManagerImpl::VerifyContentMetaDatabase(StorageId storage_id) {
|
|
std::scoped_lock lk(m_mutex);
|
|
|
|
/* Obtain the integrated content meta database root. */
|
|
IntegratedContentMetaDatabaseRoot *root;
|
|
R_TRY(this->GetIntegratedContentMetaDatabaseRoot(std::addressof(root), storage_id));
|
|
|
|
R_RETURN(root->Verify());
|
|
}
|
|
|
|
Result ContentManagerImpl::OpenContentStorage(sf::Out<sf::SharedPointer<IContentStorage>> out, StorageId storage_id) {
|
|
std::scoped_lock lk(m_mutex);
|
|
|
|
/* Obtain the integrated content storage root. */
|
|
IntegratedContentStorageRoot *root;
|
|
R_TRY(this->GetIntegratedContentStorageRoot(std::addressof(root), storage_id));
|
|
|
|
R_RETURN(root->Open(out, m_rights_id_cache, m_registered_host_content));
|
|
}
|
|
|
|
Result ContentManagerImpl::OpenContentMetaDatabase(sf::Out<sf::SharedPointer<IContentMetaDatabase>> out, StorageId storage_id) {
|
|
std::scoped_lock lk(m_mutex);
|
|
|
|
/* Obtain the integrated content meta database root. */
|
|
IntegratedContentMetaDatabaseRoot *root;
|
|
R_TRY(this->GetIntegratedContentMetaDatabaseRoot(std::addressof(root), storage_id));
|
|
|
|
R_RETURN(root->Open(out));
|
|
}
|
|
|
|
Result ContentManagerImpl::CloseContentStorageForcibly(StorageId storage_id) {
|
|
R_RETURN(this->InactivateContentStorage(storage_id));
|
|
}
|
|
|
|
Result ContentManagerImpl::CloseContentMetaDatabaseForcibly(StorageId storage_id) {
|
|
R_RETURN(this->InactivateContentMetaDatabase(storage_id));
|
|
}
|
|
|
|
Result ContentManagerImpl::CleanupContentMetaDatabase(StorageId storage_id) {
|
|
std::scoped_lock lk(m_mutex);
|
|
|
|
/* Obtain the integrated content meta database root. */
|
|
IntegratedContentMetaDatabaseRoot *root;
|
|
R_TRY(this->GetIntegratedContentMetaDatabaseRoot(std::addressof(root), storage_id));
|
|
|
|
R_RETURN(root->Cleanup());
|
|
}
|
|
|
|
Result ContentManagerImpl::ActivateContentStorage(StorageId storage_id) {
|
|
std::scoped_lock lk(m_mutex);
|
|
|
|
/* Obtain the integrated content storage root. */
|
|
IntegratedContentStorageRoot *root;
|
|
R_TRY(this->GetIntegratedContentStorageRoot(std::addressof(root), storage_id));
|
|
|
|
R_RETURN(root->Activate(m_rights_id_cache, m_registered_host_content));
|
|
}
|
|
|
|
Result ContentManagerImpl::InactivateContentStorage(StorageId storage_id) {
|
|
std::scoped_lock lk(m_mutex);
|
|
|
|
/* Obtain the integrated content storage root. */
|
|
IntegratedContentStorageRoot *root;
|
|
R_TRY(this->GetIntegratedContentStorageRoot(std::addressof(root), storage_id));
|
|
|
|
R_RETURN(root->Inactivate(m_registered_host_content));
|
|
}
|
|
|
|
Result ContentManagerImpl::ActivateContentMetaDatabase(StorageId storage_id) {
|
|
std::scoped_lock lk(m_mutex);
|
|
|
|
/* Obtain the integrated content meta database root. */
|
|
IntegratedContentMetaDatabaseRoot *root;
|
|
R_TRY(this->GetIntegratedContentMetaDatabaseRoot(std::addressof(root), storage_id));
|
|
|
|
R_RETURN(root->Activate());
|
|
}
|
|
|
|
Result ContentManagerImpl::InactivateContentMetaDatabase(StorageId storage_id) {
|
|
std::scoped_lock lk(m_mutex);
|
|
|
|
/* Obtain the integrated content meta database root. */
|
|
IntegratedContentMetaDatabaseRoot *root;
|
|
R_TRY(this->GetIntegratedContentMetaDatabaseRoot(std::addressof(root), storage_id));
|
|
|
|
R_RETURN(root->Inactivate());
|
|
}
|
|
|
|
Result ContentManagerImpl::InvalidateRightsIdCache() {
|
|
m_rights_id_cache.Invalidate();
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::GetMemoryReport(sf::Out<MemoryReport> out) {
|
|
/* Populate content meta resource states. */
|
|
MemoryReport report = {
|
|
.system_content_meta_resource_state = {
|
|
.peak_total_alloc_size = g_system_content_meta_memory_resource.GetPeakTotalAllocationSize(),
|
|
.peak_alloc_size = g_system_content_meta_memory_resource.GetPeakAllocationSize(),
|
|
.allocatable_size = g_system_content_meta_memory_resource.GetAllocator()->GetAllocatableSize(),
|
|
.total_free_size = g_system_content_meta_memory_resource.GetAllocator()->GetTotalFreeSize(),
|
|
},
|
|
.sd_and_user_content_meta_resource_state {
|
|
.peak_total_alloc_size = g_sd_and_user_content_meta_memory_resource.GetPeakTotalAllocationSize(),
|
|
.peak_alloc_size = g_sd_and_user_content_meta_memory_resource.GetPeakAllocationSize(),
|
|
.allocatable_size = g_sd_and_user_content_meta_memory_resource.GetAllocator()->GetAllocatableSize(),
|
|
.total_free_size = g_sd_and_user_content_meta_memory_resource.GetAllocator()->GetTotalFreeSize(),
|
|
},
|
|
.gamecard_content_meta_resource_state {
|
|
.peak_total_alloc_size = g_gamecard_content_meta_memory_resource.GetPeakTotalAllocationSize(),
|
|
.peak_alloc_size = g_gamecard_content_meta_memory_resource.GetPeakAllocationSize(),
|
|
.allocatable_size = g_gamecard_content_meta_memory_resource.GetAllocator()->GetAllocatableSize(),
|
|
.total_free_size = g_gamecard_content_meta_memory_resource.GetAllocator()->GetTotalFreeSize(),
|
|
},
|
|
.heap_resource_state = {},
|
|
};
|
|
|
|
/* Populate heap memory resource state. */
|
|
GetHeapState().GetMemoryResourceState(std::addressof(report.heap_resource_state));
|
|
|
|
/* Output the report. */
|
|
out.SetValue(report);
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result ContentManagerImpl::ActivateFsContentStorage(fs::ContentStorageId content_storage_id) {
|
|
/* Get the integrated config for the storage. */
|
|
IntegratedContentStorageConfig *integrated_config;
|
|
R_TRY(this->GetIntegratedContentStorageConfig(std::addressof(integrated_config), content_storage_id));
|
|
|
|
{
|
|
/* Obtain the integrated content meta database root. */
|
|
IntegratedContentStorageRoot *root;
|
|
R_TRY(this->GetIntegratedContentStorageRoot(std::addressof(root), integrated_config->storage_id));
|
|
|
|
R_TRY(root->Activate(m_rights_id_cache, m_registered_host_content, content_storage_id));
|
|
}
|
|
{
|
|
/* Obtain the integrated content meta database root. */
|
|
IntegratedContentMetaDatabaseRoot *root;
|
|
R_TRY(this->GetIntegratedContentMetaDatabaseRoot(std::addressof(root), integrated_config->storage_id));
|
|
|
|
R_TRY(root->Activate(content_storage_id));
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
}
|