aboutsummaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-27 17:04:06 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-27 17:04:06 -0400
commitdce3adbc4cae486e2f3c080b995669d6db5ef58f (patch)
tree638ec3ffef50441c7229160ee20b0fd9423ee271 /src/core
parent2835fec7fc66e1a1659710de8eddf2dfe67e20f9 (diff)
Switch 2: window image sizes, queue semaphores, built-in fonts, memory budget
- gsv 1510 CalculateRequirements: block-linear size/alignment for window images. - nvhost-gpu: Switch 2 channel event 7 is a real (unsignalled) event. - Puller mirrors NVN2 queue semaphore releases into the page the CPU and GPU wait on (the first two-page nx2:gpu region); EDEN_NX2_METHOD_TRACE logs methods. - pl:u 2000/2010 GetBuiltInFontSharedMemory/GetBuiltInFontData serve plain TTF copies of the shared fonts. - Switch 2 processes count the alias region in their max memory, so the game's nninitStartup heap (total - 6.3 GiB) has room for its sound heap. - gdbrsp.py: interrupt, threads, selthread, hits, continue_async, sleep.
Diffstat (limited to 'src/core')
-rw-r--r--src/core/file_sys/program_metadata.cpp1
-rw-r--r--src/core/file_sys/program_metadata.h5
-rw-r--r--src/core/hle/kernel/k_process.cpp7
-rw-r--r--src/core/hle/service/ns/platform_service_manager.cpp56
-rw-r--r--src/core/hle/service/ns/platform_service_manager.h3
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp13
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.h2
-rw-r--r--src/core/hle/service/nx2_gsv.h35
8 files changed, 122 insertions, 0 deletions
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
index ad09b19e68..464e496ea3 100644
--- a/src/core/file_sys/program_metadata.cpp
+++ b/src/core/file_sys/program_metadata.cpp
@@ -47,6 +47,7 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
return Loader::ResultStatus::ErrorBadACIDHeader;
}
LOG_INFO(Loader, "NPDM uses the Switch 2 ACID layout");
+ is_switch2 = true;
}
if (sizeof(AciHeader) != file->ReadObject(&aci_header, npdm_header.aci_offset)) {
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h
index 115e6d6cd1..5ce32af1a4 100644
--- a/src/core/file_sys/program_metadata.h
+++ b/src/core/file_sys/program_metadata.h
@@ -72,6 +72,10 @@ public:
KernelCapabilityDescriptors capabilities);
bool Is64BitProgram() const;
+ /// True when the NPDM uses the Switch 2 ACID layout.
+ bool IsSwitch2() const {
+ return is_switch2;
+ }
ProgramAddressSpaceType GetAddressSpaceType() const;
u8 GetMainThreadPriority() const;
u8 GetMainThreadCore() const;
@@ -179,6 +183,7 @@ private:
Header npdm_header{};
AciHeader aci_header{};
AcidHeader acid_header{};
+ bool is_switch2{};
FileAccessControl acid_file_access{};
FileAccessHeader aci_file_access{};
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index 27d1b1bfc4..4aac818b77 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -1261,6 +1261,13 @@ Result KProcess::LoadFromMetadata(KernelCore& kernel, const FileSys::ProgramMeta
// Assign remaining properties.
m_ideal_core_id = metadata.GetMainThreadCore();
+ // Switch 2 applications get more memory than a 64-bit heap region holds (nninitStartup sizes
+ // its heap as total memory minus ~6.3 GiB), so count the alias region as well, bounded by the
+ // pool through the resource limit.
+ if (metadata.IsSwitch2()) {
+ m_max_process_memory = m_page_table.GetHeapRegionSize() + m_page_table.GetAliasRegionSize();
+ }
+
// Set up emulation context.
this->InitializeInterfaces(kernel);
diff --git a/src/core/hle/service/ns/platform_service_manager.cpp b/src/core/hle/service/ns/platform_service_manager.cpp
index bf93a2edfa..88aac56b1c 100644
--- a/src/core/hle/service/ns/platform_service_manager.cpp
+++ b/src/core/hle/service/ns/platform_service_manager.cpp
@@ -22,7 +22,9 @@
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/cmif_serialization.h"
+#include "common/alignment.h"
#include "core/hle/service/filesystem/filesystem.h"
+#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/ns/platform_service_manager.h"
namespace Service::NS {
@@ -83,6 +85,8 @@ 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.
+ boost::container::static_vector<FontRegion, 8> builtin_font_regions;
/// Backing memory for the shared font data
std::array<u8, SHARED_FONT_MEM_SIZE> shared_font;
};
@@ -107,6 +111,8 @@ IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const ch
{106, nullptr, "GetFunctionBlackListVersion"},
{1000, nullptr, "LoadNgWordDataForPlatformRegionChina"},
{1001, nullptr, "GetNgWordDataSizeForPlatformRegionChina"},
+ {2000, &IPlatformServiceManager::GetBuiltInFontSharedMemory, "GetBuiltInFontSharedMemory (Switch 2)"},
+ {2010, &IPlatformServiceManager::GetBuiltInFontData, "GetBuiltInFontData (Switch 2)"},
};
// clang-format on
RegisterHandlers(functions);
@@ -116,6 +122,7 @@ 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;
@@ -151,9 +158,58 @@ 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();
+ }
+}
+
+// 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.
+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());
+ IPC::ResponseBuilder rb{ctx, 3, 1};
+ rb.Push(ResultSuccess);
+ rb.Push<u32>(static_cast<u32>(SHARED_FONT_MEM_SIZE));
+ rb.PushCopyObjects(ctx, kernel.GetFontSharedMem());
+}
+
+// nn::pl::GetBuiltInFontData(BuiltInFontDataOunce*, BuiltInFontTypeOunce, bool): returns a 16-byte
+// bool that says whether the font exists and a 16-byte descriptor (u8 flags, u8 flag bit, u16,
+// u32 offset into the font shared memory, u32 size, u32). The client expects plain TrueType/OpenType data
+// there. The type numbering is unknown, so types map onto Eden's shared fonts in order.
+void IPlatformServiceManager::GetBuiltInFontData(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u32 type = rp.Pop<u32>();
+ const bool flag = rp.Pop<u8>() != 0;
+ const auto& regions = impl->builtin_font_regions;
+ const bool found = !regions.empty();
+ const FontRegion region = found ? regions[std::min<size_t>(type, regions.size() - 1)] : EMPTY_REGION;
+ LOG_WARNING(Service_NS, "(STUBBED) Switch 2 GetBuiltInFontData type={} flag={} -> offset={:#x} size={:#x}",
+ type, flag, region.offset, region.size);
+ // Raw reply: the bool at offset 0, the descriptor at offset 8.
+ IPC::ResponseBuilder rb{ctx, 2 + 6};
+ rb.Push(ResultSuccess);
+ rb.Push<u32>(found ? 1 : 0);
+ rb.Push<u32>(0);
+ rb.Push<u32>(0);
+ rb.Push<u32>(region.offset);
+ rb.Push<u32>(region.size);
+ rb.Push<u32>(0);
}
IPlatformServiceManager::~IPlatformServiceManager() = default;
diff --git a/src/core/hle/service/ns/platform_service_manager.h b/src/core/hle/service/ns/platform_service_manager.h
index 73521abbc2..ed5b1c1e39 100644
--- a/src/core/hle/service/ns/platform_service_manager.h
+++ b/src/core/hle/service/ns/platform_service_manager.h
@@ -73,6 +73,9 @@ private:
Out<bool> out_fonts_are_loaded, Out<u32> out_font_count,
Set::LanguageCode language_code);
+ void GetBuiltInFontSharedMemory(HLERequestContext& ctx);
+ void GetBuiltInFontData(HLERequestContext& ctx);
+
struct Impl;
std::unique_ptr<Impl> impl;
};
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
index b650d3cac8..a72ccd9b4f 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
@@ -49,6 +49,9 @@ nvhost_gpu::~nvhost_gpu() {
events_interface.FreeEvent(sm_exception_breakpoint_int_report_event);
events_interface.FreeEvent(sm_exception_breakpoint_pause_report_event);
events_interface.FreeEvent(error_notifier_event);
+ for (auto& [id, event] : nx2_events) {
+ events_interface.FreeEvent(event);
+ }
syncpoint_manager.FreeSyncpoint(channel_syncpoint);
}
@@ -543,6 +546,16 @@ Kernel::KEvent* nvhost_gpu::QueryEvent(u32 event_id) {
case 3:
return error_notifier_event;
default:
+ if (g_nx2_driver) {
+ // Switch 2 NVN also asks for event 7 and multi-waits on it with its syncpoint events.
+ // Nothing signals it yet.
+ auto*& event = nx2_events[event_id];
+ if (event == nullptr) {
+ event = events_interface.CreateEvent(fmt::format("GpuChannelNx2Event{}", event_id));
+ }
+ LOG_WARNING(Service_NVDRV, "(STUBBED) Switch 2 channel event {}", event_id);
+ return event;
+ }
LOG_CRITICAL(Service_NVDRV, "Unknown Ctrl GPU Event {}", event_id);
return nullptr;
}
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
index 70a6b1bd68..ab968d6c7e 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
@@ -6,6 +6,7 @@
#pragma once
+#include <map>
#include <memory>
#include <vector>
#include "common/bit_field.h"
@@ -225,6 +226,7 @@ private:
Kernel::KEvent* sm_exception_breakpoint_int_report_event;
Kernel::KEvent* sm_exception_breakpoint_pause_report_event;
Kernel::KEvent* error_notifier_event;
+ std::map<u32, Kernel::KEvent*> nx2_events;
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h
index ef222befca..9d1a4cdc93 100644
--- a/src/core/hle/service/nx2_gsv.h
+++ b/src/core/hle/service/nx2_gsv.h
@@ -9,6 +9,7 @@
#include <map>
#include <vector>
+#include "common/alignment.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/k_client_session.h"
@@ -21,6 +22,7 @@
#include "core/hle/kernel/k_transfer_memory.h"
#include "core/memory.h"
#include "video_core/host1x/host1x.h"
+#include "video_core/nx2.h"
#include "core/hle/service/hle_ipc.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/kernel_helpers.h"
@@ -291,6 +293,14 @@ private:
last, b[6], b[7]);
if (last > start) {
Nvidia::Devices::Nx2GpuMemoryRegistry::Get().Register(start, last - start + 1);
+ // The first two-page region holds the queue semaphores: GPU releases go to the second
+ // page, waits read the first (see video_core/nx2.h).
+ if (last - start + 1 == 0x2000 && Tegra::g_nx2_semaphore_source_page == 0) {
+ Tegra::g_nx2_semaphore_mirror_page = start;
+ Tegra::g_nx2_semaphore_source_page = start + 0x1000;
+ LOG_WARNING(Service, "nx2:gpu semaphore page {:#x} mirrored to {:#x}",
+ start + 0x1000, start);
+ }
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -376,6 +386,7 @@ public:
{305, &INx2GraphicsSystemSession::OpenViSession, "OpenViSession (guessed)"},
{497, &INx2GraphicsSystemSession::OpenGpuSession, "OpenGpuSession (guessed)"},
{1402, &INx2GraphicsSystemSession::OpenNvsrvSession, "OpenNvsrvSession (guessed)"},
+ {1510, &INx2GraphicsSystemSession::CalculateRequirements, "CalculateRequirements"},
});
}
@@ -522,6 +533,30 @@ private:
}
}
+ // nn::gsv::shim::CalculateRequirements(u64* size, u64* alignment, preference, u32): the 0x40-byte
+ // InterModuleUserImagePreference (words 2..17) and a u32 (word 18). A block-linear preference
+ // (MakeInterModuleBlockUserImagePreference) holds kind 2 at word 3, (width - 1) | (height - 1)
+ // << 16 at word 4, two u32s at words 6-7 and the pixel format at word 8. Reply: result, u64
+ // size, u64 alignment; the client raises the alignment to at least 4 KiB and rounds the size.
+ void CalculateRequirements(HLERequestContext& ctx) {
+ const auto* w = ctx.CommandBuffer();
+ const u32 width = (w[4] & 0xFFFF) + 1;
+ const u32 height = (w[4] >> 16) + 1;
+ const u32 pixel_format = w[8] & 0xFF;
+ // Pixel format numbering is unknown; 4 bytes per pixel. Oversizing (8 for format 4) left
+ // the game's 1.5 GiB heap short of its 300 MiB sound heap.
+ const u64 bytes_per_pixel = 4;
+ const u64 pitch = Common::AlignUp(width * bytes_per_pixel, 64);
+ const u64 size = pitch * Common::AlignUp(height, 128);
+ constexpr u64 alignment = 0x10000;
+ LOG_WARNING(Service,
+ "(STUBBED) nx2:gsv TIPC 1510 kind={} {}x{} [6]={:#x} [7]={:#x} format={} "
+ "[9]={:#x} [18]={:#x} -> size={:#x}",
+ w[3], width, height, w[6], w[7], pixel_format, w[9], w[18], size);
+ TipcReplyWords(ctx, {static_cast<u32>(size), static_cast<u32>(size >> 32),
+ static_cast<u32>(alignment), 0});
+ }
+
void OpenViSession(HLERequestContext& ctx) {
// Used by nn::vi2::sdk::Initialize; takes one u64, returns a session (handle at word 3,
// result at word 4).