Merge pull request #4682 from bubblegg/feat/netctl-get-if-stat

feat(libSceNetCtl): add sceNetCtlGetIfStat
This commit is contained in:
Adrià 2026-10-13 09:26:47 +02:00 committed by GitHub
commit 30caf2b2a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 88 additions and 0 deletions

View File

@ -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;

View File

@ -7,12 +7,18 @@
#include <ifaddrs.h>
#include <net/if.h>
#include <netinet/in.h>
#ifdef __linux__
#include <netpacket/packet.h>
#endif
#endif
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include <fstream>
#include <mutex>
#include <stdexcept>
#include <string>
#ifdef _WIN32
#include <vector>
#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;

View File

@ -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<int>(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<int>(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;

View File

@ -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