diff options
Diffstat (limited to 'src/core')
56 files changed, 1947 insertions, 400 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index fd45642876..eeb3202d12 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -531,6 +531,7 @@ add_library(core STATIC hle/service/audio/audio_out.h hle/service/audio/audio_renderer_manager.cpp hle/service/audio/audio_renderer_manager.h + hle/service/audio/nx2_audio_device_manager.h hle/service/audio/nx2_audio_user_client.h hle/service/audio/audio_renderer.cpp hle/service/audio/audio_renderer.h diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index a59aa3cac3..3a875493c6 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -416,6 +416,10 @@ void ArmDynarmic32::ClearInstructionCache() { } void ArmDynarmic32::InvalidateCacheRange(u64 addr, std::size_t size) { + // Drop the cached code page too: blocks recompiled after the invalidation would + // otherwise still decode the old instructions (e.g. a gdbstub breakpoint written + // into or removed from that page). + m_cb->last_code_addr = u64(-1); m_jit->InvalidateCacheRange(static_cast<u32>(addr), size); } diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index e65e38dd9e..c5be4d2520 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp @@ -481,6 +481,10 @@ void ArmDynarmic64::ClearInstructionCache() { } void ArmDynarmic64::InvalidateCacheRange(u64 addr, std::size_t size) { + // Drop the cached code page too: blocks recompiled after the invalidation would + // otherwise still decode the old instructions (e.g. a gdbstub breakpoint written + // into or removed from that page). + m_cb->last_code_addr = u64(-1); m_jit->InvalidateCacheRange(addr, size); } diff --git a/src/core/device_memory_manager.h b/src/core/device_memory_manager.h index 27ff342213..d275bdbf2d 100644 --- a/src/core/device_memory_manager.h +++ b/src/core/device_memory_manager.h @@ -18,6 +18,7 @@ #include "common/common_types.h" #include "common/range_mutex.h" #include "common/scratch_buffer.h" +#include "common/thread_worker.h" #include "common/sparse_large_vector.h" namespace Core { @@ -53,6 +54,10 @@ public: void BindInterface(DeviceInterface* device_inter); + /// Marks the memory behind [address, address + size) cold (Common::HostMemory::HintCold), + /// from a low-priority thread: data the host GPU has its own copy of, e.g. uploaded textures. + void HintCold(DAddr address, size_t size); + DAddr Allocate(size_t size); void AllocateFixed(DAddr start, size_t size); void Free(DAddr start, size_t size); @@ -197,7 +202,9 @@ private: std::unique_ptr<DeviceMemoryManagerAllocator<Traits>> impl; const uintptr_t physical_base; + const DeviceMemory* device_memory_ptr; DeviceInterface* device_inter; + std::unique_ptr<Common::ThreadWorker> cold_hint_worker; struct TrackedEntry { VAddr cpu_backing_address; diff --git a/src/core/device_memory_manager.inc b/src/core/device_memory_manager.inc index 83faa1b3f3..3523e3cf1c 100644 --- a/src/core/device_memory_manager.inc +++ b/src/core/device_memory_manager.inc @@ -171,6 +171,7 @@ struct DeviceMemoryManagerAllocator { template <typename Traits> DeviceMemoryManager<Traits>::DeviceMemoryManager(const DeviceMemory& device_memory_) : physical_base{uintptr_t(device_memory_.buffer.BackingBasePointer())} + , device_memory_ptr{&device_memory_} , device_inter{nullptr} , compressed_device_addr(1ULL << ((Settings::values.memory_layout_mode.GetValue() == Settings::MemoryLayout::Memory_4Gb ? physical_min_bits : physical_max_bits) - Memory::YUZU_PAGEBITS)) , tracked_entries(device_as_size >> Memory::YUZU_PAGEBITS) @@ -183,6 +184,49 @@ template <typename Traits> DeviceMemoryManager<Traits>::~DeviceMemoryManager() = default; template <typename Traits> +void DeviceMemoryManager<Traits>::HintCold(DAddr address, size_t size) { + struct Run { + u64 physical_offset; + VAddr virtual_address; ///< 0: no CPU mapping known + u64 size; + }; + std::vector<Run> runs; + const size_t page_end = Common::DivCeil(address + size, Memory::YUZU_PAGESIZE); + for (size_t page = address >> Memory::YUZU_PAGEBITS; page < page_end; ++page) { + const u32 compressed = tracked_entries[page].compressed_physical_ptr; + if (compressed == 0) { + continue; + } + const u64 physical = u64{compressed - 1} << Memory::YUZU_PAGEBITS; + const VAddr vaddr = ExtractCPUBacking(page).second; + if (!runs.empty()) { + Run& last = runs.back(); + const bool contiguous_view = + last.virtual_address == 0 ? vaddr == 0 : last.virtual_address + last.size == vaddr; + if (last.physical_offset + last.size == physical && contiguous_view) { + last.size += Memory::YUZU_PAGESIZE; + continue; + } + } + runs.push_back({physical, vaddr, Memory::YUZU_PAGESIZE}); + } + if (runs.empty()) { + return; + } + if (!cold_hint_worker) { + cold_hint_worker = std::make_unique<Common::ThreadWorker>(1, "DeviceMemory:ColdHint"); + } + cold_hint_worker->QueueWork([this, runs = std::move(runs)] { + for (const Run& run : runs) { + device_memory_ptr->buffer.HintCold( + run.physical_offset, run.size, + run.virtual_address != 0 ? std::optional<size_t>{run.virtual_address} + : std::nullopt); + } + }); +} + +template <typename Traits> void DeviceMemoryManager<Traits>::BindInterface(DeviceInterface* device_inter_) { device_inter = device_inter_; } diff --git a/src/core/file_sys/fsmitm_romfsbuild.cpp b/src/core/file_sys/fsmitm_romfsbuild.cpp index 8807bbd0f7..d34e24f177 100644 --- a/src/core/file_sys/fsmitm_romfsbuild.cpp +++ b/src/core/file_sys/fsmitm_romfsbuild.cpp @@ -55,15 +55,18 @@ static_assert(sizeof(RomFSFileEntry) == 0x20, "RomFSFileEntry has incorrect size struct RomFSBuildFileContext; +// The context's directories and files own the nodes; the links between them don't, or every +// parent and child would own each other and no build would ever be freed (title 1's loose RomFS: +// 206k files, ~50 MiB per build). struct RomFSBuildDirectoryContext { std::string path; u32 cur_path_ofs = 0; u32 path_len = 0; u32 entry_offset = 0; - std::shared_ptr<RomFSBuildDirectoryContext> parent; - std::shared_ptr<RomFSBuildDirectoryContext> child; - std::shared_ptr<RomFSBuildDirectoryContext> sibling; - std::shared_ptr<RomFSBuildFileContext> file; + RomFSBuildDirectoryContext* parent = nullptr; + RomFSBuildDirectoryContext* child = nullptr; + RomFSBuildDirectoryContext* sibling = nullptr; + RomFSBuildFileContext* file = nullptr; }; struct RomFSBuildFileContext { @@ -73,8 +76,8 @@ struct RomFSBuildFileContext { u32 entry_offset = 0; u64 offset = 0; u64 size = 0; - std::shared_ptr<RomFSBuildDirectoryContext> parent; - std::shared_ptr<RomFSBuildFileContext> sibling; + RomFSBuildDirectoryContext* parent = nullptr; + RomFSBuildFileContext* sibling = nullptr; VirtualFile source; }; @@ -168,7 +171,7 @@ bool RomFSBuildContext::AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> num_dirs++; dir_table_size += sizeof(RomFSDirectoryEntry) + Common::AlignUp(dir_ctx->path_len - dir_ctx->cur_path_ofs, 4); - dir_ctx->parent = std::move(parent_dir_ctx); + dir_ctx->parent = parent_dir_ctx.get(); directories.emplace_back(std::move(dir_ctx)); return true; @@ -180,7 +183,7 @@ bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> pare num_files++; file_table_size += sizeof(RomFSFileEntry) + Common::AlignUp(file_ctx->path_len - file_ctx->cur_path_ofs, 4); - file_ctx->parent = std::move(parent_dir_ctx); + file_ctx->parent = parent_dir_ctx.get(); files.emplace_back(std::move(file_ctx)); return true; @@ -245,11 +248,11 @@ std::vector<std::pair<u64, VirtualFile>> RomFSBuildContext::Build() { Common::AlignUp(cur_file->path_len - cur_file->cur_path_ofs, 4)); prev_file = cur_file; } - // Assign deferred parent/sibling ownership. + // Assign deferred parent/sibling links. for (auto it = files.rbegin(); it != files.rend(); ++it) { auto& cur_file = *it; cur_file->sibling = cur_file->parent->file; - cur_file->parent->file = cur_file; + cur_file->parent->file = cur_file.get(); } // Determine directory offsets. @@ -260,11 +263,11 @@ std::vector<std::pair<u64, VirtualFile>> RomFSBuildContext::Build() { static_cast<u32>(sizeof(RomFSDirectoryEntry) + Common::AlignUp(cur_dir->path_len - cur_dir->cur_path_ofs, 4)); } - // Assign deferred parent/sibling ownership. + // Assign deferred parent/sibling links. for (auto it = directories.rbegin(); (*it) != root; ++it) { auto& cur_dir = *it; cur_dir->sibling = cur_dir->parent->child; - cur_dir->parent->child = cur_dir; + cur_dir->parent->child = cur_dir.get(); } // Create output map. diff --git a/src/core/file_sys/system_archive/system_archive.cpp b/src/core/file_sys/system_archive/system_archive.cpp index 1743d01b12..c3931b6f62 100644 --- a/src/core/file_sys/system_archive/system_archive.cpp +++ b/src/core/file_sys/system_archive/system_archive.cpp @@ -4,6 +4,8 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include <cstdlib> + #include "common/logging.h" #include "core/file_sys/romfs.h" #include "core/file_sys/system_archive/mii_model.h" @@ -12,6 +14,7 @@ #include "core/file_sys/system_archive/system_archive.h" #include "core/file_sys/system_archive/system_version.h" #include "core/file_sys/system_archive/time_zone_binary.h" +#include "core/file_sys/vfs/vfs_vector.h" namespace FileSys::SystemArchive { @@ -76,6 +79,22 @@ constexpr inline SystemArchiveDescriptor GetSystemArchive(u64 title_id) { VirtualFile SynthesizeSystemArchive(const u64 title_id) { auto const desc = GetSystemArchive(title_id); + if (desc.name == nullptr) { + // Not a system archive Eden knows (fmt throws on a null string, ending the process). + // Switch 2 system data (ids 04...) is not installed with Switch 1 firmware, and titles + // abort when it cannot be mounted (Nintendo Switch Sports Resort, 0400000000000849): + // serve an empty archive. EDEN_NX2_EMPTY_SYSTEM_DATA=0 fails the request instead. + static const bool empty_nx2 = [] { + const char* setting = std::getenv("EDEN_NX2_EMPTY_SYSTEM_DATA"); + return setting == nullptr || setting[0] != '0'; + }(); + if (empty_nx2 && (title_id >> 56) == 0x04) { + LOG_WARNING(Service_FS, "Empty archive for Switch 2 system data {:#016x}", title_id); + return CreateRomFS(std::make_shared<VectorVfsDirectory>()); + } + LOG_WARNING(Service_FS, "No system archive {:#016x} to synthesize", title_id); + return nullptr; + } LOG_INFO(Service_FS, "Synthesizing system archive '{}' ({:#016x}).", desc.name, title_id); if (desc.supplier != nullptr) { if (auto const dir = desc.supplier(); dir != nullptr) { diff --git a/src/core/file_sys/vfs/vfs_real.cpp b/src/core/file_sys/vfs/vfs_real.cpp index 5b5b54017c..7cc5315a70 100644 --- a/src/core/file_sys/vfs/vfs_real.cpp +++ b/src/core/file_sys/vfs/vfs_real.cpp @@ -114,7 +114,6 @@ VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const { } VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::optional<u64> size, - std::optional<std::string> parent_path, OpenMode perms) { const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); std::scoped_lock lk{list_lock}; @@ -132,14 +131,14 @@ VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::op auto reference = std::make_unique<FileReference>(); this->InsertReferenceIntoListLocked(*reference); - auto file = std::make_shared<RealVfsFile>(*this, std::move(reference), path, perms, size, std::move(parent_path)); + auto file = std::make_shared<RealVfsFile>(*this, std::move(reference), path, perms, size); cache[path] = file; return file; } VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, OpenMode perms) { - return OpenFileFromEntry(path_, {}, {}, perms); + return OpenFileFromEntry(path_, {}, perms); } VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, OpenMode perms) { @@ -257,7 +256,8 @@ std::unique_lock<std::mutex> RealVfsFilesystem::RefreshReference(const std::stri return lk; } -void RealVfsFilesystem::DropReference(std::unique_ptr<FileReference>&& reference) { +void RealVfsFilesystem::DropReference(std::unique_ptr<FileReference>&& reference, + const std::string& path) { if (in_dtor) return; @@ -266,6 +266,11 @@ void RealVfsFilesystem::DropReference(std::unique_ptr<FileReference>&& reference // Remove from list. this->RemoveReferenceFromListLocked(*reference); + // Forget the path, unless it was opened again since this file's last reference went away. + if (const auto it = cache.find(path); it != cache.end() && it->second.expired()) { + cache.erase(it); + } + // Close the file. if (reference->file) { reference->file.reset(); @@ -309,14 +314,11 @@ void RealVfsFilesystem::RemoveReferenceFromListLocked(FileReference& reference) } RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::unique_ptr<FileReference> reference_, - const std::string& path_, OpenMode perms_, std::optional<u64> size_, - std::optional<std::string> parent_path_) - : base(base_), reference(std::move(reference_)), path(path_), - parent_path(parent_path_ ? std::move(*parent_path_) : FS::GetParentPath(path_)), - path_components(FS::SplitPathComponentsCopy(path_)), size(size_), perms(perms_) {} + const std::string& path_, OpenMode perms_, std::optional<u64> size_) + : base(base_), reference(std::move(reference_)), path(path_), size(size_), perms(perms_) {} RealVfsFile::~RealVfsFile() { - base.DropReference(std::move(reference)); + base.DropReference(std::move(reference), path); } std::string RealVfsFile::GetName() const { @@ -325,7 +327,14 @@ std::string RealVfsFile::GetName() const { return FS::Android::GetFilename(path); } #endif - return path_components.empty() ? "" : std::string(path_components.back()); + // The last path component. + const auto end = path.find_last_not_of("\\/"); + if (end == std::string::npos) { + return ""; + } + const auto separator = path.find_last_of("\\/", end); + const auto begin = separator == std::string::npos ? 0 : separator + 1; + return path.substr(begin, end + 1 - begin); } std::size_t RealVfsFile::GetSize() const { @@ -343,7 +352,7 @@ bool RealVfsFile::Resize(std::size_t new_size) { } VirtualDir RealVfsFile::GetContainingDirectory() const { - return base.OpenDirectory(parent_path, perms); + return base.OpenDirectory(FS::GetParentPath(path), perms); } bool RealVfsFile::IsWritable() const { @@ -372,7 +381,7 @@ std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t o } bool RealVfsFile::Rename(std::string_view name) { - return base.MoveFile(path, parent_path + '/' + std::string(name)) != nullptr; + return base.MoveFile(path, FS::GetParentPath(path) + '/' + std::string(name)) != nullptr; } // TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if @@ -390,7 +399,7 @@ std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>( &out](const std::filesystem::directory_entry& entry) { const auto full_path_string = FS::PathToUTF8String(entry.path()); - out.emplace_back(base.OpenFileFromEntry(full_path_string, entry.file_size(), path, perms)); + out.emplace_back(base.OpenFileFromEntry(full_path_string, entry.file_size(), perms)); return true; }; diff --git a/src/core/file_sys/vfs/vfs_real.h b/src/core/file_sys/vfs/vfs_real.h index a97f7d32ea..2df125b7eb 100644 --- a/src/core/file_sys/vfs/vfs_real.h +++ b/src/core/file_sys/vfs/vfs_real.h @@ -62,12 +62,11 @@ private: friend class RealVfsFile; std::unique_lock<std::mutex> RefreshReference(const std::string& path, OpenMode perms, FileReference& reference); - void DropReference(std::unique_ptr<FileReference>&& reference); + void DropReference(std::unique_ptr<FileReference>&& reference, const std::string& path); private: friend class RealVfsDirectory; VirtualFile OpenFileFromEntry(std::string_view path, std::optional<u64> size, - std::optional<std::string> parent_path, OpenMode perms = OpenMode::Read); private: @@ -84,7 +83,7 @@ class RealVfsFile : public VfsFile { public: RealVfsFile(RealVfsFilesystem& base, std::unique_ptr<FileReference> reference, const std::string& path, OpenMode perms = OpenMode::Read, - std::optional<u64> size = {}, std::optional<std::string> parent_path = {}); + std::optional<u64> size = {}); ~RealVfsFile() override; std::string GetName() const override; @@ -101,9 +100,8 @@ private: RealVfsFilesystem& base; std::unique_ptr<FileReference> reference; + // Only the path: a loose RomFS keeps one of these per file (title 1: 206k). std::string path; - std::string parent_path; - std::vector<std::string> path_components; std::optional<u64> size; OpenMode perms; }; diff --git a/src/core/hle/kernel/board/nintendo/nx/k_system_control.h b/src/core/hle/kernel/board/nintendo/nx/k_system_control.h index cc5572bde8..9aa527adb1 100644 --- a/src/core/hle/kernel/board/nintendo/nx/k_system_control.h +++ b/src/core/hle/kernel/board/nintendo/nx/k_system_control.h @@ -23,7 +23,10 @@ public: // Extra DRAM on the 12 GB layout for memory the Switch 2 graphics server keeps itself (nx2:gpu // 214 GPU-only regions). It sits in the non-secure system pool, outside the application pool. - static constexpr size_t Nx2GraphicsServerMemorySize = 8ULL * 1024 * 1024 * 1024; // 8_GiB + // GPU-only regions get memory only where guest memory is written and sparse regions take + // their tiles from the game's own memory (216), so much less of it is used (title 1: ~1.1 GiB, + // all of it at boot); all of it counts against the host's commit limit from boot. + static constexpr size_t Nx2GraphicsServerMemorySize = 2ULL * 1024 * 1024 * 1024; // 2_GiB public: class Init { diff --git a/src/core/hle/kernel/k_memory_manager.cpp b/src/core/hle/kernel/k_memory_manager.cpp index 743e290541..bed3dd819d 100644 --- a/src/core/hle/kernel/k_memory_manager.cpp +++ b/src/core/hle/kernel/k_memory_manager.cpp @@ -48,6 +48,8 @@ KMemoryManager::KMemoryManager(Core::System& system) } {} void KMemoryManager::Initialize(KVirtualAddress management_region, size_t management_region_size) { + // Memory a previous kernel handed out may still hold its data. + m_system.DeviceMemory().buffer.ForgetContents(); // Clear the management region to zero. const KVirtualAddress management_region_end = management_region + management_region_size; @@ -242,10 +244,21 @@ KPhysicalAddress KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, siz // Open the first reference to the pages. chosen_manager->OpenFirst(allocated_block, num_pages); + this->MarkUsed(allocated_block, num_pages); return allocated_block; } +void KMemoryManager::MarkUsed(KPhysicalAddress address, size_t num_pages) { + m_system.DeviceMemory().buffer.MarkUsed(GetInteger(address) - Core::DramMemoryMap::Base, + num_pages * PageSize); +} + +void KMemoryManager::MarkDirty(KPhysicalAddress address, size_t num_pages) { + m_system.DeviceMemory().buffer.MarkDirty(GetInteger(address) - Core::DramMemoryMap::Base, + num_pages * PageSize); +} + Result KMemoryManager::AllocatePageGroupImpl(KPageGroup* out, size_t num_pages, Pool pool, Direction dir, bool unoptimized, bool random) { // Choose a heap based on our page size request. @@ -297,6 +310,10 @@ Result KMemoryManager::AllocatePageGroupImpl(KPageGroup* out, size_t num_pages, // Only succeed if we allocated as many pages as we wanted. R_UNLESS(num_pages == 0, ResultOutOfMemory); + for (const auto& block : *out) { + this->MarkUsed(block.GetAddress(), block.GetNumPages()); + } + // We succeeded! R_SUCCEED(); } @@ -525,9 +542,10 @@ bool KMemoryManager::Impl::ProcessOptimizedAllocation(KernelCore& kernel, KPhysi // If not, it's new. any_new = true; - // Fill the page. - auto* ptr = device_memory.GetPointer<u8>(m_heap.GetAddress()); - std::memset(ptr + offset * PageSize, fill_pattern, PageSize); + // Fill the page (a zero fill skips pages that are still zero). + device_memory.buffer.ClearBackingRegion( + GetInteger(m_heap.GetAddress()) + offset * PageSize - Core::DramMemoryMap::Base, + PageSize, fill_pattern); } offset++; diff --git a/src/core/hle/kernel/k_memory_manager.h b/src/core/hle/kernel/k_memory_manager.h index 2f4ae7f04f..66b97fef8a 100644 --- a/src/core/hle/kernel/k_memory_manager.h +++ b/src/core/hle/kernel/k_memory_manager.h @@ -100,6 +100,9 @@ public: } void Close(KPhysicalAddress address, size_t num_pages) { + // Pages that may be freed here may hold data: the next allocation clears them. + this->MarkDirty(address, num_pages); + // Repeatedly close references until we've done so for all pages. while (num_pages) { auto& manager = this->GetManager(address); @@ -333,6 +336,9 @@ private: }; private: + void MarkUsed(KPhysicalAddress address, size_t num_pages); + void MarkDirty(KPhysicalAddress address, size_t num_pages); + Impl& GetManager(KPhysicalAddress address) { return m_managers[m_memory_layout.GetPhysicalLinearRegion(address).GetAttributes()]; } diff --git a/src/core/hle/kernel/k_shared_memory.cpp b/src/core/hle/kernel/k_shared_memory.cpp index 61f2872560..cb8877b8b2 100644 --- a/src/core/hle/kernel/k_shared_memory.cpp +++ b/src/core/hle/kernel/k_shared_memory.cpp @@ -59,9 +59,10 @@ Result KSharedMemory::Initialize(KernelCore& kernel, Core::DeviceMemory& device_ // Mark initialized. m_is_initialized = true; - // Clear all pages in the memory. + // Clear all pages in the memory (pages still zero are skipped). for (const auto& block : *m_page_group) { - std::memset(m_device_memory->GetPointer<void>(block.GetAddress()), 0, block.GetSize()); + m_device_memory->buffer.ClearBackingRegion( + GetInteger(block.GetAddress()) - Core::DramMemoryMap::Base, block.GetSize(), 0); } R_SUCCEED(); diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 53d964ecbc..925eafc472 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -14,6 +14,8 @@ #include "core/core.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/svc.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/service/kernel_helpers.h" namespace Kernel::Svc { @@ -2522,6 +2524,21 @@ void Call(Core::System& system, u32 imm) { LOG_INFO(Kernel_SVC, "svc {:#04x} in t{} [0]={:#x} [1]={:#x} [2]={:#x} [3]={:#x}", imm, GetCurrentThread(kernel).GetThreadId(), GetArg64(args, 0), GetArg64(args, 1), GetArg64(args, 2), GetArg64(args, 3)); + if (imm == 0x18) { + // WaitSynchronization: the handles, with the names services gave their events. + std::string list; + const s32 count = static_cast<s32>(GetArg64(args, 2)); + for (s32 i = 0; i < std::min(count, 8); ++i) { + const u32 handle = process.GetMemory().Read32(GetArg64(args, 1) + 4 * i); + KScopedAutoObject event = + process.GetHandleTable().GetObject<KReadableEvent>(kernel, handle); + list += fmt::format(" {:#x}{}", handle, + event.IsNotNull() + ? "=" + Service::KernelHelpers::DebugEventName(event.GetPointerUnsafe()) + : std::string{}); + } + LOG_INFO(Kernel_SVC, "svc 0x18 t{} waits on{}", GetCurrentThread(kernel).GetThreadId(), list); + } } if (imm == 0x80) { // Switch 2 only: svcSetGpuMemoryAttribute(address, size, mask, attr). nn::gpu::sdk and diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index d68b6bde0d..772da97ef6 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -696,9 +696,9 @@ public: {4, &IManagerForApplication::LoadIdTokenCache, "LoadIdTokenCache"}, {130, &IManagerForApplication::GetNintendoAccountUserResourceCacheForApplication, "GetNintendoAccountUserResourceCacheForApplication"}, {136, &IManagerForApplication::GetNintendoAccountUserResourceCacheForApplication, "GetNintendoAccountUserResourceCache"}, // 19.0.0+ - {150, nullptr, "CreateAuthorizationRequest"}, + {150, &IManagerForApplication::StubReplyInterface, "CreateAuthorizationRequest"}, {160, &IManagerForApplication::StoreOpenContext, "StoreOpenContext"}, - {170, nullptr, "LoadNetworkServiceLicenseKindAsync"}, + {170, &IManagerForApplication::StubReplyInterface, "LoadNetworkServiceLicenseKindAsync"}, }; // clang-format on diff --git a/src/core/hle/service/am/service/application_functions.cpp b/src/core/hle/service/am/service/application_functions.cpp index 37dfbfa344..c63d29b4e9 100644 --- a/src/core/hle/service/am/service/application_functions.cpp +++ b/src/core/hle/service/am/service/application_functions.cpp @@ -58,18 +58,19 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_, std::shared_ : ServiceFramework{system_, "IApplicationFunctions"}, m_applet{std::move(applet)} { // clang-format off static const FunctionInfo functions[] = { + {0, &IApplicationFunctions::StubReplyZeroed, "Unknown0"}, {1, D<&IApplicationFunctions::PopLaunchParameter>, "PopLaunchParameter"}, - {10, nullptr, "CreateApplicationAndPushAndRequestToStart"}, - {11, nullptr, "CreateApplicationAndPushAndRequestToStartForQuest"}, + {10, &IApplicationFunctions::StubReplyZeroed, "CreateApplicationAndPushAndRequestToStart"}, + {11, &IApplicationFunctions::StubReplyZeroed, "CreateApplicationAndPushAndRequestToStartForQuest"}, {12, D<&IApplicationFunctions::CreateApplicationAndRequestToStart>, "CreateApplicationAndRequestToStart"}, - {13, nullptr, "CreateApplicationAndRequestToStartForQuest"}, - {14, nullptr, "CreateApplicationWithAttributeAndPushAndRequestToStartForQuest"}, - {15, nullptr, "CreateApplicationWithAttributeAndRequestToStartForQuest"}, + {13, &IApplicationFunctions::StubReplyZeroed, "CreateApplicationAndRequestToStartForQuest"}, + {14, &IApplicationFunctions::StubReplyZeroed, "CreateApplicationWithAttributeAndPushAndRequestToStartForQuest"}, + {15, &IApplicationFunctions::StubReplyZeroed, "CreateApplicationWithAttributeAndRequestToStartForQuest"}, {20, D<&IApplicationFunctions::EnsureSaveData>, "EnsureSaveData"}, {21, D<&IApplicationFunctions::GetDesiredLanguage>, "GetDesiredLanguage"}, {22, D<&IApplicationFunctions::SetTerminateResult>, "SetTerminateResult"}, {23, D<&IApplicationFunctions::GetDisplayVersion>, "GetDisplayVersion"}, - {24, nullptr, "GetLaunchStorageInfoForDebug"}, + {24, &IApplicationFunctions::StubReplyZeroed, "GetLaunchStorageInfoForDebug"}, {25, D<&IApplicationFunctions::ExtendSaveData>, "ExtendSaveData"}, {26, D<&IApplicationFunctions::GetSaveDataSize>, "GetSaveDataSize"}, {27, D<&IApplicationFunctions::CreateCacheStorage>, "CreateCacheStorage"}, @@ -80,53 +81,57 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_, std::shared_ {32, D<&IApplicationFunctions::BeginBlockingHomeButton>, "BeginBlockingHomeButton"}, {33, D<&IApplicationFunctions::EndBlockingHomeButton>, "EndBlockingHomeButton"}, {34, nullptr, "SelectApplicationLicense"}, - {35, nullptr, "GetDeviceSaveDataSizeMax"}, - {36, nullptr, "GetLimitedApplicationLicense"}, - {37, nullptr, "GetLimitedApplicationLicenseUpgradableEvent"}, + {35, &IApplicationFunctions::StubReplyZeroed, "GetDeviceSaveDataSizeMax"}, + {36, &IApplicationFunctions::StubReplyZeroed, "GetLimitedApplicationLicense"}, + {37, &IApplicationFunctions::StubReplyEvent, "GetLimitedApplicationLicenseUpgradableEvent"}, {40, D<&IApplicationFunctions::NotifyRunning>, "NotifyRunning"}, {50, D<&IApplicationFunctions::GetPseudoDeviceId>, "GetPseudoDeviceId"}, {60, D<&IApplicationFunctions::SetMediaPlaybackStateForApplication>, "SetMediaPlaybackStateForApplication"}, {65, D<&IApplicationFunctions::IsGamePlayRecordingSupported>, "IsGamePlayRecordingSupported"}, {66, D<&IApplicationFunctions::InitializeGamePlayRecording>, "InitializeGamePlayRecording"}, {67, D<&IApplicationFunctions::SetGamePlayRecordingState>, "SetGamePlayRecordingState"}, - {68, nullptr, "RequestFlushGamePlayingMovieForDebug"}, - {70, nullptr, "RequestToShutdown"}, - {71, nullptr, "RequestToReboot"}, - {72, nullptr, "RequestToSleep"}, - {80, nullptr, "ExitAndRequestToShowThanksMessage"}, + {68, &IApplicationFunctions::StubReplyZeroed, "RequestFlushGamePlayingMovieForDebug"}, + {70, &IApplicationFunctions::StubReplyZeroed, "RequestToShutdown"}, + {71, &IApplicationFunctions::StubReplyZeroed, "RequestToReboot"}, + {72, &IApplicationFunctions::StubReplyZeroed, "RequestToSleep"}, + {80, &IApplicationFunctions::StubReplyZeroed, "ExitAndRequestToShowThanksMessage"}, {90, D<&IApplicationFunctions::EnableApplicationCrashReport>, "EnableApplicationCrashReport"}, {100, D<&IApplicationFunctions::InitializeApplicationCopyrightFrameBuffer>, "InitializeApplicationCopyrightFrameBuffer"}, {101, D<&IApplicationFunctions::SetApplicationCopyrightImage>, "SetApplicationCopyrightImage"}, {102, D<&IApplicationFunctions::SetApplicationCopyrightVisibility>, "SetApplicationCopyrightVisibility"}, {110, D<&IApplicationFunctions::QueryApplicationPlayStatistics>, "QueryApplicationPlayStatistics"}, {111, D<&IApplicationFunctions::QueryApplicationPlayStatisticsByUid>, "QueryApplicationPlayStatisticsByUid"}, + {112, &IApplicationFunctions::StubReplyZeroed, "Unknown112"}, + {113, &IApplicationFunctions::StubReplyZeroed, "Unknown113"}, {120, D<&IApplicationFunctions::ExecuteProgram>, "ExecuteProgram"}, {121, D<&IApplicationFunctions::ClearUserChannel>, "ClearUserChannel"}, {122, D<&IApplicationFunctions::UnpopToUserChannel>, "UnpopToUserChannel"}, {123, D<&IApplicationFunctions::GetPreviousProgramIndex>, "GetPreviousProgramIndex"}, - {124, nullptr, "EnableApplicationAllThreadDumpOnCrash"}, + {124, &IApplicationFunctions::StubReplyZeroed, "EnableApplicationAllThreadDumpOnCrash"}, {130, D<&IApplicationFunctions::GetGpuErrorDetectedSystemEvent>, "GetGpuErrorDetectedSystemEvent"}, - {131, nullptr, "SetDelayTimeToAbortOnGpuError"}, + {131, &IApplicationFunctions::StubReplyZeroed, "SetDelayTimeToAbortOnGpuError"}, {140, D<&IApplicationFunctions::GetFriendInvitationStorageChannelEvent>, "GetFriendInvitationStorageChannelEvent"}, {141, D<&IApplicationFunctions::TryPopFromFriendInvitationStorageChannel>, "TryPopFromFriendInvitationStorageChannel"}, {150, D<&IApplicationFunctions::GetNotificationStorageChannelEvent>, "GetNotificationStorageChannelEvent"}, - {151, nullptr, "TryPopFromNotificationStorageChannel"}, + {151, &IApplicationFunctions::StubReplyInterface, "TryPopFromNotificationStorageChannel"}, {160, D<&IApplicationFunctions::GetHealthWarningDisappearedSystemEvent>, "GetHealthWarningDisappearedSystemEvent"}, - {170, nullptr, "SetHdcpAuthenticationActivated"}, - {180, nullptr, "GetLaunchRequiredVersion"}, - {181, nullptr, "UpgradeLaunchRequiredVersion"}, - {190, nullptr, "SendServerMaintenanceOverlayNotification"}, - {200, nullptr, "GetLastApplicationExitReason"}, + {170, &IApplicationFunctions::StubReplyZeroed, "SetHdcpAuthenticationActivated"}, + {180, &IApplicationFunctions::StubReplyZeroed, "GetLaunchRequiredVersion"}, + {181, &IApplicationFunctions::StubReplyZeroed, "UpgradeLaunchRequiredVersion"}, + {190, &IApplicationFunctions::StubReplyZeroed, "SendServerMaintenanceOverlayNotification"}, + {200, &IApplicationFunctions::StubReplyZeroed, "GetLastApplicationExitReason"}, {210, D<&IApplicationFunctions::GetOperationModeRefreshEvent>, "GetOperationModeRefreshEvent (guessed)"}, - {220, nullptr, "Unknown220"}, // [20.0.0+] - {300, nullptr, "CreateMovieWriter"}, // [19.0.0+] - {310, nullptr, "Unknown310"}, // [20.0.0+] + {220, &IApplicationFunctions::StubReplyZeroed, "Unknown220"}, // [20.0.0+] + {230, &IApplicationFunctions::StubReplyZeroed, "Unknown230"}, + {300, &IApplicationFunctions::StubReplyInterface, "CreateMovieWriter"}, // [19.0.0+] + {310, &IApplicationFunctions::StubReplyZeroed, "Unknown310"}, // [20.0.0+] {320, D<&IApplicationFunctions::Unknown320>, "Unknown320"}, // [20.0.0+] {321, D<&IApplicationFunctions::Unknown321>, "Unknown321"}, // Switch 2 {330, D<&IApplicationFunctions::Unknown330>, "Unknown330"}, // [20.0.0+] - {500, nullptr, "StartContinuousRecordingFlushForDebug"}, - {1000, nullptr, "CreateMovieMaker"}, + {500, &IApplicationFunctions::StubReplyEvent, "StartContinuousRecordingFlushForDebug"}, + {1000, &IApplicationFunctions::StubReplyInterface, "CreateMovieMaker"}, {1001, D<&IApplicationFunctions::PrepareForJit>, "PrepareForJit"}, + {2000, &IApplicationFunctions::StubReplyZeroed, "Unknown2000"}, }; // clang-format on @@ -571,15 +576,18 @@ Result IApplicationFunctions::GetOperationModeRefreshEvent( R_SUCCEED(); } -Result IApplicationFunctions::Unknown320() { - // Called by Switch 2 titles during nn::oe::Initialize; arguments unknown. +Result IApplicationFunctions::Unknown320(Out<u16> out) { + // Called by Switch 2 titles during nn::oe::Initialize. It returns two bytes of data (meaning + // unknown); a reply without them left the caller reading stale message words. LOG_WARNING(Service_AM, "(STUBBED) called"); + *out = 0; R_SUCCEED(); } -Result IApplicationFunctions::Unknown321() { - // Called by Switch 2 titles right after 320; arguments unknown. +Result IApplicationFunctions::Unknown321(Out<u8> out) { + // Called by Switch 2 titles right after 320; returns one byte (meaning unknown). LOG_WARNING(Service_AM, "(STUBBED) called"); + *out = 0; R_SUCCEED(); } diff --git a/src/core/hle/service/am/service/application_functions.h b/src/core/hle/service/am/service/application_functions.h index 44b5fadff0..aca2f8c694 100644 --- a/src/core/hle/service/am/service/application_functions.h +++ b/src/core/hle/service/am/service/application_functions.h @@ -83,8 +83,8 @@ private: Result GetHealthWarningDisappearedSystemEvent(OutCopyHandle<Kernel::KReadableEvent> out_event); Result CreateApplicationAndRequestToStart(u64 application_id); Result GetOperationModeRefreshEvent(OutCopyHandle<Kernel::KReadableEvent> out_event); - Result Unknown320(); - Result Unknown321(); + Result Unknown320(Out<u16> out); + Result Unknown321(Out<u8> out); Result Unknown330(Out<u8> out); Result PrepareForJit(); diff --git a/src/core/hle/service/am/service/application_proxy_service.cpp b/src/core/hle/service/am/service/application_proxy_service.cpp index 67306aadc0..9d332798b4 100644 --- a/src/core/hle/service/am/service/application_proxy_service.cpp +++ b/src/core/hle/service/am/service/application_proxy_service.cpp @@ -21,6 +21,7 @@ IApplicationProxyService::IApplicationProxyService(Core::System& system_, {0, D<&IApplicationProxyService::OpenApplicationProxy>, "OpenApplicationProxy"}, // Switch 2 SDKs open the proxy with command 10, same arguments. {10, D<&IApplicationProxyService::OpenApplicationProxy>, "OpenApplicationProxy (Switch 2)"}, + {2000, &IApplicationProxyService::StubReplyInterface, "Unknown2000"}, }; RegisterHandlers(functions); } diff --git a/src/core/hle/service/am/service/audio_controller.cpp b/src/core/hle/service/am/service/audio_controller.cpp index 4e9af756f2..7901f67c27 100644 --- a/src/core/hle/service/am/service/audio_controller.cpp +++ b/src/core/hle/service/am/service/audio_controller.cpp @@ -18,7 +18,7 @@ IAudioController::IAudioController(Core::System& system_) {2, D<&IAudioController::GetLibraryAppletExpectedMasterVolume>, "GetLibraryAppletExpectedMasterVolume"}, {3, D<&IAudioController::ChangeMainAppletMasterVolume>, "ChangeMainAppletMasterVolume"}, {4, D<&IAudioController::SetTransparentVolumeRate>, "SetTransparentVolumeRate"}, - {5, nullptr, "Unknown5"}, //20.0.0+ + {5, &IAudioController::StubReplyZeroed, "Unknown5"}, //20.0.0+ }; // clang-format on diff --git a/src/core/hle/service/am/service/common_state_getter.cpp b/src/core/hle/service/am/service/common_state_getter.cpp index 044929e434..2c5e536414 100644 --- a/src/core/hle/service/am/service/common_state_getter.cpp +++ b/src/core/hle/service/am/service/common_state_getter.cpp @@ -24,67 +24,73 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_, std::shared_ptr<Ap static const FunctionInfo functions[] = { {0, D<&ICommonStateGetter::GetEventHandle>, "GetEventHandle"}, {1, D<&ICommonStateGetter::ReceiveMessage>, "ReceiveMessage"}, - {2, nullptr, "GetThisAppletKind"}, - {3, nullptr, "AllowToEnterSleep"}, - {4, nullptr, "DisallowToEnterSleep"}, + {2, &ICommonStateGetter::StubReplyZeroed, "GetThisAppletKind"}, + {3, &ICommonStateGetter::StubReplyZeroed, "AllowToEnterSleep"}, + {4, &ICommonStateGetter::StubReplyZeroed, "DisallowToEnterSleep"}, {5, D<&ICommonStateGetter::GetOperationMode>, "GetOperationMode"}, {6, D<&ICommonStateGetter::GetPerformanceMode>, "GetPerformanceMode"}, - {7, nullptr, "GetCradleStatus"}, + {7, &ICommonStateGetter::StubReplyZeroed, "GetCradleStatus"}, {8, D<&ICommonStateGetter::GetBootMode>, "GetBootMode"}, {9, D<&ICommonStateGetter::GetCurrentFocusState>, "GetCurrentFocusState"}, {10, D<&ICommonStateGetter::RequestToAcquireSleepLock>, "RequestToAcquireSleepLock"}, {11, D<&ICommonStateGetter::ReleaseSleepLock>, "ReleaseSleepLock"}, {12, D<&ICommonStateGetter::ReleaseSleepLockTransiently>, "ReleaseSleepLockTransiently"}, {13, D<&ICommonStateGetter::GetAcquiredSleepLockEvent>, "GetAcquiredSleepLockEvent"}, - {14, nullptr, "GetWakeupCount"}, //11.0.0+ - {15, nullptr, "Unknown15"}, //19.0.0+ + {14, &ICommonStateGetter::StubReplyZeroed, "GetWakeupCount"}, //11.0.0+ + {15, &ICommonStateGetter::StubReplyEvent, "Unknown15"}, //19.0.0+ {20, D<&ICommonStateGetter::PushToGeneralChannel>, "PushToGeneralChannel"}, {30, D<&ICommonStateGetter::GetHomeButtonReaderLockAccessor>, "GetHomeButtonReaderLockAccessor"}, {31, D<&ICommonStateGetter::GetReaderLockAccessorEx>, "GetReaderLockAccessorEx"}, //2.0.0+ {32, D<&ICommonStateGetter::GetWriterLockAccessorEx>, "GetWriterLockAccessorEx"}, //7.0.0+ - {40, nullptr, "GetCradleFwVersion"}, //2.0.0+ + {40, &ICommonStateGetter::StubReplyZeroed, "GetCradleFwVersion"}, //2.0.0+ {50, D<&ICommonStateGetter::IsVrModeEnabled>, "IsVrModeEnabled"}, //3.0.0+ {51, D<&ICommonStateGetter::SetVrModeEnabled>, "SetVrModeEnabled"}, //3.0.0+ {52, D<&ICommonStateGetter::SetLcdBacklighOffEnabled>, "SetLcdBacklighOffEnabled"}, //4.0.0+ {53, D<&ICommonStateGetter::BeginVrModeEx>, "BeginVrModeEx"}, //7.0.0+ {54, D<&ICommonStateGetter::EndVrModeEx>, "EndVrModeEx"}, //7.0.0+ {55, D<&ICommonStateGetter::IsInControllerFirmwareUpdateSection>, "IsInControllerFirmwareUpdateSection"}, //3.0.0+ - {59, nullptr, "SetVrPositionForDebug"}, //1.0.0+ + {59, &ICommonStateGetter::StubReplyZeroed, "SetVrPositionForDebug"}, //1.0.0+ {60, D<&ICommonStateGetter::GetDefaultDisplayResolution>, "GetDefaultDisplayResolution"}, {61, D<&ICommonStateGetter::GetDefaultDisplayResolutionChangeEvent>, "GetDefaultDisplayResolutionChangeEvent"}, {62, D<&ICommonStateGetter::GetHdcpAuthenticationState>, "GetHdcpAuthenticationState"}, {63, D<&ICommonStateGetter::GetHdcpAuthenticationStateChangeEvent>, "GetHdcpAuthenticationStateChangeEvent"}, - {64, nullptr, "SetTvPowerStateMatchingMode"}, - {65, nullptr, "GetApplicationIdByContentActionName"}, + {64, &ICommonStateGetter::StubReplyZeroed, "SetTvPowerStateMatchingMode"}, + {65, &ICommonStateGetter::StubReplyZeroed, "GetApplicationIdByContentActionName"}, {66, &ICommonStateGetter::SetCpuBoostMode, "SetCpuBoostMode"}, - {67, nullptr, "CancelCpuBoostMode"}, + {67, &ICommonStateGetter::StubReplyZeroed, "CancelCpuBoostMode"}, {68, D<&ICommonStateGetter::GetBuiltInDisplayType>, "GetBuiltInDisplayType"}, {80, D<&ICommonStateGetter::PerformSystemButtonPressingIfInFocus>, "PerformSystemButtonPressingIfInFocus"}, - {90, nullptr, "SetPerformanceConfigurationChangedNotification"}, - {91, nullptr, "GetCurrentPerformanceConfiguration"}, + {90, &ICommonStateGetter::StubReplyZeroed, "SetPerformanceConfigurationChangedNotification"}, + {91, &ICommonStateGetter::StubReplyZeroed, "GetCurrentPerformanceConfiguration"}, {100, D<&ICommonStateGetter::SetHandlingHomeButtonShortPressedEnabled>, "SetHandlingHomeButtonShortPressedEnabled"}, - {110, nullptr, "OpenMyGpuErrorHandler"}, + {110, &ICommonStateGetter::StubReplyInterface, "OpenMyGpuErrorHandler"}, {120, D<&ICommonStateGetter::GetAppletLaunchedHistory>, "GetAppletLaunchedHistory"}, //13.0.0+ {130, D<&ICommonStateGetter::EnableStartupLogoDisappearedMessage>, "EnableStartupLogoDisappearedMessage"}, //21.0.0+ {200, D<&ICommonStateGetter::GetOperationModeSystemInfo>, "GetOperationModeSystemInfo"}, {300, D<&ICommonStateGetter::GetSettingsPlatformRegion>, "GetSettingsPlatformRegion"}, - {400, nullptr, "ActivateMigrationService"}, - {401, nullptr, "DeactivateMigrationService"}, - {500, nullptr, "DisableSleepTillShutdown"}, - {501, nullptr, "SuppressDisablingSleepTemporarily"}, - {502, nullptr, "IsSleepEnabled"}, - {503, nullptr, "IsDisablingSleepSuppressed"}, - {600, nullptr, "SetHidInputMagnificationForApplication"}, //20.0.0+ + {310, &ICommonStateGetter::StubReplyZeroed, "Unknown310"}, + {320, &ICommonStateGetter::StubReplyZeroed, "Unknown320"}, + {400, &ICommonStateGetter::StubReplyZeroed, "ActivateMigrationService"}, + {401, &ICommonStateGetter::StubReplyZeroed, "DeactivateMigrationService"}, + {500, &ICommonStateGetter::StubReplyZeroed, "DisableSleepTillShutdown"}, + {501, &ICommonStateGetter::StubReplyZeroed, "SuppressDisablingSleepTemporarily"}, + {502, &ICommonStateGetter::StubReplyZeroed, "IsSleepEnabled"}, + {503, &ICommonStateGetter::StubReplyZeroed, "IsDisablingSleepSuppressed"}, + {600, &ICommonStateGetter::StubReplyZeroed, "SetHidInputMagnificationForApplication"}, //20.0.0+ {610, D<&ICommonStateGetter::Unknown610>, "Unknown610"}, //21.0.0+ {611, D<&ICommonStateGetter::Unknown611>, "Unknown611"}, //22.0.0+ + {650, &ICommonStateGetter::StubReplyZeroed, "Unknown650"}, + {651, &ICommonStateGetter::StubReplyZeroed, "Unknown651"}, {900, D<&ICommonStateGetter::SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled>, "SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled"}, //11.0.0+ - {910, nullptr, "GetLaunchRequiredTick"}, //17.0.0+ + {910, &ICommonStateGetter::StubReplyZeroed, "GetLaunchRequiredTick"}, //17.0.0+ {1000, D<&ICommonStateGetter::BeginVrMode3d>, "BeginVrMode3d"}, //19.0.0+ {1001, D<&ICommonStateGetter::EndVrMode3d>, "EndVrMode3d"}, //19.0.0+ {1002, D<&ICommonStateGetter::IsVrModeEnabled3d>, "IsVrModeEnabled3d"}, //19.0.0+ {1003, D<&ICommonStateGetter::GetVrLaboGoggleViewport>, "GetVrLaboGoggleViewport"}, //21.0.0+ {1004, D<&ICommonStateGetter::GetPanelPhysicalSizeForSpecificTitle>, "GetPanelPhysicalSizeForSpecificTitle"}, //21.0.0+ {1005, D<&ICommonStateGetter::GetPanelResolutionForSpecificTitle>, "GetPanelResolutionForSpecificTitle"}, //21.0.0+ + {2000, &ICommonStateGetter::StubReplyZeroed, "Unknown2000"}, + {2010, &ICommonStateGetter::StubReplyZeroed, "Unknown2010"}, }; // clang-format on diff --git a/src/core/hle/service/am/service/display_controller.cpp b/src/core/hle/service/am/service/display_controller.cpp index 8f87327b17..7e7db23708 100644 --- a/src/core/hle/service/am/service/display_controller.cpp +++ b/src/core/hle/service/am/service/display_controller.cpp @@ -16,34 +16,35 @@ IDisplayController::IDisplayController(Core::System& system_, std::shared_ptr<Ap : ServiceFramework{system_, "IDisplayController"}, applet(std::move(applet_)) { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "GetLastForegroundCaptureImage"}, - {1, nullptr, "UpdateLastForegroundCaptureImage"}, - {2, nullptr, "GetLastApplicationCaptureImage"}, - {3, nullptr, "GetCallerAppletCaptureImage"}, - {4, nullptr, "UpdateCallerAppletCaptureImage"}, + {0, &IDisplayController::StubReplyZeroed, "GetLastForegroundCaptureImage"}, + {1, &IDisplayController::StubReplyZeroed, "UpdateLastForegroundCaptureImage"}, + {2, &IDisplayController::StubReplyZeroed, "GetLastApplicationCaptureImage"}, + {3, &IDisplayController::StubReplyZeroed, "GetCallerAppletCaptureImage"}, + {4, &IDisplayController::StubReplyZeroed, "UpdateCallerAppletCaptureImage"}, {5, D<&IDisplayController::GetLastForegroundCaptureImageEx>, "GetLastForegroundCaptureImageEx"}, - {6, nullptr, "GetLastApplicationCaptureImageEx"}, + {6, &IDisplayController::StubReplyZeroed, "GetLastApplicationCaptureImageEx"}, {7, D<&IDisplayController::GetCallerAppletCaptureImageEx>, "GetCallerAppletCaptureImageEx"}, {8, D<&IDisplayController::TakeScreenShotOfOwnLayer>, "TakeScreenShotOfOwnLayer"}, - {9, nullptr, "CopyBetweenCaptureBuffers"}, - {10, nullptr, "AcquireLastApplicationCaptureBuffer"}, - {11, nullptr, "ReleaseLastApplicationCaptureBuffer"}, - {12, nullptr, "AcquireLastForegroundCaptureBuffer"}, - {13, nullptr, "ReleaseLastForegroundCaptureBuffer"}, - {14, nullptr, "AcquireCallerAppletCaptureBuffer"}, - {15, nullptr, "ReleaseCallerAppletCaptureBuffer"}, - {16, nullptr, "AcquireLastApplicationCaptureBufferEx"}, - {17, nullptr, "AcquireLastForegroundCaptureBufferEx"}, - {18, nullptr, "AcquireCallerAppletCaptureBufferEx"}, + {9, &IDisplayController::StubReplyZeroed, "CopyBetweenCaptureBuffers"}, + {10, &IDisplayController::StubReplyEvent, "AcquireLastApplicationCaptureBuffer"}, + {11, &IDisplayController::StubReplyZeroed, "ReleaseLastApplicationCaptureBuffer"}, + {12, &IDisplayController::StubReplyEvent, "AcquireLastForegroundCaptureBuffer"}, + {13, &IDisplayController::StubReplyZeroed, "ReleaseLastForegroundCaptureBuffer"}, + {14, &IDisplayController::StubReplyEvent, "AcquireCallerAppletCaptureBuffer"}, + {15, &IDisplayController::StubReplyZeroed, "ReleaseCallerAppletCaptureBuffer"}, + {16, &IDisplayController::StubReplyEvent, "AcquireLastApplicationCaptureBufferEx"}, + {17, &IDisplayController::StubReplyEvent, "AcquireLastForegroundCaptureBufferEx"}, + {18, &IDisplayController::StubReplyEvent, "AcquireCallerAppletCaptureBufferEx"}, + {19, &IDisplayController::StubReplyZeroed, "Unknown19"}, {20, D<&IDisplayController::ClearCaptureBuffer>, "ClearCaptureBuffer"}, - {21, nullptr, "ClearAppletTransitionBuffer"}, + {21, &IDisplayController::StubReplyZeroed, "ClearAppletTransitionBuffer"}, {22, D<&IDisplayController::AcquireLastApplicationCaptureSharedBuffer>, "AcquireLastApplicationCaptureSharedBuffer"}, {23, D<&IDisplayController::ReleaseLastApplicationCaptureSharedBuffer>, "ReleaseLastApplicationCaptureSharedBuffer"}, {24, D<&IDisplayController::AcquireLastForegroundCaptureSharedBuffer>, "AcquireLastForegroundCaptureSharedBuffer"}, {25, D<&IDisplayController::ReleaseLastForegroundCaptureSharedBuffer>, "ReleaseLastForegroundCaptureSharedBuffer"}, {26, D<&IDisplayController::AcquireCallerAppletCaptureSharedBuffer>, "AcquireCallerAppletCaptureSharedBuffer"}, {27, D<&IDisplayController::ReleaseCallerAppletCaptureSharedBuffer>, "ReleaseCallerAppletCaptureSharedBuffer"}, - {28, nullptr, "TakeScreenShotOfOwnLayerEx"}, + {28, &IDisplayController::StubReplyZeroed, "TakeScreenShotOfOwnLayerEx"}, }; // clang-format on diff --git a/src/core/hle/service/am/service/graphics_scale_controller.h b/src/core/hle/service/am/service/graphics_scale_controller.h index 07e38c6ac8..b31858d3ed 100644 --- a/src/core/hle/service/am/service/graphics_scale_controller.h +++ b/src/core/hle/service/am/service/graphics_scale_controller.h @@ -25,9 +25,13 @@ public: } private: - Result GetCurrentGraphicsScale(Out<u32> out_scale) { + // The reply is 12 bytes; only the first word (the scale) is known. The other two used to be + // left to the message buffer's leftovers. + Result GetCurrentGraphicsScale(Out<u32> out_scale, Out<u32> out_unknown1, Out<u32> out_unknown2) { LOG_WARNING(Service_AM, "(STUBBED) called, returning 10"); *out_scale = 10; + *out_unknown1 = 0; + *out_unknown2 = 0; R_SUCCEED(); } }; diff --git a/src/core/hle/service/am/service/library_applet_accessor.cpp b/src/core/hle/service/am/service/library_applet_accessor.cpp index 849e5794cf..7119c60596 100644 --- a/src/core/hle/service/am/service/library_applet_accessor.cpp +++ b/src/core/hle/service/am/service/library_applet_accessor.cpp @@ -62,21 +62,22 @@ ILibraryAppletAccessor::ILibraryAppletAccessor(Core::System& system_, {20, D<&ILibraryAppletAccessor::RequestExit>, "RequestExit"}, {25, D<&ILibraryAppletAccessor::Terminate>, "Terminate"}, {30, D<&ILibraryAppletAccessor::GetResult>, "GetResult"}, - {50, nullptr, "SetOutOfFocusApplicationSuspendingEnabled"}, + {50, &ILibraryAppletAccessor::StubReplyZeroed, "SetOutOfFocusApplicationSuspendingEnabled"}, {60, D<&ILibraryAppletAccessor::PresetLibraryAppletGpuTimeSliceZero>, "PresetLibraryAppletGpuTimeSliceZero"}, //10.0.0+ - {80, nullptr, "RequestForLibraryAppletToGetForeground"}, //19.0.0+ - {81, nullptr, "GetCurrentChildLibraryApplet"}, //19.0.0+ + {70, &ILibraryAppletAccessor::StubReplyZeroed, "Unknown70"}, + {80, &ILibraryAppletAccessor::StubReplyZeroed, "RequestForLibraryAppletToGetForeground"}, //19.0.0+ + {81, &ILibraryAppletAccessor::StubReplyInterface, "GetCurrentChildLibraryApplet"}, //19.0.0+ {90, D<&ILibraryAppletAccessor::Unknown90>, "Unknown90"}, //20.0.0+ {100, D<&ILibraryAppletAccessor::PushInData>, "PushInData"}, {101, D<&ILibraryAppletAccessor::PopOutData>, "PopOutData"}, - {102, nullptr, "PushExtraStorage"}, + {102, &ILibraryAppletAccessor::StubReplyZeroed, "PushExtraStorage"}, {103, D<&ILibraryAppletAccessor::PushInteractiveInData>, "PushInteractiveInData"}, {104, D<&ILibraryAppletAccessor::PopInteractiveOutData>, "PopInteractiveOutData"}, {105, D<&ILibraryAppletAccessor::GetPopOutDataEvent>, "GetPopOutDataEvent"}, {106, D<&ILibraryAppletAccessor::GetPopInteractiveOutDataEvent>, "GetPopInteractiveOutDataEvent"}, - {110, nullptr, "NeedsToExitProcess"}, + {110, &ILibraryAppletAccessor::StubReplyZeroed, "NeedsToExitProcess"}, {120, D<&ILibraryAppletAccessor::GetLibraryAppletInfo>, "GetLibraryAppletInfo"}, - {150, nullptr, "RequestForAppletToGetForeground"}, + {150, &ILibraryAppletAccessor::StubReplyZeroed, "RequestForAppletToGetForeground"}, {160, D<&ILibraryAppletAccessor::GetIndirectLayerConsumerHandle>, "GetIndirectLayerConsumerHandle"}, //2.0.0+ {170, D<&ILibraryAppletAccessor::Unknown170>, "Unknown170"}, //22.0.0+ }; diff --git a/src/core/hle/service/am/service/library_applet_creator.cpp b/src/core/hle/service/am/service/library_applet_creator.cpp index afdb609c00..2993ed7436 100644 --- a/src/core/hle/service/am/service/library_applet_creator.cpp +++ b/src/core/hle/service/am/service/library_applet_creator.cpp @@ -204,8 +204,8 @@ ILibraryAppletCreator::ILibraryAppletCreator(Core::System& system_, std::shared_ m_window_system{window_system}, m_applet{std::move(applet)} { static const FunctionInfo functions[] = { {0, D<&ILibraryAppletCreator::CreateLibraryApplet>, "CreateLibraryApplet"}, - {1, nullptr, "TerminateAllLibraryApplets"}, - {2, nullptr, "AreAnyLibraryAppletsLeft"}, + {1, &ILibraryAppletCreator::StubReplyZeroed, "TerminateAllLibraryApplets"}, + {2, &ILibraryAppletCreator::StubReplyZeroed, "AreAnyLibraryAppletsLeft"}, {3, D<&ILibraryAppletCreator::CreateLibraryAppletEx>, "CreateLibraryAppletEx"}, {10, D<&ILibraryAppletCreator::CreateStorage>, "CreateStorage"}, {11, D<&ILibraryAppletCreator::CreateTransferMemoryStorage>, "CreateTransferMemoryStorage"}, diff --git a/src/core/hle/service/am/service/self_controller.cpp b/src/core/hle/service/am/service/self_controller.cpp index a8078e6048..d67e1b32cd 100644 --- a/src/core/hle/service/am/service/self_controller.cpp +++ b/src/core/hle/service/am/service/self_controller.cpp @@ -37,44 +37,59 @@ ISelfController::ISelfController(Core::System& system_, std::shared_ptr<Applet> {14, D<&ISelfController::SetRestartMessageEnabled>, "SetRestartMessageEnabled"}, {15, D<&ISelfController::SetScreenShotAppletIdentityInfo>, "SetScreenShotAppletIdentityInfo"}, {16, D<&ISelfController::SetOutOfFocusSuspendingEnabled>, "SetOutOfFocusSuspendingEnabled"}, - {17, nullptr, "SetControllerFirmwareUpdateSection"}, - {18, nullptr, "SetRequiresCaptureButtonShortPressedMessage"}, + {17, &ISelfController::StubReplyZeroed, "SetControllerFirmwareUpdateSection"}, + {18, &ISelfController::StubReplyZeroed, "SetRequiresCaptureButtonShortPressedMessage"}, {19, D<&ISelfController::SetAlbumImageOrientation>, "SetAlbumImageOrientation"}, - {20, nullptr, "SetDesirableKeyboardLayout"}, - {21, nullptr, "GetScreenShotProgramId"}, + {20, &ISelfController::StubReplyZeroed, "SetDesirableKeyboardLayout"}, + {21, &ISelfController::StubReplyZeroed, "GetScreenShotProgramId"}, + {22, &ISelfController::StubReplyZeroed, "Unknown22"}, + {23, &ISelfController::StubReplyZeroed, "Unknown23"}, + {24, &ISelfController::StubReplyZeroed, "Unknown24"}, {40, D<&ISelfController::CreateManagedDisplayLayer>, "CreateManagedDisplayLayer"}, {41, D<&ISelfController::IsSystemBufferSharingEnabled>, "IsSystemBufferSharingEnabled"}, {42, D<&ISelfController::GetSystemSharedLayerHandle>, "GetSystemSharedLayerHandle"}, {43, D<&ISelfController::GetSystemSharedBufferHandle>, "GetSystemSharedBufferHandle"}, {44, D<&ISelfController::CreateManagedDisplaySeparableLayer>, "CreateManagedDisplaySeparableLayer"}, - {45, nullptr, "SetManagedDisplayLayerSeparationMode"}, - {46, nullptr, "SetRecordingLayerCompositionEnabled"}, + {45, &ISelfController::StubReplyZeroed, "SetManagedDisplayLayerSeparationMode"}, + {46, &ISelfController::StubReplyZeroed, "SetRecordingLayerCompositionEnabled"}, {50, D<&ISelfController::SetHandlesRequestToDisplay>, "SetHandlesRequestToDisplay"}, {51, D<&ISelfController::ApproveToDisplay>, "ApproveToDisplay"}, {60, D<&ISelfController::OverrideAutoSleepTimeAndDimmingTime>, "OverrideAutoSleepTimeAndDimmingTime"}, {61, D<&ISelfController::SetMediaPlaybackState>, "SetMediaPlaybackState"}, {62, D<&ISelfController::SetIdleTimeDetectionExtension>, "SetIdleTimeDetectionExtension"}, {63, D<&ISelfController::GetIdleTimeDetectionExtension>, "GetIdleTimeDetectionExtension"}, - {64, nullptr, "SetInputDetectionSourceSet"}, + {64, &ISelfController::StubReplyZeroed, "SetInputDetectionSourceSet"}, {65, D<&ISelfController::ReportUserIsActive>, "ReportUserIsActive"}, - {66, nullptr, "GetCurrentIlluminance"}, + {66, &ISelfController::StubReplyZeroed, "GetCurrentIlluminance"}, {67, D<&ISelfController::IsIlluminanceAvailable>, "IsIlluminanceAvailable"}, {68, D<&ISelfController::SetAutoSleepDisabled>, "SetAutoSleepDisabled"}, {69, D<&ISelfController::IsAutoSleepDisabled>, "IsAutoSleepDisabled"}, - {70, nullptr, "ReportMultimediaError"}, - {71, nullptr, "GetCurrentIlluminanceEx"}, + {70, &ISelfController::StubReplyZeroed, "ReportMultimediaError"}, + {71, &ISelfController::StubReplyZeroed, "GetCurrentIlluminanceEx"}, {72, D<&ISelfController::SetInputDetectionPolicy>, "SetInputDetectionPolicy"}, - {80, nullptr, "SetWirelessPriorityMode"}, + {73, &ISelfController::StubReplyZeroed, "Unknown73"}, + {80, &ISelfController::StubReplyZeroed, "SetWirelessPriorityMode"}, {90, D<&ISelfController::GetAccumulatedSuspendedTickValue>, "GetAccumulatedSuspendedTickValue"}, {91, D<&ISelfController::GetAccumulatedSuspendedTickChangedEvent>, "GetAccumulatedSuspendedTickChangedEvent"}, {100, D<&ISelfController::SetAlbumImageTakenNotificationEnabled>, "SetAlbumImageTakenNotificationEnabled"}, - {110, nullptr, "SetApplicationAlbumUserData"}, + {110, &ISelfController::StubReplyZeroed, "SetApplicationAlbumUserData"}, {120, D<&ISelfController::SaveCurrentScreenshot>, "SaveCurrentScreenshot"}, {130, D<&ISelfController::SetRecordVolumeMuted>, "SetRecordVolumeMuted"}, + {200, &ISelfController::StubReplyZeroed, "Unknown200"}, + {210, &ISelfController::StubReplyZeroed, "Unknown210"}, + {211, &ISelfController::StubReplyZeroed, "Unknown211"}, + {220, &ISelfController::StubReplyZeroed, "Unknown220"}, + {221, &ISelfController::StubReplyZeroed, "Unknown221"}, {230, D<&ISelfController::Unknown230>, "Unknown230"}, {240, D<&ISelfController::Unknown240>, "Unknown240"}, - {1000, nullptr, "GetDebugStorageChannel"}, + {1000, &ISelfController::StubReplyInterface, "GetDebugStorageChannel"}, {2000, &ISelfController::GetNx2GraphicsSystemSession, "GetGraphicsSystemSession (Switch 2, guessed)"}, + {2100, &ISelfController::StubReplyZeroed, "Unknown2100"}, + {2200, &ISelfController::StubReplyZeroed, "Unknown2200"}, + {2300, &ISelfController::StubReplyZeroed, "Unknown2300"}, + {2400, &ISelfController::StubReplyZeroed, "Unknown2400"}, + {2401, &ISelfController::StubReplyZeroed, "Unknown2401"}, + {2402, &ISelfController::StubReplyZeroed, "Unknown2402"}, }; // clang-format on diff --git a/src/core/hle/service/am/service/window_controller.cpp b/src/core/hle/service/am/service/window_controller.cpp index f2b9dccb27..20bf71083e 100644 --- a/src/core/hle/service/am/service/window_controller.cpp +++ b/src/core/hle/service/am/service/window_controller.cpp @@ -18,7 +18,7 @@ IWindowController::IWindowController(Core::System& system_, std::shared_ptr<Appl m_window_system{window_system}, m_applet{std::move(applet)} { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "CreateWindow"}, + {0, &IWindowController::StubReplyInterface, "CreateWindow"}, {1, D<&IWindowController::GetAppletResourceUserId>, "GetAppletResourceUserId"}, {2, D<&IWindowController::GetAppletResourceUserIdOfCallerApplet>, "GetAppletResourceUserIdOfCallerApplet"}, {10, D<&IWindowController::AcquireForegroundRights>, "AcquireForegroundRights"}, diff --git a/src/core/hle/service/aoc/addon_content_manager.cpp b/src/core/hle/service/aoc/addon_content_manager.cpp index be2aa34baa..c63b08e40d 100644 --- a/src/core/hle/service/aoc/addon_content_manager.cpp +++ b/src/core/hle/service/aoc/addon_content_manager.cpp @@ -68,19 +68,19 @@ IAddOnContentManager::IAddOnContentManager(Core::System& system_) {6, nullptr, "PrepareAddOnContentByApplicationId"}, {7, D<&IAddOnContentManager::PrepareAddOnContent>, "PrepareAddOnContent"}, {8, D<&IAddOnContentManager::GetAddOnContentListChangedEvent>, "GetAddOnContentListChangedEvent"}, - {9, nullptr, "GetAddOnContentLostErrorCode"}, + {9, &IAddOnContentManager::StubReplyZeroed, "GetAddOnContentLostErrorCode"}, {10, D<&IAddOnContentManager::GetAddOnContentListChangedEventWithProcessId>, "GetAddOnContentListChangedEventWithProcessId"}, {11, D<&IAddOnContentManager::NotifyMountAddOnContent>, "NotifyMountAddOnContent"}, {12, D<&IAddOnContentManager::NotifyUnmountAddOnContent>, "NotifyUnmountAddOnContent"}, - {13, nullptr, "IsAddOnContentMountedForDebug"}, + {13, &IAddOnContentManager::StubReplyZeroed, "IsAddOnContentMountedForDebug"}, {50, D<&IAddOnContentManager::CheckAddOnContentMountStatus>, "CheckAddOnContentMountStatus"}, {100, D<&IAddOnContentManager::CreateEcPurchasedEventManager>, "CreateEcPurchasedEventManager"}, {101, D<&IAddOnContentManager::CreatePermanentEcPurchasedEventManager>, "CreatePermanentEcPurchasedEventManager"}, - {110, nullptr, "CreateContentsServiceManager"}, - {200, nullptr, "SetRequiredAddOnContentsOnContentsAvailabilityTransition"}, - {300, nullptr, "SetupHostAddOnContent"}, - {301, nullptr, "GetRegisteredAddOnContentPath"}, - {302, nullptr, "UpdateCachedList"}, + {110, &IAddOnContentManager::StubReplyInterface, "CreateContentsServiceManager"}, + {200, &IAddOnContentManager::StubReplyZeroed, "SetRequiredAddOnContentsOnContentsAvailabilityTransition"}, + {300, &IAddOnContentManager::StubReplyZeroed, "SetupHostAddOnContent"}, + {301, &IAddOnContentManager::StubReplyZeroed, "GetRegisteredAddOnContentPath"}, + {302, &IAddOnContentManager::StubReplyZeroed, "UpdateCachedList"}, }; // clang-format on diff --git a/src/core/hle/service/audio/audio.cpp b/src/core/hle/service/audio/audio.cpp index 211e1cb6bc..eb1511857b 100644 --- a/src/core/hle/service/audio/audio.cpp +++ b/src/core/hle/service/audio/audio.cpp @@ -6,6 +6,7 @@ #include "core/core.h" #include "core/hle/service/audio/audio.h" +#include "core/hle/service/audio/nx2_audio_device_manager.h" #include "core/hle/service/audio/nx2_audio_user_client.h" #include "core/hle/service/audio/audio_controller.h" #include "core/hle/service/audio/audio_in_manager.h" @@ -206,6 +207,8 @@ void LoopProcess(Core::System& system) { server_manager->RegisterNamedService("audren:u", std::make_shared<IAudioRendererManager>(system), 30); // Switch 2 audio entry point (nn::audio::CreateUserClientSessionByHipc). server_manager->RegisterNamedService("aud:u", std::make_shared<INx2AudioUserClient>(system), 30); + server_manager->RegisterNamedService("auddmg:u", std::make_shared<INx2AudioDeviceManagerService>(system), 30); + server_manager->RegisterNamedService("audsmx:u", std::make_shared<INx2AudioSoundMixerService>(system), 30); server_manager->RegisterNamedService("audren:a", std::make_shared<IAudioRendererManagerForApplet>(system), 30); server_manager->RegisterNamedService("hwopus", std::make_shared<IHardwareOpusDecoderManager>(system), 25); ServerManager::RunServer(std::move(server_manager)); diff --git a/src/core/hle/service/audio/audio_device.cpp b/src/core/hle/service/audio/audio_device.cpp index e9f7e074a1..8f5856b76c 100644 --- a/src/core/hle/service/audio/audio_device.cpp +++ b/src/core/hle/service/audio/audio_device.cpp @@ -16,7 +16,9 @@ IAudioDevice::IAudioDevice(Core::System& system_, u64 applet_resource_user_id, u u32 device_num) : ServiceFramework{system_, "IAudioDevice"}, service_context{system_, "IAudioDevice"}, impl{std::make_unique<AudioDevice>(system_, applet_resource_user_id, revision)}, - event{service_context.CreateEvent(fmt::format("IAudioDeviceEvent-{}", device_num))} { + event{service_context.CreateEvent(fmt::format("IAudioDeviceEvent-{}", device_num))}, + notification_event{service_context.CreateEvent( + fmt::format("IAudioDeviceNotification-{}", device_num))} { static const FunctionInfo functions[] = { {0, D<&IAudioDevice::ListAudioDeviceName>, "ListAudioDeviceName"}, {1, D<&IAudioDevice::SetAudioDeviceOutputVolume>, "SetAudioDeviceOutputVolume"}, @@ -27,18 +29,19 @@ IAudioDevice::IAudioDevice(Core::System& system_, u64 applet_resource_user_id, u {6, D<&IAudioDevice::ListAudioDeviceNameAuto>, "ListAudioDeviceNameAuto"}, {7, D<&IAudioDevice::SetAudioDeviceOutputVolumeAuto>, "SetAudioDeviceOutputVolumeAuto"}, {8, D<&IAudioDevice::GetAudioDeviceOutputVolumeAuto>, "GetAudioDeviceOutputVolumeAuto"}, + {9, &IAudioDevice::StubReplyZeroed, "Unknown9"}, {10, D<&IAudioDevice::GetActiveAudioDeviceNameAuto>, "GetActiveAudioDeviceNameAuto"}, {11, D<&IAudioDevice::QueryAudioDeviceInputEvent>, "QueryAudioDeviceInputEvent"}, {12, D<&IAudioDevice::QueryAudioDeviceOutputEvent>, "QueryAudioDeviceOutputEvent"}, {13, D<&IAudioDevice::GetActiveAudioDeviceName>, "GetActiveAudioOutputDeviceName"}, {14, D<&IAudioDevice::ListAudioOutputDeviceName>, "ListAudioOutputDeviceName"}, - {15, nullptr, "AcquireAudioInputDeviceNotification"}, //17.0.0+ - {16, nullptr, "ReleaseAudioInputDeviceNotification"}, //17.0.0+ - {17, nullptr, "AcquireAudioOutputDeviceNotification"}, //17.0.0+ - {18, nullptr, "ReleaseAudioOutputDeviceNotification"}, //17.0.0+ + {15, D<&IAudioDevice::AcquireAudioDeviceNotification>, "AcquireAudioInputDeviceNotification"}, //17.0.0+ + {16, D<&IAudioDevice::ReleaseAudioDeviceNotification>, "ReleaseAudioInputDeviceNotification"}, //17.0.0+ + {17, D<&IAudioDevice::AcquireAudioDeviceNotification>, "AcquireAudioOutputDeviceNotification"}, //17.0.0+ + {18, D<&IAudioDevice::ReleaseAudioDeviceNotification>, "ReleaseAudioOutputDeviceNotification"}, //17.0.0+ {19, D<&IAudioDevice::SetAudioDeviceOutputVolumeAutoTuneEnabled>, "SetAudioDeviceOutputVolumeAutoTuneEnabled"}, //18.0.0+ {20, D<&IAudioDevice::IsAudioDeviceOutputVolumeAutoTuneEnabled>, "IsAudioDeviceOutputVolumeAutoTuneEnabled"}, //18.0.0+ - {21, nullptr, "IsActiveOutputDeviceEstimatedLowLatency"} //21.0.0+ + {21, &IAudioDevice::StubReplyZeroed, "IsActiveOutputDeviceEstimatedLowLatency"} //21.0.0+ }; RegisterHandlers(functions); @@ -46,6 +49,7 @@ IAudioDevice::IAudioDevice(Core::System& system_, u64 applet_resource_user_id, u } IAudioDevice::~IAudioDevice() { + service_context.CloseEvent(notification_event); service_context.CloseEvent(event); } @@ -145,6 +149,21 @@ Result IAudioDevice::QueryAudioDeviceOutputEvent(OutCopyHandle<Kernel::KReadable R_SUCCEED(); } +// 15/17(u64): an event that fires when the input/output device set changes, which it never +// does here. Switch 2 titles (Kirby Air Riders, Switch Sports) ask for it; the auto stub +// replied without a handle. +Result IAudioDevice::AcquireAudioDeviceNotification(OutCopyHandle<Kernel::KReadableEvent> out_event, + u64 unknown) { + LOG_DEBUG(Service_Audio, "called, unknown={:#x}", unknown); + *out_event = ¬ification_event->GetReadableEvent(); + R_SUCCEED(); +} + +Result IAudioDevice::ReleaseAudioDeviceNotification(u64 unknown) { + LOG_DEBUG(Service_Audio, "called, unknown={:#x}", unknown); + R_SUCCEED(); +} + Result IAudioDevice::GetActiveChannelCount(Out<u32> out_active_channel_count) { *out_active_channel_count = system.AudioCore().GetOutputSink().GetSystemChannels(); LOG_DEBUG(Service_Audio, "(STUBBED) called. Channels={}", *out_active_channel_count); diff --git a/src/core/hle/service/audio/audio_device.h b/src/core/hle/service/audio/audio_device.h index f6c12fbfeb..8a7b9a70b0 100644 --- a/src/core/hle/service/audio/audio_device.h +++ b/src/core/hle/service/audio/audio_device.h @@ -54,10 +54,14 @@ private: Out<s32> out_count); Result SetAudioDeviceOutputVolumeAutoTuneEnabled(bool enabled); Result IsAudioDeviceOutputVolumeAutoTuneEnabled(Out<bool> out_enabled); + Result AcquireAudioDeviceNotification(OutCopyHandle<Kernel::KReadableEvent> out_event, + u64 unknown); + Result ReleaseAudioDeviceNotification(u64 unknown); KernelHelpers::ServiceContext service_context; std::unique_ptr<AudioCore::Renderer::AudioDevice> impl; Kernel::KEvent* event; + Kernel::KEvent* notification_event; }; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audio_renderer_manager.cpp b/src/core/hle/service/audio/audio_renderer_manager.cpp index 97d9f44ddd..d669d64cec 100644 --- a/src/core/hle/service/audio/audio_renderer_manager.cpp +++ b/src/core/hle/service/audio/audio_renderer_manager.cpp @@ -27,7 +27,7 @@ IAudioRendererManager::IAudioRendererManager(Core::System& system_) {0, D<&IAudioRendererManager::OpenAudioRenderer>, "OpenAudioRenderer"}, {1, D<&IAudioRendererManager::GetWorkBufferSize>, "GetWorkBufferSize"}, {2, D<&IAudioRendererManager::GetAudioDeviceService>, "GetAudioDeviceService"}, - {3, nullptr, "OpenAudioRendererForManualExecution"}, + {3, &IAudioRendererManager::StubReplyInterface, "OpenAudioRendererForManualExecution"}, {4, D<&IAudioRendererManager::GetAudioDeviceServiceWithRevisionInfo>, "GetAudioDeviceServiceWithRevisionInfo"}, }; // clang-format on diff --git a/src/core/hle/service/audio/nx2_audio_device_manager.h b/src/core/hle/service/audio/nx2_audio_device_manager.h new file mode 100644 index 0000000000..746454660e --- /dev/null +++ b/src/core/hle/service/audio/nx2_audio_device_manager.h @@ -0,0 +1,539 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <algorithm> +#include <array> +#include <atomic> +#include <chrono> +#include <cstring> +#include <map> +#include <memory> +#include <mutex> +#include <optional> +#include <string_view> +#include <vector> + +#include <fmt/ranges.h> + +#include "common/string_util.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/hle/kernel/k_event.h" +#include "core/memory.h" +#include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/kernel_helpers.h" +#include "core/hle/service/service.h" + +namespace Service::Audio { + +// Switch 2 sound mixer streams (nn::audio::sysmix, behind audsmx:u). A stream is a ring buffer in +// the game's own memory: CreateStream hands over its address and size (4 KiB aligned), OpenStream +// its format and length in frames, and once the streams are engaged the system takes frames out of +// the ring at the stream's sample rate. Eden doesn't mix these into its output yet; it drains the +// rings in real time so the game sees its audio being played (Switch Sports' menu movie feeds one). +// The ring's shared indices, in frames modulo (frame count + 1): the system's read index at +// buffer + 0x44, the game's write index at buffer + 0x1004. +struct Nx2SysmixStream { + u32 id{}; + u32 owner{}; ///< the user session whose timer drains it + u64 buffer{}; + u64 size{}; + u32 sample_type{}; + u32 channels{}; + u32 sample_rate{}; + u32 frames{}; + bool open{}; + bool engaged{}; + double owed_frames{}; + u32 data_mode{}; ///< command 7's mode; 0 leaves the data event alone + s32 data_threshold{}; ///< free frames that signal the data event + Kernel::KEvent* state_event{}; + Kernel::KEvent* data_event{}; +}; + +struct Nx2SysmixStreams { + std::mutex mutex; + std::map<u32, std::shared_ptr<Nx2SysmixStream>> streams; + u32 next_id{1}; +}; + +// Streams are created through audsmx:u but started and stopped through auddmg:u (AttachStream and +// DetachStream), so both services share one list. +inline std::shared_ptr<Nx2SysmixStreams> Nx2SysmixStreamList() { + static const auto list = std::make_shared<Nx2SysmixStreams>(); + return list; +} + +// Switch 2 virtual output devices (nn::audio::ListVirtualOutputDevices and friends), reached through +// the object auddmg:u command 0 returns. Eden plays everything through one stereo 48 kHz output, so +// there is one virtual device. The nx2: name keeps the other commands auto-stubbed and logged. +// - 0(u32): u32 count, the device names in the out buffer (0x100 bytes each). +// - 100(u32): the default device's name in the out buffer. +// - 300(16-byte stream format, name buffer): whether the format is supported (result only). +// - 401(u32, name buffer): the device's 0x228-byte property block in the out buffer. The sdk hands +// the game the name, the channel count (0x108) and the u64 at 0x218; with the zeroed placeholder +// reply it saw 0 channels and Switch Sports failed to set up audio (2153-1604). +// - 500(16 bytes, name buffer): an event signalled when the device's state changes. +// - 700(u32 stream id, u32 device functionality, name buffer): attach a sysmix stream to the +// device, which starts it (nn::audio::devmgr::AttachStream, behind OutputStream Start). +// - 800(u32 stream id): detach it again (Stop). +class INx2AudioVirtualDeviceManager final : public ServiceFramework<INx2AudioVirtualDeviceManager> { +public: + explicit INx2AudioVirtualDeviceManager(Core::System& system_) + : ServiceFramework{system_, "nx2:auddmg:u:manager"}, + service_context{system_, "nx2:auddmg:u:manager"}, + state_event{service_context.CreateEvent("nx2:auddmg:u:StateEvent")} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &INx2AudioVirtualDeviceManager::ListVirtualOutputDevices, "ListVirtualOutputDevices"}, + {100, &INx2AudioVirtualDeviceManager::GetDefaultVirtualOutputDevice, "GetDefaultVirtualOutputDevice"}, + {300, &INx2AudioVirtualDeviceManager::IsStreamFormatSupported, "IsVirtualOutputDeviceStreamFormatSupported"}, + {401, &INx2AudioVirtualDeviceManager::GetVirtualOutputDeviceProperties, "GetVirtualOutputDeviceProperties"}, + {500, &INx2AudioVirtualDeviceManager::AcquireStateChangeEvent, "AcquireVirtualOutputDeviceStateEvent (guessed)"}, + {700, &INx2AudioVirtualDeviceManager::AttachStream, "AttachStream"}, + {800, &INx2AudioVirtualDeviceManager::DetachStream, "DetachStream"}, + }; + // clang-format on + RegisterHandlers(functions); + } + + ~INx2AudioVirtualDeviceManager() override { + service_context.CloseEvent(state_event); + } + +private: + static constexpr std::size_t NameSize = 0x100; + static constexpr std::string_view DeviceName = "AudioVirtualOutput0"; + static constexpr std::string_view PhysicalDeviceName = "AudioTvOutput"; + + static void CopyName(u8* out, std::string_view name) { + std::memcpy(out, name.data(), std::min(name.size(), NameSize - 1)); + } + + void ListVirtualOutputDevices(HLERequestContext& ctx) { + std::array<u8, NameSize> names{}; + CopyName(names.data(), DeviceName); + const u32 count = ctx.GetWriteBufferSize() >= NameSize ? 1 : 0; + if (count) { + ctx.WriteBuffer(names); + } + LOG_DEBUG(Service_Audio, "called, {} device", count); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(count); + } + + void GetDefaultVirtualOutputDevice(HLERequestContext& ctx) { + std::array<u8, NameSize> name{}; + CopyName(name.data(), DeviceName); + ctx.WriteBuffer(name); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void IsStreamFormatSupported(HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + // Property block: name (0x000), u32 type (0x100, 3), u32 (0x104, 2), u32 channel count + // (0x108), u32 sample rate (0x10c), physical output name (0x110), its id (0x210), u64 latency + // in ns (0x218), u32 device id (0x220). The sdk's output streams pass the device id to + // sysmix CreateStream, which refuses 0 (Switch Sports failed with 2153-1604 on a zero there). + void GetVirtualOutputDeviceProperties(HLERequestContext& ctx) { + std::array<u8, 0x228> block{}; + const auto name = ctx.ReadBuffer(0); + std::memcpy(block.data(), name.data(), std::min(name.size(), NameSize - 1)); + const auto put32 = [&](std::size_t offset, u32 value) { + std::memcpy(block.data() + offset, &value, sizeof(value)); + }; + put32(0x100, 3); + put32(0x104, 2); + put32(0x108, 2); // stereo + put32(0x10c, 48000); // Hz + CopyName(block.data() + 0x110, PhysicalDeviceName); + const u64 latency_ns = 10'000'000; + std::memcpy(block.data() + 0x218, &latency_ns, sizeof(latency_ns)); + put32(0x220, 1); + ctx.WriteBuffer(block); + LOG_DEBUG(Service_Audio, "called for {}", Common::StringFromBuffer(name)); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void AcquireStateChangeEvent(HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(ctx, state_event->GetReadableEvent()); + } + + void AttachStream(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto stream_id = rp.Pop<u32>(); + const auto functionality = rp.Pop<u32>(); + LOG_INFO(Service_Audio, "attach stream {} to {} (functionality {})", stream_id, + Common::StringFromBuffer(ctx.ReadBuffer(0)), functionality); + SetStreamEngaged(stream_id, true); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void DetachStream(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto stream_id = rp.Pop<u32>(); + LOG_INFO(Service_Audio, "detach stream {}", stream_id); + SetStreamEngaged(stream_id, false); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void SetStreamEngaged(u32 stream_id, bool engaged) { + const auto list = Nx2SysmixStreamList(); + std::scoped_lock lk{list->mutex}; + if (const auto it = list->streams.find(stream_id); it != list->streams.end()) { + it->second->engaged = engaged; + it->second->owed_frames = 0; + if (it->second->state_event) { + it->second->state_event->Signal(system.Kernel()); + } + } + } + + KernelHelpers::ServiceContext service_context; + Kernel::KEvent* state_event; +}; + +// Switch 2 "auddmg:u": command 0 (16 bytes and the pid) returns the virtual device manager. +class INx2AudioDeviceManagerService final : public ServiceFramework<INx2AudioDeviceManagerService> { +public: + explicit INx2AudioDeviceManagerService(Core::System& system_) + : ServiceFramework{system_, "nx2:auddmg:u"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &INx2AudioDeviceManagerService::OpenVirtualDeviceManager, "OpenVirtualDeviceManager (guessed)"}, + }; + // clang-format on + RegisterHandlers(functions); + } + +private: + void OpenVirtualDeviceManager(HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<INx2AudioVirtualDeviceManager>(ctx, system); + } +}; + +// The stream object IUserSession command 1 returns. Commands (ids, request -> reply): +// - 0(u32 sample type, u32 channels, u32 sample rate, u32 frames, u32 prefill, u32 flags): open. +// - 1: close. 2: u32 state. 3: f32 gain. 4(f32 gain, s64 ramp ns): set the gain. +// - 5 / 6: acquire (copy handle) / release the state-change event. +// - 7(u32 mode, s32 threshold) / 8: acquire (copy handle) / release the data event. +// - 9(s64 ns): clock the stream. 10(s32 frames) -> u32: prime. 11(s32 frames) -> u32: purge. +// - 12(name buffer): rename. +class INx2SysmixStreamSession final : public ServiceFramework<INx2SysmixStreamSession> { +public: + explicit INx2SysmixStreamSession(Core::System& system_, std::shared_ptr<Nx2SysmixStreams> streams_, + std::shared_ptr<Nx2SysmixStream> stream_) + : ServiceFramework{system_, "nx2:audsmx:u:stream"}, service_context{system_, "nx2:audsmx:u:stream"}, + streams{std::move(streams_)}, stream{std::move(stream_)} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &INx2SysmixStreamSession::OpenStream, "OpenStream"}, + {1, &INx2SysmixStreamSession::CloseStream, "CloseStream"}, + {2, &INx2SysmixStreamSession::GetStreamState, "GetStreamState"}, + {3, &INx2SysmixStreamSession::GetStreamGain, "GetStreamGain"}, + {4, &INx2SysmixStreamSession::SetStreamGain, "SetStreamGain"}, + {5, &INx2SysmixStreamSession::AcquireStateChangeEvent, "AcquireStreamStateChangeEvent"}, + {6, &INx2SysmixStreamSession::Succeed, "ReleaseStreamStateChangeEvent"}, + {7, &INx2SysmixStreamSession::AcquireDataEvent, "AcquireStreamDataEvent"}, + {8, &INx2SysmixStreamSession::Succeed, "ReleaseStreamDataEvent"}, + {9, &INx2SysmixStreamSession::Succeed, "ClockStream"}, + {10, &INx2SysmixStreamSession::EchoFrames, "PrimeStream"}, + {11, &INx2SysmixStreamSession::EchoFrames, "PurgeStream"}, + {12, &INx2SysmixStreamSession::Succeed, "RenameStream"}, + }; + // clang-format on + RegisterHandlers(functions); + std::scoped_lock lk{streams->mutex}; + stream->state_event = service_context.CreateEvent("nx2:audsmx:u:StreamStateEvent"); + stream->data_event = service_context.CreateEvent("nx2:audsmx:u:StreamDataEvent"); + } + + ~INx2SysmixStreamSession() override { + // The drain timer signals these events under the lock: unlist the stream first. + { + std::scoped_lock lk{streams->mutex}; + streams->streams.erase(stream->id); + service_context.CloseEvent(stream->state_event); + service_context.CloseEvent(stream->data_event); + stream->state_event = nullptr; + stream->data_event = nullptr; + } + } + +private: + // States the session reports: 0 created, 1 open, 2 open and engaged. + void OpenStream(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto sample_type = rp.Pop<u32>(); + const auto channels = rp.Pop<u32>(); + const auto sample_rate = rp.Pop<u32>(); + const auto frames = rp.Pop<u32>(); + const auto prefill = rp.Pop<u32>(); + const auto flags = rp.Pop<u32>(); + LOG_INFO(Service_Audio, + "stream {} open: sample type {}, {} channels, {} Hz, {} frames, prefill {}, flags {:#x}", + stream->id, sample_type, channels, sample_rate, frames, prefill, flags); + { + std::scoped_lock lk{streams->mutex}; + stream->sample_type = sample_type; + stream->channels = channels; + stream->sample_rate = sample_rate; + stream->frames = frames; + stream->owed_frames = 0; + stream->open = true; + stream->state_event->Signal(system.Kernel()); + } + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void CloseStream(HLERequestContext& ctx) { + { + std::scoped_lock lk{streams->mutex}; + stream->open = false; + stream->state_event->Signal(system.Kernel()); + } + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void GetStreamState(HLERequestContext& ctx) { + u32 state{}; + { + std::scoped_lock lk{streams->mutex}; + state = stream->open ? (stream->engaged ? 2 : 1) : 0; + } + LOG_DEBUG(Service_Audio, "stream {} state {}", stream->id, state); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(state); + } + + void GetStreamGain(HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(gain); + } + + void SetStreamGain(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + gain = rp.Pop<f32>(); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void AcquireStateChangeEvent(HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(ctx, stream->state_event->GetReadableEvent()); + } + + void AcquireDataEvent(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto mode = rp.Pop<u32>(); + const auto threshold = rp.Pop<s32>(); + LOG_INFO(Service_Audio, "stream {} data event: mode {}, threshold {}", stream->id, mode, threshold); + { + // The game waits on this before its first write, so signal it at once when the ring + // already has room (it is empty right after OpenStream); the drain timer keeps it up. + std::scoped_lock lk{streams->mutex}; + stream->data_mode = mode; + stream->data_threshold = threshold; + if (mode != 0 && stream->open && static_cast<s64>(stream->frames) >= threshold) { + stream->data_event->Signal(system.Kernel()); + } + } + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(ctx, stream->data_event->GetReadableEvent()); + } + + void EchoFrames(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto frames = rp.Pop<s32>(); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push<u32>(static_cast<u32>((std::max)(frames, 0))); + } + + void Succeed(HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + KernelHelpers::ServiceContext service_context; + std::shared_ptr<Nx2SysmixStreams> streams; + std::shared_ptr<Nx2SysmixStream> stream; + f32 gain{1.0f}; +}; + +// The session audsmx:u command 0 returns. Commands: +// - 1(u32, u32 creation flags, u64 pid, u64 buffer address, u64 buffer size, name buffer): +// u32 stream id and the stream object. +// - 2 / 3(buffer of u32 stream ids): engage / disengage those streams. +class INx2SysmixUserSession final : public ServiceFramework<INx2SysmixUserSession> { +public: + explicit INx2SysmixUserSession(Core::System& system_) : ServiceFramework{system_, "nx2:audsmx:u:session"} { + // clang-format off + static const FunctionInfo functions[] = { + {1, &INx2SysmixUserSession::CreateStream, "CreateStream"}, + {2, &INx2SysmixUserSession::EngageStreams, "EngageStreams"}, + {3, &INx2SysmixUserSession::DisengageStreams, "DisengageStreams"}, + }; + // clang-format on + RegisterHandlers(functions); + drain_timer = Core::Timing::CreateEvent( + "nx2:audsmx:u drain", + [streams = streams, &system = system, owner = session_id](s64, std::chrono::nanoseconds) + -> std::optional<std::chrono::nanoseconds> { + Drain(system, *streams, owner); + return std::nullopt; + }); + system.CoreTiming().ScheduleLoopingEvent(DrainInterval, DrainInterval, drain_timer); + } + + ~INx2SysmixUserSession() override { + system.CoreTiming().UnscheduleEvent(drain_timer); + } + +private: + static constexpr std::chrono::nanoseconds DrainInterval{std::chrono::milliseconds{5}}; + static constexpr u64 ReadIndexOffset = 0x44; + static constexpr u64 WriteIndexOffset = 0x1004; + + // Takes up to one interval's worth of frames out of each engaged ring (frames owed while a ring + // is empty are dropped, as an underrun plays silence), and signals the data event of every open + // stream with at least its threshold of free frames, engaged or not: the game fills the ring + // before it engages the stream. + static void Drain(Core::System& system, Nx2SysmixStreams& state, u32 owner) { + std::scoped_lock lk{state.mutex}; + auto& memory = system.ApplicationMemory(); + for (auto& [id, stream] : state.streams) { + if (stream->owner != owner || !stream->open || stream->frames == 0 || !stream->buffer) { + continue; + } + const u32 capacity = stream->frames + 1; + const u32 write = memory.Read32(stream->buffer + WriteIndexOffset); + const u32 read = memory.Read32(stream->buffer + ReadIndexOffset); + if (write >= capacity || read >= capacity) { + continue; + } + u32 filled = (write + capacity - read) % capacity; + if (stream->engaged) { + stream->owed_frames += stream->sample_rate * + std::chrono::duration<double>(DrainInterval).count(); + const auto owed = static_cast<u32>(stream->owed_frames); + stream->owed_frames -= owed; + const u32 taken = (std::min)(filled, owed); + if (taken != 0) { + memory.Write32(stream->buffer + ReadIndexOffset, (read + taken) % capacity); + filled -= taken; + } + } + const u32 free_frames = capacity - 1 - filled; + if (stream->data_event && stream->data_mode != 0 && + static_cast<s64>(free_frames) >= stream->data_threshold) { + stream->data_event->Signal(system.Kernel()); + } + } + } + + void CreateStream(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto kind = rp.Pop<u32>(); + const auto flags = rp.Pop<u32>(); + [[maybe_unused]] const auto pid = rp.Pop<u64>(); + const auto buffer = rp.Pop<u64>(); + const auto size = rp.Pop<u64>(); + const auto name = Common::StringFromBuffer(ctx.ReadBuffer(0)); + auto stream = std::make_shared<Nx2SysmixStream>(); + stream->buffer = buffer; + stream->size = size; + { + std::scoped_lock lk{streams->mutex}; + stream->id = streams->next_id++; + stream->owner = session_id; + streams->streams.emplace(stream->id, stream); + } + LOG_INFO(Service_Audio, "stream {} '{}': kind {}, flags {:#x}, buffer {:#x} size {:#x}", + stream->id, name, kind, flags, buffer, size); + IPC::ResponseBuilder rb{ctx, 3, 0, 1}; + rb.Push(ResultSuccess); + rb.Push(stream->id); + rb.PushIpcInterface<INx2SysmixStreamSession>(ctx, system, streams, stream); + } + + void SetEngaged(HLERequestContext& ctx, bool engaged) { + const auto buffer = ctx.ReadBuffer(0); + std::vector<u32> ids(buffer.size() / sizeof(u32)); + std::memcpy(ids.data(), buffer.data(), ids.size() * sizeof(u32)); + { + std::scoped_lock lk{streams->mutex}; + for (const u32 id : ids) { + if (const auto it = streams->streams.find(id); it != streams->streams.end()) { + it->second->engaged = engaged; + it->second->owed_frames = 0; + if (it->second->state_event) { + it->second->state_event->Signal(system.Kernel()); + } + } + } + } + LOG_INFO(Service_Audio, "{} streams {}", engaged ? "engage" : "disengage", fmt::join(ids, ",")); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void EngageStreams(HLERequestContext& ctx) { + SetEngaged(ctx, true); + } + + void DisengageStreams(HLERequestContext& ctx) { + SetEngaged(ctx, false); + } + + static inline std::atomic<u32> next_session_id{1}; + const u32 session_id = next_session_id++; + std::shared_ptr<Nx2SysmixStreams> streams = Nx2SysmixStreamList(); + std::shared_ptr<Core::Timing::EventType> drain_timer; +}; + +// Switch 2 "audsmx:u": command 0 (16 bytes, the pid and a copy handle) returns the user session. +class INx2AudioSoundMixerService final : public ServiceFramework<INx2AudioSoundMixerService> { +public: + explicit INx2AudioSoundMixerService(Core::System& system_) + : ServiceFramework{system_, "nx2:audsmx:u"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &INx2AudioSoundMixerService::OpenUserSession, "OpenUserSession"}, + }; + // clang-format on + RegisterHandlers(functions); + } + +private: + void OpenUserSession(HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<INx2SysmixUserSession>(ctx, system); + } +}; + +} // namespace Service::Audio diff --git a/src/core/hle/service/caps/caps_su.cpp b/src/core/hle/service/caps/caps_su.cpp index 4ecfc840e4..b19e6b7a27 100644 --- a/src/core/hle/service/caps/caps_su.cpp +++ b/src/core/hle/service/caps/caps_su.cpp @@ -21,10 +21,10 @@ IScreenShotApplicationService::IScreenShotApplicationService( // clang-format off static const FunctionInfo functions[] = { {32, C<&IScreenShotApplicationService::SetShimLibraryVersion>, "SetShimLibraryVersion"}, - {201, nullptr, "SaveScreenShot"}, + {201, &IScreenShotApplicationService::StubReplyZeroed, "SaveScreenShot"}, {203, C<&IScreenShotApplicationService::SaveScreenShotEx0>, "SaveScreenShotEx0"}, {205, C<&IScreenShotApplicationService::SaveScreenShotEx1>, "SaveScreenShotEx1"}, - {210, nullptr, "SaveScreenShotEx2"}, + {210, &IScreenShotApplicationService::StubReplyZeroed, "SaveScreenShotEx2"}, }; // clang-format on diff --git a/src/core/hle/service/caps/caps_u.cpp b/src/core/hle/service/caps/caps_u.cpp index 6472b2bac6..eb4a10193e 100644 --- a/src/core/hle/service/caps/caps_u.cpp +++ b/src/core/hle/service/caps/caps_u.cpp @@ -20,17 +20,21 @@ IAlbumApplicationService::IAlbumApplicationService(Core::System& system_, static const FunctionInfo functions[] = { {32, C<&IAlbumApplicationService::SetShimLibraryVersion>, "SetShimLibraryVersion"}, {102, C<&IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated>, "GetAlbumFileList0AafeAruidDeprecated"}, - {103, nullptr, "DeleteAlbumFileByAruid"}, - {104, nullptr, "GetAlbumFileSizeByAruid"}, - {105, nullptr, "DeleteAlbumFileByAruidForDebug"}, - {110, nullptr, "LoadAlbumScreenShotImageByAruid"}, - {120, nullptr, "LoadAlbumScreenShotThumbnailImageByAruid"}, - {130, nullptr, "PrecheckToCreateContentsByAruid"}, - {140, nullptr, "GetAlbumFileList1AafeAruidDeprecated"}, - {141, nullptr, "GetAlbumFileList2AafeUidAruidDeprecated"}, + {103, &IAlbumApplicationService::StubReplyZeroed, "DeleteAlbumFileByAruid"}, + {104, &IAlbumApplicationService::StubReplyZeroed, "GetAlbumFileSizeByAruid"}, + {105, &IAlbumApplicationService::StubReplyZeroed, "DeleteAlbumFileByAruidForDebug"}, + {110, &IAlbumApplicationService::StubReplyZeroed, "LoadAlbumScreenShotImageByAruid"}, + {120, &IAlbumApplicationService::StubReplyZeroed, "LoadAlbumScreenShotThumbnailImageByAruid"}, + {130, &IAlbumApplicationService::StubReplyZeroed, "PrecheckToCreateContentsByAruid"}, + {140, &IAlbumApplicationService::StubReplyZeroed, "GetAlbumFileList1AafeAruidDeprecated"}, + {141, &IAlbumApplicationService::StubReplyZeroed, "GetAlbumFileList2AafeUidAruidDeprecated"}, {142, C<&IAlbumApplicationService::GetAlbumFileList3AaeAruid>, "GetAlbumFileList3AaeAruid"}, - {143, nullptr, "GetAlbumFileList4AaeUidAruid"}, - {144, nullptr, "GetAllAlbumFileList3AaeAruid"}, + {143, &IAlbumApplicationService::StubReplyZeroed, "GetAlbumFileList4AaeUidAruid"}, + {144, &IAlbumApplicationService::StubReplyZeroed, "GetAllAlbumFileList3AaeAruid"}, + {145, &IAlbumApplicationService::StubReplyZeroed, "Unknown145"}, + {146, &IAlbumApplicationService::StubReplyZeroed, "Unknown146"}, + {147, &IAlbumApplicationService::StubReplyZeroed, "Unknown147"}, + {148, &IAlbumApplicationService::StubReplyZeroed, "Unknown148"}, {60002, nullptr, "OpenAccessorSessionForApplication"}, }; // clang-format on diff --git a/src/core/hle/service/hid/hid_server.cpp b/src/core/hle/service/hid/hid_server.cpp index 04b4663bc2..20118345a9 100644 --- a/src/core/hle/service/hid/hid_server.cpp +++ b/src/core/hle/service/hid/hid_server.cpp @@ -74,12 +74,12 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr<ResourceManager> r {70, C<&IHidServer::SetSixAxisSensorFusionParameters>, "SetSixAxisSensorFusionParameters"}, {71, C<&IHidServer::GetSixAxisSensorFusionParameters>, "GetSixAxisSensorFusionParameters"}, {72, C<&IHidServer::ResetSixAxisSensorFusionParameters>, "ResetSixAxisSensorFusionParameters"}, - {73, nullptr, "SetAccelerometerParameters"}, - {74, nullptr, "GetAccelerometerParameters"}, - {75, nullptr, "ResetAccelerometerParameters"}, - {76, nullptr, "SetAccelerometerPlayMode"}, - {77, nullptr, "GetAccelerometerPlayMode"}, - {78, nullptr, "ResetAccelerometerPlayMode"}, + {73, &IHidServer::StubReplyZeroed, "SetAccelerometerParameters"}, + {74, &IHidServer::StubReplyZeroed, "GetAccelerometerParameters"}, + {75, &IHidServer::StubReplyZeroed, "ResetAccelerometerParameters"}, + {76, &IHidServer::StubReplyZeroed, "SetAccelerometerPlayMode"}, + {77, &IHidServer::StubReplyZeroed, "GetAccelerometerPlayMode"}, + {78, &IHidServer::StubReplyZeroed, "ResetAccelerometerPlayMode"}, {79, C<&IHidServer::SetGyroscopeZeroDriftMode>, "SetGyroscopeZeroDriftMode"}, {80, C<&IHidServer::GetGyroscopeZeroDriftMode>, "GetGyroscopeZeroDriftMode"}, {81, C<&IHidServer::ResetGyroscopeZeroDriftMode>, "ResetGyroscopeZeroDriftMode"}, @@ -87,7 +87,7 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr<ResourceManager> r {83, C<&IHidServer::IsFirmwareUpdateAvailableForSixAxisSensor>, "IsFirmwareUpdateAvailableForSixAxisSensor"}, {84, C<&IHidServer::EnableSixAxisSensorUnalteredPassthrough>, "EnableSixAxisSensorUnalteredPassthrough"}, {85, C<&IHidServer::IsSixAxisSensorUnalteredPassthroughEnabled>, "IsSixAxisSensorUnalteredPassthroughEnabled"}, - {86, nullptr, "StoreSixAxisSensorCalibrationParameter"}, + {86, &IHidServer::StubReplyZeroed, "StoreSixAxisSensorCalibrationParameter"}, {87, C<&IHidServer::LoadSixAxisSensorCalibrationParameter>, "LoadSixAxisSensorCalibrationParameter"}, {88, C<&IHidServer::GetSixAxisSensorIcInformation>, "GetSixAxisSensorIcInformation"}, {89, C<&IHidServer::ResetIsSixAxisSensorDeviceNewlyAssigned>, "ResetIsSixAxisSensorDeviceNewlyAssigned"}, @@ -119,6 +119,13 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr<ResourceManager> r {134, C<&IHidServer::SetNpadAnalogStickUseCenterClamp>, "SetNpadAnalogStickUseCenterClamp"}, {135, C<&IHidServer::SetNpadCaptureButtonAssignment>, "SetNpadCaptureButtonAssignment"}, {136, C<&IHidServer::ClearNpadCaptureButtonAssignment>, "ClearNpadCaptureButtonAssignment"}, + {137, &IHidServer::StubReplyZeroed, "Unknown137"}, + {138, &IHidServer::StubReplyZeroed, "Unknown138"}, + {139, &IHidServer::StubReplyZeroed, "Unknown139"}, + {140, &IHidServer::StubReplyZeroed, "Unknown140"}, + {150, &IHidServer::StubReplyZeroed, "Unknown150"}, + {151, &IHidServer::StubReplyZeroed, "Unknown151"}, + {160, &IHidServer::StubReplyZeroed, "Unknown160"}, {200, C<&IHidServer::GetVibrationDeviceInfo>, "GetVibrationDeviceInfo"}, {201, C<&IHidServer::SendVibrationValue>, "SendVibrationValue"}, {202, C<&IHidServer::GetActualVibrationValue>, "GetActualVibrationValue"}, @@ -132,7 +139,9 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr<ResourceManager> r {210, C<&IHidServer::EndPermitVibrationSession>, "EndPermitVibrationSession"}, {211, C<&IHidServer::IsVibrationDeviceMounted>, "IsVibrationDeviceMounted"}, {212, C<&IHidServer::SendVibrationValueInBool>, "SendVibrationValueInBool"}, + {213, C<&IHidServer::Unknown213>, "Unknown213"}, {214, C<&IHidServer::SendNx2VibrationValues>, "SendNx2VibrationValues (guessed)"}, + {220, C<&IHidServer::Unknown220>, "Unknown220"}, {300, C<&IHidServer::ActivateConsoleSixAxisSensor>, "ActivateConsoleSixAxisSensor"}, {301, C<&IHidServer::StartConsoleSixAxisSensor>, "StartConsoleSixAxisSensor"}, {302, C<&IHidServer::StopConsoleSixAxisSensor>, "StopConsoleSixAxisSensor"}, @@ -141,19 +150,24 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr<ResourceManager> r {305, C<&IHidServer::StopSevenSixAxisSensor>, "StopSevenSixAxisSensor"}, {306, C<&IHidServer::InitializeSevenSixAxisSensor>, "InitializeSevenSixAxisSensor"}, {307, C<&IHidServer::FinalizeSevenSixAxisSensor>, "FinalizeSevenSixAxisSensor"}, - {308, nullptr, "SetSevenSixAxisSensorFusionStrength"}, - {309, nullptr, "GetSevenSixAxisSensorFusionStrength"}, + {308, &IHidServer::StubReplyZeroed, "SetSevenSixAxisSensorFusionStrength"}, + {309, &IHidServer::StubReplyZeroed, "GetSevenSixAxisSensorFusionStrength"}, {310, C<&IHidServer::ResetSevenSixAxisSensorTimestamp>, "ResetSevenSixAxisSensorTimestamp"}, - {320, nullptr, "EnableNpadImu"}, //21.0.0+ - {321, nullptr, "DisableNpadImu"}, //21.0.0+ + {311, &IHidServer::StubReplyZeroed, "Unknown311"}, + {312, &IHidServer::StubReplyZeroed, "Unknown312"}, + {320, &IHidServer::StubReplyZeroed, "EnableNpadImu"}, //21.0.0+ + {321, &IHidServer::StubReplyZeroed, "DisableNpadImu"}, //21.0.0+ + {322, &IHidServer::StubReplyZeroed, "Unknown322"}, + {323, &IHidServer::StubReplyZeroed, "Unknown323"}, + {324, &IHidServer::StubReplyZeroed, "Unknown324"}, {400, C<&IHidServer::IsUsbFullKeyControllerEnabled>, "IsUsbFullKeyControllerEnabled"}, - {401, nullptr, "EnableUsbFullKeyController"}, - {402, nullptr, "IsUsbFullKeyControllerConnected"}, - {403, nullptr, "HasBattery"}, - {404, nullptr, "HasLeftRightBattery"}, - {405, nullptr, "GetNpadInterfaceType"}, - {406, nullptr, "GetNpadLeftRightInterfaceType"}, - {407, nullptr, "GetNpadOfHighestBatteryLevel"}, + {401, &IHidServer::StubReplyZeroed, "EnableUsbFullKeyController"}, + {402, &IHidServer::StubReplyZeroed, "IsUsbFullKeyControllerConnected"}, + {403, &IHidServer::StubReplyZeroed, "HasBattery"}, + {404, &IHidServer::StubReplyZeroed, "HasLeftRightBattery"}, + {405, &IHidServer::StubReplyZeroed, "GetNpadInterfaceType"}, + {406, &IHidServer::StubReplyZeroed, "GetNpadLeftRightInterfaceType"}, + {407, &IHidServer::StubReplyZeroed, "GetNpadOfHighestBatteryLevel"}, {408, nullptr, "GetNpadOfHighestBatteryLevelForJoyRight"}, {500, C<&IHidServer::GetPalmaConnectionHandle>, "GetPalmaConnectionHandle"}, {501, C<&IHidServer::InitializePalma>, "InitializePalma"}, @@ -185,11 +199,13 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr<ResourceManager> r {527, C<&IHidServer::EnablePalmaBoostMode>, "EnablePalmaBoostMode"}, {528, C<&IHidServer::GetPalmaBluetoothAddress>, "GetPalmaBluetoothAddress"}, {529, C<&IHidServer::SetDisallowedPalmaConnection>, "SetDisallowedPalmaConnection"}, + {550, &IHidServer::StubReplyZeroed, "Unknown550"}, {1000, C<&IHidServer::SetNpadCommunicationMode>, "SetNpadCommunicationMode"}, {1001, C<&IHidServer::GetNpadCommunicationMode>, "GetNpadCommunicationMode"}, {1002, C<&IHidServer::SetTouchScreenConfiguration>, "SetTouchScreenConfiguration"}, {1003, C<&IHidServer::IsFirmwareUpdateNeededForNotification>, "IsFirmwareUpdateNeededForNotification"}, {1004, C<&IHidServer::SetTouchScreenResolution>, "SetTouchScreenResolution"}, + {1005, &IHidServer::StubReplyZeroed, "Unknown1005"}, {1270, nullptr, "DeleteButtonConfigStorageRight"}, {1271, nullptr, "IsUsingCustomButtonConfig"}, {1272, nullptr, "IsAnyCustomButtonConfigEnabled"}, @@ -222,23 +238,49 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr<ResourceManager> r {1325, nullptr, "IsTouchScreenAutoTuneRequiredForRepairProviderReplacement"}, {1420, nullptr, "GetAppletResourceProperty"}, {2000, nullptr, "ActivateDigitizer"}, - {3000, nullptr, "GetDebugPadGenericPadMap"}, - {3001, nullptr, "SetDebugPadGenericPadMap"}, - {3002, nullptr, "ResetDebugPadGenericPadMap"}, - {3003, nullptr, "GetDebugPadKeyboardMap"}, - {3004, nullptr, "SetDebugPadKeyboardMap"}, - {3005, nullptr, "ResetDebugPadKeyboardMap"}, - {3006, nullptr, "GetFullKeyGenericPadMap"}, - {3007, nullptr, "SetFullKeyGenericPadMap"}, - {3008, nullptr, "ResetFullKeyGenericPadMap"}, - {3009, nullptr, "GetFullKeyKeyboardMap"}, - {3010, nullptr, "SetFullKeyKeyboardMap"}, - {3011, nullptr, "ResetFullKeyKeyboardMap"}, - {3012, nullptr, "GetDebugPadGenericPadMap"}, //21.0.0+ - {3013, nullptr, "SetDebugPadGenericPadMap"}, //21.0.0+ - {3014, nullptr, "GetDebugPadKeyboardMap"}, //21.0.0+ - {3015, nullptr, "SetDebugPadKeyboardMap"}, //21.0.0+ + {3000, &IHidServer::StubReplyZeroed, "GetDebugPadGenericPadMap"}, + {3001, &IHidServer::StubReplyZeroed, "SetDebugPadGenericPadMap"}, + {3002, &IHidServer::StubReplyZeroed, "ResetDebugPadGenericPadMap"}, + {3003, &IHidServer::StubReplyZeroed, "GetDebugPadKeyboardMap"}, + {3004, &IHidServer::StubReplyZeroed, "SetDebugPadKeyboardMap"}, + {3005, &IHidServer::StubReplyZeroed, "ResetDebugPadKeyboardMap"}, + {3006, &IHidServer::StubReplyZeroed, "GetFullKeyGenericPadMap"}, + {3007, &IHidServer::StubReplyZeroed, "SetFullKeyGenericPadMap"}, + {3008, &IHidServer::StubReplyZeroed, "ResetFullKeyGenericPadMap"}, + {3009, &IHidServer::StubReplyZeroed, "GetFullKeyKeyboardMap"}, + {3010, &IHidServer::StubReplyZeroed, "SetFullKeyKeyboardMap"}, + {3011, &IHidServer::StubReplyZeroed, "ResetFullKeyKeyboardMap"}, + {3012, &IHidServer::StubReplyZeroed, "GetDebugPadGenericPadMap"}, //21.0.0+ + {3013, &IHidServer::StubReplyZeroed, "SetDebugPadGenericPadMap"}, //21.0.0+ + {3014, &IHidServer::StubReplyZeroed, "GetDebugPadKeyboardMap"}, //21.0.0+ + {3015, &IHidServer::StubReplyZeroed, "SetDebugPadKeyboardMap"}, //21.0.0+ + {3110, &IHidServer::StubReplyZeroed, "Unknown3110"}, + {3111, &IHidServer::StubReplyZeroed, "Unknown3111"}, + {3112, &IHidServer::StubReplyZeroed, "Unknown3112"}, + {3113, &IHidServer::StubReplyZeroed, "Unknown3113"}, + {3114, &IHidServer::StubReplyZeroed, "Unknown3114"}, + {3130, &IHidServer::StubReplyZeroed, "Unknown3130"}, + {3131, &IHidServer::StubReplyZeroed, "Unknown3131"}, + {3132, &IHidServer::StubReplyZeroed, "Unknown3132"}, + {3133, &IHidServer::StubReplyZeroed, "Unknown3133"}, + {3134, &IHidServer::StubReplyZeroed, "Unknown3134"}, + {3135, &IHidServer::StubReplyZeroed, "Unknown3135"}, + {3136, &IHidServer::StubReplyZeroed, "Unknown3136"}, + {3137, &IHidServer::StubReplyZeroed, "Unknown3137"}, + {3138, &IHidServer::StubReplyZeroed, "Unknown3138"}, + {3139, &IHidServer::StubReplyZeroed, "Unknown3139"}, + {3140, &IHidServer::StubReplyZeroed, "Unknown3140"}, + {3141, &IHidServer::StubReplyZeroed, "Unknown3141"}, + {3142, &IHidServer::StubReplyZeroed, "Unknown3142"}, + {3143, &IHidServer::StubReplyZeroed, "Unknown3143"}, + {3144, &IHidServer::StubReplyZeroed, "Unknown3144"}, + {3145, &IHidServer::StubReplyZeroed, "Unknown3145"}, + {3146, &IHidServer::StubReplyZeroed, "Unknown3146"}, + {3147, &IHidServer::StubReplyZeroed, "Unknown3147"}, + {3148, &IHidServer::StubReplyZeroed, "Unknown3148"}, + {3149, &IHidServer::StubReplyZeroed, "Unknown3149"}, {3150, C<&IHidServer::SetMouseLibraryVersion>, "SetMouseLibraryVersion"}, //21.0.0+ + {5000, &IHidServer::StubReplyZeroed, "Unknown5000"}, // What? -- {12010, nullptr, "SetButtonConfigLeft"}, }; // clang-format on @@ -1075,6 +1117,17 @@ Result IHidServer::SendNx2VibrationValues() { R_SUCCEED(); } +Result IHidServer::Unknown213() { + LOG_DEBUG(Service_HID, "(STUBBED) called"); + R_SUCCEED(); +} + +Result IHidServer::Unknown220(Out<u8> out) { + LOG_DEBUG(Service_HID, "(STUBBED) called"); + *out = 0; + R_SUCCEED(); +} + Result IHidServer::SendVibrationValueInBool( bool is_vibrating, Core::HID::VibrationDeviceHandle vibration_device_handle, ClientAppletResourceUserId aruid) { diff --git a/src/core/hle/service/hid/hid_server.h b/src/core/hle/service/hid/hid_server.h index 574d54c415..0767bb91b7 100644 --- a/src/core/hle/service/hid/hid_server.h +++ b/src/core/hle/service/hid/hid_server.h @@ -192,6 +192,12 @@ private: // Switch 2 sends vibration every frame with 214 (pointer buffers of device handles and // values, like 206); Eden doesn't emulate vibration, so accept and drop it. Result SendNx2VibrationValues(); + // 213 (32 bytes, aruid): sent per controller every frame by Kirby Air Riders (tens of + // thousands of auto stub log lines a run); no reply data. Accepted and dropped like 214. + Result Unknown213(); + // 220 (16 bytes, aruid): replies one byte (meaning unknown); answer 0 instead of leaving it to + // the message buffer's leftovers. + Result Unknown220(Out<u8> out); Result ActivateConsoleSixAxisSensor(ClientAppletResourceUserId aruid); Result StartConsoleSixAxisSensor(Core::HID::ConsoleSixAxisSensorHandle console_sixaxis_handle, ClientAppletResourceUserId aruid); diff --git a/src/core/hle/service/kernel_helpers.cpp b/src/core/hle/service/kernel_helpers.cpp index e4d6925f19..db55c6f81a 100644 --- a/src/core/hle/service/kernel_helpers.cpp +++ b/src/core/hle/service/kernel_helpers.cpp @@ -48,6 +48,17 @@ ServiceContext::~ServiceContext() { } } +namespace { +std::mutex debug_names_mutex; +std::unordered_map<const Kernel::KReadableEvent*, std::string> debug_names; +} // Anonymous namespace + +std::string DebugEventName(const Kernel::KReadableEvent* event) { + std::scoped_lock lk{debug_names_mutex}; + const auto it = debug_names.find(event); + return it != debug_names.end() ? it->second : std::string{"?"}; +} + Kernel::KEvent* ServiceContext::CreateEvent(std::string&& name) { // Reserve a new event from the process resource limit Kernel::KScopedResourceReservation event_reservation(kernel, process, @@ -74,6 +85,10 @@ Kernel::KEvent* ServiceContext::CreateEvent(std::string&& name) { Kernel::KEvent::Register(kernel, event); { + std::scoped_lock lk{debug_names_mutex}; + debug_names[&event->GetReadableEvent()] = name; + } + { std::scoped_lock lk{event_names_mutex}; event_names[event] = std::move(name); } @@ -86,6 +101,10 @@ void ServiceContext::CloseEvent(Kernel::KEvent* event) { std::scoped_lock lk{event_names_mutex}; event_names.erase(event); } + { + std::scoped_lock lk{debug_names_mutex}; + debug_names.erase(&event->GetReadableEvent()); + } event->GetReadableEvent().Close(kernel); event->Close(kernel); } diff --git a/src/core/hle/service/kernel_helpers.h b/src/core/hle/service/kernel_helpers.h index a62dd838aa..b03e49300f 100644 --- a/src/core/hle/service/kernel_helpers.h +++ b/src/core/hle/service/kernel_helpers.h @@ -16,6 +16,7 @@ namespace Kernel { class KernelCore; class KEvent; class KProcess; +class KReadableEvent; } // namespace Kernel namespace Service::KernelHelpers { @@ -37,4 +38,7 @@ public: bool process_created{false}; }; +/// The name a service gave an event (EDEN_NX2_SVC_TRACE shows the handles a thread waits on). +std::string DebugEventName(const Kernel::KReadableEvent* event); + } // namespace Service::KernelHelpers diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp index d631a6dd80..a7d6d6e707 100644 --- a/src/core/hle/service/lm/lm.cpp +++ b/src/core/hle/service/lm/lm.cpp @@ -91,8 +91,9 @@ public: static const FunctionInfo functions[] = { {0, &ILogger::Log, "Log"}, {1, &ILogger::SetDestination, "SetDestination"}, - {2, nullptr, "TransmitHashedLog"}, //20.0.0+ - {3, nullptr, "DevNotify"}, //20.0.0+ + {2, &ILogger::StubReplyZeroed, "TransmitHashedLog"}, //20.0.0+ + {3, &ILogger::StubReplyZeroed, "DevNotify"}, //20.0.0+ + {11, &ILogger::StubReplyInterface, "Unknown11"}, }; RegisterHandlers(functions); } diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp index 513bb671f8..ffdb17fd14 100644 --- a/src/core/hle/service/mii/mii.cpp +++ b/src/core/hle/service/mii/mii.cpp @@ -48,8 +48,8 @@ public: {15, D<&IDatabaseService::DestroyFile>, "DestroyFile"}, {16, D<&IDatabaseService::DeleteFile>, "DeleteFile"}, {17, D<&IDatabaseService::Format>, "Format"}, - {18, nullptr, "Import"}, - {19, nullptr, "Export"}, + {18, &IDatabaseService::StubReplyZeroed, "Import"}, + {19, &IDatabaseService::StubReplyZeroed, "Export"}, {20, D<&IDatabaseService::IsBrokenDatabaseWithClearFlag>, "IsBrokenDatabaseWithClearFlag"}, {21, D<&IDatabaseService::GetIndex>, "GetIndex"}, {22, D<&IDatabaseService::SetInterfaceVersion>, "SetInterfaceVersion"}, @@ -57,6 +57,8 @@ public: {24, D<&IDatabaseService::ConvertCoreDataToCharInfo>, "ConvertCoreDataToCharInfo"}, {25, D<&IDatabaseService::ConvertCharInfoToCoreData>, "ConvertCharInfoToCoreData"}, {26, D<&IDatabaseService::Append>, "Append"}, + {27, &IDatabaseService::StubReplyZeroed, "Unknown27"}, + {28, &IDatabaseService::StubReplyZeroed, "Unknown28"}, }; // clang-format on diff --git a/src/core/hle/service/ns/platform_service_manager.cpp b/src/core/hle/service/ns/platform_service_manager.cpp index 88aac56b1c..7bd8980a08 100644 --- a/src/core/hle/service/ns/platform_service_manager.cpp +++ b/src/core/hle/service/ns/platform_service_manager.cpp @@ -85,8 +85,12 @@ struct IPlatformServiceManager::Impl { // Automatically populated based on shared_fonts dump or system archives. // 6 builtin fonts + extra 2 for whatever may come after boost::container::static_vector<FontRegion, 8> shared_font_regions; - /// Plain TTF copies of the same fonts (Switch 2 GetBuiltInFontData), after the BFTTF data. + /// Where Switch 2 GetBuiltInFontData finds the same fonts as plain TTF data: page-aligned + /// copies in builtin_font_memory (the Switch 1 font memory is full with the system fonts). boost::container::static_vector<FontRegion, 8> builtin_font_regions; + u32 builtin_font_memory_size{}; + /// Made by the first GetBuiltInFontSharedMemory (Switch 2 titles only). + Kernel::KSharedMemory* builtin_font_memory{}; /// Backing memory for the shared font data std::array<u8, SHARED_FONT_MEM_SIZE> shared_font; }; @@ -122,7 +126,6 @@ IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const ch // Attempt to load shared font data from disk const auto* nand = fsc.GetSystemNANDContents(); std::size_t offset = 0; - std::vector<std::vector<u8>> plain_fonts; // Rebuild shared fonts from data ncas or synthesize for (auto& font : SHARED_FONTS) { FileSys::VirtualFile romfs; @@ -158,34 +161,52 @@ IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const ch Common::swap32); // Font offset and size do not account for the header const FontRegion region{u32(offset + 8), u32((font_data_u32.size() * sizeof(u32)) - 8)}; - std::vector<u8> plain(region.size); - DecryptSharedFontToTTF(font_data_u32, plain); - plain_fonts.push_back(std::move(plain)); DecryptSharedFont(font_data_u32, impl->shared_font, offset); impl->shared_font_regions.push_back(region); } - for (const auto& plain : plain_fonts) { - offset = Common::AlignUp(offset, 0x1000); - if (offset + plain.size() > SHARED_FONT_MEM_SIZE) { - LOG_WARNING(Service_NS, "No shared memory left for plain built-in font copies"); - break; - } - std::memcpy(impl->shared_font.data() + offset, plain.data(), plain.size()); - impl->builtin_font_regions.push_back({u32(offset), u32(plain.size())}); - offset += plain.size(); + // The data after each BFTTF header is the plain font: GetBuiltInFontSharedMemory copies it. + u32 builtin_offset = 0; + for (const auto& region : impl->shared_font_regions) { + impl->builtin_font_regions.push_back({builtin_offset, region.size}); + builtin_offset = Common::AlignUp(builtin_offset + region.size, 0x1000); } + impl->builtin_font_memory_size = std::max<u32>(builtin_offset, 0x1000); } // Switch 2: the shared memory GetBuiltInFontData offsets point into (copy handle, u32 size); the -// SDK maps it read-only. Eden serves its font shared memory, which holds plain copies too. +// SDK maps it read-only. It holds plain copies of the fonts, apart from the Switch 1 font memory. void IPlatformServiceManager::GetBuiltInFontSharedMemory(HLERequestContext& ctx) { - LOG_WARNING(Service_NS, "(STUBBED) Switch 2 GetBuiltInFontSharedMemory"); - std::memcpy(kernel.GetFontSharedMem().GetPointer(), impl->shared_font.data(), impl->shared_font.size()); + LOG_WARNING(Service_NS, "(STUBBED) Switch 2 GetBuiltInFontSharedMemory, {:#x} bytes", + impl->builtin_font_memory_size); + if (impl->builtin_font_memory == nullptr) { + auto* const memory = Kernel::KSharedMemory::Create(kernel); + if (const Result result = memory->Initialize(kernel, system.DeviceMemory(), nullptr, + Kernel::Svc::MemoryPermission::None, + Kernel::Svc::MemoryPermission::Read, + impl->builtin_font_memory_size); + result.IsError()) { + LOG_ERROR(Service_NS, "Failed to make {:#x} bytes of built-in font memory: {:#x}", + impl->builtin_font_memory_size, result.raw); + memory->Close(kernel); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + Kernel::KSharedMemory::Register(kernel, memory); + u8* const data = memory->GetPointer(); + std::memset(data, 0, impl->builtin_font_memory_size); + for (size_t i = 0; i < impl->builtin_font_regions.size(); ++i) { + std::memcpy(data + impl->builtin_font_regions[i].offset, + impl->shared_font.data() + impl->shared_font_regions[i].offset, + impl->builtin_font_regions[i].size); + } + impl->builtin_font_memory = memory; + } IPC::ResponseBuilder rb{ctx, 3, 1}; rb.Push(ResultSuccess); - rb.Push<u32>(static_cast<u32>(SHARED_FONT_MEM_SIZE)); - rb.PushCopyObjects(ctx, kernel.GetFontSharedMem()); + rb.Push<u32>(impl->builtin_font_memory_size); + rb.PushCopyObjects(ctx, *impl->builtin_font_memory); } // nn::pl::GetBuiltInFontData(BuiltInFontDataOunce*, BuiltInFontTypeOunce, bool): returns a 16-byte @@ -212,7 +233,11 @@ void IPlatformServiceManager::GetBuiltInFontData(HLERequestContext& ctx) { rb.Push<u32>(0); } -IPlatformServiceManager::~IPlatformServiceManager() = default; +IPlatformServiceManager::~IPlatformServiceManager() { + if (impl->builtin_font_memory != nullptr) { + impl->builtin_font_memory->Close(kernel); + } +} Result IPlatformServiceManager::RequestLoad(SharedFontType type) { // Games don't call this so all fonts should be loaded diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp index 9d696c027e..40313a10fb 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -174,7 +174,11 @@ void nvhost_as_gpu::Nx2Map(u64 gpu_address, u64 cpu_address, u64 size) { const DAddr device_address = range.address; nx2_device_ranges[{cpu_address, size}] = range; Nx2GpuMemoryRegistry::Get().RecordDeviceRange(cpu_address, size, device_address); - gmmu->Map(gpu_address, device_address, size, Tegra::PTEKind::PITCH, false); + // On the GPU thread, like unmaps: a GMMU map takes the texture cache lock, and the callers hold + // nx2:gpu locks that the GPU thread takes under that lock (sparse blocks backed on write). + system.GPU().RunInOrder([gmmu = gmmu.get(), gpu_address, device_address, size] { + gmmu->Map(gpu_address, device_address, size, Tegra::PTEKind::PITCH, false); + }); LOG_DEBUG(Service_NVDRV, "Switch 2 GPU mapping {:#x}+{:#x} -> CPU {:#x}", gpu_address, size, cpu_address); } @@ -248,7 +252,10 @@ void nvhost_as_gpu::Nx2Sparse(u64 gpu_address, u64 size, bool create) { return; } nx2_sparse_ranges[gpu_address] = {size, device_address}; - gmmu->Map(gpu_address, device_address, size, Tegra::PTEKind::PITCH, false); + // On the GPU thread, as in Nx2Map. + system.GPU().RunInOrder([gmmu = gmmu.get(), gpu_address, device_address, size] { + gmmu->Map(gpu_address, device_address, size, Tegra::PTEKind::PITCH, false); + }); LOG_DEBUG(Service_NVDRV, "Switch 2 sparse region {:#x}+{:#x} -> device {:#x}", gpu_address, size, device_address); } diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp index cf4a473496..93d51cd538 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp @@ -228,9 +228,11 @@ void ApplyNx2Characteristics(GC& gc) { if (remap) { gc.flags |= 1ULL << 35; } - // Bits 40..43: the 2D, 3D, compute and inline-to-memory class families. The SDK filters its - // class enumeration by them; without them Fire Emblem: Fortune's Weave aborts at boot. - gc.flags |= 0xfULL << 40; + // Engine support bits 40-44 (2D, 3D, compute, inline-to-memory, ZBC). The SDK filters its class + // enumeration by them, and NvRm's newer device path lists a class family in the device info + // only when its bit is set; without them Fire Emblem: Fortune's Weave aborts at boot and a + // device opened that way has no compute class (Fire Emblem's CUDA driver reports no device). + gc.flags |= 0x1FULL << 40; // The Switch 2 layout keeps nine s16 interface versions at 0x70..0x80 (like nvgpu's // *_ioctl_nr_last); its NvRm requires e.g. [0x78] >= 6 and enables features above 0xa, 0x1f diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index 0cd72faed3..848c15d6f8 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -24,6 +24,7 @@ #include "video_core/engines/puller.h" #include "video_core/gpu.h" #include "video_core/host1x/host1x.h" +#include "video_core/memory_manager.h" namespace Service::Nvidia::Devices { namespace { @@ -435,6 +436,30 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandL entries.submit_hashes.push_back(Tegra::HashPushbufferWords(words)); } } + // The present returns before the GPU thread reaches this frame's work (nx2_gsv.h, + // frames ahead), and Mario Kart World then refills command memory it already + // submitted: the end of a frame ran with the next frame's present image as its render + // target, leaving an image three frames old to be shown. Keep the words as submitted. + // EDEN_NX2_PB_SNAPSHOT=0: execute from memory. + static const bool snapshot = [] { + const char* value = std::getenv("EDEN_NX2_PB_SNAPSHOT"); + return value == nullptr || value[0] != '0'; + }(); + if (snapshot && channel_state->memory_manager) { + size_t words = 0; + for (const auto& entry : entries.command_lists) { + words += entry.size; + } + entries.submitted_words.resize(words); + entries.submitted_offsets.reserve(entries.command_lists.size()); + size_t offset = 0; + for (const auto& entry : entries.command_lists) { + entries.submitted_offsets.push_back(static_cast<u32>(offset)); + channel_state->memory_manager->ReadBlockUnsafe( + entry.addr, entries.submitted_words.data() + offset, entry.size * sizeof(u32)); + offset += entry.size; + } + } } auto& flags = params.flags; diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp index 12923496fb..7853d17235 100644 --- a/src/core/hle/service/nvdrv/nvdrv.cpp +++ b/src/core/hle/service/nvdrv/nvdrv.cpp @@ -133,6 +133,7 @@ Module::Module(Core::System& system) Devices::Nx2GpuMemoryRegistry::Get().Reset(); Tegra::Nx2ClearSemaphoreRegions(); Tegra::Nx2ClearSparseRegions(); + Tegra::Nx2ClearGpuWrittenPages(); Devices::g_nx2_driver = false; Tegra::g_nx2_ampere = false; Core::g_nx2_present_count = 0; diff --git a/src/core/hle/service/nvdrv/nvdrv_interface.cpp b/src/core/hle/service/nvdrv/nvdrv_interface.cpp index 020036d74b..2ee0d726a2 100644 --- a/src/core/hle/service/nvdrv/nvdrv_interface.cpp +++ b/src/core/hle/service/nvdrv/nvdrv_interface.cpp @@ -13,6 +13,7 @@ #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_readable_event.h" #include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/nvdrv/devices/nx2_stub_device.h" #include "core/hle/service/nvdrv/nvdata.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvdrv/nvdrv_interface.h" @@ -36,7 +37,10 @@ void NVDRV::Open(HLERequestContext& ctx) { const std::string device_name(Common::StringFromBuffer(buffer)); LOG_DEBUG(Service_NVDRV, "device_name={}", device_name); - if (device_name == "/dev/nvhost-prof-gpu") { + // Switch 2 lists nvhost-prof-gpu with the other GPU nodes and the CUDA driver (Fire + // Emblem's subsdk1) opens it, so only refuse it to Switch 1 callers. Switch 2 opens get + // the logged stub device. + if (device_name == "/dev/nvhost-prof-gpu" && !Devices::g_nx2_driver) { rb.Push<DeviceFD>(0); rb.PushEnum(NvResult::NotSupported); diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h index 5a2963f179..37c797fa3f 100644 --- a/src/core/hle/service/nx2_gsv.h +++ b/src/core/hle/service/nx2_gsv.h @@ -107,6 +107,14 @@ inline void TipcReplyWords(HLERequestContext& ctx, std::initializer_list<u32> wo TipcReplyWords(ctx, std::span<const u32>{words.begin(), words.size()}); } +// TIPC 0(u64 token) on the Switch 2 sessions: a handshake newer SDKs send first; the reply echoes +// the token. (The auto stub's reply only echoed it by leaving the request's words in place.) +inline void TipcHandshake(HLERequestContext& ctx, std::string_view session) { + const auto* w = ctx.CommandBuffer(); + LOG_INFO(Service, "{} TIPC 0 handshake {:#x}", session, u64{w[2]} | (u64{w[3]} << 32)); + TipcReplyWords(ctx, {w[2], w[3]}); +} + // Replies with success and a copied handle to a new session served by `iface`. template <class T> inline void ReturnRawSession(Core::System& system, HLERequestContext& ctx, std::shared_ptr<T> iface) { @@ -144,6 +152,8 @@ public: {103, &INx2SyncptSession::QueryCounter, "QueryCounter"}, {201, &INx2SyncptSession::RequestSlotEvent, "RequestSlotEvent (guessed)"}, {202, &INx2SyncptSession::CancelSlotEvent, "CancelSlotEvent (guessed)"}, + // Firmware commands without a known meaning: zeroed replies. + {21, &INx2SyncptSession::StubReplyZeroed, "Unknown21"}, }); } @@ -165,9 +175,7 @@ public: private: // Newer SDKs (title 1) open with TIPC 0(u64 token) and abort unless the reply echoes it. void Handshake(HLERequestContext& ctx) { - const auto* w = ctx.CommandBuffer(); - LOG_INFO(Service, "nx2:syncpt TIPC 0 handshake {:#x}", u64{w[2]} | (u64{w[3]} << 32)); - TipcReplyWords(ctx, {w[2], w[3]}); + TipcHandshake(ctx, "nx2:syncpt"); } void GetCount(HLERequestContext& ctx) { @@ -304,6 +312,7 @@ public: explicit INx2GpuSession(Core::System& system_) : ServiceFramework{system_, "nx2:gpu"}, m_context{system_, "nx2:gpu"} { RegisterHandlersTipc({ + {0, &INx2GpuSession::Handshake, "Handshake"}, {11, &INx2GpuSession::GetIdRange, "GetIdRange (guessed)"}, {12, &INx2GpuSession::GetIdEvent, "GetIdEvent (guessed)"}, {21, &INx2GpuSession::Start, "Start (guessed)"}, @@ -323,10 +332,22 @@ public: {214, &INx2GpuSession::SubCreateGpuOnlyRegion, "CreateSparseRegion"}, {215, &INx2GpuSession::SubDestroySparseRegion, "DestroySparseRegion"}, {216, &INx2GpuSession::SubSparseMap, "ExecuteSparseMapRequests"}, + // Firmware commands without a known meaning: zeroed replies. + {23, &INx2GpuSession::StubReplyZeroed, "Unknown23"}, + {24, &INx2GpuSession::StubReplyZeroed, "Unknown24"}, + {25, &INx2GpuSession::StubReplyZeroed, "Unknown25"}, + {34, &INx2GpuSession::StubReplyZeroed, "Unknown34"}, + {51, &INx2GpuSession::StubReplyZeroed, "Unknown51"}, + {8001, &INx2GpuSession::StubReplyZeroed, "Unknown8001"}, + {8002, &INx2GpuSession::StubReplyZeroed, "Unknown8002"}, + {8003, &INx2GpuSession::StubReplyZeroed, "Unknown8003"}, + {8004, &INx2GpuSession::StubReplyZeroed, "Unknown8004"}, }); if (LazySparseRegions()) { Tegra::Nx2SetSparseBacking( - [this](u64 gpu_address, u64 size) { BackSparseWrite(gpu_address, size); }); + [this](u64 gpu_address, u64 size, const u8* data) { + BackSparseWrite(gpu_address, size, data); + }); } } @@ -341,6 +362,10 @@ public: } private: + void Handshake(HLERequestContext& ctx) { + TipcHandshake(ctx, "nx2:gpu"); + } + static constexpr u32 NumIds = 32; Kernel::KEvent* EventFor(u32 id) { @@ -588,12 +613,29 @@ private: // backed whole, they ran the pool dry and the later ones got no memory at all. // The region gets a device range of its own, unbacked pages read zero, and // BackSparseWrite gives a block memory before the GPU writes into it. - m_sparse_regions[start] = {size, std::vector<u8>(Common::DivCeil(size, SparseBlockSize))}; + m_sparse_regions[start] = { + .size = size, + .blocks = std::vector<u8>(Common::DivCeil(size, SparseBlockSize)), + }; Nvidia::Devices::Nx2GpuMemoryRegistry::Get().CreateSparse(start, size); Tegra::Nx2SparseRegionCreated(start, size, SparseBlockSize); Tegra::Nx2AddSparseSpan(start, size); LOG_INFO(Service, "nx2:gpu 214 sparse region [{:#x}, {:#x}] backed as the GPU writes it", start, last); + } else if (LazyGpuOnlyRegions()) { + // The rest is written by the host GPU, not through guest memory (title 1's and + // title 4's render targets, 285-300 MB regions): like a sparse region it gets a + // device range of its own and memory per block only when something writes guest + // memory there (downloads, copies). The texture cache sees ordinary memory. + m_sparse_regions[start] = { + .gpu_only = true, + .size = size, + .blocks = std::vector<u8>(Common::DivCeil(size, SparseBlockSize)), + }; + Nvidia::Devices::Nx2GpuMemoryRegistry::Get().CreateSparse(start, size); + Tegra::Nx2AddSparseSpan(start, size); + LOG_INFO(Service, "nx2:gpu 214 GPU-only region [{:#x}, {:#x}] backed as it is written", + start, last); } else if (const auto address = BackGpuOnlyRegion(start, size)) { Nvidia::Devices::Nx2GpuMemoryRegistry::Get().MapAt(start, *address, size); LOG_INFO(Service, "nx2:gpu 214 GPU-only region [{:#x}, {:#x}] backed at {:#x} " @@ -702,6 +744,23 @@ private: TipcReplyWords(ctx, {static_cast<u32>(count), static_cast<u32>(u64{count} >> 32)}); } + static bool IsAllZero(const u8* data, size_t size) { + size_t i = 0; + for (; i + sizeof(u64) <= size; i += sizeof(u64)) { + u64 word; + std::memcpy(&word, data + i, sizeof(word)); + if (word != 0) { + return false; + } + } + for (; i < size; ++i) { + if (data[i] != 0) { + return false; + } + } + return true; + } + // Returns the pages of destroyed regions the GPU thread is done with to the kernel. void FreeReleasedRegions() { auto* const process = system.ApplicationProcess(); @@ -809,7 +868,7 @@ private: // usually on the GPU thread): the blocks the write touches get memory, cleared, behind the // region's device pages. The GPU mapping stays as it is, so the images over the region keep // their storage and only see the write. - void BackSparseWrite(u64 gpu_address, u64 size) { + void BackSparseWrite(u64 gpu_address, u64 size, const u8* data) { struct Pending { u64 region; u64 block; @@ -832,6 +891,7 @@ private: } }; bool waits_for_other = false; + u64 zero_bytes = 0; { std::unique_lock lk{m_region_mutex}; for_regions([&](u64 start, SparseRegion& region, u64 first, u64 last) { @@ -842,6 +902,18 @@ private: region.blocks[block] = BlockWritten; Tegra::Nx2SparseBlockWritten(start, block); } else if (region.blocks[block] == BlockEmpty) { + // Zeros over a block nobody wrote change nothing: it reads zero without + // memory. Title 1 clears ~1 GB of them at boot. + const u64 block_begin = start + block * SparseBlockSize; + const u64 write_begin = (std::max)(gpu_address, block_begin); + const u64 write_end = (std::min)( + {end, block_begin + SparseBlockSize, start + region.size}); + if (data != nullptr && + IsAllZero(data + (write_begin - gpu_address), write_end - write_begin) && + !Tegra::Nx2SparseBlockIsWritten(start, block)) { + zero_bytes += write_end - write_begin; + continue; + } region.blocks[block] = BlockPending; todo.push_back({start, block}); } else if (region.blocks[block] == BlockPending) { @@ -850,6 +922,16 @@ private: } }); } + if (zero_bytes != 0) { + std::scoped_lock lk{m_region_mutex}; + const u64 before = m_sparse_zero_bytes; + m_sparse_zero_bytes += zero_bytes; + if (before == 0 || (before >> 28) != (m_sparse_zero_bytes >> 28)) { + LOG_INFO(Service, "nx2:gpu {} MiB of zeros over unwritten sparse blocks left out " + "(they read zero without memory)", + m_sparse_zero_bytes >> 20); + } + } // Allocating and mapping happens outside the lock (see AllocateGpuOnlyBacking). for (const auto& [start, block] : todo) { u64 block_size = SparseBlockSize; @@ -933,6 +1015,15 @@ private: return lazy; } + // EDEN_NX2_GPU_ONLY_LAZY=0 backs GPU-only regions whole, as before. + static bool LazyGpuOnlyRegions() { + static const bool lazy = [] { + const char* value = std::getenv("EDEN_NX2_GPU_ONLY_LAZY"); + return value == nullptr || value[0] != '0'; + }(); + return lazy; + } + static constexpr u32 SparseRegionFlag = 0x200; ///< Word 7 of 214 (0x60c arrays, 0xa0c pools) static constexpr u64 SparseBlockSize = 4ULL << 20; static constexpr u8 BlockEmpty = 0; @@ -941,6 +1032,7 @@ private: static constexpr u8 BlockPending = 3; ///< Being allocated and mapped by BackSparseWrite static constexpr u8 BlockWritten = 4; ///< Written in a region the game maps tiles into struct SparseRegion { + bool gpu_only{}; ///< A GPU-only region (no sparse flag), backed lazily as well u64 size; std::vector<u8> blocks; ///< BlockEmpty / Backed / Failed / Pending / Written per block bool warned{}; ///< Logged that the pool ran out for its blocks @@ -950,6 +1042,7 @@ private: std::map<u64, SparseRegion> m_sparse_regions; ///< By GPU address u64 m_sparse_blocks{}; ///< Sparse blocks backed so far u64 m_sparse_backed_bytes{}; ///< Sparse region memory given out so far + u64 m_sparse_zero_bytes{}; ///< Zeros written over unwritten sparse blocks, left out u64 m_sparse_released_bytes{}; ///< Blocks given back once the game mapped tiles there // nn::gpu::sdk::SparseMapRequest (216), filled from NvRm's nvgpu_as_remap_op. @@ -1284,6 +1377,46 @@ private: std::map<u32, GpuMemory> m_memory; u32 m_next_memory_handle{1}; std::array<bool, 64> m_devices{}; // gpu 41 graphics device ids (0 unused) + + // Every 30 s: the backing of sparse blocks and GPU-only regions is marked cold. The game + // writes it once (texture uploads by 2D blits: title 1 fills 2-4 GB of texture pools) and it + // is read again only when the texture cache builds an image over it, so the OS may compress or + // page it out before anything else. Last member: stops before the regions are destroyed. + void HintColdBacking() { + struct Range { + u64 physical_offset; + u64 virtual_address; + u64 size; + }; + std::vector<Range> ranges; + { + std::scoped_lock lk{m_region_mutex}; + for (const auto& [gpu_address, region] : m_gpu_only_regions) { + u64 virtual_address = region.cpu_address; + for (const auto& block : *region.pages) { + ranges.push_back({GetInteger(block.GetAddress()) - Core::DramMemoryMap::Base, + virtual_address, block.GetSize()}); + virtual_address += block.GetSize(); + } + } + } + auto& buffer = system.DeviceMemory().buffer; + for (const Range& range : ranges) { + buffer.HintCold(range.physical_offset, range.size, range.virtual_address); + } + } + std::jthread m_cold_hint_thread{[this](std::stop_token stop) { + Common::SetCurrentThreadName("nx2:ColdHint"); + Common::SetCurrentThreadPriority(Common::ThreadPriority::Low); + while (!stop.stop_requested()) { + for (int i = 0; i < 30 && !stop.stop_requested(); ++i) { + std::this_thread::sleep_for(std::chrono::seconds{1}); + } + if (!stop.stop_requested()) { + HintColdBacking(); + } + } + }}; }; // Native memory and images handed out by the Switch 2 graphics server (gsv 111/114/423/429/315), @@ -1389,6 +1522,34 @@ private: std::map<u32, Kernel::KEvent*> m_events; }; +// nn::camera::sdk camera context (gsv 602). GameChat builds its local camera video storage from it +// and polls it every frame. Nothing about the commands is known beyond the arguments seen (3 and +// 21 take the camera name "Default", 29 a u32 0x180 = 384, the width of the 384x216 camera images), +// and the game runs on an empty success, so they answer that without a log line per call. +class INx2CameraSession final : public ServiceFramework<INx2CameraSession> { +public: + explicit INx2CameraSession(Core::System& system_) : ServiceFramework{system_, "nx2:camera"} { + RegisterHandlersTipc({ + {3, &INx2CameraSession::Stub, "Unknown3 (camera name \"Default\")"}, + {21, &INx2CameraSession::Stub, "Unknown21 (camera name \"Default\")"}, + {29, &INx2CameraSession::Stub, "Unknown29 (u32 0x180, polled every frame)"}, + }); + } + +private: + void Stub(HLERequestContext& ctx) { + const u32 command = ctx.GetCommand(); + if (m_logged.insert(command).second) { + LOG_WARNING(Service, "(STUBBED) nx2:camera TIPC {} [2]={:#x} [3]={:#x}, returning success", + command, ctx.CommandBuffer()[2], ctx.CommandBuffer()[3]); + } + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + std::set<u32> m_logged; +}; + // Video decoder output images (nn::codec image pools, gsv 991/911/961). The preference // (MakeDecodeImagePreference) holds an output layout code at word 1 and (width - 1) | // (height - 1) << 16 at word 2. Codes come in pairs, one per CodecMode: 1/4 NV12 (format 3, what @@ -1443,20 +1604,73 @@ public: : ServiceFramework{system_, "nx2:vi2"}, m_context{system_, "nx2:vi2"}, m_objects{std::move(objects_)} { RegisterHandlersTipc({ + {0, &INx2Vi2Session::Handshake, "Handshake"}, {1, &INx2Vi2Session::ReturnOne, "Unknown1"}, {3, &INx2Vi2Session::ReturnOne, "GetDisplayCount (guessed)"}, {5, &INx2Vi2Session::GetTimestampRecorderCount, "GetTimestampRecorderCount (guessed)"}, {11, &INx2Vi2Session::ReturnEvent, "Unknown11"}, - {31, &INx2Vi2Session::GetVsyncEvent, "GetVsyncEvent (guessed)"}, - {32, &INx2Vi2Session::GetVsyncEvent, "GetDisplayVsyncEvent (guessed)"}, - {104, &INx2Vi2Session::GetHdrStatus, "GetHdrStatus"}, - // Title 1 calls this ~75 times a second with the display name "default"; the - // auto stub's empty success reply works, but logged two lines per call (a 50 MB log - // in an 18 minute session). - {105, &INx2Vi2Session::SucceedQuietly, "Unknown105 (display \"default\")"}, + {31, &INx2Vi2Session::AcquireNativeEvent, "AcquireNativeEvent"}, + {32, &INx2Vi2Session::DuplicateNativeEvent, "DuplicateNativeEvent"}, + {261, &INx2Vi2Session::BindVsync, "BindDisplayVsyncEvent"}, + {262, &INx2Vi2Session::UnbindNativeEvent, "UnbindDisplayVsyncEvent"}, + {263, &INx2Vi2Session::BindChange, "BindDisplayRefreshRateEvent"}, + {264, &INx2Vi2Session::UnbindNativeEvent, "UnbindDisplayRefreshRateEvent"}, + {265, &INx2Vi2Session::BindChange, "BindDisplayHdrStatusEvent"}, + {266, &INx2Vi2Session::UnbindNativeEvent, "UnbindDisplayHdrStatusEvent"}, + {267, &INx2Vi2Session::BindChange, "BindDisplayHdrPaperWhiteEvent"}, + {268, &INx2Vi2Session::UnbindNativeEvent, "UnbindDisplayHdrPaperWhiteEvent"}, + {269, &INx2Vi2Session::BindChange, "BindDisplayBufferMaxResolutionEvent"}, + {270, &INx2Vi2Session::UnbindNativeEvent, "UnbindDisplayBufferMaxResolutionEvent"}, + {271, &INx2Vi2Session::BindChange, "BindDisplayModeCapabilityEvent"}, + {272, &INx2Vi2Session::UnbindNativeEvent, "UnbindDisplayModeCapabilityEvent"}, + // Title 1 calls these ~75 times a second with the display name "default"; quiet + // handlers (the auto stub logged two lines per call: a 50 MB log in an 18 minute + // session). + {103, &INx2Vi2Session::QueryDisplayRefreshRate, "QueryDisplayRefreshRate"}, + {104, &INx2Vi2Session::QueryDisplayHdrStatus, "QueryDisplayHdrStatus"}, + {105, &INx2Vi2Session::QueryDisplayHdrPaperWhite, "QueryDisplayHdrPaperWhite"}, + {212, &INx2Vi2Session::GetLayerBufferBits, "GetLayerBufferBits"}, {106, &INx2Vi2Session::GetBufferMaxResolution, "GetBufferMaxResolution"}, {228, &INx2Vi2Session::CancelClientBuffer, "CancelClientBuffer"}, {229, &INx2Vi2Session::PresentClientBuffer, "PresentClientBuffer"}, + // Firmware commands without a known meaning: zeroed replies. + {13, &INx2Vi2Session::StubReplyZeroed, "Unknown13"}, + {51, &INx2Vi2Session::StubReplyZeroed, "Unknown51"}, + {101, &INx2Vi2Session::StubReplyZeroed, "Unknown101"}, + {102, &INx2Vi2Session::StubReplyZeroed, "Unknown102"}, + {107, &INx2Vi2Session::StubReplyZeroed, "Unknown107"}, + {108, &INx2Vi2Session::StubReplyZeroed, "Unknown108"}, + {111, &INx2Vi2Session::StubReplyZeroed, "Unknown111"}, + {112, &INx2Vi2Session::StubReplyZeroed, "Unknown112"}, + {113, &INx2Vi2Session::StubReplyZeroed, "Unknown113"}, + {202, &INx2Vi2Session::StubReplyZeroed, "Unknown202"}, + {230, &INx2Vi2Session::StubReplyZeroed, "Unknown230"}, + {231, &INx2Vi2Session::StubReplyZeroed, "Unknown231"}, + {232, &INx2Vi2Session::StubReplyZeroed, "Unknown232"}, + {251, &INx2Vi2Session::StubReplyZeroed, "Unknown251"}, + {252, &INx2Vi2Session::StubReplyZeroed, "Unknown252"}, + {261, &INx2Vi2Session::StubReplyZeroed, "Unknown261"}, + {262, &INx2Vi2Session::StubReplyZeroed, "Unknown262"}, + {263, &INx2Vi2Session::StubReplyZeroed, "Unknown263"}, + {264, &INx2Vi2Session::StubReplyZeroed, "Unknown264"}, + {265, &INx2Vi2Session::StubReplyZeroed, "Unknown265"}, + {266, &INx2Vi2Session::StubReplyZeroed, "Unknown266"}, + {267, &INx2Vi2Session::StubReplyZeroed, "Unknown267"}, + {268, &INx2Vi2Session::StubReplyZeroed, "Unknown268"}, + {269, &INx2Vi2Session::StubReplyZeroed, "Unknown269"}, + {270, &INx2Vi2Session::StubReplyZeroed, "Unknown270"}, + {271, &INx2Vi2Session::StubReplyZeroed, "Unknown271"}, + {272, &INx2Vi2Session::StubReplyZeroed, "Unknown272"}, + {301, &INx2Vi2Session::StubReplyZeroed, "Unknown301"}, + {302, &INx2Vi2Session::StubReplyZeroed, "Unknown302"}, + {351, &INx2Vi2Session::StubReplyZeroed, "Unknown351"}, + {352, &INx2Vi2Session::StubReplyZeroed, "Unknown352"}, + {353, &INx2Vi2Session::StubReplyZeroed, "Unknown353"}, + {354, &INx2Vi2Session::StubReplyZeroed, "Unknown354"}, + {355, &INx2Vi2Session::StubReplyZeroed, "Unknown355"}, + {356, &INx2Vi2Session::StubReplyZeroed, "Unknown356"}, + {358, &INx2Vi2Session::StubReplyZeroed, "Unknown358"}, + {359, &INx2Vi2Session::StubReplyZeroed, "Unknown359"}, }); } @@ -1476,6 +1690,10 @@ public: } private: + void Handshake(HLERequestContext& ctx) { + TipcHandshake(ctx, "nx2:vi2"); + } + // 229(u64 client token, u32 slot, u32 image, ...fences): result, u64 mask of buffer slots the // client may acquire next, four zero words. Nothing is shown yet; every slot is handed back // at once (changes.patch's default mask is 0x7fff). @@ -1500,7 +1718,19 @@ private: LOG_DEBUG(Service, "nx2:vi2 present #{} fence ({}, {:#x}), waiting for ({}, {:#x}); now {:#x}", m_presents, ~w[22], w[24], m_previous_fence_id, m_previous_fence_value, ~w[22] < 192 ? system.Host1x().GetSyncpointManager().GetGuestSyncpointValue(~w[22]) : 0U); + // Only run a frame ahead while the GPU keeps up. With the previous frame still on the + // GPU here, the GPU is the limit and running ahead gains nothing; and Splatoon Raiders, + // which reuses its command memory as if the GPU were at most a frame behind, rewrote + // command lists Eden had not read yet (misread commands, then crashes, with a slow + // GPU). Wait for this frame too then. + const bool gpu_behind = + m_previous_fence_id < 192 && + !system.Host1x().GetSyncpointManager().IsReadyGuest(m_previous_fence_id, + m_previous_fence_value); WaitForFrame(m_previous_fence_id, m_previous_fence_value); + if (gpu_behind) { + WaitForFrame(~w[22], w[24]); + } m_previous_fence_id = ~w[22]; m_previous_fence_value = w[24]; } else { @@ -1650,9 +1880,9 @@ private: // Shows a presented native image: word 5 is the image id, ~word 22 / word 24 the GPU fence // (syncpoint, value) to wait for (layout from changes.patch). The image is a block-linear // RGBA8 surface in the console's native GOB layout at GPU address == CPU address. - // present_size: word 7 of the present, (height - 1) << 16 | (width - 1) of the frame drawn - // into the image's top-left corner (Fire Emblem: Fortune's Weave: 1280x720 in 3840x2160). - void Composite(u32 image_id, u32 fence_id, u32 fence_value, u32 present_size = 0) { + // shown_size: the present's word 7, ((height - 1) << 16) | (width - 1) of the area the game drew + // into from the image's top-left corner (0 when it gives none). + void Composite(u32 image_id, u32 fence_id, u32 fence_value, u32 shown_size) { u64 address{}; Nx2ImageLayout layout{}; u32 image_format{}; @@ -1678,10 +1908,15 @@ private: } u32 crop_width = layout.width; u32 crop_height = layout.height; - // Present word 7 holds the size of the frame drawn into the image's top-left corner. - if (present_size != 0) { - crop_width = (std::min)(crop_width, (present_size & 0xffffU) + 1); - crop_height = (std::min)(crop_height, (present_size >> 16) + 1); + // The present says how much of the image the game drew: Kirby Air Riders draws 1920x1080 + // into 2560x1440 images, Mario Kart World 2560x1440 into 3840x2160 ones. Showing the whole + // image put the frame in the top-left corner. + const u32 shown_width = (shown_size & 0xFFFF) + 1, shown_height = (shown_size >> 16) + 1; + const bool has_shown_size = shown_size != 0 && shown_width <= layout.width && + shown_height <= layout.height; + if (has_shown_size) { + crop_width = shown_width; + crop_height = shown_height; } // Handheld titles keep their docked present images (up to 3840x2160) and draw the frame // into the top-left 1920x1080, the handheld panel's resolution; the console's display @@ -1690,7 +1925,8 @@ private: if (!Settings::IsDockedMode()) { crop_width = (std::min)(crop_width, 1920U); crop_height = (std::min)(crop_height, 1080U); - } else if (system.GetApplicationProcessProgramID() == 0x0400C3F00006E000ULL && + } else if (!has_shown_size && + system.GetApplicationProcessProgramID() == 0x0400C3F00006E000ULL && layout.width == 3840 && layout.height == 2160) { // Mario Kart World docked draws 2560x1440 into the top-left of its 4K images. crop_width = 2560; @@ -1712,8 +1948,9 @@ private: (u64{crop_width} << 16) | crop_height; if (shown != m_shown_layout) { m_shown_layout = shown; - LOG_INFO(Service, "nx2:vi2 composite image {} layout {}x{}, crop {}x{}", - image_id, layout.width, layout.height, crop_width, crop_height); + LOG_INFO(Service, + "nx2:vi2 composite image {} layout {}x{}, drawn area {:#x}, crop {}x{}", + image_id, layout.width, layout.height, shown_size, crop_width, crop_height); } std::vector<Tegra::FramebufferConfig> layers{Tegra::FramebufferConfig{ .address = *device, @@ -1822,32 +2059,45 @@ private: static_cast<u32>(AvailableSlots >> 32), 0, 0, 0, 0}); } + // nn::vi2::QueryDisplayRefreshRate(display name): u32 refresh rate in Hz, then a bool (a u64 + // in all). nn::vi::detail::DefaultDisplay::QueryRefreshRate takes only -1, 60 or 120 and fails + // on anything else; Minecraft polls it and never draws while it fails. A one-word reply left + // the bool to whatever the message buffer held. + void QueryDisplayRefreshRate(HLERequestContext& ctx) { + TipcReplyWords(ctx, {60, 0}); + } + + // nn::vi2::QueryDisplayHdrPaperWhite(display name): u16 paper white in nits. The empty reply + // left it to the message buffer's leftovers; answer the BT.2408 reference white. + void QueryDisplayHdrPaperWhite(HLERequestContext& ctx) { + TipcReplyWords(ctx, {203}); + } + + // nn::vi2::QueryDisplayHdrStatus(display name): u64 HdrOutputFormat, u16. The sdk's + // DefaultDisplay::QueryHdrStatus takes format 1 as HDR output and fails with 0x272 on + // anything above 1. With the empty auto stub reply it read the request's leftover words as + // the format; Kirby Air Riders re-queries whenever its HDR status event fires and rebuilt its + // window images every few frames, which showed black. Report SDR output. + // nn::vi2::QueryDisplayHdrStatus(display name): u64 output format, u16 peak luminance in + // nits. The sdk takes format 1 as SDR and 2 as HDR and fails the query on any other value. + // Report SDR output with a typical SDR panel's peak. + void QueryDisplayHdrStatus(HLERequestContext& ctx) { + TipcReplyWords(ctx, {1, 0, 400}); + } + + // 212(u64 layer): u64 bits of the layer's buffer slots that are ready. The client polls it once + // a second until some are set; it only got going because the auto stub's reply happened to + // hold non-zero leftovers. Report the same slots 228/229 hand back. + void GetLayerBufferBits(HLERequestContext& ctx) { + constexpr u64 AvailableSlots = 0x7fff; + TipcReplyWords(ctx, {static_cast<u32>(AvailableSlots), static_cast<u32>(AvailableSlots >> 32)}); + } + void ReturnOne(HLERequestContext& ctx) { LOG_WARNING(Service, "(STUBBED) nx2:vi2 TIPC {}, returning 1", ctx.GetCommand()); TipcReplyWords(ctx, {1}); } - // 104(u64 display name): result, u64 output format, u32 0. Format 1 is SDR. A bare success - // left the format to stale reply words and a game could set up HDR output from them. - void GetHdrStatus(HLERequestContext& ctx) { - const auto* packet = ctx.CommandBuffer(); - const u64 name = u64{packet[2]} | (u64{packet[3]} << 32); - constexpr u64 DefaultDisplay = 0x00746c7561666564ULL; // "default" - Result result = Kernel::ResultInvalidCombination; - u64 format{}; - if (packet[0] == 0x78 && packet[1] == 2) { - result = Result{ErrorModule::VI, 7}; - if (name == DefaultDisplay) { - format = 1; - result = ResultSuccess; - } - } - IPC::ResponseBuilder rb{ctx, 5}; - rb.Push(result.raw); - rb.Push(format); - rb.Push<u32>(0); - } - // The highest buffer resolution mode of the "default" display. Fire Emblem: Fortune's Weave picks // its output from it: 1 -> 1280x720, 2 -> 1920x1080, 4 and 8 -> 2560x1440; 0 (the auto stub's // reply), 3 and 5 fall back to 720p. Docked reports 4, handheld 2 (the screen is 1080p); @@ -1871,12 +2121,6 @@ private: rb.Push(mode); } - // The same reply as the auto stub (success, no data), without its log lines. - void SucceedQuietly(HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); - } - // Must be < 8; nn::vi::detail::LayerHistory creates 7 timestamp recorders per layer. void GetTimestampRecorderCount(HLERequestContext& ctx) { LOG_WARNING(Service, "(STUBBED) nx2:vi2 TIPC 5, returning 7"); @@ -1893,17 +2137,105 @@ private: rb.PushCopyObjects(ctx, event->GetReadableEvent()); } - // 31(u32 kind) and 32(u32 display): events signalled on every vblank. Title 1's main thread - // waits on the 32 event until it has fired twice before it submits any GPU work, and hung - // there while it never fired. - void GetVsyncEvent(HLERequestContext& ctx) { - LOG_INFO(Service, "nx2:vi2 {} vsync event, [2]={:#x}", ctx.GetCommand(), ctx.CommandBuffer()[2]); - auto* event = m_context.CreateEvent(fmt::format("nx2:vi2:Vsync{}", m_events.size())); - m_events.push_back(event); - { - std::scoped_lock lk{m_vsync->mutex}; - m_vsync->events.push_back(event); + // 31(u32 index < 8): the session's event for that slot (the sdk picks a free index). The + // 261..272 commands bind a slot to a display event source and unbind it again. Until a slot + // is bound it fires on every vblank, as all of them used to. + void AcquireNativeEvent(HLERequestContext& ctx) { + const u32 index = ctx.CommandBuffer()[2]; + if (index >= m_native_events.size()) { + LOG_ERROR(Service, "nx2:vi2 31 index {} out of range", index); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + LOG_INFO(Service, "nx2:vi2 31 native event {}", index); + ReplyNativeEvent(ctx, index); + } + + // 32(u32 index, copy handle): the client hands back its handle for an acquired slot and gets + // the slot's event again; the handle descriptor and handle come before the index. Games call + // it for slot 0 right after binding it to vsync. Title 1's main thread waits on this event + // until it has fired twice before it submits any GPU work, and hung there while it never + // fired. + void DuplicateNativeEvent(HLERequestContext& ctx) { + const u32* words = ctx.CommandBuffer(); + u32 data = 2; + if (words[1] >> 31) { + const u32 descriptor = words[2]; + data = 3 + ((descriptor >> 1) & 0xf) + ((descriptor >> 5) & 0xf) + (descriptor & 1 ? 2 : 0); + } + const u32 index = words[data]; + if (index >= m_native_events.size()) { + LOG_ERROR(Service, "nx2:vi2 32 index {} out of range", index); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + LOG_INFO(Service, "nx2:vi2 32 native event {}", index); + ReplyNativeEvent(ctx, index); + } + + // 261(u64 display name, u32 index): fire the slot on every vblank. + void BindVsync(HLERequestContext& ctx) { + const u32 index = ctx.CommandBuffer()[4]; + LOG_INFO(Service, "nx2:vi2 261 native event {} -> vsync", index); + if (index < m_native_events.size() && m_native_events[index]) { + SetVsyncSlot(index, true); + } + TipcReplyWords(ctx, {}); + } + + // 263/265/267/269/271(u64 display name, u32 index): fire the slot when the display's refresh + // rate, HDR status, HDR paper white, buffer max resolution or mode capability changes, so the + // game re-queries it. None of them change here: fire once so the game reads the current + // state, then leave the slot quiet. Kirby Air Riders re-queried its HDR status on every + // present while this slot fired with vsync. + void BindChange(HLERequestContext& ctx) { + const u32 index = ctx.CommandBuffer()[4]; + LOG_INFO(Service, "nx2:vi2 {} native event {} -> change notification", ctx.GetCommand(), index); + if (index < m_native_events.size() && m_native_events[index]) { + SetVsyncSlot(index, false); + m_native_events[index]->Signal(system.Kernel()); } + TipcReplyWords(ctx, {}); + } + + // 262..272 even (u64 display name, u32 index): detach the slot from its source. + void UnbindNativeEvent(HLERequestContext& ctx) { + const u32 index = ctx.CommandBuffer()[4]; + LOG_INFO(Service, "nx2:vi2 {} native event {} unbound", ctx.GetCommand(), index); + if (index < m_native_events.size() && m_native_events[index]) { + SetVsyncSlot(index, false); + } + TipcReplyWords(ctx, {}); + } + + void ReplyNativeEvent(HLERequestContext& ctx, u32 index) { + auto*& event = m_native_events[index]; + if (!event) { + event = m_context.CreateEvent(fmt::format("nx2:vi2:Native{}", index)); + m_events.push_back(event); + SetVsyncSlot(index, true); + StartVsyncTimer(); + } + IPC::ResponseBuilder rb{ctx, 2, 1, 0}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(ctx, event->GetReadableEvent()); + } + + void SetVsyncSlot(u32 index, bool on) { + auto* event = m_native_events[index]; + std::scoped_lock lk{m_vsync->mutex}; + auto& list = m_vsync->events; + const auto it = std::find(list.begin(), list.end(), event); + if (on && it == list.end()) { + list.push_back(event); + } else if (!on && it != list.end()) { + list.erase(it); + } + } + + void StartVsyncTimer() { if (!m_vsync_timer) { m_vsync_timer = Core::Timing::CreateEvent( "nx2:vi2 vsync", @@ -1924,14 +2256,12 @@ private: }()}; system.CoreTiming().ScheduleLoopingEvent(VblankInterval, VblankInterval, m_vsync_timer); } - IPC::ResponseBuilder rb{ctx, 2, 1, 0}; - rb.Push(ResultSuccess); - rb.PushCopyObjects(ctx, event->GetReadableEvent()); } KernelHelpers::ServiceContext m_context; std::shared_ptr<Nx2NativeObjects> m_objects; std::vector<Kernel::KEvent*> m_events; + std::array<Kernel::KEvent*, 8> m_native_events{}; ///< 31's slots, also listed in m_events // Shared with the vsync timer callback, which can outlive the session by one call. struct VsyncState { std::mutex mutex; @@ -1956,7 +2286,9 @@ inline bool Nx2FirstVideoCommand(std::string_view service, u32 command) { // The Switch 2 video decoder (nn::codec::video::detail::ipc). gsv 902/906/1002 hand out a session // whose TIPC 1 (CreateVideoDecoderService: handle at word 3, result at word 4) creates a decoder // service, another session of this class. Layouts read from the SDK's nn::codec VP9 decoder: -// - 200(copy handle: work buffer transfer memory, u64 size, u32, u32, u32): initialize. +// - 200(copy handle: work buffer transfer memory, u64 size, u32, u32, u32): initialize. 201 adds a +// fourth u32 (a flag that 200 leaves at 1) and 202 that and a u64: the same initialization. +// 210(copy handle, u32 x4) replies only a result. // - 101(A: codec name, "video/vp9\0"; u32 width, u32 height, u32 rate, u32): configure. // - 3(): the submission-ready event (copy handle at word 3, result at word 4). // - 6 / 207 / 8: start / stop / flush; 115: finalize. @@ -1967,6 +2299,8 @@ inline bool Nx2FirstVideoCommand(std::string_view service, u32 command) { // rewritten once that handle (the client's reads) has passed. Reply the image's next handle. // - 212(u32 image id): unregister it. // - 109(A: one compressed frame, u64 pts in us, u8 end of stream): submit input; reply a u64. +// 400 is the same with a second A buffer after the frame, a 0x838-byte block of frame +// information; the decoder here only needs the frame. // - 213(): the next finished frame (16 words): u32 image id, u32 width, u32 height, u32 width, // u32 height, u32 colour space, TimeSpan pts (ns), u8 end of stream. // A NativeSyncHandle is {u32 SyncCounterId (~syncpoint), u32 mode, u64 value}; clients wait on it @@ -1988,11 +2322,15 @@ public: {109, &INx2VideoSession::SubmitInput, "SubmitInputBuffer"}, {115, &INx2VideoSession::Succeed, "Finalize"}, {200, &INx2VideoSession::Succeed, "Initialize"}, + {201, &INx2VideoSession::Succeed, "InitializeWithFlag"}, + {202, &INx2VideoSession::Succeed, "InitializeWithFlagAndValue"}, {207, &INx2VideoSession::Flush, "Stop"}, + {210, &INx2VideoSession::Succeed, "Unknown210"}, {212, &INx2VideoSession::UnregisterBuffer, "UnregisterBuffer"}, {213, &INx2VideoSession::GetOutputInfo, "GetOutputInfo"}, {311, &INx2VideoSession::RegisterBuffer, "RegisterBuffer"}, {314, &INx2VideoSession::ReleaseBuffer, "ReleaseBuffer"}, + {400, &INx2VideoSession::SubmitInput, "SubmitInputBufferWithInfo"}, }); } @@ -2180,9 +2518,11 @@ private: void SubmitInput(HLERequestContext& ctx) { const auto* w = ctx.CommandBuffer(); - // Words 2-4 are the A descriptor of the compressed frame. - const u64 pts = u64{w[5]} | (u64{w[6]} << 32); - const bool end = (w[7] & 0xFF) != 0; + // Words 2-4 are the A descriptor of the compressed frame (400: 5-7 that of its information + // block), the data words follow. + const size_t words = 2 + 3 * ctx.BufferDescriptorA().size(); + const u64 pts = u64{w[words]} | (u64{w[words + 1]} << 32); + const bool end = (w[words + 2] & 0xFF) != 0; Log(ctx, "submit"); Input input{{}, pts, end}; if (!ctx.BufferDescriptorA().empty()) { @@ -2480,6 +2820,7 @@ public: "CalculateDecoderImagePoolBuffer"}, {901, &INx2GraphicsSystemSession::Stub, "VideoInitialize (guessed)"}, {902, &INx2GraphicsSystemSession::OpenVideoSession, "OpenVideoDecoderSession (guessed)"}, + {905, &INx2GraphicsSystemSession::OpenVideoSession, "OpenVideoDecoderSessionWithFlag"}, {906, &INx2GraphicsSystemSession::OpenVideoSession, "OpenVideoDecoderSessionWithVersion"}, {1001, &INx2GraphicsSystemSession::Stub, "VideoInitialize2 (guessed)"}, {1002, &INx2GraphicsSystemSession::OpenVideoSession, "OpenVideoDecoderSession2 (guessed)"}, @@ -2492,7 +2833,92 @@ public: {1291, &INx2GraphicsSystemSession::CalculateChatImagePoolSize, "CalculateChatImagePoolSize (guessed)"}, {911, &INx2GraphicsSystemSession::CreateDecoderImage, "CreateDecoderImage"}, + // 912(u32 image id): the video group's x12, freeing one decoder output image (Switch + // Sports frees its four when its movie ends). Unlisting it stops the decode thread + // writing frames into memory the game reuses. + {912, &INx2GraphicsSystemSession::DestroyNativeImage, "DestroyDecoderImage"}, {961, &INx2GraphicsSystemSession::GetCpuImageDescriptor, "GetCpuUserImageDescriptor"}, + // Firmware commands without a known meaning: zeroed replies. + {11, &INx2GraphicsSystemSession::RegisterClientHandle, "RegisterClientHandle"}, + {113, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown113"}, + {115, &INx2GraphicsSystemSession::QueryGrantedFeatures, "QueryGrantedFeatures"}, + {120, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown120"}, + {121, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown121"}, + {122, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown122"}, + {123, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown123"}, + {124, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown124"}, + {125, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown125"}, + {126, &INx2GraphicsSystemSession::GetSessionFlag, "GetSessionFlag"}, + {131, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown131"}, + {133, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown133"}, + {187, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown187"}, + {201, &INx2GraphicsSystemSession::CheckPermission, "CheckPermission201"}, + {211, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown211"}, + {212, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown212"}, + {213, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown213"}, + {214, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown214"}, + {215, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown215"}, + {301, &INx2GraphicsSystemSession::CheckPermission, "CheckPermission301"}, + {303, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown303"}, + {311, &INx2GraphicsSystemSession::CreateNativeImage, "CreateFeatureImagePool311"}, + {312, &INx2GraphicsSystemSession::DestroyNativeImage, "DestroyFeatureImagePool312"}, + {321, &INx2GraphicsSystemSession::CreateNativeImage, "CreateFeatureImagePool321"}, + {361, &INx2GraphicsSystemSession::GetCpuImageDescriptor, "GetFeatureCpuImageDescriptor361"}, + {391, &INx2GraphicsSystemSession::CalculateChatImagePoolSize, "CalculateFeatureImagePoolSize391"}, + {401, &INx2GraphicsSystemSession::CheckPermission, "CheckPermission401"}, + {402, &INx2GraphicsSystemSession::CheckPermission, "CheckPermission402"}, + {403, &INx2GraphicsSystemSession::CheckPermission, "CheckPermission403"}, + {424, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown424"}, + {425, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown425"}, + {427, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown427"}, + {434, &INx2GraphicsSystemSession::AttachGpuAddressSpace, "AttachGpuAddressSpace"}, + {436, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown436"}, + {461, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown461"}, + {462, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown462"}, + {463, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown463"}, + {464, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown464"}, + {465, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown465"}, + {466, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown466"}, + {467, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown467"}, + {468, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown468"}, + {493, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown493"}, + {494, &INx2GraphicsSystemSession::CheckPermission, "CheckPermission494"}, + {603, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown603"}, + {612, &INx2GraphicsSystemSession::DestroyNativeImage, "DestroyFeatureImagePool612"}, + {613, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown613"}, + {614, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown614"}, + {615, &INx2GraphicsSystemSession::CreateNativeImage, "CreateFeatureImagePool615"}, + {691, &INx2GraphicsSystemSession::CalculateChatImagePoolSize, "CalculateFeatureImagePoolSize691"}, + {801, &INx2GraphicsSystemSession::CheckPermission, "CheckPermission801"}, + {802, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown802"}, + {803, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown803"}, + {811, &INx2GraphicsSystemSession::CreateNativeImage, "CreateFeatureImagePool811"}, + {812, &INx2GraphicsSystemSession::DestroyNativeImage, "DestroyFeatureImagePool812"}, + {813, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown813"}, + {814, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown814"}, + {861, &INx2GraphicsSystemSession::GetCpuImageDescriptor, "GetFeatureCpuImageDescriptor861"}, + {891, &INx2GraphicsSystemSession::CalculateChatImagePoolSize, "CalculateFeatureImagePoolSize891"}, + {1003, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown1003"}, + {1011, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown1011"}, + {1012, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown1012"}, + {1091, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown1091"}, + {1101, &INx2GraphicsSystemSession::CheckPermission, "CheckPermission1101"}, + {1102, &INx2GraphicsSystemSession::CheckPermission, "CheckPermission1102"}, + {1111, &INx2GraphicsSystemSession::SetFeatureVersion, "SetFeatureVersion"}, + {1112, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown1112"}, + {1113, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown1113"}, + {1121, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown1121"}, + {1203, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown1203"}, + {1212, &INx2GraphicsSystemSession::DestroyNativeImage, "DestroyFeatureImagePool1212"}, + {1501, &INx2GraphicsSystemSession::CheckPermission, "CheckPermission1501"}, + {1502, &INx2GraphicsSystemSession::CheckPermission, "CheckPermission1502"}, + {1503, &INx2GraphicsSystemSession::CheckPermission, "CheckPermission1503"}, + {1601, &INx2GraphicsSystemSession::CheckPermission, "CheckPermission1601"}, + {1602, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown1602"}, + {1603, &INx2GraphicsSystemSession::StubReplyZeroed, "Unknown1603"}, + {1611, &INx2GraphicsSystemSession::CreateNativeImage, "CreateFeatureImagePool1611"}, + {1612, &INx2GraphicsSystemSession::DestroyNativeImage, "DestroyFeatureImagePool1612"}, + {1691, &INx2GraphicsSystemSession::CalculateChatImagePoolSize, "CalculateFeatureImagePoolSize1691"}, }); } @@ -2508,6 +2934,56 @@ public: } private: + // Feature permission checks (201, 301, 401-403, 1101, 1102, 1501-1503): each tests one bit of + // the permissions the session was opened with and fails when it is clear. Games only ask for + // features they are granted. + void CheckPermission(HLERequestContext& ctx) { + LOG_DEBUG(Service, "nx2:gsv TIPC {} permission check", ctx.GetCommand()); + TipcReplyWords(ctx, {}); + } + + // 115(u64 requested feature flags): the subset that is granted, which is all of them here. + // A zeroed reply would deny every feature. + void QueryGrantedFeatures(HLERequestContext& ctx) { + const auto* w = ctx.CommandBuffer(); + LOG_INFO(Service, "nx2:gsv TIPC 115 features {:#x}", u64{w[2]} | (u64{w[3]} << 32)); + TipcReplyWords(ctx, {w[2], w[3]}); + } + + // 126: one bit of the session's state (meaning unknown); clear. + void GetSessionFlag(HLERequestContext& ctx) { + TipcReplyWords(ctx, {0}); + } + + // 434(u32 id from nx2:gpu 33): the session takes a reference on that GPU address space. + void AttachGpuAddressSpace(HLERequestContext& ctx) { + const u32 id = ctx.CommandBuffer()[2]; + LOG_INFO(Service, "nx2:gsv TIPC 434 address space {:#x}", id); + m_address_spaces.insert(id); + TipcReplyWords(ctx, {}); + } + + // 11(copy handle): handed over once per session and kept; a second call fails. + void RegisterClientHandle(HLERequestContext& ctx) { + if (m_client_handle_registered) { + LOG_WARNING(Service, "nx2:gsv TIPC 11 called twice"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + m_client_handle_registered = true; + TipcReplyWords(ctx, {}); + } + + // 1111(u32): a version or mode number the server checks and records. + void SetFeatureVersion(HLERequestContext& ctx) { + LOG_INFO(Service, "nx2:gsv TIPC 1111 value {:#x}", ctx.CommandBuffer()[2]); + TipcReplyWords(ctx, {}); + } + + std::set<u32> m_address_spaces; ///< Ids attached with 434 + bool m_client_handle_registered = false; ///< Set by 11 + // Index of the first raw data word of a TIPC request: header (2 words), then an optional // special header with pid and copy/move handle words. static size_t TipcDataOffset(const u32* cmd_buf) { @@ -2531,7 +3007,9 @@ private: // and InitializeVp9Decoder failed with 2001-0114 (title 1 aborts when a movie starts). // Newer SDKs (nn::codec::video::detail::InitializeServerContext) send 906(u8, u64 decoder // library version) instead of 902; title 4 sends version 0x3'00030000 and aborted with - // InvalidHandle at its first movie while 906 was auto-stubbed. + // InvalidHandle at its first movie while 906 was auto-stubbed. The SDK of later titles + // (ipc::Initialize(bool)) sends 905(u8) instead of 902 when its flag is set; the reply is the + // same. 1001/1002 open the encoder's creator session (CreateVideoEncoderService uses it). void OpenVideoSession(HLERequestContext& ctx) { const auto* w = ctx.CommandBuffer(); const bool second = ctx.GetCommand() == 1002; @@ -2544,13 +3022,11 @@ private: // nn::camera::sdk::CreateCameraContext sends 601() then 602(u64 1), which returns the camera // context session (handle at word 3, result at word 4). Auto-stubbed, 602 had no handle, the - // SDK sent 603 (destroy) and title 1 (1.8.0) aborted with 2034-0000. Its commands fall - // through to the nx2 auto stub. + // SDK sent 603 (destroy) and title 1 (1.8.0) aborted with 2034-0000. Its commands are + // answered by INx2CameraSession. void OpenCameraSession(HLERequestContext& ctx) { LOG_WARNING(Service, "(STUBBED) nx2:gsv TIPC 602, returning a nx2:camera session"); - ReturnRawSession(system, ctx, - std::make_shared<INx2StubInterface>( - system, INx2StubInterface::InternName("nx2:camera"))); + ReturnRawSession(system, ctx, std::make_shared<INx2CameraSession>(system)); } // nn::chat::gsv::Initialize sends 1201 then 1202, the same pair as 901/902 and 1001/1002. @@ -2798,6 +3274,8 @@ private: TipcReplyWords(ctx, {id}); } + // Pool size of a feature's images (1291 here; 391, 691, 891 and 1691 for the other feature + // groups, which share the server template). // nn::chat::gsv::CalculateRequiredBufferMemory: 1291(preference[16], u32 count), the request // of 1510, replies result, u64 size, u64 alignment. Auto-stubbed, the size became 0x1000 and // the camera pool (1211) got memory for none of its images. diff --git a/src/core/hle/service/ro/ro.cpp b/src/core/hle/service/ro/ro.cpp index 335efa3ad3..2af4f1cf58 100644 --- a/src/core/hle/service/ro/ro.cpp +++ b/src/core/hle/service/ro/ro.cpp @@ -356,6 +356,22 @@ public: // Read NRR. NrrHeader header{}; process->GetMemory().ReadBlock(nrr_address, std::addressof(header), sizeof(header)); + size_t hashes_offset = header.GetHashesOffset(); + u32 num_hashes = header.GetNumHashes(); + if (process->IsSwitch2()) { + // Switch 2 NRRs carry 3072-bit keys and signatures: the fields after them (program + // id, size, kind, hash list offset and count) sit 0x180 bytes later, the hash list + // offset and count at 0x4c0. + std::array<u32, 2> fields{}; + process->GetMemory().ReadBlock(nrr_address + 0x4c0, fields.data(), sizeof(fields)); + hashes_offset = fields[0]; + num_hashes = fields[1]; + } + // The hash list lies inside the NRR (read as a Switch 1 header, a Switch 2 one asked for + // 51 GB of hashes). + R_UNLESS(hashes_offset <= nrr_size && + num_hashes <= (nrr_size - hashes_offset) / sizeof(Sha256Hash), + RO::ResultInvalidNrr); // Set NRR info. context->SetNrrInfoInUse(nrr_info, true); @@ -363,10 +379,9 @@ public: nrr_info->nrr_heap_size = nrr_size; // Read NRR hash list. - nrr_info->hashes.resize(header.GetNumHashes()); - process->GetMemory().ReadBlock(nrr_address + header.GetHashesOffset(), - nrr_info->hashes.data(), - sizeof(Sha256Hash) * header.GetNumHashes()); + nrr_info->hashes.resize(num_hashes); + process->GetMemory().ReadBlock(nrr_address + hashes_offset, nrr_info->hashes.data(), + sizeof(Sha256Hash) * num_hashes); R_SUCCEED(); } diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 1490af0328..b47d67468b 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -9,6 +9,10 @@ #include <tuple> #include <mutex> #include <map> +#include <memory> +#include <set> +#include <string> +#include <string_view> #include <chrono> #include <algorithm> #include <cstdlib> @@ -18,8 +22,10 @@ #include "common/settings.h" #include "core/core.h" #include "core/hle/ipc.h" +#include "core/hle/kernel/k_event.h" #include "core/hle/kernel/kernel.h" #include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/kernel_helpers.h" #include "core/hle/service/service.h" #include "core/hle/service/nx2_stub_interface.h" #include "core/hle/service/sm/sm.h" @@ -49,6 +55,75 @@ ServiceFrameworkBase::ServiceFrameworkBase(Core::System& system_, const char* se ServiceFrameworkBase::~ServiceFrameworkBase() { // Wait for other threads to release access before destroying const auto guard = ServiceFrameworkBase::LockService(); + if (stub_event) { + stub_context->CloseEvent(stub_event); + } +} + +namespace { + +// Callers read the reply words they expect without checking how many were written, so an empty +// reply hands them the request's leftover words as data (an HDR output format, a refresh rate, a +// device id). Stub replies carry this many zeroed words. +constexpr u32 StubZeroedWords = 32; + +void PushStubZeroes(IPC::ResponseBuilder& rb) { + for (u32 i = 0; i < StubZeroedWords; ++i) { + rb.Push<u32>(0); + } +} + +} // namespace + +bool ServiceFrameworkBase::StubAllowed(HLERequestContext& ctx) { + static const bool env_auto_stub = std::getenv("EDEN_NX2_AUTO_STUB") != nullptr; + const auto* const caller = ctx.GetThread().GetOwnerProcess(); + if (Settings::values.use_auto_stub || env_auto_stub || + std::string_view{service_name}.starts_with(Nx2StubPrefix) || + (caller != nullptr && caller->IsSwitch2())) { + // Logged once per command: these are still unimplemented. + static std::mutex mutex; + static std::set<std::pair<std::string_view, u32>> logged; + std::scoped_lock lk{mutex}; + if (logged.emplace(service_name, ctx.GetCommand()).second) { + LOG_WARNING(Service, "(STUBBED) {}::{} (placeholder reply)", service_name, ctx.GetCommand()); + } + return true; + } + const auto& table = ctx.IsTipc() ? handlers_tipc : handlers; + const auto it = table.find(ctx.GetCommand()); + ReportUnimplementedFunction(ctx, it == table.end() ? nullptr : &it->second); + return false; +} + +void ServiceFrameworkBase::StubReplyZeroed(HLERequestContext& ctx) { + if (!StubAllowed(ctx)) { + return; + } + IPC::ResponseBuilder rb{ctx, 2 + StubZeroedWords}; + rb.Push(ResultSuccess); + PushStubZeroes(rb); +} + +void ServiceFrameworkBase::StubReplyEvent(HLERequestContext& ctx) { + if (!StubAllowed(ctx)) { + return; + } + if (!stub_event) { + stub_context = std::make_unique<KernelHelpers::ServiceContext>(system, service_name); + stub_event = stub_context->CreateEvent(fmt::format("{}:StubEvent", service_name)); + } + IPC::ResponseBuilder rb{ctx, 2 + StubZeroedWords, 1, 0}; + rb.Push(ResultSuccess); + PushStubZeroes(rb); + rb.PushCopyObjects(ctx, stub_event->GetReadableEvent()); +} + +void ServiceFrameworkBase::StubReplyInterface(HLERequestContext& ctx) { + if (!StubAllowed(ctx)) { + return; + } + ReturnNx2StubInterface(system, ctx, service_name); } void ServiceFrameworkBase::RegisterHandlersBase(const FunctionInfoBase* functions, std::size_t n) { @@ -67,6 +142,27 @@ void ServiceFrameworkBase::RegisterHandlersBaseTipc(const FunctionInfoBase* func void ServiceFrameworkBase::ReportUnimplementedFunction(HLERequestContext& ctx, const FunctionInfoBase* info) { + // EDEN_NX2_AUTO_STUB=1 auto-stubs every unknown command without touching the config. Switch 2 + // programs are always auto-stubbed: they still call many commands nobody has implemented. + static const bool env_auto_stub = std::getenv("EDEN_NX2_AUTO_STUB") != nullptr; + const auto* const caller = ctx.GetThread().GetOwnerProcess(); + const bool auto_stub = Settings::values.use_auto_stub || env_auto_stub || + std::string_view{service_name}.starts_with(Nx2StubPrefix) || + (caller != nullptr && caller->IsSwitch2()); + // Auto-stubbed commands are reported once each: title 1 polls nx2:camera 29 every frame + // (~47 calls a second), and every report wrote a Critical line, which flushes the log file. + if (auto_stub) { + static std::mutex reported_mutex; + static std::set<std::pair<std::string, u32>> reported; + std::scoped_lock lk{reported_mutex}; + if (!reported.emplace(service_name, ctx.GetCommand()).second) { + IPC::ResponseBuilder rb{ctx, 2 + StubZeroedWords}; + rb.Push(ResultSuccess); + PushStubZeroes(rb); + return; + } + } + auto cmd_buf = ctx.CommandBuffer(); std::string function_name = info == nullptr ? "<unknown>" : info->name; @@ -80,16 +176,11 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(HLERequestContext& ctx, system.GetReporter().SaveUnimplementedFunctionReport(ctx, ctx.GetCommand(), function_name, service_name); UNIMPLEMENTED_MSG("Unknown / unimplemented {}", fmt::to_string(buf)); - // EDEN_NX2_AUTO_STUB=1 auto-stubs every unknown command without touching the config. Switch 2 - // programs are always auto-stubbed: they still call many commands nobody has implemented. - static const bool env_auto_stub = std::getenv("EDEN_NX2_AUTO_STUB") != nullptr; - const auto* const caller = ctx.GetThread().GetOwnerProcess(); - if (Settings::values.use_auto_stub || env_auto_stub || - std::string_view{service_name}.starts_with(Nx2StubPrefix) || - (caller != nullptr && caller->IsSwitch2())) { - LOG_WARNING(Service, "Using auto stub fallback!"); - IPC::ResponseBuilder rb{ctx, 2}; + if (auto_stub) { + LOG_WARNING(Service, "Using auto stub fallback (repeats of this command are not logged)"); + IPC::ResponseBuilder rb{ctx, 2 + StubZeroedWords}; rb.Push(ResultSuccess); + PushStubZeroes(rb); } } diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 68b0d636d8..d1be62c8c7 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -7,6 +7,7 @@ #pragma once #include <cstddef> +#include <memory> #include <mutex> #include "common/container/unordered_map.h" #include "common/common_types.h" @@ -20,12 +21,17 @@ class System; } namespace Kernel { +class KEvent; class KServerSession; class ServiceThread; } // namespace Kernel namespace Service { +namespace KernelHelpers { +class ServiceContext; +} + namespace FileSystem { class FileSystemController; } @@ -77,6 +83,24 @@ protected: [[nodiscard]] virtual std::unique_lock<std::mutex> LockService() noexcept { return std::unique_lock{lock_service}; } + + /// Placeholder handlers for commands whose reply shape is known but whose meaning is not, for + /// Switch 2 callers (others get the unimplemented-function path, as with nullptr): + /// StubReplyZeroed replies with zeroed data words, StubReplyEvent adds a copy handle to an + /// event that never fires, and StubReplyInterface returns an nx2: stub interface whose + /// commands are auto-stubbed. + void StubReplyZeroed(HLERequestContext& ctx); + void StubReplyEvent(HLERequestContext& ctx); + void StubReplyInterface(HLERequestContext& ctx); + +private: + /// False (after reporting the command as unimplemented) for callers the stubs don't serve. + bool StubAllowed(HLERequestContext& ctx); + + /// Event handed out by StubReplyEvent, created on first use. + Kernel::KEvent* stub_event{}; + std::unique_ptr<KernelHelpers::ServiceContext> stub_context; + private: template <typename T> friend class ServiceFramework; diff --git a/src/core/hle/service/set/settings_server.cpp b/src/core/hle/service/set/settings_server.cpp index 942dddc9bc..78847ba082 100644 --- a/src/core/hle/service/set/settings_server.cpp +++ b/src/core/hle/service/set/settings_server.cpp @@ -102,9 +102,14 @@ ISettingsServer::ISettingsServer(Core::System& system_) : ServiceFramework{syste {7, C<&ISettingsServer::GetKeyCodeMap>, "GetKeyCodeMap"}, {8, C<&ISettingsServer::GetQuestFlag>, "GetQuestFlag"}, {9, C<&ISettingsServer::GetKeyCodeMap2>, "GetKeyCodeMap2"}, - {10, nullptr, "GetFirmwareVersionForDebug"}, + {10, &ISettingsServer::StubReplyZeroed, "GetFirmwareVersionForDebug"}, {11, C<&ISettingsServer::GetDeviceNickName>, "GetDeviceNickName"}, {12, C<&ISettingsServer::GetKeyCodeMapByPort>, "GetKeyCodeMapByPort"}, + {13, &ISettingsServer::StubReplyZeroed, "Unknown13"}, + {14, &ISettingsServer::StubReplyZeroed, "Unknown14"}, + {15, &ISettingsServer::StubReplyZeroed, "Unknown15"}, + {16, &ISettingsServer::StubReplyZeroed, "Unknown16"}, + {17, &ISettingsServer::StubReplyZeroed, "Unknown17"}, }; // clang-format on diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index 0137a3be66..02c6bb7762 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -1089,10 +1089,10 @@ BSD_USA::BSD_USA(Core::System& system_, const char* name, bool is_user_) {1, &BSD_USA::StartMonitoring, "StartMonitoring"}, {2, &BSD_USA::Socket, "Socket"}, {3, &BSD_USA::SocketExempt, "SocketExempt"}, - {4, nullptr, "Open"}, + {4, &BSD_USA::StubReplyZeroed, "Open"}, {5, &BSD_USA::Select, "Select"}, {6, &BSD_USA::Poll, "Poll"}, - {7, nullptr, "Sysctl"}, + {7, &BSD_USA::StubReplyZeroed, "Sysctl"}, {8, &BSD_USA::Recv, "Recv"}, {9, &BSD_USA::RecvFrom, "RecvFrom"}, {10, &BSD_USA::Send, "Send"}, @@ -1104,33 +1104,37 @@ BSD_USA::BSD_USA(Core::System& system_, const char* name, bool is_user_) {16, &BSD_USA::GetSockName, "GetSockName"}, {17, &BSD_USA::GetSockOpt, "GetSockOpt"}, {18, &BSD_USA::Listen, "Listen"}, - {19, nullptr, "Ioctl"}, + {19, &BSD_USA::StubReplyZeroed, "Ioctl"}, {20, &BSD_USA::Fcntl, "Fcntl"}, {21, &BSD_USA::SetSockOpt, "SetSockOpt"}, {22, &BSD_USA::Shutdown, "Shutdown"}, - {23, nullptr, "ShutdownAllSockets"}, + {23, &BSD_USA::StubReplyZeroed, "ShutdownAllSockets"}, {24, &BSD_USA::Write, "Write"}, {25, &BSD_USA::Read, "Read"}, {26, &BSD_USA::Close, "Close"}, {27, &BSD_USA::DuplicateSocket, "DuplicateSocket"}, - {28, nullptr, "GetResourceStatistics"}, - {29, nullptr, "RecvMMsg"}, //3.0.0+ - {30, nullptr, "SendMMsg"}, //3.0.0+ + {28, &BSD_USA::StubReplyZeroed, "GetResourceStatistics"}, + {29, &BSD_USA::StubReplyZeroed, "RecvMMsg"}, //3.0.0+ + {30, &BSD_USA::StubReplyZeroed, "SendMMsg"}, //3.0.0+ {31, &BSD_USA::EventFd, "EventFd"}, //7.0.0+ - {32, nullptr, "RegisterResourceStatisticsName"}, //7.0.0+ - {33, nullptr, "RegisterClientShared"}, //10.0.0+ - {34, nullptr, "GetSocketStatistics"}, //15.0.0+ - {35, nullptr, "NifIoctl"}, //17.0.0+ - {36, nullptr, "Unknown36"}, //18.0.0+ - {37, nullptr, "Unknown37"}, //18.0.0+ - {38, nullptr, "Unknown38"}, //18.0.0+ - {39, nullptr, "Unknown39"}, //20.0.0+ - {40, nullptr, "Unknown40"}, //20.0.0+ - {41, nullptr, "Unknown41"}, //21.0.0+ - {42, nullptr, "Unknown42"}, //21.0.0+ - {43, nullptr, "Unknown43"}, //21.0.0+ - {200, nullptr, "SetThreadCoreMask"}, //15.0.0+ - {201, nullptr, "GetThreadCoreMask"}, //15.0.0+ + {32, &BSD_USA::StubReplyZeroed, "RegisterResourceStatisticsName"}, //7.0.0+ + {33, &BSD_USA::StubReplyZeroed, "RegisterClientShared"}, //10.0.0+ + {34, &BSD_USA::StubReplyZeroed, "GetSocketStatistics"}, //15.0.0+ + {35, &BSD_USA::StubReplyZeroed, "NifIoctl"}, //17.0.0+ + // Switch 2 titles register with 36 (16 bytes and the pid; a bsd errno back) instead of + // RegisterClient. The auto stub left the errno to the message buffer's leftovers, and + // Minecraft's socket layer failed later in EventFd. + {36, &BSD_USA::RegisterClient, "RegisterClientSwitch2"}, //18.0.0+ + {37, &BSD_USA::StubReplyZeroed, "Unknown37"}, //18.0.0+ + {38, &BSD_USA::StubReplyZeroed, "Unknown38"}, //18.0.0+ + {39, &BSD_USA::StubReplyZeroed, "Unknown39"}, //20.0.0+ + {40, &BSD_USA::StubReplyZeroed, "Unknown40"}, //20.0.0+ + {41, &BSD_USA::StubReplyEvent, "Unknown41"}, //21.0.0+ + {42, &BSD_USA::StubReplyZeroed, "Unknown42"}, //21.0.0+ + {43, &BSD_USA::StubReplyZeroed, "Unknown43"}, //21.0.0+ + {200, &BSD_USA::StubReplyZeroed, "SetThreadCoreMask"}, //15.0.0+ + {201, &BSD_USA::StubReplyZeroed, "GetThreadCoreMask"}, //15.0.0+ + {44, &BSD_USA::StubReplyZeroed, "Unknown44"}, }; // clang-format on @@ -1158,22 +1162,28 @@ BSDCFG::BSDCFG(Core::System& system_, const char *name) : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "SetIfUp"}, - {1, nullptr, "SetIfUpWithEvent"}, - {2, nullptr, "CancelIf"}, - {3, nullptr, "SetIfDown"}, - {4, nullptr, "GetIfState"}, - {5, nullptr, "DhcpRenew"}, - {6, nullptr, "AddStaticArpEntry"}, - {7, nullptr, "RemoveArpEntry"}, - {8, nullptr, "LookupArpEntry"}, - {9, nullptr, "LookupArpEntry2"}, - {10, nullptr, "ClearArpEntries"}, - {11, nullptr, "ClearArpEntries2"}, - {12, nullptr, "PrintArpEntries"}, - {13, nullptr, "Unknown13"}, - {14, nullptr, "Unknown14"}, - {15, nullptr, "Unknown15"}, + {0, &BSDCFG::StubReplyZeroed, "SetIfUp"}, + {1, &BSDCFG::StubReplyZeroed, "SetIfUpWithEvent"}, + {2, &BSDCFG::StubReplyZeroed, "CancelIf"}, + {3, &BSDCFG::StubReplyZeroed, "SetIfDown"}, + {4, &BSDCFG::StubReplyZeroed, "GetIfState"}, + {5, &BSDCFG::StubReplyZeroed, "DhcpRenew"}, + {6, &BSDCFG::StubReplyZeroed, "AddStaticArpEntry"}, + {7, &BSDCFG::StubReplyZeroed, "RemoveArpEntry"}, + {8, &BSDCFG::StubReplyZeroed, "LookupArpEntry"}, + {9, &BSDCFG::StubReplyZeroed, "LookupArpEntry2"}, + {10, &BSDCFG::StubReplyZeroed, "ClearArpEntries"}, + {11, &BSDCFG::StubReplyZeroed, "ClearArpEntries2"}, + {12, &BSDCFG::StubReplyZeroed, "PrintArpEntries"}, + {13, &BSDCFG::StubReplyZeroed, "Unknown13"}, + {14, &BSDCFG::StubReplyZeroed, "Unknown14"}, + {15, &BSDCFG::StubReplyZeroed, "Unknown15"}, + {16, &BSDCFG::StubReplyZeroed, "Unknown16"}, + {100, &BSDCFG::StubReplyZeroed, "Unknown100"}, + {101, &BSDCFG::StubReplyZeroed, "Unknown101"}, + {102, &BSDCFG::StubReplyZeroed, "Unknown102"}, + {103, &BSDCFG::StubReplyZeroed, "Unknown103"}, + {104, &BSDCFG::StubReplyZeroed, "Unknown104"}, }; // clang-format on diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp index 241f2971ca..eb867cf08a 100644 --- a/src/core/hle/service/sockets/nsd.cpp +++ b/src/core/hle/service/sockets/nsd.cpp @@ -86,7 +86,7 @@ void NSD::SetChangeEnvironmentIdentifierDisabled(HLERequestContext& ctx) { LOG_WARNING(Service, "(STUBBED) called, disabled={}", disabled); - IPC::ResponseBuilder rb{ctx, 1}; + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } @@ -114,7 +114,7 @@ void NSD::ResolveEx(HLERequestContext& ctx) { } ctx.WriteBuffer(fqdn_out); - IPC::ResponseBuilder rb{ctx, 4}; + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); rb.Push(ResultSuccess); } diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index 999efd4295..15aa478b75 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp @@ -87,7 +87,7 @@ public: {3, D<&ISslConnection::SetIoMode>, "SetIoMode"}, {4, D<&ISslConnection::GetSocketDescriptor>, "GetSocketDescriptor"}, {5, D<&ISslConnection::GetHostName>, "GetHostName"}, - {6, nullptr, "GetVerifyOption"}, + {6, &ISslConnection::StubReplyZeroed, "GetVerifyOption"}, {7, D<&ISslConnection::GetIoMode>, "GetIoMode"}, {8, D<&ISslConnection::DoHandshake>, "DoHandshake"}, {9, &ISslConnection::DoHandshakeGetServerCert, "DoHandshakeGetServerCert"}, @@ -105,18 +105,20 @@ public: {21, D<&ISslConnection::GetRenegotiationMode>, "GetRenegotiationMode"}, {22, D<&ISslConnection::SetOption>, "SetOption"}, {23, D<&ISslConnection::GetOption>, "GetOption"}, - {24, nullptr, "GetVerifyCertErrors"}, - {25, nullptr, "GetCipherInfo"}, + {24, &ISslConnection::StubReplyZeroed, "GetVerifyCertErrors"}, + {25, &ISslConnection::StubReplyZeroed, "GetCipherInfo"}, {26, D<&ISslConnection::SetNextAlpnProto>, "SetNextAlpnProto"}, {27, D<&ISslConnection::GetNextAlpnProto>, "GetNextAlpnProto"}, - {28, nullptr, "SetDtlsSocketDescriptor"}, - {29, nullptr, "GetDtlsHandshakeTimeout"}, - {30, nullptr, "SetPrivateOption"}, - {31, nullptr, "SetSrtpCiphers"}, - {32, nullptr, "GetSrtpCipher"}, - {33, nullptr, "ExportKeyingMaterial"}, - {34, nullptr, "SetIoTimeout"}, - {35, nullptr, "GetIoTimeout"}, + {28, &ISslConnection::StubReplyZeroed, "SetDtlsSocketDescriptor"}, + {29, &ISslConnection::StubReplyZeroed, "GetDtlsHandshakeTimeout"}, + {30, &ISslConnection::StubReplyZeroed, "SetPrivateOption"}, + {31, &ISslConnection::StubReplyZeroed, "SetSrtpCiphers"}, + {32, &ISslConnection::StubReplyZeroed, "GetSrtpCipher"}, + {33, &ISslConnection::StubReplyZeroed, "ExportKeyingMaterial"}, + {34, &ISslConnection::StubReplyZeroed, "SetIoTimeout"}, + {35, &ISslConnection::StubReplyZeroed, "GetIoTimeout"}, + {36, &ISslConnection::StubReplyZeroed, "Unknown36"}, + {37, &ISslConnection::StubReplyZeroed, "Unknown37"}, }; // clang-format on @@ -420,10 +422,14 @@ private: R_SUCCEED(); } - Result GetNextAlpnProto(OutBuffer<BufferAttr_HipcMapAlias> data, Out<u32> to_write) { - *to_write = u32((std::min)(next_alpn_proto.size(), data.size())); - next_alpn_proto.assign(data.begin(), data.begin() + *to_write); - LOG_DEBUG(Service_SSL, "GetNextAlpnProto called, size={}", *to_write); + // Replies with two words, the ALPN state and the size written to the buffer. It used to reply + // with one and copy the caller's (output) buffer over the protocols SetNextAlpnProto stored. + // No TLS negotiation happens here, so report that nothing was negotiated. + Result GetNextAlpnProto(OutBuffer<BufferAttr_HipcMapAlias> data, Out<u32> out_state, + Out<u32> out_size) { + LOG_DEBUG(Service_SSL, "GetNextAlpnProto called, buffer size={}", data.size()); + *out_state = 0; + *out_size = 0; R_SUCCEED(); } @@ -557,8 +563,10 @@ private: rb.Push(client_id); } - Result RegisterInternalPki() { + // Replies with a u64 (the registered certificate's id); the reply used to carry no data. + Result RegisterInternalPki(Out<u64> out_id) { LOG_WARNING(Service_SSL, "(STUBBED) called"); + *out_id = 0; R_SUCCEED(); } }; @@ -636,7 +644,7 @@ class ISslServiceForSystem final : public ServiceFramework<ISslServiceForSystem> explicit ISslServiceForSystem(Core::System& system_) : ServiceFramework{system_, "ssl:s"} { // clang-format off static const FunctionInfo functions[] = { - {0, D<&ISslServiceForSystem::CreateContext>, "CreateContext"}, + {0, &ISslServiceForSystem::CreateContext, "CreateContext"}, {1, D<&ISslServiceForSystem::GetContextCount>, "GetContextCount"}, {2, D<&ISslServiceForSystem::GetCertificates>, "GetCertificates"}, {3, D<&ISslServiceForSystem::GetCertificateBufSize>, "GetCertificateBufSize"}, @@ -646,7 +654,7 @@ class ISslServiceForSystem final : public ServiceFramework<ISslServiceForSystem> {7, D<&ISslServiceForSystem::SetDebugOption>, "SetDebugOption"}, {8, D<&ISslServiceForSystem::GetDebugOption>, "GetDebugOption"}, {9, D<&ISslServiceForSystem::ClearTls12FallbackFlag>, "ClearTls12FallbackFlag"}, - {100, D<&ISslServiceForSystem::CreateContextForSystem>, "CreateContextForSystem"}, + {100, &ISslServiceForSystem::CreateContext, "CreateContextForSystem"}, {101, D<&ISslServiceForSystem::SetThreadCoreMask>, "SetThreadCoreMask"}, {102, D<&ISslServiceForSystem::GetThreadCoreMask>, "GetThreadCoreMask"}, {103, D<&ISslServiceForSystem::VerifySignature>, "VerifySignature"} @@ -656,19 +664,30 @@ class ISslServiceForSystem final : public ServiceFramework<ISslServiceForSystem> RegisterHandlers(functions); }; - Result CreateContext() { - LOG_DEBUG(Service_SSL, "(STUBBED) called."); + // 0 and 100(SslVersion, padding, u64 pid placeholder; pid): an ISslContext, like ssl's + // CreateContext. The empty reply returned no object, so the caller got handle 0 + // (Minecraft calls 100). + void CreateContext(HLERequestContext& ctx) { + struct Parameters { + SslVersion ssl_version; + INSERT_PADDING_BYTES(0x4); + u64 pid_placeholder; + }; + static_assert(sizeof(Parameters) == 0x10, "Parameters is an invalid size"); - // TODO (jarrodnorwell) + IPC::RequestParser rp{ctx}; + const auto parameters = rp.PopRaw<Parameters>(); + LOG_WARNING(Service_SSL, "(STUBBED) called, command={}, api_version={}", ctx.GetCommand(), + parameters.ssl_version.api_version); - return ResultSuccess; - }; + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<ISslContext>(ctx, system, parameters.ssl_version); + } - Result GetContextCount() { + Result GetContextCount(Out<u32> out_count) { LOG_DEBUG(Service_SSL, "(STUBBED) called."); - - // TODO (jarrodnorwell) - + *out_count = 0; return ResultSuccess; }; @@ -704,11 +723,9 @@ class ISslServiceForSystem final : public ServiceFramework<ISslServiceForSystem> return ResultSuccess; }; - Result FlushSessionCache() { + Result FlushSessionCache(Out<u32> out_count) { LOG_DEBUG(Service_SSL, "(STUBBED) called."); - - // TODO (jarrodnorwell) - + *out_count = 0; return ResultSuccess; }; @@ -736,14 +753,6 @@ class ISslServiceForSystem final : public ServiceFramework<ISslServiceForSystem> return ResultSuccess; }; - Result CreateContextForSystem() { - LOG_DEBUG(Service_SSL, "(STUBBED) called."); - - // TODO (jarrodnorwell) - - return ResultSuccess; - }; - Result SetThreadCoreMask() { LOG_DEBUG(Service_SSL, "(STUBBED) called."); @@ -752,11 +761,9 @@ class ISslServiceForSystem final : public ServiceFramework<ISslServiceForSystem> return ResultSuccess; }; - Result GetThreadCoreMask() { + Result GetThreadCoreMask(Out<u64> out_mask) { LOG_DEBUG(Service_SSL, "(STUBBED) called."); - - // TODO (jarrodnorwell) - + *out_mask = 0; return ResultSuccess; }; diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index cda1645548..04b05d188f 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -7,6 +7,9 @@ #include <algorithm> #include <cctype> #include <cstring> +#include <map> +#include <memory> +#include <mutex> #include <string_view> #include "common/logging.h" #include "common/random.h" @@ -90,8 +93,19 @@ FileSys::VirtualFile FindLooseRomFS(const FileSys::VirtualDir& exefs_dir) { } if (const auto parent = exefs_dir->GetParentDirectory()) { if (const auto romfs_dir = parent->GetSubdirectory("romfs")) { + // Building it walks every file and keeps an object for each (title 1: 206k files, ~150 + // MiB), so loaders of the same directory share one build while any of them holds it. + static std::mutex cache_mutex; + static std::map<std::string, std::weak_ptr<FileSys::VfsFile>> cache; + std::scoped_lock lk{cache_mutex}; + auto& cached = cache[romfs_dir->GetFullPath()]; + if (auto built = cached.lock()) { + return built; + } LOG_INFO(Loader, "Using RomFS directory next to the ExeFS"); - return FileSys::CreateRomFS(romfs_dir); + auto built = FileSys::CreateRomFS(romfs_dir); + cached = built; + return built; } } return nullptr; @@ -233,7 +247,7 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys name = control->GetApplicationName(); } - romfs = FindLooseRomFS(file_dir); + romfs_exefs_dir = file_dir; if (const auto title_dir = file_dir->GetParentDirectory()) { romfs_version = RomFSProductVersion(title_dir->GetSubdirectory("romfs")); } @@ -245,7 +259,17 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory( , dir(std::move(directory)) , override_update(override_update_) { - romfs = FindLooseRomFS(dir); + romfs_exefs_dir = dir; +} + +// Loaders made only for metadata (the game list, title lookups) never need the RomFS, and several +// of them were alive with a whole copy each. +const FileSys::VirtualFile& AppLoader_DeconstructedRomDirectory::LooseRomFS() { + if (!romfs_resolved) { + romfs_resolved = true; + romfs = FindLooseRomFS(romfs_exefs_dir); + } + return romfs; } FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) { @@ -304,7 +328,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect // Loose-ExeFS loads bypass the NCA/NSP loaders, which are the ones that normally apply // LayeredFS to the RomFS; apply it here so mods in user/load/<titleid>/ work too. - if (romfs != nullptr) { + if (LooseRomFS() != nullptr) { const FileSys::PatchManager patch_manager(metadata.GetTitleID(), system.GetFileSystemController(), system.GetContentProvider()); @@ -414,7 +438,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect } ResultStatus AppLoader_DeconstructedRomDirectory::ReadRomFS(FileSys::VirtualFile& out_dir) { - if (romfs == nullptr) { + if (LooseRomFS() == nullptr) { return ResultStatus::ErrorNoRomFS; } diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h index b976b71ecb..db0120f4ad 100644 --- a/src/core/loader/deconstructed_rom_directory.h +++ b/src/core/loader/deconstructed_rom_directory.h @@ -58,8 +58,13 @@ public: ResultStatus ReadNSOModules(Modules& out_modules) override; private: + /// The loose RomFS next to the ExeFS, found (and built) on first use. + const FileSys::VirtualFile& LooseRomFS(); + FileSys::ProgramMetadata metadata; FileSys::VirtualFile romfs; + FileSys::VirtualDir romfs_exefs_dir; ///< Where LooseRomFS looks + bool romfs_resolved{}; FileSys::VirtualDir dir; std::vector<u8> icon_data; |
