aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorxbzk <xbzk@eden-emu.dev>2026-07-23 21:59:55 +0200
committercrueter <crueter@eden-emu.dev>2026-07-23 21:59:55 +0200
commitb85f289048fc4ea3636761788e3126ab2e79d591 (patch)
tree9b409e5a7646c852142d2ccb29414b0b0a04ba50 /src
parent89004124a55a5ce8c45a90dcb60afd8a107238c3 (diff)
[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 <lizzie@eden-emu.dev> Reviewed-by: Maufeat <sahyno1996@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/core/arm/nce/patcher.cpp19
-rw-r--r--src/core/arm/nce/patcher.h2
-rw-r--r--src/core/file_sys/registered_cache.cpp16
-rw-r--r--src/core/file_sys/registered_cache.h1
-rw-r--r--src/core/file_sys/vfs/vfs_real.cpp4
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp48
-rw-r--r--src/core/hle/service/ncm/ncm.cpp299
-rw-r--r--src/core/hle/service/ns/application_manager_interface.cpp52
-rw-r--r--src/core/hle/service/ns/application_manager_interface.h3
-rw-r--r--src/core/hle/service/spl/spl.cpp7
-rw-r--r--src/core/hle/service/spl/spl_module.cpp30
-rw-r--r--src/core/hle/service/spl/spl_module.h5
-rw-r--r--src/video_core/control/channel_state.cpp21
-rw-r--r--src/video_core/engines/puller.cpp17
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<const u8> 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<int>(mrs.GetRt())}, c_pre);
+ } else {
+ WriteCntfrqHandler(ret, oaknut::XReg{static_cast<int>(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<NcaID> 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 <typename T>
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<VirtualFile> 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 <algorithm>
+#include <array>
#include <memory>
+#include <vector>
+#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<IContentStorage> {
+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<FileSys::NcaID>();
+ content_id = rp.PopRaw<FileSys::NcaID>();
+ } else {
+ content_id = rp.PopRaw<FileSys::NcaID>();
+ placeholder_id = rp.PopRaw<FileSys::NcaID>();
+ }
+ const auto size = rp.Pop<s64>();
+
+ 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<u64>(size)));
+
+ if (succeeded) {
+ LOG_DEBUG(Service_NCM, "called, storage_id={}, size={}", static_cast<u32>(storage),
+ size);
+ } else {
+ LOG_WARNING(Service_NCM, "failed, storage_id={}, size={}", static_cast<u32>(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<FileSys::NcaID>();
+
+ 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<u32>(storage));
+ } else {
+ LOG_WARNING(Service_NCM, "failed, storage_id={}", static_cast<u32>(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<FileSys::NcaID>();
+ const auto offset = rp.Pop<u64>();
+ const auto data = ctx.ReadBuffer();
+
+ auto* const placeholder_cache =
+ system.GetFileSystemController().GetPlaceholderCacheForStorage(storage);
+ const std::vector<u8> 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<u32>(storage), offset, data.size());
+ } else {
+ LOG_WARNING(Service_NCM, "failed, storage_id={}, offset={}, size={}",
+ static_cast<u32>(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<FileSys::NcaID>();
+ content_id = rp.PopRaw<FileSys::NcaID>();
+ } else {
+ content_id = rp.PopRaw<FileSys::NcaID>();
+ placeholder_id = rp.PopRaw<FileSys::NcaID>();
+ }
+
+ 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<u32>(storage));
+ } else {
+ LOG_WARNING(Service_NCM, "failed, storage_id={}", static_cast<u32>(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<FileSys::NcaID>();
+
+ 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<u32>(storage));
+ } else {
+ LOG_WARNING(Service_NCM, "failed, storage_id={}", static_cast<u32>(storage));
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(succeeded ? ResultSuccess : ResultUnknown);
+ }
+
+ FileSys::StorageId storage;
+};
+
+class IContentMetaDatabase final : public ServiceFramework<IContentMetaDatabase> {
+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<u8, 2> padding;
+ };
+ static_assert(sizeof(ContentMetaKey) == 0x10);
+
+ void Set(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto key = rp.PopRaw<ContentMetaKey>();
+
+ 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<u32>(storage), key.id, key.version, static_cast<u8>(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<ContentMetaKey>();
+
+ 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<u32>(storage), key.id, key.version, static_cast<u8>(key.type));
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+
+ void Has(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto key = rp.PopRaw<ContentMetaKey>();
+
+ 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<u32>(storage), key.id, key.version, static_cast<u8>(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<u32>(storage));
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+
+ FileSys::StorageId storage;
+ std::vector<ContentMetaKey> entries;
+};
+
class LR final : public ServiceFramework<LR> {
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<FileSys::StorageId>();
+
+ LOG_DEBUG(Service_NCM, "called, storage_id={}", static_cast<u32>(storage_id));
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IContentStorage>(ctx, system, storage_id);
+ }
+
+ void OpenContentMetaDatabase(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto storage_id = rp.PopEnum<FileSys::StorageId>();
+
+ LOG_DEBUG(Service_NCM, "called, storage_id={}", static_cast<u32>(storage_id));
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IContentMetaDatabase>(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 <algorithm>
+#include <cstring>
#include <vector>
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<ApplicationViewV19, BufferAttr_HipcMapAlias> out_application_views,
InArray<u64, BufferAttr_HipcMapAlias> 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<bool> out_is_supported,
u64 process_id);
+ Result DeleteApplicationEntity(u64 application_id);
+ Result DeleteApplicationCompletely(u64 application_id);
Result GetStorageSize(Out<s64> out_total_space_size, Out<s64> 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> 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<KeySource>();
+ const auto generation = rp.Pop<u32>();
+ const auto option = rp.Pop<u32>();
+
+ 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<AccessKey>();
+ [[maybe_unused]] const auto key_source = rp.PopRaw<KeySource>();
+
+ 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<EngineID>(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<EngineID>(engine);
bound_engines[method_call.subchannel] = engine_id;
switch (engine_id) {
case EngineID::FERMI_TWOD_A: