aboutsummaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/core')
-rw-r--r--src/core/CMakeLists.txt1
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.cpp4
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.cpp4
-rw-r--r--src/core/device_memory_manager.h7
-rw-r--r--src/core/device_memory_manager.inc44
-rw-r--r--src/core/file_sys/fsmitm_romfsbuild.cpp27
-rw-r--r--src/core/file_sys/system_archive/system_archive.cpp19
-rw-r--r--src/core/file_sys/vfs/vfs_real.cpp37
-rw-r--r--src/core/file_sys/vfs/vfs_real.h8
-rw-r--r--src/core/hle/kernel/board/nintendo/nx/k_system_control.h5
-rw-r--r--src/core/hle/kernel/k_memory_manager.cpp24
-rw-r--r--src/core/hle/kernel/k_memory_manager.h6
-rw-r--r--src/core/hle/kernel/k_shared_memory.cpp5
-rw-r--r--src/core/hle/kernel/svc.cpp17
-rw-r--r--src/core/hle/service/acc/acc.cpp4
-rw-r--r--src/core/hle/service/am/service/application_functions.cpp70
-rw-r--r--src/core/hle/service/am/service/application_functions.h4
-rw-r--r--src/core/hle/service/am/service/application_proxy_service.cpp1
-rw-r--r--src/core/hle/service/am/service/audio_controller.cpp2
-rw-r--r--src/core/hle/service/am/service/common_state_getter.cpp50
-rw-r--r--src/core/hle/service/am/service/display_controller.cpp37
-rw-r--r--src/core/hle/service/am/service/graphics_scale_controller.h6
-rw-r--r--src/core/hle/service/am/service/library_applet_accessor.cpp13
-rw-r--r--src/core/hle/service/am/service/library_applet_creator.cpp4
-rw-r--r--src/core/hle/service/am/service/self_controller.cpp41
-rw-r--r--src/core/hle/service/am/service/window_controller.cpp2
-rw-r--r--src/core/hle/service/aoc/addon_content_manager.cpp14
-rw-r--r--src/core/hle/service/audio/audio.cpp3
-rw-r--r--src/core/hle/service/audio/audio_device.cpp31
-rw-r--r--src/core/hle/service/audio/audio_device.h4
-rw-r--r--src/core/hle/service/audio/audio_renderer_manager.cpp2
-rw-r--r--src/core/hle/service/audio/nx2_audio_device_manager.h539
-rw-r--r--src/core/hle/service/caps/caps_su.cpp4
-rw-r--r--src/core/hle/service/caps/caps_u.cpp24
-rw-r--r--src/core/hle/service/hid/hid_server.cpp121
-rw-r--r--src/core/hle/service/hid/hid_server.h6
-rw-r--r--src/core/hle/service/kernel_helpers.cpp19
-rw-r--r--src/core/hle/service/kernel_helpers.h4
-rw-r--r--src/core/hle/service/lm/lm.cpp5
-rw-r--r--src/core/hle/service/mii/mii.cpp6
-rw-r--r--src/core/hle/service/ns/platform_service_manager.cpp65
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp11
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp8
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp25
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.cpp1
-rw-r--r--src/core/hle/service/nvdrv/nvdrv_interface.cpp6
-rw-r--r--src/core/hle/service/nx2_gsv.h624
-rw-r--r--src/core/hle/service/ro/ro.cpp23
-rw-r--r--src/core/hle/service/service.cpp109
-rw-r--r--src/core/hle/service/service.h24
-rw-r--r--src/core/hle/service/set/settings_server.cpp7
-rw-r--r--src/core/hle/service/sockets/bsd.cpp84
-rw-r--r--src/core/hle/service/sockets/nsd.cpp4
-rw-r--r--src/core/hle/service/ssl/ssl.cpp93
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp34
-rw-r--r--src/core/loader/deconstructed_rom_directory.h5
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 = &notification_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;