Skip to content
Closed
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
57 changes: 57 additions & 0 deletions core/libs/prx/libSceNet/Export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,24 @@ struct NetMsghdr {
static_assert(sizeof(NetMsghdr) == 48 && offsetof(NetMsghdr, iov) == 16 && offsetof(NetMsghdr, control) == 32 &&
offsetof(NetMsghdr, flags) == 44);

struct ResolverRecordEx {
std::uint8_t address[16];
std::int32_t family;
std::int32_t reserved[3];
};

struct ResolverInfoEx {
static constexpr int MaxRecords = 10;
ResolverRecordEx records[MaxRecords];
std::int32_t count;
std::int32_t ipv4Count;
std::int32_t ipv6Count;
std::int32_t ipv6Resolver;
std::int32_t reserved[12];
};
static_assert(sizeof(ResolverRecordEx) == 32 && offsetof(ResolverRecordEx, family) == 16);
static_assert(sizeof(ResolverInfoEx) == 384 && offsetof(ResolverInfoEx, count) == 320 && offsetof(ResolverInfoEx, ipv4Count) == 324);

std::int64_t message_length(const NetMsghdr* message) {
if (!message) return fail(NET_EFAULT);
if (message->iov_length < 0 || message->iov_length > NET_UIO_MAXIOV) return fail(NET_EMSGSIZE);
Expand Down Expand Up @@ -1145,6 +1163,45 @@ int APS5_VABI sceNetResolverStartNtoaMultipleRecords() {
return 0;
}

int APS5_VABI sceNetResolverStartNtoaMultipleRecordsEx(int rid, const char* hostname, ResolverInfoEx* info, int timeout, int retry, int flags) {
(void)timeout;
(void)retry;
if (flags != 0) throw std::runtime_error("sceNetResolverStartNtoaMultipleRecordsEx: flags are not supported");
if (!hostname || !info) return fail(NET_EINVAL);
{
std::lock_guard<std::mutex> lk(g_mutex);
if (g_resolvers.count(rid) == 0) return fail(NET_EBADF);
}
if (!initialize_sockets()) return fail(5);
addrinfo hints{};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
addrinfo* results = nullptr;
const int result = ::getaddrinfo(hostname, nullptr, &hints, &results);
if (result != 0) {
*errno_slot() = result == EAI_AGAIN ? NET_ETIMEDOUT : NET_ENOENT;
log_soft(__func__, "host DNS lookup failed");
set_resolver_error(rid, NET_ERROR_RESOLVER_ENODNS);
return NET_ERROR_RESOLVER_ENODNS;
}
std::memset(info, 0, sizeof(*info));
int count = 0;
for (const addrinfo* entry = results; entry && count < ResolverInfoEx::MaxRecords; entry = entry->ai_next) {
const auto& address = reinterpret_cast<const sockaddr_in*>(entry->ai_addr)->sin_addr;
const auto* begin = info->records;
const auto* end = info->records + count;
if (std::any_of(begin, end, [&](const ResolverRecordEx& record) { return std::memcmp(record.address, &address, sizeof(address)) == 0; })) continue;
std::memcpy(info->records[count].address, &address, sizeof(address));
info->records[count].family = NET_AF_INET;
++count;
}
::freeaddrinfo(results);
info->count = count;
info->ipv4Count = count;
set_resolver_error(rid, 0);
return 0;
}

extern const std::uint8_t in6addr_any_nid_postfix[16] = {};
extern const std::uint8_t in6addr_loopback_nid_postfix[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};

Expand Down
45 changes: 45 additions & 0 deletions core/libs/tests/GuestSceNet.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include "SceTypes.hpp"
#include <algorithm>
#include <array>
#include <chrono>
#include <cstdint>
Expand Down Expand Up @@ -32,6 +33,7 @@ int APS5_VABI sceNetEpollDestroy(int);
extern const std::uint32_t sce_net_in6addr_any[4];
int APS5_VABI sceNetResolverCreate(const char*, int, int);
int APS5_VABI sceNetResolverStartNtoa(int, const char*, void*, int, int, int);
int APS5_VABI sceNetResolverStartNtoaMultipleRecordsEx(int, const char*, void*, int, int, int);
int APS5_VABI sceNetResolverDestroy(int);
int APS5_VABI sceNetResolverGetError(int, int*);
int APS5_VABI sceNetCtlGetState(int*);
Expand Down Expand Up @@ -307,6 +309,49 @@ int main() {
Require(ipv4[0] == 127);
Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == 0);
Require(sceNetResolverGetError(resolver, nullptr) == static_cast<int>(0x80410116) && *sceNetErrnoLoc() == 22);
std::array<std::uint8_t, 448> records{};
const auto field = [&records](std::size_t offset) {
std::int32_t value = 0;
std::memcpy(&value, records.data() + offset, sizeof(value));
return value;
};
records.fill(0xa5);
Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "localhost", records.data(), 0, 0, 0) == 0);
const std::int32_t record_count = field(320);
Require(record_count >= 1 && record_count <= 10 && field(324) == record_count && field(328) == 0 && field(332) == 0);
for (std::size_t offset = 336; offset < 384; ++offset) Require(records[offset] == 0);
for (std::size_t offset = 384; offset < records.size(); ++offset) Require(records[offset] == 0xa5);
for (std::int32_t index = 0; index < 10; ++index) {
const std::uint8_t* record = records.data() + index * 32;
if (index < record_count) {
Require(record[0] == 127 && field(index * 32 + 16) == 2);
for (std::int32_t other = 0; other < index; ++other) Require(std::memcmp(record, records.data() + other * 32, 4) != 0);
for (std::size_t offset = 4; offset < 16; ++offset) Require(record[offset] == 0);
for (std::size_t offset = 20; offset < 32; ++offset) Require(record[offset] == 0);
} else {
for (std::size_t offset = 0; offset < 32; ++offset) Require(record[offset] == 0);
}
}
Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == 0);
records.fill(0xa5);
Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "guest-sce-net.invalid", records.data(), 0, 0, 0) ==
static_cast<int>(0x804101E1));
Require(sceNetResolverGetError(resolver, &resolver_error) == 0 &&
resolver_error == static_cast<int>(0x804101E1));
Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, nullptr, records.data(), 0, 0, 0) == static_cast<int>(0x80410116) &&
*sceNetErrnoLoc() == 22);
Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "localhost", nullptr, 0, 0, 0) == static_cast<int>(0x80410116) &&
*sceNetErrnoLoc() == 22);
Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver + 1000, "localhost", records.data(), 0, 0, 0) ==
static_cast<int>(0x80410109) && *sceNetErrnoLoc() == 9);
Require(std::all_of(records.begin(), records.end(), [](std::uint8_t value) { return value == 0xa5; }));
bool flags_rejected = false;
try {
sceNetResolverStartNtoaMultipleRecordsEx(resolver, "localhost", records.data(), 0, 0, 0x2000000);
} catch (const std::runtime_error&) {
flags_rejected = true;
}
Require(flags_rejected);
Require(sceNetResolverDestroy(resolver) == 0);
resolver_error = -1;
Require(sceNetResolverGetError(resolver, &resolver_error) == static_cast<int>(0x80410109) &&
Expand Down
1 change: 1 addition & 0 deletions docs/dev/TechnicalDebt.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,3 +343,4 @@ Throughout the project, every function at every stage either **does exactly what
- [Guest path resolution](../../core/libs/prx/libc/src/General.cpp) falls back to ASCII case-insensitive component lookup, following [shadPS4's host filesystem](https://github.com/shadps4-emu/shadPS4/blob/945dbc3cc3eee80ac3e053b438502ed936fa6bb2/src/core/file_sys/backends/host_fs.cpp). PPSA03747 requests `Skyrim.INI`, `Data/Video` and lowercase archive/plugin names that differ from the dump's filenames. Exact host names take precedence; multiple fallback matches throw instead of selecting an arbitrary file. Existing parent names are resolved when creating a new file, and guest mount aliases use the same ASCII comparison. Results are not cached, so renames and newly created files remain visible. Unicode case folding and PS5 behaviour for case-colliding host entries are unverified; host roots are used as supplied.
- [Static scalar calls](../../core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp) (`s_call_b64` and `s_swappc_b64`) require one call per ordinary even-aligned SGPR link pair (s0:s1 through s104:s105) and one later `s_setpc_b64` return reading that exact pair. Regions must nest or be disjoint; recursion, shared links, escaping link values (including overlapping tuples and resource descriptors), invalid targets, interleaved regions, cross-region branches or fall-through, relative SGPR reads and dynamic `s_setpc_b64` in a program with calls throw. The analysis checks all decoded instructions, including unreachable code. Arbitrary indirect calls and general call frames are not implemented.
- [s_call_b64](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) saves `PC + 4` over `GetShaderBase` and branches to `PC + 4 + signext(SIMM16) * 4`, following [AMD RDNA2 ISA](https://gpuopen.com/download/rdna2-shader-instruction-set-architecture.pdf) sections 3.6.3, 12.2 and 12.3. Forward callees after `s_endpgm` are decoded. Native RDNA2/PS5 execution and title compatibility for these bounded scalar calls remain unverified; `s_rfe_b64` still throws in translation.
- [sceNetResolverStartNtoaMultipleRecordsEx](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) looks the name up through host DNS, as `sceNetResolverStartNtoa` does, and returns up to ten distinct IPv4 addresses, each tagged `AF_INET`. The IPv6 count and the IPv6 resolver ID are 0. Only flags 0 is handled (shadPS4 reads 0x1000000 as AAAA and 0x2000000 as A plus AAAA; the asynchronous flag needs a worker). Other flags throw, and timeout and retry are ignored. The 384-byte result layout follows SharpProspero's `SceNetResolverInfoEx`, which EVO-PLAYER-PS5 checks with a canary on the console. The record order is the host resolver's
Loading