aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authortheofficialhackclean <85363951+theofficialhackclean@users.noreply.github.com>2026-10-29 14:20:02 -0400
committertheofficialhackclean <85363951+theofficialhackclean@users.noreply.github.com>2026-10-29 14:20:02 -0400
commita8657997e1eb594ffde9e28d44672ef5b697ad59 (patch)
tree759edef2561e23b2d3a74372a35cb1f2e20f79c2 /src
parentee743563ed54a761b5daeaf36f7c35b217ba433f (diff)
push for fix for mkw
Diffstat (limited to 'src')
-rw-r--r--src/core/hle/service/friend/friend.cpp52
-rw-r--r--src/core/hle/service/nx2_gsv.h145
2 files changed, 196 insertions, 1 deletions
diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp
index f8690dbb8b..bfb2d6b503 100644
--- a/src/core/hle/service/friend/friend.cpp
+++ b/src/core/hle/service/friend/friend.cpp
@@ -558,6 +558,55 @@ public:
}
};
+// Session returned by chat:u 0. Command 1 hands back a handle the title waits on, so it gets an
+// event that is never signalled; everything else falls through to the nx2 auto stub.
+class IChatSession final : public ServiceFramework<IChatSession> {
+public:
+ explicit IChatSession(Core::System& system_)
+ : ServiceFramework{system_, "nx2:chat:u::0"}, service_context{system_, "IChatSession"} {
+ static const FunctionInfo functions[] = {
+ {1, &IChatSession::GetEvent, "GetEvent (guessed)"},
+ };
+ RegisterHandlers(functions);
+ event = service_context.CreateEvent("IChatSession:Event");
+ }
+
+ ~IChatSession() override {
+ service_context.CloseEvent(event);
+ }
+
+private:
+ void GetEvent(HLERequestContext& ctx) {
+ LOG_WARNING(Service_Friend, "(STUBBED) chat:u::0 1 [5]={:#x}", ctx.CommandBuffer()[5]);
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(ctx, event->GetReadableEvent());
+ }
+
+ KernelHelpers::ServiceContext service_context;
+ Kernel::KEvent* event;
+};
+
+// Switch 2 GameChat (chat:u). Command 0 opens a session the title keeps using, so it has to
+// return an interface; everything else falls through to the nx2 auto stub.
+class IChatService final : public ServiceFramework<IChatService> {
+public:
+ explicit IChatService(Core::System& system_) : ServiceFramework{system_, "nx2:chat:u"} {
+ static const FunctionInfo functions[] = {
+ {0, &IChatService::OpenSession, "OpenSession"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ void OpenSession(HLERequestContext& ctx) {
+ LOG_WARNING(Service_Friend, "(STUBBED) chat:u 0, returning a chat session");
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IChatSession>(ctx, system);
+ }
+};
+
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
auto module = std::make_shared<Module>();
@@ -571,6 +620,9 @@ void LoopProcess(Core::System& system) {
server_manager->RegisterNamedService("friend:u", std::make_shared<Friend>(module, system, "friend:u"));
server_manager->RegisterNamedService("friend:v", std::make_shared<Friend>(module, system, "friend:v"));
+ // Switch 2 GameChat. Titles block in sm until it exists, so register a stub for now.
+ server_manager->RegisterNamedService("chat:u", std::make_shared<IChatService>(system));
+
ServerManager::RunServer(std::move(server_manager));
}
diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h
index 63366b6864..fb1436bd5e 100644
--- a/src/core/hle/service/nx2_gsv.h
+++ b/src/core/hle/service/nx2_gsv.h
@@ -630,6 +630,7 @@ struct Nx2NativeObjects {
u32 memory{};
std::array<u32, 16> preference{};
u64 size{};
+ u32 count{1}; // images in the pool; handle id + i is image i
bool window_pool{};
};
std::mutex mutex;
@@ -672,6 +673,48 @@ inline Nx2ImageLayout Nx2CalculateImageLayout(const u32* preference) {
return layout;
}
+// GameChat's graphics side (nn::chat::gsv), handed out by gsv 1202. Read from the SDK's
+// nn::chat::gsv::Initialize: TIPC 1() returns a u32 id at word 3 (it aborts with 2162-0001 if
+// the id is over 0x3ff), then TIPC 11(u32 id) returns an event (copy handle at word 3, result at
+// word 4) that it attaches as a system event. Everything else falls through to the nx2 auto stub.
+class INx2ChatGsvSession final : public ServiceFramework<INx2ChatGsvSession> {
+public:
+ explicit INx2ChatGsvSession(Core::System& system_)
+ : ServiceFramework{system_, "nx2:chat:gsv"}, m_context{system_, "nx2:chat:gsv"} {
+ RegisterHandlersTipc({
+ {1, &INx2ChatGsvSession::GetEventId, "GetEventId (guessed)"},
+ {11, &INx2ChatGsvSession::GetEvent, "GetEvent (guessed)"},
+ });
+ }
+
+ ~INx2ChatGsvSession() override {
+ for (auto& [id, event] : m_events) {
+ m_context.CloseEvent(event);
+ }
+ }
+
+private:
+ void GetEventId(HLERequestContext& ctx) {
+ LOG_WARNING(Service, "(STUBBED) nx2:chat:gsv TIPC 1, returning id 0");
+ TipcReplyWords(ctx, {0});
+ }
+
+ void GetEvent(HLERequestContext& ctx) {
+ const u32 id = ctx.CommandBuffer()[2];
+ LOG_WARNING(Service, "(STUBBED) nx2:chat:gsv TIPC 11 id={}, returning an event", id);
+ auto*& event = m_events[id];
+ if (event == nullptr) {
+ event = m_context.CreateEvent(fmt::format("nx2:chat:gsv:Event{}", id));
+ }
+ IPC::ResponseBuilder rb{ctx, 2, 1, 0};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(ctx, event->GetReadableEvent());
+ }
+
+ KernelHelpers::ServiceContext m_context;
+ std::map<u32, Kernel::KEvent*> m_events;
+};
+
// Placeholder for the Switch 2 display session (nn::vi2::sdk), handed out by gsv 305. Reply
// shapes read from the SDK: TIPC 1 -> u32 < 0x400, TIPC 3 -> u32 < 8, TIPC 11/31(u32) and
// 32(handle, u32) -> one handle at word 3 then the result. Handles returned here are events (a
@@ -1292,6 +1335,8 @@ public:
{423, &INx2GraphicsSystemSession::BindNativeGpuMemory, "BindNativeGpuMemory"},
{429, &INx2GraphicsSystemSession::CreateNativeImage, "CreateNativeImage"},
{430, &INx2GraphicsSystemSession::DestroyNativeImage, "DestroyNativeImage"},
+ {426, &INx2GraphicsSystemSession::QueryNativeImage, "MapNativeImage"},
+ {428, &INx2GraphicsSystemSession::QueryNativeImage, "QueryNativeImage"},
{435, &INx2GraphicsSystemSession::CalculateNativeImageSize, "CalculateNativeImageSize"},
{1402, &INx2GraphicsSystemSession::OpenNvsrvSession, "OpenNvsrvSession (guessed)"},
{1510, &INx2GraphicsSystemSession::CalculateRequirements, "CalculateRequirements"},
@@ -1301,6 +1346,14 @@ public:
{902, &INx2GraphicsSystemSession::OpenVideoSession, "OpenVideoDecoderSession (guessed)"},
{1001, &INx2GraphicsSystemSession::Stub, "VideoInitialize2 (guessed)"},
{1002, &INx2GraphicsSystemSession::OpenVideoSession, "OpenVideoDecoderSession2 (guessed)"},
+ {601, &INx2GraphicsSystemSession::Stub, "CameraInitialize (guessed)"},
+ {602, &INx2GraphicsSystemSession::OpenCameraSession, "OpenCameraContext (guessed)"},
+ {1201, &INx2GraphicsSystemSession::Stub, "ChatInitialize (guessed)"},
+ {1202, &INx2GraphicsSystemSession::OpenChatSession, "OpenChatSession (guessed)"},
+ // Same request as 429: nn::chat::gsv::CreateImagePool (GameChat camera images).
+ {1211, &INx2GraphicsSystemSession::CreateNativeImage, "CreateChatImagePool (guessed)"},
+ {1291, &INx2GraphicsSystemSession::CalculateChatImagePoolSize,
+ "CalculateChatImagePoolSize (guessed)"},
// Same request as 429: the image backing a decoder output (nn::codec image pools).
{911, &INx2GraphicsSystemSession::CreateNativeImage, "CreateDecoderImage (guessed)"},
});
@@ -1348,6 +1401,24 @@ private:
second ? "nx2:vdec2" : "nx2:vdec"));
}
+ // 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.
+ 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")));
+ }
+
+ // nn::chat::gsv::Initialize sends 1201 then 1202, the same pair as 901/902 and 1001/1002.
+ // Left to the auto stub it aborted with 2162-0001 (GameChat), so 1202 gets a session too.
+ void OpenChatSession(HLERequestContext& ctx) {
+ LOG_WARNING(Service, "(STUBBED) nx2:gsv TIPC 1202, returning a nx2:chat session");
+ ReturnRawSession(system, ctx, std::make_shared<INx2ChatGsvSession>(system));
+ }
+
void OpenSyncptSession(HLERequestContext& ctx) {
// Used by nn::syncpt::sdk::InitializeEx; handle at word 3, result at word 4.
LOG_WARNING(Service, "(STUBBED) nx2:gsv TIPC 202, returning a nx2:syncpt session");
@@ -1571,8 +1642,11 @@ private:
void CreateNativeImage(HLERequestContext& ctx) {
const auto* w = ctx.CommandBuffer();
std::scoped_lock lk{m_objects->mutex};
- const u32 id = m_objects->next_image++;
+ const u32 id = m_objects->next_image;
auto& image = m_objects->images[id];
+ image.count = std::max<u32>(w[18], 1);
+ // nn::gs::sdk::CalculateNativeImageHandle(pool, i) is pool + i, so keep those free.
+ m_objects->next_image += image.count;
image.memory = w[19];
std::copy_n(w + 2, image.preference.size(), image.preference.begin());
image.size = Nx2CalculateImageLayout(w + 2).size;
@@ -1581,6 +1655,75 @@ private:
TipcReplyWords(ctx, {id});
}
+ // 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.
+ void CalculateChatImagePoolSize(HLERequestContext& ctx) {
+ const auto* w = ctx.CommandBuffer();
+ const auto layout = Nx2CalculateImageLayout(w + 2);
+ const u64 size = layout.size * std::max<u32>(w[18], 1);
+ LOG_DEBUG(Service, "nx2:gsv 1291 {}x{} count={} -> size={:#x}", layout.width,
+ layout.height, w[18], size);
+ TipcReplyWords(ctx, {static_cast<u32>(size), static_cast<u32>(size >> 32),
+ static_cast<u32>(layout.alignment),
+ static_cast<u32>(layout.alignment >> 32)});
+ }
+
+ // 428(u32 native image handle) nn::gpu::sdk::QueryNativeImage and 426(u32 handle, u32, u32
+ // address space, u8 permission) nn::gpu::sdk::MapNativeImage, used by NVN for images another
+ // server created (GameChat's camera pool from 1211). Reply words 3-4 the GPU address of the
+ // image (426 only; GPU == CPU address, see Nx2NativeObjects::Memory), 5-6 the u64 offset of
+ // the image in its memory, 7-38 a GpuOunceUserImageDescriptor and 39 the flags. The SDK
+ // requires descriptor byte 0x00 == 1, byte 0x20 == 1 and a non-zero size at 0x2c (offset at
+ // 0x28); NVN reads (width - 1) | (height - 1) << 16 at 0x08 and the layout kind at 0x24 (1
+ // takes RGBA formats 0x25/0x37/0x38, 2 the 0x99-0x9c formats). Block height and pitch use
+ // the 429 preference words (0x10, 0x14), a guess.
+ void QueryNativeImage(HLERequestContext& ctx) {
+ const u32 handle = ctx.CommandBuffer()[2];
+ std::array<u32, 16> preference{};
+ u64 image_offset = 0;
+ u64 memory_address = 0;
+ {
+ std::scoped_lock lk{m_objects->mutex};
+ auto it = m_objects->images.upper_bound(handle);
+ if (it != m_objects->images.begin()) {
+ --it;
+ if (handle - it->first < it->second.count) {
+ preference = it->second.preference;
+ image_offset = (handle - it->first) * it->second.size;
+ if (const auto memory = m_objects->memories.find(it->second.memory);
+ memory != m_objects->memories.end()) {
+ memory_address = memory->second.address;
+ }
+ }
+ }
+ }
+ const auto layout = Nx2CalculateImageLayout(preference.data());
+ const u64 gpu_address = ctx.GetCommand() == 426 ? memory_address + image_offset : 0;
+ std::array<u32, 37> words{};
+ words[0] = static_cast<u32>(gpu_address);
+ words[1] = static_cast<u32>(gpu_address >> 32);
+ words[2] = static_cast<u32>(image_offset);
+ words[3] = static_cast<u32>(image_offset >> 32);
+ u32* const desc = words.data() + 4;
+ desc[0] = 1;
+ desc[1] = preference[1];
+ desc[2] = preference[2];
+ desc[4] = layout.block_height_log2;
+ desc[5] = static_cast<u32>(layout.pitch / 64);
+ desc[8] = 1;
+ desc[9] = 1;
+ desc[11] = static_cast<u32>(layout.size);
+ LOG_WARNING(Service, "(STUBBED) nx2:gsv {} handle={} {}x{} size={:#x} gpu={:#x}",
+ ctx.GetCommand(), handle, layout.width, layout.height, layout.size,
+ gpu_address);
+ IPC::ResponseBuilder rb{ctx, static_cast<u32>(2 + words.size())};
+ rb.PushRaw<u32>(ResultSuccess.raw);
+ for (const u32 w : words) {
+ rb.Push<u32>(w);
+ }
+ }
+
void DestroyNativeImage(HLERequestContext& ctx) {
const u32 id = ctx.CommandBuffer()[2];
std::scoped_lock lk{m_objects->mutex};