diff --git a/core/libs/SceTypes.hpp b/core/libs/SceTypes.hpp index 5a9df3031..3f7323294 100644 --- a/core/libs/SceTypes.hpp +++ b/core/libs/SceTypes.hpp @@ -1265,6 +1265,13 @@ struct NetCtlNatInfo { std::uint8_t opaque[128]; }; union NetCtlInfo { std::uint8_t opaque[256]; }; +struct NetCtlIfStat { + std::uint32_t device; + std::uint64_t tx_bytes; + std::uint64_t rx_bytes; + std::uint32_t reserved[8]; +}; + using NetCtlCallback = void (*)(int, void*); struct HttpEpoll; diff --git a/core/libs/prx/libSceNetCtl/Export.cpp b/core/libs/prx/libSceNetCtl/Export.cpp index 4bae50760..5f0ab11e2 100644 --- a/core/libs/prx/libSceNetCtl/Export.cpp +++ b/core/libs/prx/libSceNetCtl/Export.cpp @@ -7,12 +7,18 @@ #include #include #include +#ifdef __linux__ +#include +#endif #endif #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include #include +#include +#include #ifdef _WIN32 #include #endif @@ -78,12 +84,56 @@ static bool HostHasAddress(int family) { #endif } +extern "C" std::uint64_t NetCtlReadByteCounter_nid_no_patch(const char* path) { + std::ifstream file(path); + std::uint64_t value = 0; + if (!(file >> value)) throw std::runtime_error(std::string("sceNetCtlGetIfStat: no byte counter in ") + path); + return value; +} + +static void HostTraffic(std::uint64_t& sent, std::uint64_t& received) { + sent = 0; + received = 0; +#ifdef _WIN32 + MIB_IF_TABLE2* table = nullptr; + if (GetIfTable2(&table) != NO_ERROR) throw std::runtime_error("sceNetCtlGetIfStat: the host interface table is unavailable"); + for (ULONG index = 0; index < table->NumEntries; ++index) { + const auto& row = table->Table[index]; + if (row.OperStatus != IfOperStatusUp || row.Type == IF_TYPE_SOFTWARE_LOOPBACK || !row.InterfaceAndOperStatusFlags.HardwareInterface) continue; + sent += row.OutOctets; + received += row.InOctets; + } + FreeMibTable(table); +#elif defined(__linux__) + ifaddrs* interfaces = nullptr; + if (getifaddrs(&interfaces) != 0) throw std::runtime_error("sceNetCtlGetIfStat: the host interface list is unavailable"); + for (auto* item = interfaces; item; item = item->ifa_next) { + if (!item->ifa_addr || item->ifa_addr->sa_family != AF_PACKET) continue; + if (!(item->ifa_flags & IFF_UP) || (item->ifa_flags & IFF_LOOPBACK)) continue; + const std::string statistics = std::string("/sys/class/net/") + item->ifa_name + "/statistics/"; + sent += NetCtlReadByteCounter_nid_no_patch((statistics + "tx_bytes").c_str()); + received += NetCtlReadByteCounter_nid_no_patch((statistics + "rx_bytes").c_str()); + } + freeifaddrs(interfaces); +#else + throw std::runtime_error("sceNetCtlGetIfStat: host interface byte counters are not implemented on this platform"); +#endif +} + extern "C" { int APS5_VABI sceNetCtlCheckCallback(void) { return 0; } +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; + *stat = {}; + HostTraffic(stat->tx_bytes, stat->rx_bytes); + return 0; +} + int APS5_VABI sceNetCtlGetInfo(int code, NetCtlInfo* info) { (void)code; (void)info; diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 96d7ded24..4064c1184 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -57,6 +57,8 @@ int APS5_VABI sceNetResolverAbort(int, int); int APS5_VABI sceNetResolverDestroy(int); int APS5_VABI sceNetResolverGetError(int, int*); int APS5_VABI sceNetCtlGetState(int*); +int APS5_VABI sceNetCtlGetIfStat(NetCtlIfStat*); +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); int APS5_VABI socket_nid_postfix(int, int, int); @@ -777,6 +779,34 @@ int main() { int state = -1; Require(sceNetCtlGetState(&state) == 0); Require(state == 0 || state == 3); + Require(sceNetCtlGetIfStat(nullptr) == static_cast(0x80412107)); + { + const char* counterPath = "guest_sce_net_byte_counter.txt"; + std::FILE* counter = std::fopen(counterPath, "w"); + Require(counter != nullptr); + Require(std::fputs("1099511627776", counter) >= 0); + Require(std::fclose(counter) == 0); + Require(NetCtlReadByteCounter_nid_no_patch(counterPath) == 1099511627776ull); + Require(std::remove(counterPath) == 0); + bool missing = false; + try { + NetCtlReadByteCounter_nid_no_patch(counterPath); + } catch (const std::runtime_error&) { + missing = true; + } + Require(missing); + } + NetCtlIfStat first{}; + first.device = 7; + first.reserved[7] = 7; + const int statResult = sceNetCtlGetIfStat(&first); + Require(statResult == (state == 3 ? 0 : static_cast(0x80412108))); + if (statResult == 0) { + Require(first.device == 0 && first.reserved[7] == 0); + NetCtlIfStat second{}; + Require(sceNetCtlGetIfStat(&second) == 0); + Require(second.tx_bytes >= first.tx_bytes && second.rx_bytes >= first.rx_bytes); + } if (ipv6Unavailable) { std::puts("skipped the IPv6 socket checks, the host has no usable IPv6 loopback"); return 77; diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 6ef98f546..1a7c6bbe8 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -22,6 +22,7 @@ Throughout the project, every function at every stage either **does exactly what ### Unknown function info - AudioIn and NpSessionSignaling exports added without an implementation have assumed signatures +- `sceNetCtlGetIfStat` signature and 56-byte layout come from PPSA09030's call site and SharpProspero's `SceNetCtlIfStat`; the device kind is always 0, the byte counters are the host's totals over its up non-loopback hardware interfaces (not "since cleared"), and the not-connected result is assumed to match `sceNetCtlGetState` - Font, Http2, Net, Ssl, SystemService and libkernel exports imported by PPSA12544 and added without an implementation have unknown signatures - The Share, NpTrophy2, NpEntitlementAccess and WebBrowserDialog exports imported by PPSA12544's Unity plugins and added without an implementation have unknown signatures - Acm, GameLiveStreaming, Net, NpEntitlementAccess, NpManager, NpWebApi2, VoiceQoS, WebBrowserDialog and libkernel exports imported by PPSA14632 and added without an implementation are declared without parameters