From 4bc90fbe4fbc303870789a261cec6b6b9c8e4618 Mon Sep 17 00:00:00 2001 From: xbzk Date: Thu, 11 Jun 2026 09:54:28 -0300 Subject: [PATCH] [service, ncm] implement content storage install operations --- src/core/file_sys/registered_cache.cpp | 16 +++ src/core/file_sys/registered_cache.h | 1 + src/core/hle/service/ncm/ncm.cpp | 180 ++++++++++++++++++++++++- 3 files changed, 196 insertions(+), 1 deletion(-) diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index af41820a36..e7aa8251c6 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -981,6 +981,22 @@ bool RegisteredCache::RemoveExistingEntry(u64 title_id) const { return removed_data; } +bool RegisteredCache::Delete(const NcaID& id) const { + const auto path = GetRelativePathFromNcaID(id, false, true, false); + + const bool is_file = dir->GetFileRelative(path) != nullptr; + const bool is_dir = dir->GetDirectoryRelative(path) != nullptr; + + if (is_file) { + return dir->DeleteFile(path); + } + if (is_dir) { + return dir->DeleteSubdirectoryRecursive(path); + } + + return true; +} + InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy, bool overwrite_if_exists, std::optional override_id) { diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index 32134d1c48..0cc6cb56b4 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -188,6 +188,7 @@ public: // Removes an existing entry based on title id bool RemoveExistingEntry(u64 title_id) const; + bool Delete(const NcaID& id) const; private: template diff --git a/src/core/hle/service/ncm/ncm.cpp b/src/core/hle/service/ncm/ncm.cpp index 650666d6b7..85f7c0deab 100644 --- a/src/core/hle/service/ncm/ncm.cpp +++ b/src/core/hle/service/ncm/ncm.cpp @@ -1,9 +1,17 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include +#include +#include "core/core.h" +#include "core/file_sys/registered_cache.h" #include "core/file_sys/romfs_factory.h" +#include "core/hle/api_version.h" +#include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/ncm/ncm.h" #include "core/hle/service/server_manager.h" @@ -88,6 +96,163 @@ public: } }; +class IContentStorage final : public ServiceFramework { +public: + explicit IContentStorage(Core::System& system_, FileSys::StorageId id) + : ServiceFramework{system_, "IContentStorage"}, storage{id} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IContentStorage::GeneratePlaceHolderId, "GeneratePlaceHolderId"}, + {1, &IContentStorage::CreatePlaceHolder, "CreatePlaceHolder"}, + {2, &IContentStorage::DeletePlaceHolder, "DeletePlaceHolder"}, + {4, &IContentStorage::WritePlaceHolder, "WritePlaceHolder"}, + {5, &IContentStorage::Register, "Register"}, + {6, &IContentStorage::Delete, "Delete"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void GeneratePlaceHolderId(HLERequestContext& ctx) { + LOG_DEBUG(Service_NCM, "called"); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(ResultSuccess); + rb.PushRaw(FileSys::PlaceholderCache::Generate()); + } + + void CreatePlaceHolder(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + [[maybe_unused]] FileSys::NcaID content_id{}; + FileSys::NcaID placeholder_id{}; + if constexpr (HLE::ApiVersion::HOS_VERSION_MAJOR >= 16) { + placeholder_id = rp.PopRaw(); + content_id = rp.PopRaw(); + } else { + content_id = rp.PopRaw(); + placeholder_id = rp.PopRaw(); + } + const auto size = rp.Pop(); + + auto* const placeholder_cache = + system.GetFileSystemController().GetPlaceholderCacheForStorage(storage); + const bool succeeded = + placeholder_cache != nullptr && size >= 0 && + (placeholder_cache->Exists(placeholder_id) || + placeholder_cache->Create(placeholder_id, static_cast(size))); + + if (succeeded) { + LOG_DEBUG(Service_NCM, "called, storage_id={}, size={}", static_cast(storage), + size); + } else { + LOG_WARNING(Service_NCM, "failed, storage_id={}, size={}", static_cast(storage), + size); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(succeeded ? ResultSuccess : ResultUnknown); + } + + void DeletePlaceHolder(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto placeholder_id = rp.PopRaw(); + + auto* const placeholder_cache = + system.GetFileSystemController().GetPlaceholderCacheForStorage(storage); + const bool succeeded = + placeholder_cache != nullptr && + (!placeholder_cache->Exists(placeholder_id) || placeholder_cache->Delete(placeholder_id)); + + if (succeeded) { + LOG_DEBUG(Service_NCM, "called, storage_id={}", static_cast(storage)); + } else { + LOG_WARNING(Service_NCM, "failed, storage_id={}", static_cast(storage)); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(succeeded ? ResultSuccess : ResultUnknown); + } + + void WritePlaceHolder(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto placeholder_id = rp.PopRaw(); + const auto offset = rp.Pop(); + const auto data = ctx.ReadBuffer(); + + auto* const placeholder_cache = + system.GetFileSystemController().GetPlaceholderCacheForStorage(storage); + const std::vector write_data{data.begin(), data.end()}; + const bool succeeded = + placeholder_cache != nullptr && + placeholder_cache->Write(placeholder_id, offset, write_data); + + if (succeeded) { + LOG_DEBUG(Service_NCM, "called, storage_id={}, offset={}, size={}", + static_cast(storage), offset, data.size()); + } else { + LOG_WARNING(Service_NCM, "failed, storage_id={}, offset={}, size={}", + static_cast(storage), offset, data.size()); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(succeeded ? ResultSuccess : ResultUnknown); + } + + void Register(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + FileSys::NcaID content_id{}; + FileSys::NcaID placeholder_id{}; + if constexpr (HLE::ApiVersion::HOS_VERSION_MAJOR >= 16) { + placeholder_id = rp.PopRaw(); + content_id = rp.PopRaw(); + } else { + content_id = rp.PopRaw(); + placeholder_id = rp.PopRaw(); + } + + auto& fsc = system.GetFileSystemController(); + auto* const placeholder_cache = fsc.GetPlaceholderCacheForStorage(storage); + auto* const registered_cache = fsc.GetRegisteredCacheForStorage(storage); + const bool succeeded = + placeholder_cache != nullptr && registered_cache != nullptr && + placeholder_cache->Register(registered_cache, placeholder_id, content_id); + + if (succeeded) { + LOG_DEBUG(Service_NCM, "called, storage_id={}", static_cast(storage)); + } else { + LOG_WARNING(Service_NCM, "failed, storage_id={}", static_cast(storage)); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(succeeded ? ResultSuccess : ResultUnknown); + } + + void Delete(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto content_id = rp.PopRaw(); + + auto* const registered_cache = + system.GetFileSystemController().GetRegisteredCacheForStorage(storage); + const bool succeeded = registered_cache != nullptr && registered_cache->Delete(content_id); + if (succeeded) { + registered_cache->Refresh(); + } + + if (succeeded) { + LOG_DEBUG(Service_NCM, "called, storage_id={}", static_cast(storage)); + } else { + LOG_WARNING(Service_NCM, "failed, storage_id={}", static_cast(storage)); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(succeeded ? ResultSuccess : ResultUnknown); + } + + FileSys::StorageId storage; +}; + class LR final : public ServiceFramework { public: explicit LR(Core::System& system_) : ServiceFramework{system_, "lr"} { @@ -113,7 +278,7 @@ public: {1, nullptr, "CreateContentMetaDatabase"}, {2, nullptr, "VerifyContentStorage"}, {3, nullptr, "VerifyContentMetaDatabase"}, - {4, nullptr, "OpenContentStorage"}, + {4, &NCM::OpenContentStorage, "OpenContentStorage"}, {5, nullptr, "OpenContentMetaDatabase"}, {6, nullptr, "CloseContentStorageForcibly"}, {7, nullptr, "CloseContentMetaDatabaseForcibly"}, @@ -130,6 +295,19 @@ public: RegisterHandlers(functions); } + +private: + void OpenContentStorage(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto storage_id = rp.PopEnum(); + + LOG_DEBUG(Service_NCM, "called, storage_id={}", static_cast(storage_id)); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(system, storage_id); + } + }; void LoopProcess(Core::System& system) {