#include "prx/libkernel/include/Api.hpp" #include "prx/libkernel/File/include/File.hpp" #include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" #include #include #include #include #include #include #include #include #include #include #include #include static_assert(sizeof(GuestSigaction) == 32); static_assert(sizeof(GuestMsghdr) == 48); static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "underscore entry check failed at line %d, errno %d\n", line, *__error_nid_postfix()); std::abort(); } } #define Require(value) Check((value), __LINE__) constexpr int Inet = 2; constexpr int Stream = 1; constexpr int Datagram = 2; constexpr int TcpLevel = 6; constexpr int TcpNoDelay = 1; constexpr int UnknownTcpOption = 0x7777; constexpr int OutOfBand = 0x1; constexpr int Truncated = 0x10; constexpr int Interrupted = 4; constexpr int BadDescriptor = 9; constexpr int BadAddress = 14; constexpr int Invalid = 22; constexpr int NoProtocolOption = 42; constexpr int NotSupported = 45; using Address = std::array; static bool Fails(std::int64_t result, int error) { return result == -1 && *__error_nid_postfix() == error; } static bool Filled(const Address& address, std::uint8_t value) { return std::all_of(address.begin(), address.end(), [value](std::uint8_t byte) { return byte == value; }); } static void StreamSockets() { const int listener = socket_nid_postfix(Inet, Stream, 0); Require(listener >= 0); Address address{16, Inet, 0, 0, 127, 0, 0, 1}; Require(_bind_nid_postfix(listener, address.data(), address.size()) == 0); Require(Fails(_bind_nid_postfix(listener, address.data(), address.size()), Invalid)); Require(_listen_nid_postfix(listener, 4) == 0); std::uint32_t size = address.size(); Require(_getsockname_nid_postfix(listener, address.data(), &size) == 0 && size == 16); Require(address[0] == 16 && address[1] == Inet && (address[2] != 0 || address[3] != 0) && address[4] == 127 && address[7] == 1); const int client = socket_nid_postfix(Inet, Stream, 0); Require(client >= 0); Require(Fails(_accept_nid_postfix(client, nullptr, nullptr), Invalid)); Require(_connect_nid_postfix(client, address.data(), address.size()) == 0); Address peer; peer.fill(0xa5); size = peer.size(); const int accepted = _accept_nid_postfix(listener, peer.data(), &size); Require(accepted >= 0 && size == 16); Address local{}; size = local.size(); Require(_getsockname_nid_postfix(client, local.data(), &size) == 0 && size == 16 && local == peer); Address remote{}; size = remote.size(); Require(_getpeername_nid_postfix(client, remote.data(), &size) == 0 && size == 16 && remote == address); remote.fill(0); size = remote.size(); Require(_getpeername_nid_postfix(accepted, remote.data(), &size) == 0 && size == 16 && remote == local); int value = -1; std::uint32_t valueSize = sizeof(value); Require(_getsockopt_nid_postfix(client, TcpLevel, TcpNoDelay, &value, &valueSize) == 0 && value == 0 && valueSize == sizeof(value)); const int enabled = 1; Require(_setsockopt_nid_postfix(client, TcpLevel, TcpNoDelay, &enabled, sizeof(enabled)) == 0); valueSize = sizeof(value); Require(_getsockopt_nid_postfix(client, TcpLevel, TcpNoDelay, &value, &valueSize) == 0 && value == 4 && valueSize == sizeof(value)); Require(Fails(_setsockopt_nid_postfix(client, TcpLevel, UnknownTcpOption, &enabled, sizeof(enabled)), NoProtocolOption)); const char text[] = "underscore stream"; Require(_sendto_nid_postfix(client, text, sizeof(text), 0, nullptr, 0) == sizeof(text)); char received[sizeof(text)]{}; Address sender; sender.fill(0xa5); std::uint32_t senderSize = sender.size(); Require(_recvfrom_nid_postfix(accepted, received, sizeof(received), 0, sender.data(), &senderSize) == sizeof(text)); Require(std::memcmp(received, text, sizeof(text)) == 0 && senderSize == 0 && Filled(sender, 0xa5)); for (const int descriptor : {accepted, client, listener}) Require(close_nid_postfix(descriptor) == 0); Address untouched; untouched.fill(0xa5); std::uint32_t untouchedSize = 7; value = -1; Require(Fails(_getsockname_nid_postfix(listener, untouched.data(), &untouchedSize), BadDescriptor)); Require(Fails(_getpeername_nid_postfix(client, untouched.data(), &untouchedSize), BadDescriptor)); Require(Fails(_accept_nid_postfix(listener, untouched.data(), &untouchedSize), BadDescriptor)); Require(Fails(_getsockopt_nid_postfix(client, TcpLevel, TcpNoDelay, &value, &untouchedSize), BadDescriptor)); Require(Fails(_listen_nid_postfix(listener, 4), BadDescriptor)); Require(Filled(untouched, 0xa5) && untouchedSize == 7 && value == -1); } static void DatagramSockets() { const int receiver = socket_nid_postfix(Inet, Datagram, 0); const int sender = socket_nid_postfix(Inet, Datagram, 0); Require(receiver >= 0 && sender >= 0); Address destination{16, Inet, 0, 0, 127, 0, 0, 1}; Require(_bind_nid_postfix(receiver, destination.data(), destination.size()) == 0); std::uint32_t size = destination.size(); Require(_getsockname_nid_postfix(receiver, destination.data(), &size) == 0 && size == 16 && (destination[2] != 0 || destination[3] != 0)); Require(Fails(_listen_nid_postfix(receiver, 1), NotSupported)); const char datagram[] = "underscore datagram"; Require(Fails(_sendto_nid_postfix(sender, datagram, sizeof(datagram), OutOfBand, destination.data(), destination.size()), NotSupported)); Require(_sendto_nid_postfix(sender, datagram, sizeof(datagram), 0, destination.data(), destination.size()) == sizeof(datagram)); Address senderName{}; size = senderName.size(); Require(_getsockname_nid_postfix(sender, senderName.data(), &size) == 0 && size == 16); char buffer[32]{}; Address source{}; size = source.size(); Require(_recvfrom_nid_postfix(receiver, buffer, sizeof(buffer), 0, source.data(), &size) == sizeof(datagram)); Require(std::strcmp(buffer, datagram) == 0 && size == 16 && source[0] == 16 && source[1] == Inet); Require(source[2] == senderName[2] && source[3] == senderName[3] && source[4] == 127 && source[7] == 1); char head[] = "scatter "; char tail[] = "gather"; KernelIovec parts[2]{{head, 8}, {tail, 6}}; GuestMsghdr outgoing{destination.data(), static_cast(destination.size()), parts, 2, nullptr, 0, 0}; Require(_sendmsg_nid_postfix(sender, &outgoing, 0) == 14); char first[4]{}; char second[16]{}; KernelIovec pieces[2]{{first, sizeof(first)}, {second, sizeof(second)}}; source.fill(0); GuestMsghdr incoming{source.data(), static_cast(source.size()), pieces, 2, nullptr, 0, -1}; Require(_recvmsg_nid_postfix(receiver, &incoming, 0) == 14); Require(std::memcmp(first, "scat", 4) == 0 && std::memcmp(second, "ter gather", 10) == 0); Require(incoming.flags == 0 && incoming.nameLength == 16 && source[2] == senderName[2] && source[3] == senderName[3] && source[4] == 127); Require(_sendmsg_nid_postfix(sender, &outgoing, 0) == 14); char shortBuffer[5]{}; KernelIovec single{shortBuffer, sizeof(shortBuffer)}; GuestMsghdr truncated{nullptr, 0, &single, 1, nullptr, 0, 0}; Require(_recvmsg_nid_postfix(receiver, &truncated, 0) == 5 && std::memcmp(shortBuffer, "scatt", 5) == 0 && truncated.flags == Truncated); Require(Fails(_sendmsg_nid_postfix(sender, nullptr, 0), BadAddress)); Require(Fails(_recvmsg_nid_postfix(receiver, nullptr, 0), BadAddress)); Require(Fails(_connect_nid_postfix(sender, destination.data(), 15), Invalid)); Require(_connect_nid_postfix(sender, destination.data(), destination.size()) == 0); Address peer{}; size = peer.size(); Require(_getpeername_nid_postfix(sender, peer.data(), &size) == 0 && size == 16 && peer == destination); Require(_sendto_nid_postfix(sender, datagram, sizeof(datagram), 0, nullptr, 0) == sizeof(datagram)); std::memset(buffer, 0, sizeof(buffer)); Require(_recvfrom_nid_postfix(receiver, buffer, sizeof(buffer), 0, nullptr, nullptr) == sizeof(datagram) && std::strcmp(buffer, datagram) == 0); Require(close_nid_postfix(receiver) == 0 && close_nid_postfix(sender) == 0); std::memset(buffer, 0x5a, sizeof(buffer)); source.fill(0xa5); size = 9; const int enabled = 1; Require(Fails(_recvfrom_nid_postfix(receiver, buffer, sizeof(buffer), 0, source.data(), &size), BadDescriptor)); Require(Fails(_recvmsg_nid_postfix(receiver, &incoming, 0), BadDescriptor)); Require(Fails(_sendto_nid_postfix(sender, datagram, sizeof(datagram), 0, nullptr, 0), BadDescriptor)); Require(Fails(_sendmsg_nid_postfix(sender, &outgoing, 0), BadDescriptor)); Require(Fails(_bind_nid_postfix(receiver, destination.data(), destination.size()), BadDescriptor)); Require(Fails(_connect_nid_postfix(sender, destination.data(), destination.size()), BadDescriptor)); Require(Fails(_setsockopt_nid_postfix(sender, TcpLevel, TcpNoDelay, &enabled, sizeof(enabled)), BadDescriptor)); Require(std::all_of(std::begin(buffer), std::end(buffer), [](char byte) { return byte == 0x5a; })); Require(Filled(source, 0xa5) && size == 9); } static std::set Names(const char* records, int bytes) { std::set names; for (int offset = 0; offset < bytes;) { std::uint16_t recordLength = 0; std::memcpy(&recordLength, records + offset + 4, sizeof(recordLength)); Require(recordLength >= 12 && offset + recordLength <= bytes); names.emplace(records + offset + 8, static_cast(records[offset + 7])); offset += recordLength; } return names; } static void DirectoryEntries() { const auto root = std::filesystem::path("anyps5-underscore-entries-test-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); Require(std::filesystem::create_directory(root)); { std::ofstream stream(root / "entry"); } const int directory = sceKernelOpen(root.string().c_str(), SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0); Require(directory >= 0); std::array records{}; std::int64_t base = -1; const int read = _getdirentries_nid_postfix(directory, records.data(), static_cast(records.size()), &base); Require(read > 0 && base == 0); const std::set listed{".", "..", "entry"}; Require(Names(records.data(), read) == listed); Require(sceKernelLseek(directory, 0, 0) == 0); std::array expected{}; std::int64_t expectedBase = -1; Require(getdirentries_nid_postfix(directory, expected.data(), static_cast(expected.size()), &expectedBase) == read); Require(expectedBase == base && std::memcmp(expected.data(), records.data(), static_cast(read)) == 0); std::int64_t end = -1; Require(_getdirentries_nid_postfix(directory, records.data(), static_cast(records.size()), &end) == 0 && end > 0); records.fill(0x5a); std::int64_t untouched = 1234; Require(Fails(_getdirentries_nid_postfix(directory, records.data(), 0, &untouched), Invalid)); Require(Fails(_getdirentries_nid_postfix(directory, nullptr, 64, &untouched), BadAddress)); Require(sceKernelClose(directory) == 0); Require(Fails(_getdirentries_nid_postfix(directory, records.data(), static_cast(records.size()), &untouched), BadDescriptor)); Require(untouched == 1234 && std::all_of(records.begin(), records.end(), [](char byte) { return byte == 0x5a; })); std::filesystem::remove_all(root); } volatile std::sig_atomic_t received = 0; void APS5_VABI Handler(int value) { received = value; } static bool Same(const GuestSigaction& left, const GuestSigaction& right) { return left.handler == right.handler && left.flags == right.flags && std::memcmp(&left.mask, &right.mask, sizeof(left.mask)) == 0; } static void Signals() { const auto handler = reinterpret_cast(Handler); const GuestSigaction term{handler, 0, {{0x4000, 0, 0, 0x80000000u}}}; GuestSigaction initial{}; Require(_sigaction_nid_postfix(15, nullptr, &initial) == 0); GuestSigaction previous; std::memset(&previous, 0xa5, sizeof(previous)); Require(_sigaction_nid_postfix(15, &term, &previous) == 0 && Same(previous, initial)); received = 0; Require(raise_nid_postfix(15) == 0 && received == 15); GuestSigaction current; std::memset(¤t, 0xa5, sizeof(current)); Require(_sigaction_nid_postfix(15, nullptr, ¤t) == 0 && Same(current, term)); GuestSigaction untouched; std::memset(&untouched, 0xa5, sizeof(untouched)); for (const int invalid : {0, 129}) { std::memset(¤t, 0xa5, sizeof(current)); Require(Fails(_sigaction_nid_postfix(invalid, &term, ¤t), Invalid) && std::memcmp(¤t, &untouched, sizeof(current)) == 0); } for (const int fixed : {9, 17}) { std::memset(¤t, 0xa5, sizeof(current)); Require(Fails(_sigaction_nid_postfix(fixed, &term, ¤t), Invalid) && std::memcmp(¤t, &untouched, sizeof(current)) == 0); } Require(_sigaction_nid_postfix(15, &initial, ¤t) == 0 && Same(current, term)); const GuestSigaction user{handler, 0, {}}; GuestSigaction initialUser{}; Require(_sigaction_nid_postfix(30, &user, &initialUser) == 0); const GuestSignalSet blocked{{1u << 29, 0, 0, 0}}; const GuestSignalSet empty{{0, 0, 0, 0}}; GuestSignalSet saved{}; Require(sigprocmask_nid_postfix(3, &blocked, &saved) == 0); received = 0; Require(raise_nid_postfix(30) == 0 && received == 0); Require(Fails(_sigsuspend_nid_postfix(&empty), Interrupted) && received == 30); GuestSignalSet restored{}; Require(sigprocmask_nid_postfix(1, nullptr, &restored) == 0 && std::memcmp(&restored, &blocked, sizeof(restored)) == 0); Require(sigprocmask_nid_postfix(3, &saved, nullptr) == 0); Require(_sigaction_nid_postfix(30, &initialUser, nullptr) == 0); } int main() { StreamSockets(); DatagramSockets(); DirectoryEntries(); Signals(); }