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: