diff --git a/libraries/libstratosphere/include/stratosphere/ncm.hpp b/libraries/libstratosphere/include/stratosphere/ncm.hpp index 04cb7515f..142ab844f 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm.hpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_manager_impl.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_manager_impl.hpp index 4c00e694e..a0e25cf9a 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_manager_impl.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_manager_impl.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include namespace ams::ncm { @@ -108,8 +109,11 @@ namespace ams::ncm { u32 num_content_storage_entries; u32 num_content_meta_entries; RightsIdCache rights_id_cache; + RegisteredHostContent registered_host_content; public: - ContentManagerImpl() : mutex(true), initialized(false) { /* ... */ }; + ContentManagerImpl() : mutex(true), initialized(false), num_content_storage_entries(0), num_content_meta_entries(0), rights_id_cache(), registered_host_content() { + /* ... */ + }; ~ContentManagerImpl(); public: Result Initialize(const ContentManagerConfig &config); diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_host_content.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_host_content.hpp deleted file mode 100644 index fdfe59fcc..000000000 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_host_content.hpp +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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 . - */ -#pragma once -#include - -namespace ams::ncm { - -} diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_registered_host_content.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_registered_host_content.hpp new file mode 100644 index 000000000..65fe178c3 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_registered_host_content.hpp @@ -0,0 +1,42 @@ +/* + * 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 . + */ +#pragma once +#include +#include +#include + +namespace ams::ncm { + + class RegisteredHostContent { + NON_COPYABLE(RegisteredHostContent); + NON_MOVEABLE(RegisteredHostContent); + private: + class RegisteredPath; + private: + using RegisteredPathList = ams::util::IntrusiveListBaseTraits::ListType; + private: + os::SdkMutex mutex; + RegisteredPathList path_list; + public: + RegisteredHostContent() : mutex(), path_list() { /* ... */ } + ~RegisteredHostContent(); + + Result RegisterPath(const ncm::ContentId &content_id, const ncm::Path &path); + Result GetPath(Path *out, const ncm::ContentId &content_id); + void ClearPaths(); + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_rights_id_cache.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_rights_id_cache.hpp index 93dadc0f3..8613aa3a1 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_rights_id_cache.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_rights_id_cache.hpp @@ -35,9 +35,9 @@ namespace ams::ncm { private: Entry entries[MaxEntries]; u64 counter; - os::Mutex mutex; + os::SdkMutex mutex; public: - RightsIdCache() : mutex(false) { + RightsIdCache() : mutex() { this->Invalidate(); } diff --git a/libraries/libstratosphere/source/ncm/ncm_content_manager_impl.cpp b/libraries/libstratosphere/source/ncm/ncm_content_manager_impl.cpp index 4d72ce91a..89526518e 100644 --- a/libraries/libstratosphere/source/ncm/ncm_content_manager_impl.cpp +++ b/libraries/libstratosphere/source/ncm/ncm_content_manager_impl.cpp @@ -16,6 +16,7 @@ #include #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" @@ -551,7 +552,11 @@ namespace ams::ncm { /* Unmount on failure. */ auto mount_guard = SCOPE_GUARD { fs::Unmount(root->mount_name); }; - if (storage_id == StorageId::GameCard) { + if (storage_id == StorageId::Host) { + /* Create a host content storage. */ + auto content_storage = sf::CreateSharedObjectEmplaced(std::addressof(this->registered_host_content)); + root->content_storage = std::move(content_storage); + } else if (storage_id == StorageId::GameCard) { /* Game card content storage is read only. */ auto content_storage = sf::CreateSharedObjectEmplaced(); R_TRY(content_storage.GetImpl().Initialize(root->path, MakeFlatContentFilePath)); @@ -593,7 +598,12 @@ namespace ams::ncm { /* N doesn't bother checking the result of this */ root->content_storage->DisableForcibly(); root->content_storage = nullptr; - fs::Unmount(root->mount_name); + + if (storage_id == StorageId::Host) { + this->registered_host_content.ClearPaths(); + } else { + fs::Unmount(root->mount_name); + } } return ResultSuccess(); diff --git a/libraries/libstratosphere/source/ncm/ncm_host_content_storage_impl.cpp b/libraries/libstratosphere/source/ncm/ncm_host_content_storage_impl.cpp new file mode 100644 index 000000000..54416d756 --- /dev/null +++ b/libraries/libstratosphere/source/ncm/ncm_host_content_storage_impl.cpp @@ -0,0 +1,193 @@ +/* + * 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 . + */ +#include +#include "ncm_host_content_storage_impl.hpp" + +namespace ams::ncm { + + Result HostContentStorageImpl::GeneratePlaceHolderId(sf::Out out) { + return ncm::ResultNotSupported(); + } + + Result HostContentStorageImpl::CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, s64 size) { + return ncm::ResultNotSupported(); + } + + Result HostContentStorageImpl::DeletePlaceHolder(PlaceHolderId placeholder_id) { + return ncm::ResultNotSupported(); + } + + Result HostContentStorageImpl::HasPlaceHolder(sf::Out out, PlaceHolderId placeholder_id) { + return ncm::ResultNotSupported(); + } + + Result HostContentStorageImpl::WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, const sf::InBuffer &data) { + return ncm::ResultNotSupported(); + } + + Result HostContentStorageImpl::Register(PlaceHolderId placeholder_id, ContentId content_id) { + return ncm::ResultNotSupported(); + } + + Result HostContentStorageImpl::Delete(ContentId content_id) { + return ncm::ResultNotSupported(); + } + + Result HostContentStorageImpl::Has(sf::Out out, ContentId content_id) { + R_TRY(this->EnsureEnabled()); + + /* Attempt to locate the content. */ + Path path; + R_TRY_CATCH(this->registered_content->GetPath(std::addressof(path), content_id)) { + /* The content is absent, this is fine. */ + R_CATCH(ncm::ResultContentNotFound) { + out.SetValue(false); + return ResultSuccess(); + } + } R_END_TRY_CATCH; + + out.SetValue(true); + return ResultSuccess(); + } + + Result HostContentStorageImpl::GetPath(sf::Out out, ContentId content_id) { + R_TRY(this->EnsureEnabled()); + return this->registered_content->GetPath(out.GetPointer(), content_id); + } + + Result HostContentStorageImpl::GetPlaceHolderPath(sf::Out out, PlaceHolderId placeholder_id) { + return ncm::ResultNotSupported(); + } + + Result HostContentStorageImpl::CleanupAllPlaceHolder() { + return ncm::ResultNotSupported(); + } + + Result HostContentStorageImpl::ListPlaceHolder(sf::Out out_count, const sf::OutArray &out_buf) { + return ncm::ResultNotSupported(); + } + + Result HostContentStorageImpl::GetContentCount(sf::Out out_count) { + return ncm::ResultNotSupported(); + } + + Result HostContentStorageImpl::ListContentId(sf::Out out_count, const sf::OutArray &out_buf, s32 offset) { + return ncm::ResultNotSupported(); + } + + Result HostContentStorageImpl::GetSizeFromContentId(sf::Out out_size, ContentId content_id) { + return ncm::ResultInvalidOperation(); + } + + Result HostContentStorageImpl::DisableForcibly() { + this->disabled = true; + return ResultSuccess(); + } + + Result HostContentStorageImpl::RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) { + return ncm::ResultNotSupported(); + } + + Result HostContentStorageImpl::SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size) { + return ncm::ResultNotSupported(); + } + + Result HostContentStorageImpl::ReadContentIdFile(const sf::OutBuffer &buf, ContentId content_id, s64 offset) { + return ncm::ResultInvalidOperation(); + } + + Result HostContentStorageImpl::GetRightsIdFromPlaceHolderIdDeprecated(sf::Out out_rights_id, PlaceHolderId placeholder_id) { + return ncm::ResultNotSupported(); + } + + Result HostContentStorageImpl::GetRightsIdFromPlaceHolderId(sf::Out out_rights_id, PlaceHolderId placeholder_id) { + return ncm::ResultNotSupported(); + } + + Result HostContentStorageImpl::GetRightsIdFromContentIdDeprecated(sf::Out 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 HostContentStorageImpl::GetRightsIdFromContentId(sf::Out out_rights_id, ContentId content_id) { + R_TRY(this->EnsureEnabled()); + + /* Get the content path. */ + Path path; + R_TRY(this->registered_content->GetPath(std::addressof(path), content_id)); + + /* Acquire the rights id for the content. */ + RightsId rights_id; + R_TRY_CATCH(GetRightsId(std::addressof(rights_id), path)) { + /* The content is absent, output a blank rights id. */ + R_CATCH(fs::ResultTargetNotFound) { + out_rights_id.SetValue({}); + return ResultSuccess(); + } + } R_END_TRY_CATCH; + + /* Output the rights id. */ + out_rights_id.SetValue(rights_id); + return ResultSuccess(); + } + + Result HostContentStorageImpl::WriteContentForDebug(ContentId content_id, s64 offset, const sf::InBuffer &data) { + return ncm::ResultNotSupported(); + } + + Result HostContentStorageImpl::GetFreeSpaceSize(sf::Out out_size) { + out_size.SetValue(0); + return ResultSuccess(); + } + + Result HostContentStorageImpl::GetTotalSpaceSize(sf::Out out_size) { + out_size.SetValue(0); + return ResultSuccess(); + } + + Result HostContentStorageImpl::FlushPlaceHolder() { + return ncm::ResultNotSupported(); + } + + Result HostContentStorageImpl::GetSizeFromPlaceHolderId(sf::Out out, PlaceHolderId placeholder_id) { + return ncm::ResultNotSupported(); + } + + Result HostContentStorageImpl::RepairInvalidFileAttribute() { + return ncm::ResultNotSupported(); + } + + Result HostContentStorageImpl::GetRightsIdFromPlaceHolderIdWithCache(sf::Out out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id) { + return ncm::ResultNotSupported(); + } + + Result HostContentStorageImpl::RegisterPath(const ContentId &content_id, const Path &path) { + AMS_ABORT_UNLESS(spl::IsDevelopment()); + return this->registered_content->RegisterPath(content_id, path); + } + + Result HostContentStorageImpl::ClearRegisteredPath() { + AMS_ABORT_UNLESS(spl::IsDevelopment()); + this->registered_content->ClearPaths(); + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/source/ncm/ncm_host_content_storage_impl.hpp b/libraries/libstratosphere/source/ncm/ncm_host_content_storage_impl.hpp new file mode 100644 index 000000000..66133547a --- /dev/null +++ b/libraries/libstratosphere/source/ncm/ncm_host_content_storage_impl.hpp @@ -0,0 +1,81 @@ +/* + * 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 . + */ +#pragma once +#include + +namespace ams::ncm { + + class HostContentStorageImpl { + protected: + RegisteredHostContent *registered_content; + bool disabled; + 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_3_0_0) { + 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(); + } + public: + HostContentStorageImpl(RegisteredHostContent *registered_content) : registered_content(registered_content), disabled(false) { /* ... */ } + ~HostContentStorageImpl(); + public: + /* Actual commands. */ + virtual Result GeneratePlaceHolderId(sf::Out out); + virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, s64 size); + virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id); + virtual Result HasPlaceHolder(sf::Out out, PlaceHolderId placeholder_id); + virtual Result WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, const sf::InBuffer &data); + virtual Result Register(PlaceHolderId placeholder_id, ContentId content_id); + virtual Result Delete(ContentId content_id); + virtual Result Has(sf::Out out, ContentId content_id); + virtual Result GetPath(sf::Out out, ContentId content_id); + virtual Result GetPlaceHolderPath(sf::Out out, PlaceHolderId placeholder_id); + virtual Result CleanupAllPlaceHolder(); + virtual Result ListPlaceHolder(sf::Out out_count, const sf::OutArray &out_buf); + virtual Result GetContentCount(sf::Out out_count); + virtual Result ListContentId(sf::Out out_count, const sf::OutArray &out_buf, s32 start_offset); + virtual Result GetSizeFromContentId(sf::Out out_size, ContentId content_id); + virtual Result DisableForcibly(); + virtual Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id); + virtual Result SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size); + virtual Result ReadContentIdFile(const sf::OutBuffer &buf, ContentId content_id, s64 offset); + virtual Result GetRightsIdFromPlaceHolderIdDeprecated(sf::Out out_rights_id, PlaceHolderId placeholder_id); + virtual Result GetRightsIdFromPlaceHolderId(sf::Out out_rights_id, PlaceHolderId placeholder_id); + virtual Result GetRightsIdFromContentIdDeprecated(sf::Out out_rights_id, ContentId content_id); + virtual Result GetRightsIdFromContentId(sf::Out out_rights_id, ContentId content_id); + virtual Result WriteContentForDebug(ContentId content_id, s64 offset, const sf::InBuffer &data); + virtual Result GetFreeSpaceSize(sf::Out out_size); + virtual Result GetTotalSpaceSize(sf::Out out_size); + virtual Result FlushPlaceHolder(); + virtual Result GetSizeFromPlaceHolderId(sf::Out out, PlaceHolderId placeholder_id); + virtual Result RepairInvalidFileAttribute(); + virtual Result GetRightsIdFromPlaceHolderIdWithCache(sf::Out out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id); + virtual Result RegisterPath(const ContentId &content_id, const Path &path); + virtual Result ClearRegisteredPath(); + }; + static_assert(ncm::IsIContentStorage); + +} diff --git a/libraries/libstratosphere/source/ncm/ncm_registered_host_content.cpp b/libraries/libstratosphere/source/ncm/ncm_registered_host_content.cpp new file mode 100644 index 000000000..007365462 --- /dev/null +++ b/libraries/libstratosphere/source/ncm/ncm_registered_host_content.cpp @@ -0,0 +1,86 @@ +/* + * 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 . + */ +#include + +namespace ams::ncm { + + class RegisteredHostContent::RegisteredPath : public util::IntrusiveListBaseNode { + NON_COPYABLE(RegisteredPath); + NON_MOVEABLE(RegisteredPath); + private: + ContentId content_id; + Path path; + public: + RegisteredPath(const ncm::ContentId &content_id, const Path &p) : content_id(content_id), path(p) { + /* ... */ + } + + ncm::ContentId GetContentId() const { + return this->content_id; + } + + void GetPath(Path *out) const { + *out = this->path; + } + + void SetPath(const Path &path) { + this->path = path; + } + }; + + Result RegisteredHostContent::RegisterPath(const ncm::ContentId &content_id, const ncm::Path &path) { + std::scoped_lock lk(this->mutex); + + /* Replace the path of any existing entries. */ + for (auto ®istered_path : this->path_list) { + if (registered_path.GetContentId() == content_id) { + registered_path.SetPath(path); + return ResultSuccess(); + } + } + + /* Allocate a new registered path. TODO: Verify allocator. */ + RegisteredPath *registered_path = new RegisteredPath(content_id, path); + R_UNLESS(registered_path != nullptr, ncm::ResultBufferInsufficient()); + + /* Insert the path into the list. */ + this->path_list.push_back(*registered_path); + return ResultSuccess(); + } + + Result RegisteredHostContent::GetPath(Path *out, const ncm::ContentId &content_id) { + std::scoped_lock lk(this->mutex); + + /* Obtain the path of the content. */ + for (const auto ®istered_path : this->path_list) { + if (registered_path.GetContentId() == content_id) { + registered_path.GetPath(out); + return ResultSuccess(); + } + } + return ncm::ResultContentNotFound(); + } + + void RegisteredHostContent::ClearPaths() { + /* Remove all paths. */ + for (auto it = this->path_list.begin(); it != this->path_list.end(); /* ... */) { + auto *obj = std::addressof(*it); + it = this->path_list.erase(it); + delete obj; + } + } + +}