From b85f289048fc4ea3636761788e3126ab2e79d591 Mon Sep 17 00:00:00 2001 From: xbzk Date: Thu, 23 Jul 2026 21:59:55 +0200 Subject: [PATCH] [nce, fs] tico support, fix FS bug that would nuke entire 'switch' folder (#4086) Contains the minimal set of functionalities to allow tico installer succeed, and tico work normally EXCEPT for game launching (which me or someone else will investigate later) First three commits are from PR 4012. The other six, kinda dizzy to explain each one. All were implemented based on tico source, switchbrew and libnx. Hopefully the commit messages will do. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4086 Reviewed-by: Lizzie Reviewed-by: Maufeat --- src/core/arm/nce/patcher.cpp | 19 +- src/core/arm/nce/patcher.h | 2 + src/core/file_sys/registered_cache.cpp | 16 + src/core/file_sys/registered_cache.h | 1 + src/core/file_sys/vfs/vfs_real.cpp | 4 +- .../hle/service/filesystem/filesystem.cpp | 48 ++- src/core/hle/service/ncm/ncm.cpp | 299 +++++++++++++++++- .../ns/application_manager_interface.cpp | 52 ++- .../ns/application_manager_interface.h | 3 + src/core/hle/service/spl/spl.cpp | 7 +- src/core/hle/service/spl/spl_module.cpp | 30 ++ src/core/hle/service/spl/spl_module.h | 5 + src/video_core/control/channel_state.cpp | 21 ++ src/video_core/engines/puller.cpp | 17 +- 14 files changed, 502 insertions(+), 22 deletions(-) diff --git a/src/core/arm/nce/patcher.cpp b/src/core/arm/nce/patcher.cpp index 79ff3b1e31..7d2041d4d2 100644 --- a/src/core/arm/nce/patcher.cpp +++ b/src/core/arm/nce/patcher.cpp @@ -145,7 +145,14 @@ bool Patcher::PatchText(std::span program_image, const Kernel::CodeSet // MRS Xn, CNTFRQ_EL0 if (auto mrs = MRS{inst}; mrs.Verify() && mrs.GetSystemReg() == CntfrqEl0) { - UNREACHABLE(); + bool pre_buffer = false; + auto ret = AddRelocations(pre_buffer); + if (pre_buffer) { + WriteCntfrqHandler(ret, oaknut::XReg{static_cast(mrs.GetRt())}, c_pre); + } else { + WriteCntfrqHandler(ret, oaknut::XReg{static_cast(mrs.GetRt())}, c); + } + continue; } // MSR TPIDR_EL0, Xn @@ -577,6 +584,16 @@ void Patcher::WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg, this->BranchToModule(module_dest); } +void Patcher::WriteCntfrqHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& cg) { + cg.MOV(dest_reg, Common::WallClock::CNTFRQ); + + // Jump back to the instruction after the emulated MRS. + if (&cg == &c_pre) + this->BranchToModulePre(module_dest); + else + this->BranchToModule(module_dest); +} + void Patcher::WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& cg) { #if defined(HAS_NCE) static Common::WallClock clock(false, 1); diff --git a/src/core/arm/nce/patcher.h b/src/core/arm/nce/patcher.h index 980a0c81e0..04d8534351 100644 --- a/src/core/arm/nce/patcher.h +++ b/src/core/arm/nce/patcher.h @@ -80,6 +80,7 @@ private: void WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id, oaknut::VectorCodeGenerator& code, oaknut::Label& save_ctx, oaknut::Label& load_ctx); void WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::SystemReg src_reg, oaknut::VectorCodeGenerator& code); void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg, oaknut::VectorCodeGenerator& code); + void WriteCntfrqHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& code); void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& code); // Convenience wrappers using default code generator @@ -90,6 +91,7 @@ private: void WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) { WriteSvcTrampoline(module_dest, svc_id, c, m_save_context, m_load_context); } void WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::SystemReg src_reg) { WriteMrsHandler(module_dest, dest_reg, src_reg, c); } void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg) { WriteMsrHandler(module_dest, src_reg, c); } + void WriteCntfrqHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg) { WriteCntfrqHandler(module_dest, dest_reg, c); } void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg) { WriteCntpctHandler(module_dest, dest_reg, c); } private: diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index ebecbaf74f..9e9db0ed4d 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -984,6 +984,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/file_sys/vfs/vfs_real.cpp b/src/core/file_sys/vfs/vfs_real.cpp index 887973d305..05774b67d5 100644 --- a/src/core/file_sys/vfs/vfs_real.cpp +++ b/src/core/file_sys/vfs/vfs_real.cpp @@ -440,7 +440,7 @@ VirtualDir RealVfsDirectory::CreateDirectoryRelative(std::string_view relative_p bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) { const auto full_path = FS::SanitizePath(this->path + '/' + std::string(name)); - return base.DeleteDirectory(full_path); + return FS::RemoveDirRecursively(full_path); } std::vector RealVfsDirectory::GetFiles() const { @@ -506,7 +506,7 @@ VirtualFile RealVfsDirectory::CreateFile(std::string_view name) { bool RealVfsDirectory::DeleteSubdirectory(std::string_view name) { const std::string subdir_path = (path + '/').append(name); - return base.DeleteDirectory(subdir_path); + return FS::RemoveDir(subdir_path); } bool RealVfsDirectory::DeleteFile(std::string_view name) { diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index c192fea454..f873ebe53e 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -43,6 +43,16 @@ static FileSys::VirtualDir GetDirectoryRelativeWrapped(FileSys::VirtualDir base, return base->GetDirectoryRelative(dir_name); } +static std::string_view GetGuestParentPath(std::string_view path) { + const auto name_index = path.find_last_of("\\/"); + return name_index == std::string_view::npos ? std::string_view{} : path.substr(0, name_index); +} + +static std::string_view GetGuestFilename(std::string_view path) { + const auto name_index = path.find_last_of("\\/"); + return name_index == std::string_view::npos ? path : path.substr(name_index + 1); +} + VfsDirectoryServiceWrapper::VfsDirectoryServiceWrapper(FileSys::VirtualDir backing_) : backing(std::move(backing_)) {} @@ -83,11 +93,12 @@ Result VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) const { return ResultSuccess; } - auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); - if (dir == nullptr || dir->GetFile(Common::FS::GetFilename(path)) == nullptr) { + const auto filename = GetGuestFilename(path); + auto dir = GetDirectoryRelativeWrapped(backing, GetGuestParentPath(path)); + if (filename.empty() || dir == nullptr || dir->GetFile(filename) == nullptr) { return FileSys::ResultPathNotFound; } - if (!dir->DeleteFile(Common::FS::GetFilename(path))) { + if (!dir->DeleteFile(filename)) { // TODO(DarkLordZach): Find a better error code for this return ResultUnknown; } @@ -117,8 +128,19 @@ Result VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_) con Result VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path_) const { std::string path(Common::FS::SanitizePath(path_)); - auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); - if (!dir->DeleteSubdirectory(Common::FS::GetFilename(path))) { + const auto dirname = GetGuestFilename(path); + auto dir = GetDirectoryRelativeWrapped(backing, GetGuestParentPath(path)); + FileSys::VirtualDir target{}; + if (!dirname.empty() && dir != nullptr) { + target = dir->GetSubdirectory(dirname); + } + if (target == nullptr) { + return FileSys::ResultPathNotFound; + } + if (!target->GetFiles().empty() || !target->GetSubdirectories().empty()) { + return ResultUnknown; + } + if (!dir->DeleteSubdirectory(dirname)) { // TODO(DarkLordZach): Find a better error code for this return ResultUnknown; } @@ -127,8 +149,12 @@ Result VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path_) con Result VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::string& path_) const { std::string path(Common::FS::SanitizePath(path_)); - auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); - if (!dir->DeleteSubdirectoryRecursive(Common::FS::GetFilename(path))) { + const auto dirname = GetGuestFilename(path); + auto dir = GetDirectoryRelativeWrapped(backing, GetGuestParentPath(path)); + if (dirname.empty() || dir == nullptr || dir->GetSubdirectory(dirname) == nullptr) { + return FileSys::ResultPathNotFound; + } + if (!dir->DeleteSubdirectoryRecursive(dirname)) { // TODO(DarkLordZach): Find a better error code for this return ResultUnknown; } @@ -137,9 +163,13 @@ Result VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::string& Result VfsDirectoryServiceWrapper::CleanDirectoryRecursively(const std::string& path) const { const std::string sanitized_path(Common::FS::SanitizePath(path)); - auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(sanitized_path)); + const auto dirname = GetGuestFilename(sanitized_path); + auto dir = GetDirectoryRelativeWrapped(backing, GetGuestParentPath(sanitized_path)); - if (!dir->CleanSubdirectoryRecursive(Common::FS::GetFilename(sanitized_path))) { + if (dirname.empty() || dir == nullptr || dir->GetSubdirectory(dirname) == nullptr) { + return FileSys::ResultPathNotFound; + } + if (!dir->CleanSubdirectoryRecursive(dirname)) { // TODO(DarkLordZach): Find a better error code for this return ResultUnknown; } diff --git a/src/core/hle/service/ncm/ncm.cpp b/src/core/hle/service/ncm/ncm.cpp index 650666d6b7..f787da9825 100644 --- a/src/core/hle/service/ncm/ncm.cpp +++ b/src/core/hle/service/ncm/ncm.cpp @@ -1,9 +1,19 @@ +// 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" @@ -88,6 +98,268 @@ 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 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"} { @@ -113,8 +385,8 @@ public: {1, nullptr, "CreateContentMetaDatabase"}, {2, nullptr, "VerifyContentStorage"}, {3, nullptr, "VerifyContentMetaDatabase"}, - {4, nullptr, "OpenContentStorage"}, - {5, nullptr, "OpenContentMetaDatabase"}, + {4, &NCM::OpenContentStorage, "OpenContentStorage"}, + {5, &NCM::OpenContentMetaDatabase, "OpenContentMetaDatabase"}, {6, nullptr, "CloseContentStorageForcibly"}, {7, nullptr, "CloseContentMetaDatabaseForcibly"}, {8, nullptr, "CleanupContentMetaDatabase"}, @@ -130,6 +402,29 @@ 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(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); + } }; void LoopProcess(Core::System& system) { diff --git a/src/core/hle/service/ns/application_manager_interface.cpp b/src/core/hle/service/ns/application_manager_interface.cpp index 485eac0afa..75c41e1680 100644 --- a/src/core/hle/service/ns/application_manager_interface.cpp +++ b/src/core/hle/service/ns/application_manager_interface.cpp @@ -9,6 +9,7 @@ #include "core/file_sys/registered_cache.h" #include "core/hle/service/cmif_serialization.h" #include "core/hle/service/filesystem/filesystem.h" +#include "core/hle/service/ipc_helpers.h" #include "core/hle/service/ns/application_manager_interface.h" #include "core/file_sys/content_archive.h" @@ -19,6 +20,7 @@ #include "core/launch_timestamp_cache.h" #include +#include #include namespace Service::NS { @@ -36,14 +38,14 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_ {1, nullptr, "GenerateApplicationRecordCount"}, {2, D<&IApplicationManagerInterface::GetApplicationRecordUpdateSystemEvent>, "GetApplicationRecordUpdateSystemEvent"}, {3, nullptr, "GetApplicationViewDeprecated"}, - {4, nullptr, "DeleteApplicationEntity"}, - {5, nullptr, "DeleteApplicationCompletely"}, + {4, D<&IApplicationManagerInterface::DeleteApplicationEntity>, "DeleteApplicationEntity"}, + {5, D<&IApplicationManagerInterface::DeleteApplicationCompletely>, "DeleteApplicationCompletely"}, {6, nullptr, "IsAnyApplicationEntityRedundant"}, {7, nullptr, "DeleteRedundantApplicationEntity"}, {8, nullptr, "IsApplicationEntityMovable"}, {9, nullptr, "MoveApplicationEntity"}, {11, nullptr, "CalculateApplicationOccupiedSize"}, - {16, nullptr, "PushApplicationRecord"}, + {16, &IApplicationManagerInterface::PushApplicationRecord, "PushApplicationRecord"}, {17, nullptr, "ListApplicationRecordContentMeta"}, {19, nullptr, "LaunchApplicationOld"}, {21, nullptr, "GetApplicationContentPath"}, @@ -643,6 +645,27 @@ Result IApplicationManagerInterface::IsAnyApplicationEntityInstalled( R_SUCCEED(); } +Result IApplicationManagerInterface::DeleteApplicationEntity(u64 application_id) { + LOG_DEBUG(Service_NS, "called, application_id={:016X}", application_id); + + auto& fsc = system.GetFileSystemController(); + if (auto* const user_cache = fsc.GetUserNANDContents(); user_cache != nullptr) { + user_cache->RemoveExistingEntry(application_id); + user_cache->Refresh(); + } + if (auto* const sdmc_cache = fsc.GetSDMCContents(); sdmc_cache != nullptr) { + sdmc_cache->RemoveExistingEntry(application_id); + sdmc_cache->Refresh(); + } + + record_update_system_event.Signal(system.Kernel()); + R_SUCCEED(); +} + +Result IApplicationManagerInterface::DeleteApplicationCompletely(u64 application_id) { + R_RETURN(DeleteApplicationEntity(application_id)); +} + Result IApplicationManagerInterface::GetApplicationViewDeprecated( OutArray out_application_views, InArray application_ids) { @@ -843,6 +866,29 @@ Result IApplicationManagerInterface::Unknown4053() { R_SUCCEED(); } +void IApplicationManagerInterface::PushApplicationRecord(HLERequestContext& ctx) { + const auto record = ctx.ReadBuffer(); + u64 application_id{}; + if (record.size() >= sizeof(application_id)) { + std::memcpy(&application_id, record.data(), sizeof(application_id)); + } + + LOG_DEBUG(Service_NS, "called, application_id={:016X}, size={}", application_id, record.size()); + + auto& fsc = system.GetFileSystemController(); + if (auto* const user_cache = fsc.GetUserNANDContents(); user_cache != nullptr) { + user_cache->Refresh(); + } + if (auto* const sdmc_cache = fsc.GetSDMCContents(); sdmc_cache != nullptr) { + sdmc_cache->Refresh(); + } + + record_update_system_event.Signal(system.Kernel()); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + void IApplicationManagerInterface::ListApplicationTitle(HLERequestContext& ctx) { LOG_DEBUG(Service_NS, "called"); IReadOnlyApplicationControlDataInterface(system).ListApplicationTitle(ctx); diff --git a/src/core/hle/service/ns/application_manager_interface.h b/src/core/hle/service/ns/application_manager_interface.h index 245d59a068..a02e487416 100644 --- a/src/core/hle/service/ns/application_manager_interface.h +++ b/src/core/hle/service/ns/application_manager_interface.h @@ -56,6 +56,8 @@ public: Result ResumeAll(); Result IsQualificationTransitionSupportedByProcessId(Out out_is_supported, u64 process_id); + Result DeleteApplicationEntity(u64 application_id); + Result DeleteApplicationCompletely(u64 application_id); Result GetStorageSize(Out out_total_space_size, Out out_free_space_size, FileSys::StorageId storage_id); Result TouchApplication(u64 application_id); @@ -74,6 +76,7 @@ public: Result RequestDownloadApplicationControlDataInBackground(u64 control_source, u64 application_id); + void PushApplicationRecord(HLERequestContext& ctx); void ListApplicationTitle(HLERequestContext& ctx); private: diff --git a/src/core/hle/service/spl/spl.cpp b/src/core/hle/service/spl/spl.cpp index fde212186f..377c602610 100644 --- a/src/core/hle/service/spl/spl.cpp +++ b/src/core/hle/service/spl/spl.cpp @@ -1,3 +1,6 @@ +// 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 @@ -28,9 +31,9 @@ SPL_MIG::SPL_MIG(Core::System& system_, std::shared_ptr module_) static const FunctionInfo functions[] = { {0, &SPL::GetConfig, "GetConfig"}, {1, &SPL::ModularExponentiate, "ModularExponentiate"}, - {2, nullptr, "GenerateAesKek"}, + {2, &SPL::GenerateAesKek, "GenerateAesKek"}, {3, nullptr, "LoadAesKey"}, - {4, nullptr, "GenerateAesKey"}, + {4, &SPL::GenerateAesKey, "GenerateAesKey"}, {5, &SPL::SetConfig, "SetConfig"}, {7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"}, {11, &SPL::IsDevelopment, "IsDevelopment"}, diff --git a/src/core/hle/service/spl/spl_module.cpp b/src/core/hle/service/spl/spl_module.cpp index 17c9f8a887..d62cecf9a2 100644 --- a/src/core/hle/service/spl/spl_module.cpp +++ b/src/core/hle/service/spl/spl_module.cpp @@ -59,6 +59,36 @@ void Module::Interface::ModularExponentiate(HLERequestContext& ctx) { rb.Push(ResultSecureMonitorNotImplemented); } +void Module::Interface::GenerateAesKek(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + [[maybe_unused]] const auto key_source = rp.PopRaw(); + const auto generation = rp.Pop(); + const auto option = rp.Pop(); + + LOG_WARNING(Service_SPL, "(STUBBED) called, generation={:#x}, option={:#x}", generation, + option); + + AccessKey access_key{}; + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(ResultSuccess); + rb.PushRaw(access_key); +} + +void Module::Interface::GenerateAesKey(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + [[maybe_unused]] const auto access_key = rp.PopRaw(); + [[maybe_unused]] const auto key_source = rp.PopRaw(); + + LOG_WARNING(Service_SPL, "(STUBBED) called"); + + AesKey aes_key{}; + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(ResultSuccess); + rb.PushRaw(aes_key); +} + void Module::Interface::SetConfig(HLERequestContext& ctx) { UNIMPLEMENTED_MSG("SetConfig is not implemented!"); diff --git a/src/core/hle/service/spl/spl_module.h b/src/core/hle/service/spl/spl_module.h index 06dcffa6c4..6cf2b49f39 100644 --- a/src/core/hle/service/spl/spl_module.h +++ b/src/core/hle/service/spl/spl_module.h @@ -1,3 +1,6 @@ +// 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 @@ -25,6 +28,8 @@ public: // General void GetConfig(HLERequestContext& ctx); void ModularExponentiate(HLERequestContext& ctx); + void GenerateAesKek(HLERequestContext& ctx); + void GenerateAesKey(HLERequestContext& ctx); void SetConfig(HLERequestContext& ctx); void GenerateRandomBytes(HLERequestContext& ctx); void IsDevelopment(HLERequestContext& ctx); diff --git a/src/video_core/control/channel_state.cpp b/src/video_core/control/channel_state.cpp index c6f2c3c2ca..12a452dfc0 100644 --- a/src/video_core/control/channel_state.cpp +++ b/src/video_core/control/channel_state.cpp @@ -16,6 +16,26 @@ #include "video_core/memory_manager.h" namespace Tegra::Control { +namespace { + +// Match NVK/Nouveau's initial pushbuffer subchannel layout. +constexpr u32 Nvk3DSubchannel = 0; +constexpr u32 NvkComputeSubchannel = 1; +constexpr u32 Nvk2DSubchannel = 3; +constexpr u32 NvkCopySubchannel = 4; + +void BindNvkDefaultSubchannels(ChannelState::Payload& payload) { + auto& dma_pusher = payload.dma_pusher; + dma_pusher.BindSubchannel(&payload.maxwell_3d, Nvk3DSubchannel, Engines::EngineTypes::Maxwell3D); + dma_pusher.BindSubchannel(&payload.kepler_compute, NvkComputeSubchannel, + Engines::EngineTypes::KeplerCompute); + // Subchannel 2 is M2MF there; Eden does not expose a 0x9039 engine yet. + dma_pusher.BindSubchannel(&payload.fermi_2d, Nvk2DSubchannel, Engines::EngineTypes::Fermi2D); + dma_pusher.BindSubchannel(&payload.maxwell_dma, NvkCopySubchannel, + Engines::EngineTypes::MaxwellDMA); +} + +} // Anonymous namespace ChannelState::Payload::Payload(Core::System& system, MemoryManager& memory_manager, ChannelState& channel_state) : maxwell_3d(memory_manager) @@ -35,6 +55,7 @@ void ChannelState::Init(Core::System& system, u64 program_id_) { ASSERT(memory_manager); program_id = program_id_; payload.emplace(system, *memory_manager, *this); + BindNvkDefaultSubchannels(*payload); initialized = true; } diff --git a/src/video_core/engines/puller.cpp b/src/video_core/engines/puller.cpp index 5816b5901a..0b05d31718 100644 --- a/src/video_core/engines/puller.cpp +++ b/src/video_core/engines/puller.cpp @@ -22,10 +22,21 @@ namespace Tegra::Engines { +namespace { + +constexpr u32 Gf100BindClassMask = 0xffff; +constexpr u32 Gf100BindValidMask = 0x1f0000 | Gf100BindClassMask; + +} // Anonymous namespace + void Puller::ProcessBindMethod(DmaPusher& dma_pusher, const MethodCall& method_call) { - // Bind the current subchannel to the desired engine id. - LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {}", method_call.subchannel, method_call.argument); - const auto engine_id = static_cast(method_call.argument); + LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {:#x}", method_call.subchannel, method_call.argument); + u32 engine = method_call.argument; + if ((engine & ~Gf100BindClassMask) != 0 && (engine & ~Gf100BindValidMask) == 0) { + engine &= Gf100BindClassMask; + } + + const auto engine_id = static_cast(engine); bound_engines[method_call.subchannel] = engine_id; switch (engine_id) { case EngineID::FERMI_TWOD_A: