Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/libs/SceTypes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1288,7 +1288,7 @@ struct NetCtlIfStat {
std::uint32_t reserved[8];
};

using NetCtlCallback = void (*)(int, void*);
using NetCtlCallback = void (APS5_VABI *)(int, void*);

struct HttpEpoll;
using HttpEpollHandle = HttpEpoll*;
Expand Down
50 changes: 41 additions & 9 deletions core/libs/prx/libSceNetCtl/Export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ static constexpr int SCE_NET_CTL_ERROR_INVALID_ADDR = static_cast<int>(0x8041210
static constexpr int SCE_NET_CTL_ERROR_NOT_CONNECTED = static_cast<int>(0x80412108);
static constexpr int NET_CTL_STATE_DISCONNECTED = 0;
static constexpr int NET_CTL_STATE_IPOBTAINED = 3;
static constexpr int NET_CTL_EVENT_DISCONNECTED = 1;
static constexpr int NET_CTL_EVENT_IPOBTAINED = 3;
static constexpr int NET_CTL_INFO_DEVICE = 1;
static constexpr int NET_CTL_INFO_ETHER_ADDR = 2;
static constexpr int NET_CTL_INFO_MTU = 3;
Expand All @@ -61,30 +63,44 @@ static constexpr std::uint8_t NET_CTL_ETHER_ADDR[6] = {0x02, 0x50, 0x53, 0x35, 0
static constexpr std::size_t NET_CTL_IPV4_ADDR_STR_LEN = 16;
static constexpr int MAX_CALLBACKS = 8;

struct CallbackSlot {
NetCtlCallback func = nullptr;
void* arg = nullptr;
int lastEvent = 0;
};

static std::mutex g_callbackLock;
static NetCtlCallback g_callbacks[MAX_CALLBACKS] = {};
static NetCtlCallback g_callbacksV6[MAX_CALLBACKS] = {};
static CallbackSlot g_callbacks[MAX_CALLBACKS] = {};
static CallbackSlot g_callbacksV6[MAX_CALLBACKS] = {};

static int RegisterCallback(NetCtlCallback (&callbacks)[MAX_CALLBACKS], NetCtlCallback func, int* cid) {
static int RegisterCallback(CallbackSlot (&callbacks)[MAX_CALLBACKS], NetCtlCallback func, void* arg, int* cid) {
if (!func || !cid) return SCE_NET_CTL_ERROR_INVALID_ADDR;
std::lock_guard lock(g_callbackLock);
for (int index = 0; index < MAX_CALLBACKS; ++index) {
if (!callbacks[index]) {
callbacks[index] = func;
if (!callbacks[index].func) {
callbacks[index] = {func, arg, 0};
*cid = index;
return 0;
}
}
return SCE_NET_CTL_ERROR_CALLBACK_MAX;
}

static int UnregisterCallback(NetCtlCallback (&callbacks)[MAX_CALLBACKS], int cid) {
static int UnregisterCallback(CallbackSlot (&callbacks)[MAX_CALLBACKS], int cid) {
if (cid < 0 || cid >= MAX_CALLBACKS) return SCE_NET_CTL_ERROR_INVALID_ID;
std::lock_guard lock(g_callbackLock);
callbacks[cid] = nullptr;
callbacks[cid] = {};
return 0;
}

static bool HasIpv4Callback() {
std::lock_guard lock(g_callbackLock);
for (const auto& slot : g_callbacks) {
if (slot.func) return true;
}
return false;
}

static bool HostHasAddress(int family) {
#ifdef _WIN32
static const int socket_status = [] {
Expand Down Expand Up @@ -324,6 +340,19 @@ static void HostTraffic(std::uint64_t& sent, std::uint64_t& received) {
extern "C" {

int APS5_VABI sceNetCtlCheckCallback(void) {
if (!HasIpv4Callback()) 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) {
if (!slot.func || slot.lastEvent == event) continue;
slot.lastEvent = event;
due[count++] = slot;
}
}
for (int index = 0; index < count; ++index) due[index].func(event, due[index].arg);
return 0;
}

Expand Down Expand Up @@ -390,14 +419,17 @@ int APS5_VABI sceNetCtlInit(void) {
}

int APS5_VABI sceNetCtlRegisterCallback(NetCtlCallback func, void* arg, int* cid) {
return RegisterCallback(g_callbacks, func, cid);
return RegisterCallback(g_callbacks, func, arg, cid);
}

int APS5_VABI sceNetCtlRegisterCallbackV6(NetCtlCallback func, void* arg, int* cid) {
return RegisterCallback(g_callbacksV6, func, cid);
return RegisterCallback(g_callbacksV6, func, arg, cid);
}

void APS5_VABI sceNetCtlTerm(void) {
std::lock_guard lock(g_callbackLock);
for (auto& slot : g_callbacks) slot = {};
for (auto& slot : g_callbacksV6) slot = {};
}

int APS5_VABI sceNetCtlUnregisterCallback(int cid) {
Expand Down
99 changes: 99 additions & 0 deletions core/libs/tests/GuestNetCtl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@
#include <stdexcept>

extern "C" {
int APS5_VABI sceNetCtlCheckCallback(void);
int APS5_VABI sceNetCtlGetInfo(int, NetCtlInfo*);
int APS5_VABI sceNetCtlGetNatInfo(NetCtlNatInfo*);
int APS5_VABI sceNetCtlGetState(int*);
int APS5_VABI sceNetCtlRegisterCallback(NetCtlCallback, void*, int*);
int APS5_VABI sceNetCtlRegisterCallbackV6(NetCtlCallback, void*, int*);
void APS5_VABI sceNetCtlTerm(void);
int APS5_VABI sceNetCtlUnregisterCallback(int);
int APS5_VABI sceNetCtlUnregisterCallbackV6(int);
}

namespace {
Expand All @@ -32,9 +38,100 @@ constexpr int kErrInvalidCode = static_cast<int>(0x80412106);
constexpr int kErrInvalidAddr = static_cast<int>(0x80412107);
constexpr int kErrNotConnected = static_cast<int>(0x80412108);
constexpr int kStateIpObtained = 3;
constexpr int kEventDisconnected = 1;
constexpr int kEventIpObtained = 3;
constexpr std::uint8_t kFill = 0xa5;
constexpr std::size_t kAddressLength = 16;

struct Delivery {
int calls = 0;
int event = 0;
void* arg = nullptr;
};

Delivery g_first;
Delivery g_second;
Delivery g_v6;
Delivery g_self;
int g_selfUnregister = -1;

void APS5_VABI RecordFirst(int event, void* arg) {
++g_first.calls;
g_first.event = event;
g_first.arg = arg;
}

void APS5_VABI RecordSecond(int event, void* arg) {
++g_second.calls;
g_second.event = event;
g_second.arg = arg;
}

void APS5_VABI RecordV6(int event, void* arg) {
++g_v6.calls;
g_v6.event = event;
g_v6.arg = arg;
}

void APS5_VABI UnregisterSelf(int event, void* arg) {
++g_self.calls;
g_self.event = event;
g_self.arg = arg;
g_selfUnregister = sceNetCtlUnregisterCallback(*static_cast<int*>(arg));
}

void CheckCallbacks() {
int state = -1;
Require(sceNetCtlGetState(&state) == 0);
const int event = state == kStateIpObtained ? kEventIpObtained : kEventDisconnected;
int firstProbe = 0;
int secondProbe = 0;
int v6Probe = 0;

int v6 = -1;
Require(sceNetCtlRegisterCallbackV6(RecordV6, &v6Probe, &v6) == 0 && v6 == 0);

int first = -1;
Require(sceNetCtlRegisterCallback(RecordFirst, &firstProbe, &first) == 0 && first == 0);
Require(sceNetCtlCheckCallback() == 0);
Require(g_first.calls == 1 && g_first.event == event && g_first.arg == &firstProbe);
Require(sceNetCtlCheckCallback() == 0);
Require(g_first.calls == 1);

int second = -1;
Require(sceNetCtlRegisterCallback(RecordSecond, &secondProbe, &second) == 0 && second == 1);
Require(sceNetCtlCheckCallback() == 0);
Require(g_first.calls == 1);
Require(g_second.calls == 1 && g_second.event == event && g_second.arg == &secondProbe);

Require(sceNetCtlUnregisterCallback(second) == 0);
Require(sceNetCtlRegisterCallback(RecordSecond, &secondProbe, &second) == 0 && second == 1);
Require(sceNetCtlUnregisterCallback(second) == 0);
Require(sceNetCtlCheckCallback() == 0);
Require(g_first.calls == 1 && g_second.calls == 1);

int self = -1;
Require(sceNetCtlRegisterCallback(UnregisterSelf, &self, &self) == 0 && self == 1);
Require(sceNetCtlCheckCallback() == 0);
Require(g_self.calls == 1 && g_self.event == event && g_self.arg == &self && g_selfUnregister == 0);
Require(g_first.calls == 1 && g_second.calls == 1);
Require(sceNetCtlCheckCallback() == 0);
Require(g_self.calls == 1);

Require(sceNetCtlRegisterCallback(RecordSecond, &secondProbe, &second) == 0 && second == 1);
sceNetCtlTerm();
Require(sceNetCtlCheckCallback() == 0);
Require(g_first.calls == 1 && g_second.calls == 1);

Require(sceNetCtlRegisterCallbackV6(RecordV6, &v6Probe, &v6) == 0 && v6 == 0);
Require(sceNetCtlRegisterCallback(RecordFirst, &secondProbe, &first) == 0 && first == 0);
Require(sceNetCtlCheckCallback() == 0);
Require(g_first.calls == 2 && g_first.event == event && g_first.arg == &secondProbe);
Require(g_second.calls == 1 && g_self.calls == 1 && g_v6.calls == 0);
Require(sceNetCtlUnregisterCallback(first) == 0);
Require(sceNetCtlUnregisterCallbackV6(v6) == 0);
}

NetCtlInfo Query(int code) {
NetCtlInfo info;
std::memset(&info, kFill, sizeof(info));
Expand Down Expand Up @@ -90,6 +187,8 @@ int main() {
WSADATA data{};
Require(WSAStartup(MAKEWORD(2, 2), &data) == 0);
#endif
CheckCallbacks();

NetCtlInfo info{};
Require(sceNetCtlGetInfo(0, &info) == kErrInvalidCode);
Require(sceNetCtlGetInfo(22, &info) == kErrInvalidCode);
Expand Down
11 changes: 6 additions & 5 deletions core/libs/tests/GuestSceNet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ static void Require(bool condition) {
if (!condition) std::abort();
}

static void APS5_VABI IgnoreNetCtlEvent(int, void*) {}

static bool Failed(std::int64_t result, int error) {
return result == static_cast<int>(0x80410100u | static_cast<unsigned>(error)) && *sceNetErrnoLoc() == error;
}
Expand Down Expand Up @@ -1428,20 +1430,19 @@ int main() {
Require(second.tx_bytes >= first.tx_bytes && second.rx_bytes >= first.rx_bytes);
}
{
const auto dummyCallback = [](int, void*) {};
int cid = -1;
Require(sceNetCtlRegisterCallbackV6(nullptr, nullptr, &cid) == static_cast<int>(0x80412107));
Require(sceNetCtlRegisterCallbackV6(dummyCallback, nullptr, nullptr) == static_cast<int>(0x80412107));
Require(sceNetCtlRegisterCallbackV6(IgnoreNetCtlEvent, nullptr, nullptr) == static_cast<int>(0x80412107));
int cids[8] = {};
for (int i = 0; i < 8; ++i) {
Require(sceNetCtlRegisterCallbackV6(dummyCallback, nullptr, &cids[i]) == 0);
Require(sceNetCtlRegisterCallbackV6(IgnoreNetCtlEvent, nullptr, &cids[i]) == 0);
Require(cids[i] == i);
}
Require(sceNetCtlRegisterCallbackV6(dummyCallback, nullptr, &cid) == static_cast<int>(0x80412103));
Require(sceNetCtlRegisterCallbackV6(IgnoreNetCtlEvent, nullptr, &cid) == static_cast<int>(0x80412103));
Require(sceNetCtlUnregisterCallbackV6(-1) == static_cast<int>(0x80412105));
Require(sceNetCtlUnregisterCallbackV6(8) == static_cast<int>(0x80412105));
Require(sceNetCtlUnregisterCallbackV6(3) == 0);
Require(sceNetCtlRegisterCallbackV6(dummyCallback, nullptr, &cid) == 0);
Require(sceNetCtlRegisterCallbackV6(IgnoreNetCtlEvent, nullptr, &cid) == 0);
Require(cid == 3);
for (int i = 0; i < 8; ++i) {
Require(sceNetCtlUnregisterCallbackV6(i) == 0);
Expand Down
4 changes: 4 additions & 0 deletions docs/dev/debt/libSceNetCtl.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@
### Unknown function info

- [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) and the HTTP proxy server (20) are empty strings, as that configuration sets none of them; the HTTP proxy port (21) is 0; the Wi-Fi codes (5-10) throw

### Functional

- [sceNetCtlCheckCallback](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - the state comes from the host interfaces, as in `sceNetCtlGetState`: `IPOBTAINED` (3) while the host has a non-loopback IPv4 or IPv6 address, `DISCONNECTED` (1) otherwise; the event values follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/network/net_ctl_codes.h#L27-L29), and no other event is delivered. Each callback registered with `sceNetCtlRegisterCallback` is called on the checking thread with its registration argument when the state differs from the last one it received, so a new registration receives the current state at the next check. This edge-triggered model follows [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/9e41d8320a2774b303fbad344c2347e8d0292e83/src/libs/network.cpp#L3648-L3661); [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/network/net_ctl_obj.cpp#L47-L57) delivers the state on every check instead, and neither is measured on hardware. KytyPS5 calls the callbacks under its lock; here they are called after the lock is released, from a copy of their slots, so a `sceNetCtlUnregisterCallback` or `sceNetCtlTerm` on another thread that returns while a check is in flight does not stop that check from calling the callback once more with its old argument. Callbacks registered with `sceNetCtlRegisterCallbackV6` keep their argument but receive no event, because no source says which call delivers them
Loading