// 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 #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" #include "core/hle/service/service.h" namespace Service::NCM { class ILocationResolver final : public ServiceFramework { public: explicit ILocationResolver(Core::System& system_, FileSys::StorageId id) : ServiceFramework{system_, "ILocationResolver"}, storage{id} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "ResolveProgramPath"}, {1, nullptr, "RedirectProgramPath"}, {2, nullptr, "ResolveApplicationControlPath"}, {3, nullptr, "ResolveApplicationHtmlDocumentPath"}, {4, nullptr, "ResolveDataPath"}, {5, nullptr, "RedirectApplicationControlPath"}, {6, nullptr, "RedirectApplicationHtmlDocumentPath"}, {7, nullptr, "ResolveApplicationLegalInformationPath"}, {8, nullptr, "RedirectApplicationLegalInformationPath"}, {9, nullptr, "Refresh"}, {10, nullptr, "RedirectApplicationProgramPath"}, {11, nullptr, "ClearApplicationRedirection"}, {12, nullptr, "EraseProgramRedirection"}, {13, nullptr, "EraseApplicationControlRedirection"}, {14, nullptr, "EraseApplicationHtmlDocumentRedirection"}, {15, nullptr, "EraseApplicationLegalInformationRedirection"}, {16, nullptr, "ResolveProgramPathForDebug"}, {17, nullptr, "RedirectProgramPathForDebug"}, {18, nullptr, "RedirectApplicationProgramPathForDebug"}, {19, nullptr, "EraseProgramRedirectionForDebug"}, }; // clang-format on RegisterHandlers(functions); } private: [[maybe_unused]] FileSys::StorageId storage; }; class IRegisteredLocationResolver final : public ServiceFramework { public: explicit IRegisteredLocationResolver(Core::System& system_) : ServiceFramework{system_, "IRegisteredLocationResolver"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "ResolveProgramPath"}, {1, nullptr, "RegisterProgramPath"}, {2, nullptr, "UnregisterProgramPath"}, {3, nullptr, "RedirectProgramPath"}, {4, nullptr, "ResolveHtmlDocumentPath"}, {5, nullptr, "RegisterHtmlDocumentPath"}, {6, nullptr, "UnregisterHtmlDocumentPath"}, {7, nullptr, "RedirectHtmlDocumentPath"}, {8, nullptr, "Refresh"}, {9, nullptr, "RefreshExcluding"}, }; // clang-format on RegisterHandlers(functions); } }; class IAddOnContentLocationResolver final : public ServiceFramework { public: explicit IAddOnContentLocationResolver(Core::System& system_) : ServiceFramework{system_, "IAddOnContentLocationResolver"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "ResolveAddOnContentPath"}, {1, nullptr, "RegisterAddOnContentStorage"}, {2, nullptr, "UnregisterAllAddOnContentPath"}, {3, nullptr, "RefreshApplicationAddOnContent"}, {4, nullptr, "UnregisterApplicationAddOnContent"}, }; // clang-format on RegisterHandlers(functions); } }; 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 IContentMetaDatabase final : public ServiceFramework { public: explicit IContentMetaDatabase(Core::System& system_, FileSys::StorageId id) : ServiceFramework{system_, "IContentMetaDatabase"}, storage{id} { // clang-format off static const FunctionInfo functions[] = { {0, &IContentMetaDatabase::Set, "Set"}, {2, &IContentMetaDatabase::Remove, "Remove"}, {8, &IContentMetaDatabase::Has, "Has"}, {15, &IContentMetaDatabase::Commit, "Commit"}, }; // clang-format on RegisterHandlers(functions); } private: struct ContentMetaKey { u64 id; u32 version; FileSys::TitleType type; u8 install_type; std::array padding; }; static_assert(sizeof(ContentMetaKey) == 0x10); void Set(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto key = rp.PopRaw(); const auto entry_matches = [&key](const ContentMetaKey& entry) { return entry.id == key.id && entry.version == key.version && entry.type == key.type && entry.install_type == key.install_type; }; if (std::find_if(entries.begin(), entries.end(), entry_matches) == entries.end()) { entries.push_back(key); } LOG_DEBUG(Service_NCM, "called, storage_id={}, title_id={:016X}, version={}, type={}, size={}", static_cast(storage), key.id, key.version, static_cast(key.type), ctx.GetReadBufferSize()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void Remove(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto key = rp.PopRaw(); std::erase_if(entries, [&key](const ContentMetaKey& entry) { return entry.id == key.id && entry.version == key.version && entry.type == key.type && entry.install_type == key.install_type; }); LOG_DEBUG(Service_NCM, "called, storage_id={}, title_id={:016X}, version={}, type={}", static_cast(storage), key.id, key.version, static_cast(key.type)); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void Has(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto key = rp.PopRaw(); const bool has_pending = std::find_if(entries.begin(), entries.end(), [&key](const ContentMetaKey& entry) { return entry.id == key.id && entry.version == key.version && entry.type == key.type && entry.install_type == key.install_type; }) != entries.end(); auto* const registered_cache = system.GetFileSystemController().GetRegisteredCacheForStorage(storage); const bool has_registered = registered_cache != nullptr && registered_cache->HasEntry(key.id, FileSys::ContentRecordType::Meta); LOG_DEBUG(Service_NCM, "called, storage_id={}, title_id={:016X}, version={}, type={}, has={}", static_cast(storage), key.id, key.version, static_cast(key.type), has_pending || has_registered); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); rb.Push(has_pending || has_registered); } void Commit(HLERequestContext& ctx) { auto* const registered_cache = system.GetFileSystemController().GetRegisteredCacheForStorage(storage); if (registered_cache != nullptr) { registered_cache->Refresh(); } LOG_DEBUG(Service_NCM, "called, storage_id={}", static_cast(storage)); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } FileSys::StorageId storage; std::vector entries; }; class LR final : public ServiceFramework { public: explicit LR(Core::System& system_) : ServiceFramework{system_, "lr"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "OpenLocationResolver"}, {1, nullptr, "OpenRegisteredLocationResolver"}, {2, nullptr, "RefreshLocationResolver"}, {3, nullptr, "OpenAddOnContentLocationResolver"}, }; // clang-format on RegisterHandlers(functions); } }; class NCM final : public ServiceFramework { public: explicit NCM(Core::System& system_) : ServiceFramework{system_, "ncm"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CreateContentStorage"}, {1, nullptr, "CreateContentMetaDatabase"}, {2, nullptr, "VerifyContentStorage"}, {3, nullptr, "VerifyContentMetaDatabase"}, {4, &NCM::OpenContentStorage, "OpenContentStorage"}, {5, &NCM::OpenContentMetaDatabase, "OpenContentMetaDatabase"}, {6, nullptr, "CloseContentStorageForcibly"}, {7, nullptr, "CloseContentMetaDatabaseForcibly"}, {8, nullptr, "CleanupContentMetaDatabase"}, {9, nullptr, "ActivateContentStorage"}, {10, nullptr, "InactivateContentStorage"}, {11, nullptr, "ActivateContentMetaDatabase"}, {12, nullptr, "InactivateContentMetaDatabase"}, {13, nullptr, "InvalidateRightsIdCache"}, {14, nullptr, "GetMemoryReport"}, {15, nullptr, "ActivateFsContentStorage"}, }; // clang-format on 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(ctx, system, storage_id); } void OpenContentMetaDatabase(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(ctx, system, storage_id); } }; class NCM_V final : public ServiceFramework { public: explicit NCM_V(Core::System& system_) : ServiceFramework{system_, "ncm:v"} { static const FunctionInfo functions[] = { {0, nullptr, "GetSystemVersion"}, }; RegisterHandlers(functions); } }; void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); server_manager->RegisterNamedService("lr", std::make_shared(system)); server_manager->RegisterNamedService("ncm", std::make_shared(system)); if (1 /* not retail */) { server_manager->RegisterNamedService("ncm:v", std::make_shared(system)); } ServerManager::RunServer(std::move(server_manager)); } } // namespace Service::NCM