diff --git a/core/libs/prx/libSceNetCtl/Export.cpp b/core/libs/prx/libSceNetCtl/Export.cpp index 3a31755e82..c8b0e482f3 100644 --- a/core/libs/prx/libSceNetCtl/Export.cpp +++ b/core/libs/prx/libSceNetCtl/Export.cpp @@ -99,6 +99,7 @@ static std::atomic_bool g_initialized{true}; 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 (!g_initialized.load()) return SCE_NET_CTL_ERROR_NOT_INITIALIZED; @@ -123,9 +124,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; @@ -512,17 +513,15 @@ static void HostTraffic(std::uint64_t& sent, std::uint64_t& received) { #endif } -extern "C" { - -int APS5_VABI sceNetCtlCheckCallback(void) { +static int CheckCallbacks(CallbackSlot (&callbacks)[MAX_CALLBACKS]) { if (!g_initialized.load()) return SCE_NET_CTL_ERROR_NOT_INITIALIZED; - if (!HasIpv4Callback()) return 0; + 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; @@ -532,6 +531,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 (!g_initialized.load()) return SCE_NET_CTL_ERROR_NOT_INITIALIZED; if (!stat) return SCE_NET_CTL_ERROR_INVALID_ADDR; @@ -637,8 +642,7 @@ int APS5_VABI sceNetCtlUnregisterCallbackV6(int cid) { } int APS5_VABI sceNetCtlRegisterCallbackForNpToolkit(NetCtlCallback callback, void* argument, int* callbackId) { - NotImplemented_nid_no_patch(__func__); - return 0; + return RegisterCallback(g_callbacksNpToolkit, callback, argument, callbackId); } int APS5_VABI sceNetCtlRegisterCallbackIpcInt(NetCtlCallback callback, void* argument, int* callbackId) { @@ -652,8 +656,7 @@ int APS5_VABI sceNetCtlRegisterCallbackForLibIpcInt(NetCtlCallback callback, voi } int APS5_VABI sceNetCtlUnregisterCallbackForNpToolkit(int callbackId) { - NotImplemented_nid_no_patch(__func__); - return 0; + return UnregisterCallback(g_callbacksNpToolkit, callbackId); } int APS5_VABI sceNetCtlUnregisterCallbackIpcInt(int callbackId) { @@ -667,8 +670,7 @@ int APS5_VABI sceNetCtlUnregisterCallbackForLibIpcInt(int callbackId) { } 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/GuestNetCtl.cpp b/core/libs/tests/GuestNetCtl.cpp index facc12ccf6..9b900f4bd4 100644 --- a/core/libs/tests/GuestNetCtl.cpp +++ b/core/libs/tests/GuestNetCtl.cpp @@ -51,6 +51,9 @@ int APS5_VABI sceNetCtlRegisterCallbackV6(NetCtlCallback, void*, int*); void APS5_VABI sceNetCtlTerm(void); int APS5_VABI sceNetCtlUnregisterCallback(int); int APS5_VABI sceNetCtlUnregisterCallbackV6(int); +int APS5_VABI sceNetCtlRegisterCallbackForNpToolkit(NetCtlCallback, void*, int*); +int APS5_VABI sceNetCtlUnregisterCallbackForNpToolkit(int); +int APS5_VABI sceNetCtlCheckCallbackForNpToolkit(void); } namespace { @@ -283,6 +286,9 @@ int main() { Require(sceNetCtlRegisterCallbackV6(nullptr, nullptr, nullptr) == kErrInvalidAddr); Require(sceNetCtlUnregisterCallback(-1) == invalidId); Require(sceNetCtlUnregisterCallbackV6(-1) == invalidId); + Require(sceNetCtlRegisterCallbackForNpToolkit(nullptr, nullptr, nullptr) == kErrInvalidAddr); + Require(sceNetCtlUnregisterCallbackForNpToolkit(-1) == invalidId); + Require(sceNetCtlCheckCallbackForNpToolkit() == 0); CheckCallbacks(); Require(sceNetCtlGetStateV6(nullptr) == kErrInvalidAddr); diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 7a6be117e6..4e5f27cab2 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -91,6 +91,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); @@ -171,6 +175,19 @@ public: 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; } @@ -2400,6 +2417,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 == 3 ? 3 : 1)); + 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 e0704a1cc7..add05f78df 100644 --- a/docs/dev/debt/libSceNetCtl.md +++ b/docs/dev/debt/libSceNetCtl.md @@ -12,6 +12,7 @@ - [sceNetCtlGetInfoV6](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl, NID `Jy1EO5GdlcM`) - imported by PPSA08587. `(int code, SceNetCtlInfoV6* info)` with codes 1-5 (IP address, link-local address, default route, primary and secondary DNS) comes only from [SharpProspero](https://github.com/SvenGDK/SharpProspero), whose source is unknown; shadPS4 and fpPS4 only stub it. A null `info` answers `INVALID_ADDR`, a code outside 1-5 `INVALID_CODE`, and a host without an IPv6 address `NOT_CONNECTED`, as `sceNetCtlGetStateV6` reports it. The layout of each answer is unverified, so every code throws on a host with an IPv6 address - [sceNetCtlGetResult](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - `(int eventType, int* errorCode)` reports no error: event types 1-3 (disconnected to IP obtained, the numbering `sceNetCtlCheckCallback` delivers) write 0 to `errorCode`, as `sceNetCtlGetResultV6` does. The error code a console reports for each event, a disconnection's cause in particular, is unconfirmed. Callers found: PPSA20052 (Worms Armageddon: Anniversary Edition 01.003.000) calls it 2 s into boot, and PPSA24191 (Monster Hunter Wilds) without a network, with event 1 (disconnected) as its first call. A null `errorCode` answers `INVALID_ADDR` and other event types throw, as their error code on a console is unknown - [sceNetCtlGetResultV6](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl, NID `H5yARg37U5g`) - `(int eventType, int* errorCode)` comes only from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/NetCtl.cs#L246-L251), whose source is unknown; shadPS4 only stubs it. Callbacks registered with `sceNetCtlRegisterCallbackV6` receive no event, so event types 1-3 write 0 to `errorCode`; what a console writes before the first event is unmeasured. A null `errorCode` answers `INVALID_ADDR`, as `sceNetCtlGetResult` does, and other event types throw, as their error code on a console is unknown +- [sceNetCtlRegisterCallbackForNpToolkit, sceNetCtlUnregisterCallbackForNpToolkit, sceNetCtlCheckCallbackForNpToolkit](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - a table of eight callbacks apart from the IPv4 and IPv6 ones, with the `(int eventType, void* arg)` callback, follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/net_ctl_obj.h), whose NpToolkit check delivers the same events to that table. The three share the register, unregister and dispatch of `sceNetCtlRegisterCallback`, `sceNetCtlUnregisterCallback` and `sceNetCtlCheckCallback`, so they return the same initialization, address, slot-limit and ID errors, and the check reports the state `sceNetCtlCheckCallback` does. Whether the console keeps this table apart, and what `sceNetCtlCheckCallback` does with it, is unverified on hardware - [ApRp exports](../../../core/libs/prx/libSceNetCtl/Export.cpp) are constant-success functions in the supplied native module and consume no arguments. No callers were found in the supplied system-library decompilations, so unused callback, output and configuration argument types remain unresolved. Protected stub bodies are unchanged. ### Functional