From de2fd5bf6a2f83f2a2521a5df31e125472dc8d80 Mon Sep 17 00:00:00 2001 From: Chikimaro <49518807+Chikimaro@users.noreply.github.com> Date: Tue, 20 Oct 2026 12:10:58 +0000 Subject: [PATCH 1/2] feat(libSceNetCtl): implement the NpToolkit callback register, unregister and check sceNetCtlRegisterCallbackForNpToolkit, sceNetCtlUnregisterCallbackForNpToolkit and sceNetCtlCheckCallbackForNpToolkit keep their own table of eight callbacks next to the IPv4 and IPv6 ones and share the existing register, unregister and event delivery, which sceNetCtlCheckCallback now also goes through. sceNetCtlTerm clears the new table. The register signature and the separate table follow shadPS4; the rest follows the IPv4 functions and is recorded as unverified in the libSceNetCtl technical debt. --- core/libs/prx/libSceNetCtl/Export.cpp | 33 +++++++++------- core/libs/tests/GuestSceNet.cpp | 57 +++++++++++++++++++++++++++ docs/dev/debt/libSceNetCtl.md | 4 +- 3 files changed, 76 insertions(+), 18 deletions(-) diff --git a/core/libs/prx/libSceNetCtl/Export.cpp b/core/libs/prx/libSceNetCtl/Export.cpp index 1e11aadff5..d7e2303309 100644 --- a/core/libs/prx/libSceNetCtl/Export.cpp +++ b/core/libs/prx/libSceNetCtl/Export.cpp @@ -81,6 +81,7 @@ struct CallbackSlot { static std::mutex g_callbackLock; static CallbackSlot g_callbacks[MAX_CALLBACKS] = {}; static CallbackSlot g_callbacksV6[MAX_CALLBACKS] = {}; +static CallbackSlot g_callbacksNpToolkit[MAX_CALLBACKS] = {}; static int RegisterCallback(CallbackSlot (&callbacks)[MAX_CALLBACKS], NetCtlCallback func, void* arg, int* cid) { if (!func || !cid) return SCE_NET_CTL_ERROR_INVALID_ADDR; @@ -102,9 +103,9 @@ static int UnregisterCallback(CallbackSlot (&callbacks)[MAX_CALLBACKS], int cid) return 0; } -static bool HasIpv4Callback() { +static bool HasCallback(const CallbackSlot (&callbacks)[MAX_CALLBACKS]) { std::lock_guard lock(g_callbackLock); - for (const auto& slot : g_callbacks) { + for (const auto& slot : callbacks) { if (slot.func) return true; } return false; @@ -397,16 +398,14 @@ static void HostTraffic(std::uint64_t& sent, std::uint64_t& received) { #endif } -extern "C" { - -int APS5_VABI sceNetCtlCheckCallback(void) { - if (!HasIpv4Callback()) return 0; +static int CheckCallbacks(CallbackSlot (&callbacks)[MAX_CALLBACKS]) { + if (!HasCallback(callbacks)) return 0; const int event = HostHasAddress(AF_UNSPEC) ? NET_CTL_EVENT_IPOBTAINED : NET_CTL_EVENT_DISCONNECTED; CallbackSlot due[MAX_CALLBACKS]; int count = 0; { std::lock_guard lock(g_callbackLock); - for (auto& slot : g_callbacks) { + for (auto& slot : callbacks) { if (!slot.func || slot.lastEvent == event) continue; slot.lastEvent = event; due[count++] = slot; @@ -416,6 +415,12 @@ int APS5_VABI sceNetCtlCheckCallback(void) { return 0; } +extern "C" { + +int APS5_VABI sceNetCtlCheckCallback(void) { + return CheckCallbacks(g_callbacks); +} + int APS5_VABI sceNetCtlGetIfStat(NetCtlIfStat* stat) { if (!stat) return SCE_NET_CTL_ERROR_INVALID_ADDR; if (!HostHasAddress(AF_UNSPEC)) return SCE_NET_CTL_ERROR_NOT_CONNECTED; @@ -501,6 +506,7 @@ void APS5_VABI sceNetCtlTerm(void) { std::lock_guard lock(g_callbackLock); for (auto& slot : g_callbacks) slot = {}; for (auto& slot : g_callbacksV6) slot = {}; + for (auto& slot : g_callbacksNpToolkit) slot = {}; } int APS5_VABI sceNetCtlUnregisterCallback(int cid) { @@ -511,9 +517,8 @@ int APS5_VABI sceNetCtlUnregisterCallbackV6(int cid) { return UnregisterCallback(g_callbacksV6, cid); } -int APS5_VABI sceNetCtlRegisterCallbackForNpToolkit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNetCtlRegisterCallbackForNpToolkit(NetCtlCallback func, void* arg, int* cid) { + return RegisterCallback(g_callbacksNpToolkit, func, arg, cid); } int APS5_VABI sceNetCtlRegisterCallbackIpcInt(void) { @@ -526,9 +531,8 @@ int APS5_VABI sceNetCtlRegisterCallbackForLibIpcInt(void) { return 0; } -int APS5_VABI sceNetCtlUnregisterCallbackForNpToolkit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNetCtlUnregisterCallbackForNpToolkit(int cid) { + return UnregisterCallback(g_callbacksNpToolkit, cid); } int APS5_VABI sceNetCtlUnregisterCallbackIpcInt(void) { @@ -542,8 +546,7 @@ int APS5_VABI sceNetCtlUnregisterCallbackForLibIpcInt(void) { } int APS5_VABI sceNetCtlCheckCallbackForNpToolkit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return CheckCallbacks(g_callbacksNpToolkit); } int APS5_VABI sceNetCtlCheckCallbackForLibIpcInt(void) { diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 6e2b621d33..679051bfa4 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -78,6 +78,10 @@ int APS5_VABI sceNetCtlRegisterCallback(NetCtlCallback, void*, int*); int APS5_VABI sceNetCtlUnregisterCallback(int); int APS5_VABI sceNetCtlRegisterCallbackV6(NetCtlCallback, void*, int*); int APS5_VABI sceNetCtlUnregisterCallbackV6(int); +int APS5_VABI sceNetCtlCheckCallback(void); +int APS5_VABI sceNetCtlRegisterCallbackForNpToolkit(NetCtlCallback, void*, int*); +int APS5_VABI sceNetCtlUnregisterCallbackForNpToolkit(int); +int APS5_VABI sceNetCtlCheckCallbackForNpToolkit(void); std::uint64_t NetCtlReadByteCounter_nid_no_patch(const char*); int APS5_VABI select_nid_postfix(int, void*, void*, void*, const void*); int APS5_VABI open_nid_postfix(const char*, int, int); @@ -141,6 +145,19 @@ static void Require(bool condition) { static void APS5_VABI IgnoreNetCtlEvent(int, void*) {} +struct NetCtlEventRecord { + int calls = 0; + int event = 0; + void* arg = nullptr; +}; + +static void APS5_VABI RecordNetCtlEvent(int event, void* arg) { + auto* record = static_cast(arg); + ++record->calls; + record->event = event; + record->arg = arg; +} + static bool Failed(std::int64_t result, int error) { return result == static_cast(0x80410100u | static_cast(error)) && *sceNetErrnoLoc() == error; } @@ -1907,6 +1924,46 @@ int main() { } Require(sceNetCtlUnregisterCallbackV6(0) == 0); } + { + int cid = -1; + Require(sceNetCtlRegisterCallbackForNpToolkit(nullptr, nullptr, &cid) == static_cast(0x80412107)); + Require(sceNetCtlRegisterCallbackForNpToolkit(IgnoreNetCtlEvent, nullptr, nullptr) == static_cast(0x80412107)); + int cids[8] = {}; + for (int i = 0; i < 8; ++i) { + Require(sceNetCtlRegisterCallbackForNpToolkit(IgnoreNetCtlEvent, nullptr, &cids[i]) == 0); + Require(cids[i] == i); + } + Require(sceNetCtlRegisterCallbackForNpToolkit(IgnoreNetCtlEvent, nullptr, &cid) == static_cast(0x80412103)); + Require(sceNetCtlRegisterCallback(IgnoreNetCtlEvent, nullptr, &cid) == 0 && cid == 0); + Require(sceNetCtlUnregisterCallback(0) == 0); + Require(sceNetCtlUnregisterCallbackForNpToolkit(-1) == static_cast(0x80412105)); + Require(sceNetCtlUnregisterCallbackForNpToolkit(8) == static_cast(0x80412105)); + Require(sceNetCtlUnregisterCallbackForNpToolkit(3) == 0); + Require(sceNetCtlRegisterCallbackForNpToolkit(IgnoreNetCtlEvent, nullptr, &cid) == 0); + Require(cid == 3); + for (int i = 0; i < 8; ++i) { + Require(sceNetCtlUnregisterCallbackForNpToolkit(i) == 0); + } + + NetCtlEventRecord toolkit; + NetCtlEventRecord plain; + Require(sceNetCtlRegisterCallbackForNpToolkit(RecordNetCtlEvent, &toolkit, &cid) == 0 && cid == 0); + Require(sceNetCtlCheckCallback() == 0 && toolkit.calls == 0); + Require(sceNetCtlCheckCallbackForNpToolkit() == 0); + Require(toolkit.calls == 1 && toolkit.arg == &toolkit && (toolkit.event == 1 || toolkit.event == 3)); + int current = -1; + Require(sceNetCtlGetState(¤t) == 0 && toolkit.event == current); + Require(sceNetCtlCheckCallbackForNpToolkit() == 0 && toolkit.calls == 1); + int plainCid = -1; + Require(sceNetCtlRegisterCallback(RecordNetCtlEvent, &plain, &plainCid) == 0); + Require(sceNetCtlCheckCallbackForNpToolkit() == 0 && plain.calls == 0); + Require(sceNetCtlCheckCallback() == 0 && plain.calls == 1 && toolkit.calls == 1); + Require(sceNetCtlUnregisterCallback(plainCid) == 0); + Require(sceNetCtlUnregisterCallbackForNpToolkit(cid) == 0); + Require(sceNetCtlRegisterCallbackForNpToolkit(RecordNetCtlEvent, &toolkit, &cid) == 0); + Require(sceNetCtlCheckCallbackForNpToolkit() == 0 && toolkit.calls == 2); + Require(sceNetCtlUnregisterCallbackForNpToolkit(cid) == 0); + } if (ipv6Unavailable) { std::puts("skipped the IPv6 socket checks, the host has no usable IPv6 loopback"); return 77; diff --git a/docs/dev/debt/libSceNetCtl.md b/docs/dev/debt/libSceNetCtl.md index 99aa976623..dc6c8e8fbb 100644 --- a/docs/dev/debt/libSceNetCtl.md +++ b/docs/dev/debt/libSceNetCtl.md @@ -8,13 +8,11 @@ - [sceNetCtlGetInfoV6](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl, NID `Jy1EO5GdlcM`) - imported by PPSA08587, which fails to load without it, and not called before its loading screen; declared without parameters as a throwing stub, signature unknown. Name from the aerolib NID list - [sceNetCtlGetInfo](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - answers from the host interface that holds an IPv4 address, preferring the one that carries the default route: its address, netmask, gateway, MTU and first two IPv4 DNS servers. The device is reported as wired, the link as connected, the IP configuration as DHCP and the HTTP proxy as off, whatever the host uses, and the Ethernet address is the one `sceNetGetMacAddress` reports. A gateway or DNS server the interface does not have is reported as `0.0.0.0`; what a console reports then is unknown. The DHCP host name (12), the PPPoE user name (13), the HTTP proxy server (20) and the Wi-Fi BSSID and SSID (5, 6) are empty strings, as that configuration sets none of them; the remaining Wi-Fi codes (7-10) and the HTTP proxy port (21) report 0; this follows [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/network.cpp) and is not measured on hardware -- [sceNetCtlRegisterCallbackForNpToolkit](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature +- [sceNetCtlRegisterCallbackForNpToolkit, sceNetCtlUnregisterCallbackForNpToolkit, sceNetCtlCheckCallbackForNpToolkit](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - the `(callback, arg, int* cid)` signature of the register, the `(int eventType, void* arg)` callback and a table of eight callbacks apart from the IPv4 and IPv6 ones follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/net_ctl_obj.h), whose check delivers the same events to that table. The `(int cid)` unregister and the `()` check follow `sceNetCtlUnregisterCallback` and `sceNetCtlCheckCallback`, which shadPS4 leaves as stubs for this table; the three share those functions' errors and the check reports the state `sceNetCtlCheckCallback` does. Unverified on hardware - [sceNetCtlRegisterCallbackIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature - [sceNetCtlRegisterCallbackForLibIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlUnregisterCallbackForNpToolkit](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature - [sceNetCtlUnregisterCallbackIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature - [sceNetCtlUnregisterCallbackForLibIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature -- [sceNetCtlCheckCallbackForNpToolkit](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature - [sceNetCtlCheckCallbackForLibIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature - [sceNetCtlClearEventIpcInt](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature - [sceNetCtlClearEventForNpToolkit](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - unknown signature From d7f45911553c754c75a91b35d7b717ead7cba3fa Mon Sep 17 00:00:00 2001 From: Chikimaro <49518807+Chikimaro@users.noreply.github.com> Date: Wed, 21 Oct 2026 11:32:12 +0000 Subject: [PATCH 2/2] test(libSceNetCtl): compare the NpToolkit callback's event with the event of the current state sceNetCtlGetState reports DISCONNECTED as 0, while the callback receives the DISCONNECTED event, 1, so the check aborted on a host with no non-loopback address. Convert the state to its event before comparing. --- core/libs/tests/GuestSceNet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 679051bfa4..4b7afdb59f 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -1952,7 +1952,7 @@ int main() { Require(sceNetCtlCheckCallbackForNpToolkit() == 0); Require(toolkit.calls == 1 && toolkit.arg == &toolkit && (toolkit.event == 1 || toolkit.event == 3)); int current = -1; - Require(sceNetCtlGetState(¤t) == 0 && toolkit.event == current); + Require(sceNetCtlGetState(¤t) == 0 && toolkit.event == (current == 3 ? 3 : 1)); Require(sceNetCtlCheckCallbackForNpToolkit() == 0 && toolkit.calls == 1); int plainCid = -1; Require(sceNetCtlRegisterCallback(RecordNetCtlEvent, &plain, &plainCid) == 0);