diff --git a/core/libs/SceTypes.hpp b/core/libs/SceTypes.hpp index a16e233da55..5f6638ffb61 100644 --- a/core/libs/SceTypes.hpp +++ b/core/libs/SceTypes.hpp @@ -1826,6 +1826,13 @@ struct VideoOutOutputStatus { std::uint64_t reserved[3] = {}; }; +struct VideoOutResolutionStatus { + std::uint32_t fullWidth = 0; + std::uint32_t fullHeight = 0; + std::uint32_t paneWidth = 0; + std::uint32_t paneHeight = 0; +}; + struct VideoOutOutputOptions { std::uint32_t internalData[16] = {}; }; struct VideoOutColorSettings { diff --git a/core/libs/prx/libSceLibcInternal/CMakeLists.txt b/core/libs/prx/libSceLibcInternal/CMakeLists.txt index 2c0060756b6..55a1d7f084c 100644 --- a/core/libs/prx/libSceLibcInternal/CMakeLists.txt +++ b/core/libs/prx/libSceLibcInternal/CMakeLists.txt @@ -21,4 +21,14 @@ if(BUILD_TESTING) add_test(NAME guest_force_tls_destructor COMMAND guest_force_tls_destructor_tests) set_tests_properties(guest_force_tls_destructor PROPERTIES TIMEOUT 20) endif() + add_test_executable(guest_syslog_tests ${LIBS_INCLUDE_DIR}/tests/GuestSyslog.cpp) + target_include_directories(guest_syslog_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_syslog_tests PRIVATE libSceLibcInternal) + configure_windows_unwind(guest_syslog_tests) + add_test(NAME guest_syslog COMMAND guest_syslog_tests) + set_tests_properties(guest_syslog PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_syslog PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceLibcInternal/Export.cpp b/core/libs/prx/libSceLibcInternal/Export.cpp index f9425b744f8..dbd28e9f54d 100644 --- a/core/libs/prx/libSceLibcInternal/Export.cpp +++ b/core/libs/prx/libSceLibcInternal/Export.cpp @@ -2,8 +2,12 @@ #include "prx/libc/include/General.hpp" #include "prx/libc/include/HeapDiagnostics.hpp" #include "prx/libc/include/HostThreadLocal.hpp" +#include "prx/libc/include/WindowsFormatting.hpp" +#include #include #include +#include +#include #include #include #include @@ -130,4 +134,25 @@ int APS5_VABI _sceLibcInternalForceTlsDestructor_nid_postfix(KernelModule handle return 0; } +void APS5_VABI syslog_nid_postfix(int priority, const char* message, ...) { + static_cast(priority); + if (message == nullptr) throw std::runtime_error("syslog: null message"); + static std::mutex outputMutex; + const std::lock_guard lock(outputMutex); +#ifdef _WIN32 + __builtin_sysv_va_list args; + __builtin_sysv_va_start(args, message); + std::string buffer; + LibcDetail::FormatWindows(nullptr, 0, message, args, &buffer); + __builtin_sysv_va_end(args); + std::fwrite(buffer.data(), 1, buffer.size(), stderr); +#else + std::va_list args; + va_start(args, message); + std::vfprintf(stderr, message, args); + va_end(args); +#endif + std::fflush(stderr); +} + } diff --git a/core/libs/prx/libSceVideoOut/include/Output.hpp b/core/libs/prx/libSceVideoOut/include/Output.hpp index 91689f906e0..9bf3e6aa3ca 100644 --- a/core/libs/prx/libSceVideoOut/include/Output.hpp +++ b/core/libs/prx/libSceVideoOut/include/Output.hpp @@ -17,6 +17,7 @@ int APS5_VABI sceVideoOutSetFlipRate(int handle, int rate); int APS5_VABI sceVideoOutSetWindowModeMargins(int handle, int top, int bottom); int APS5_VABI sceVideoOutGetFlipStatus(int handle, VideoOutFlipStatus* status); int APS5_VABI sceVideoOutGetOutputStatus(int handle, VideoOutOutputStatus* status); +int APS5_VABI sceVideoOutGetResolutionStatus(int handle, VideoOutResolutionStatus* status); int APS5_VABI sceVideoOutGetVblankStatus(int handle, VideoOutVblankStatus* status); int APS5_VABI sceVideoOutIsFlipPending(int handle); int APS5_VABI sceVideoOutWaitVblank(int handle); diff --git a/core/libs/prx/libSceVideoOut/src/Output.cpp b/core/libs/prx/libSceVideoOut/src/Output.cpp index af435d9a3a9..54362e2227b 100644 --- a/core/libs/prx/libSceVideoOut/src/Output.cpp +++ b/core/libs/prx/libSceVideoOut/src/Output.cpp @@ -156,6 +156,25 @@ int APS5_VABI sceVideoOutGetOutputStatus(int handle, VideoOutOutputStatus* statu LibcAwaitExit_nid_postfix(); } +int APS5_VABI sceVideoOutGetResolutionStatus(int handle, VideoOutResolutionStatus* status) try { + if (status == nullptr) { + throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_ADDRESS"); + } + auto cfg = VideoOutDriver::Get().GetConfig(handle); + if (cfg == nullptr) { + throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE"); + } + std::unique_lock lock(cfg->mutex); + cfg->Check(); + status->fullWidth = cfg->width; + status->fullHeight = cfg->height; + status->paneWidth = cfg->width; + status->paneHeight = cfg->height; + return 0; +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); +} + int APS5_VABI sceVideoOutIsFlipPending(int handle) try { auto cfg = VideoOutDriver::Get().GetConfig(handle); if (cfg == nullptr) { diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp index a1d4a42d9a2..06ff346ed3a 100644 --- a/core/libs/prx/libkernel/Socket/src/Udp.cpp +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -2,6 +2,7 @@ #define WIN32_LEAN_AND_MEAN #include #include +#include #else #include #include @@ -13,9 +14,11 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/Socket/include/SocketRuntime.hpp" #include +#include #include #include #include +#include #include #include #include @@ -168,6 +171,193 @@ void GuestAddress(const sockaddr_storage& native, void* output, std::uint32_t* l std::memcpy(output, bytes, std::min(*length, bytes[0])); *length = bytes[0]; } +void CloseNative(NativeSocket value) { + if (value == Invalid) return; +#ifdef _WIN32 + closesocket(value); +#else + ::close(value); +#endif +} +bool NativeGetsockname(NativeSocket value, sockaddr_storage& address, socklen_t& length) { + length = sizeof(address); + return ::getsockname(value, reinterpret_cast(&address), &length) == 0; +} +bool LoopbackAddress(int nativeFamily, sockaddr_storage& address, socklen_t& length) { + std::memset(&address, 0, sizeof(address)); + if (nativeFamily == AF_INET) { + auto& v4 = reinterpret_cast(address); + v4.sin_family = AF_INET; + v4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + length = sizeof(v4); + return true; + } + if (nativeFamily == AF_INET6) { + auto& v6 = reinterpret_cast(address); + v6.sin6_family = AF_INET6; + v6.sin6_addr = in6addr_loopback; + length = sizeof(v6); + return true; + } + return false; +} +bool MakeStreamPair(int nativeFamily, NativeSocket pair[2]) { + sockaddr_storage loopback{}; + socklen_t loopbackLength = 0; + if (!LoopbackAddress(nativeFamily, loopback, loopbackLength)) return false; + const NativeSocket listener = ::socket(nativeFamily, SOCK_STREAM, 0); + if (listener == Invalid) return false; + bool ok = false; + if (::bind(listener, reinterpret_cast(&loopback), loopbackLength) == 0 && + ::listen(listener, 1) == 0) { + sockaddr_storage bound{}; + socklen_t boundLength = 0; + if (NativeGetsockname(listener, bound, boundLength)) { + const NativeSocket connector = ::socket(nativeFamily, SOCK_STREAM, 0); + if (connector != Invalid) { + if (::connect(connector, reinterpret_cast(&bound), boundLength) == 0) { + const NativeSocket acceptor = ::accept(listener, nullptr, nullptr); + if (acceptor != Invalid) { + pair[0] = connector; + pair[1] = acceptor; + ok = true; + } else { + CloseNative(connector); + } + } else { + CloseNative(connector); + } + } + } + } + CloseNative(listener); + return ok; +} +bool MakeDatagramPair(int nativeFamily, NativeSocket pair[2]) { + sockaddr_storage loopback{}; + socklen_t loopbackLength = 0; + if (!LoopbackAddress(nativeFamily, loopback, loopbackLength)) return false; + const NativeSocket first = ::socket(nativeFamily, SOCK_DGRAM, 0); + if (first == Invalid) return false; + bool ok = false; + if (::bind(first, reinterpret_cast(&loopback), loopbackLength) == 0) { + sockaddr_storage firstBound{}; + socklen_t firstLength = 0; + if (NativeGetsockname(first, firstBound, firstLength)) { + const NativeSocket second = ::socket(nativeFamily, SOCK_DGRAM, 0); + if (second != Invalid) { + if (::bind(second, reinterpret_cast(&loopback), loopbackLength) == 0) { + sockaddr_storage secondBound{}; + socklen_t secondLength = 0; + if (NativeGetsockname(second, secondBound, secondLength) && + ::connect(first, reinterpret_cast(&secondBound), secondLength) == 0 && + ::connect(second, reinterpret_cast(&firstBound), firstLength) == 0) { + pair[0] = first; + pair[1] = second; + ok = true; + } + } + if (!ok) CloseNative(second); + } + } + } + if (!ok) CloseNative(first); + return ok; +} +#ifdef _WIN32 +bool UnixSocketAddress(const char* path, sockaddr_storage& address, socklen_t& length) { + const std::size_t pathLength = std::strlen(path); + if (pathLength >= sizeof(sockaddr_un{}.sun_path)) return false; + std::memset(&address, 0, sizeof(address)); + auto& unix = reinterpret_cast(address); + unix.sun_family = AF_UNIX; + std::memcpy(unix.sun_path, path, pathLength + 1); + length = static_cast(offsetof(sockaddr_un, sun_path) + pathLength + 1); + return true; +} +bool MakeUnixPair(int nativeType, NativeSocket pair[2]) { + char directory[MAX_PATH]; + const DWORD directoryLength = GetTempPathA(sizeof(directory), directory); + if (directoryLength == 0 || directoryLength > sizeof(directory) - 1) return false; + static std::atomic sequence{0}; + const unsigned long tag = sequence.fetch_add(1); + const unsigned long process = static_cast(GetCurrentProcessId()); + char firstPath[MAX_PATH]; + char secondPath[MAX_PATH]; + const int firstLength = std::snprintf(firstPath, sizeof(firstPath), "%sanyps5-sp-%lu-%lu-a", directory, process, tag); + const int secondLength = std::snprintf(secondPath, sizeof(secondPath), "%sanyps5-sp-%lu-%lu-b", directory, process, tag); + if (firstLength <= 0 || secondLength <= 0 || + static_cast(firstLength) >= sizeof(firstPath) || + static_cast(secondLength) >= sizeof(secondPath)) + return false; + DeleteFileA(firstPath); + DeleteFileA(secondPath); + if (nativeType == SOCK_STREAM) { + sockaddr_storage address{}; + socklen_t addressLength = 0; + if (!UnixSocketAddress(firstPath, address, addressLength)) return false; + const NativeSocket listener = ::socket(AF_UNIX, SOCK_STREAM, 0); + if (listener == Invalid) return false; + bool ok = false; + if (::bind(listener, reinterpret_cast(&address), addressLength) == 0 && + ::listen(listener, 1) == 0) { + const NativeSocket connector = ::socket(AF_UNIX, SOCK_STREAM, 0); + if (connector != Invalid) { + if (::connect(connector, reinterpret_cast(&address), addressLength) == 0) { + const NativeSocket acceptor = ::accept(listener, nullptr, nullptr); + if (acceptor != Invalid) { + pair[0] = connector; + pair[1] = acceptor; + ok = true; + } else { + CloseNative(connector); + } + } else { + CloseNative(connector); + } + } + } + CloseNative(listener); + DeleteFileA(firstPath); + return ok; + } + const NativeSocket first = ::socket(AF_UNIX, SOCK_DGRAM, 0); + if (first == Invalid) return false; + bool ok = false; + sockaddr_storage firstAddress{}; + socklen_t firstAddressLength = 0; + sockaddr_storage secondAddress{}; + socklen_t secondAddressLength = 0; + if (UnixSocketAddress(firstPath, firstAddress, firstAddressLength) && + UnixSocketAddress(secondPath, secondAddress, secondAddressLength) && + ::bind(first, reinterpret_cast(&firstAddress), firstAddressLength) == 0) { + const NativeSocket second = ::socket(AF_UNIX, SOCK_DGRAM, 0); + if (second != Invalid) { + if (::bind(second, reinterpret_cast(&secondAddress), secondAddressLength) == 0 && + ::connect(first, reinterpret_cast(&secondAddress), secondAddressLength) == 0 && + ::connect(second, reinterpret_cast(&firstAddress), firstAddressLength) == 0) { + pair[0] = first; + pair[1] = second; + ok = true; + } + if (!ok) CloseNative(second); + } + } + if (!ok) CloseNative(first); + DeleteFileA(firstPath); + DeleteFileA(secondPath); + return ok; +} +#endif +bool MakeConnectedPair(int nativeFamily, int nativeType, NativeSocket pair[2]) { +#ifndef _WIN32 + if (nativeFamily == AF_UNIX) return ::socketpair(AF_UNIX, nativeType, 0, pair) == 0; +#else + if (nativeFamily == AF_UNIX) return MakeUnixPair(nativeType, pair); +#endif + if (nativeType == SOCK_STREAM) return MakeStreamPair(nativeFamily, pair); + return MakeDatagramPair(nativeFamily, pair); +} } int GuestSockets::Close(int descriptor) { @@ -286,6 +476,47 @@ int APS5_VABI socket_nid_postfix(int family, int type, int protocol) { return Fail(12); } } +int APS5_VABI socketpair_nid_postfix(int family, int type, int protocol, int* pair) { + if (pair == nullptr) return Fail(14); + if (protocol != 0) return Fail(43); + const int nativeType = type == 1 ? SOCK_STREAM : type == 2 ? SOCK_DGRAM : -1; + if (nativeType == -1) return Fail(43); + int nativeFamily; + if (family == 1) nativeFamily = AF_UNIX; + else if (family == 2) nativeFamily = AF_INET; + else if (family == 28) nativeFamily = AF_INET6; + else return Fail(47); +#ifdef _WIN32 + static const int startup = [] { WSADATA data{}; return WSAStartup(MAKEWORD(2, 2), &data); }(); + if (startup) return Fail(5); +#endif + NativeSocket native[2] = {Invalid, Invalid}; + if (!MakeConnectedPair(nativeFamily, nativeType, native)) return Fail(NativeError()); + Socket firstGuard(native[0], family, type); + Socket secondGuard(native[1], family, type); + try { + auto first = std::make_shared(native[0], family, type); + auto second = std::make_shared(native[1], family, type); + firstGuard.value = Invalid; + secondGuard.value = Invalid; + std::lock_guard lock(socketsMutex); + if (nextDescriptor >= INT_MAX - 1) return Fail(24); + const int firstDescriptor = nextDescriptor++; + const int secondDescriptor = nextDescriptor++; + sockets.emplace(firstDescriptor, std::move(first)); + try { + sockets.emplace(secondDescriptor, std::move(second)); + } catch (const std::bad_alloc&) { + sockets.erase(firstDescriptor); + return Fail(12); + } + pair[0] = firstDescriptor; + pair[1] = secondDescriptor; + return 0; + } catch (const std::bad_alloc&) { + return Fail(12); + } +} int APS5_VABI connect_nid_postfix(int descriptor, const void* address, std::uint32_t length) { const auto socket = Lookup(descriptor); if (!socket) return -1; diff --git a/core/libs/tests/GuestSyslog.cpp b/core/libs/tests/GuestSyslog.cpp new file mode 100644 index 00000000000..d316f892f50 --- /dev/null +++ b/core/libs/tests/GuestSyslog.cpp @@ -0,0 +1,34 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +extern "C" { +void APS5_VABI syslog_nid_postfix(int, const char*, ...); +} +static void Require(bool value) { if (!value) std::abort(); } +int main() { + const char* path = "guest_syslog_output.txt"; + Require(std::freopen(path, "w", stderr) != nullptr); + syslog_nid_postfix(6, "plain message"); + syslog_nid_postfix(3, "formatted %s %d", "value", 42); + std::fflush(stderr); + std::fclose(stderr); + FILE* input = std::fopen(path, "r"); + Require(input != nullptr); + std::string actual; + char chunk[256]; + std::size_t count; + while ((count = std::fread(chunk, 1, sizeof(chunk), input)) > 0) actual.append(chunk, count); + std::fclose(input); + std::remove(path); + Require(actual == "plain messageformatted value 42"); + bool threw = false; + try { + syslog_nid_postfix(6, nullptr); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw); +} diff --git a/core/libs/tests/GuestUdp.cpp b/core/libs/tests/GuestUdp.cpp index ac9b76ea13a..0e83703b2c9 100644 --- a/core/libs/tests/GuestUdp.cpp +++ b/core/libs/tests/GuestUdp.cpp @@ -7,6 +7,7 @@ #include extern "C" { int APS5_VABI socket_nid_postfix(int, int, int); +int APS5_VABI socketpair_nid_postfix(int, int, int, int*); int APS5_VABI fcntl_nid_postfix(int, int, ...); int APS5_VABI setsockopt_nid_postfix(int, int, int, const void*, std::uint32_t); int APS5_VABI getsockopt_nid_postfix(int, int, int, void*, std::uint32_t*); @@ -15,6 +16,8 @@ int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); int APS5_VABI ioctl_nid_postfix(int, std::uint64_t, void*); std::int64_t APS5_VABI sendto_nid_postfix(int, const void*, std::uint64_t, int, const void*, std::uint32_t); std::int64_t APS5_VABI recvfrom_nid_postfix(int, void*, std::uint64_t, int, void*, std::uint32_t*); +std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int); +std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int); int APS5_VABI close_nid_postfix(int); const char* APS5_VABI inet_ntop_nid_postfix(int, const void*, char*, std::uint32_t); int APS5_VABI inet_pton_nid_postfix(int, const char*, void*); @@ -138,4 +141,42 @@ int main() { const int stream = socket_nid_postfix(2, 1, 0); Require(stream >= 0); Require(close_nid_postfix(stream) == 0); + int pair[2]{}; + Require(socketpair_nid_postfix(2, 1, 0, nullptr) == -1 && *__error_nid_postfix() == 14); + Require(socketpair_nid_postfix(2, 1, 1, pair) == -1 && *__error_nid_postfix() == 43); + Require(socketpair_nid_postfix(2, 3, 0, pair) == -1 && *__error_nid_postfix() == 43); + Require(socketpair_nid_postfix(99, 1, 0, pair) == -1 && *__error_nid_postfix() == 47); + Require(socketpair_nid_postfix(2, 1, 0, pair) == 0); + Require(pair[0] >= 0 && pair[1] >= 0 && pair[0] != pair[1]); + const char ping[] = "ping"; + Require(send_nid_postfix(pair[0], ping, sizeof(ping), 0) == sizeof(ping)); + char pong[sizeof(ping)]{}; + Require(recv_nid_postfix(pair[1], pong, sizeof(pong), 0) == sizeof(pong)); + Require(std::memcmp(ping, pong, sizeof(ping)) == 0); + const char reply[] = "reply"; + Require(send_nid_postfix(pair[1], reply, sizeof(reply), 0) == sizeof(reply)); + char answer[sizeof(reply)]{}; + Require(recv_nid_postfix(pair[0], answer, sizeof(answer), 0) == sizeof(answer)); + Require(std::memcmp(reply, answer, sizeof(reply)) == 0); + Require(close_nid_postfix(pair[0]) == 0); + Require(close_nid_postfix(pair[1]) == 0); + int datagrams[2]{}; + Require(socketpair_nid_postfix(2, 2, 0, datagrams) == 0); + Require(datagrams[0] >= 0 && datagrams[1] >= 0 && datagrams[0] != datagrams[1]); + const char datagram[] = "datagram"; + Require(send_nid_postfix(datagrams[0], datagram, sizeof(datagram), 0) == sizeof(datagram)); + char received[sizeof(datagram)]{}; + Require(recv_nid_postfix(datagrams[1], received, sizeof(received), 0) == sizeof(received)); + Require(std::memcmp(datagram, received, sizeof(datagram)) == 0); + Require(close_nid_postfix(datagrams[0]) == 0); + Require(close_nid_postfix(datagrams[1]) == 0); + int local[2]{}; + Require(socketpair_nid_postfix(1, 1, 0, local) == 0); + Require(local[0] >= 0 && local[1] >= 0 && local[0] != local[1]); + Require(send_nid_postfix(local[0], ping, sizeof(ping), 0) == sizeof(ping)); + std::memset(pong, 0, sizeof(pong)); + Require(recv_nid_postfix(local[1], pong, sizeof(pong), 0) == sizeof(pong)); + Require(std::memcmp(ping, pong, sizeof(ping)) == 0); + Require(close_nid_postfix(local[0]) == 0); + Require(close_nid_postfix(local[1]) == 0); } diff --git a/core/libs/tests/GuestVideoOutWqhdDetection.cpp b/core/libs/tests/GuestVideoOutWqhdDetection.cpp index d0196accf2c..3e68fa5f15c 100644 --- a/core/libs/tests/GuestVideoOutWqhdDetection.cpp +++ b/core/libs/tests/GuestVideoOutWqhdDetection.cpp @@ -8,6 +8,7 @@ extern "C" { int APS5_VABI sceVideoOutOpen(int userId, int busType, int index, const void* param); int APS5_VABI sceVideoOutClose(int handle); int APS5_VABI sceVideoOutGetOutputStatus(int handle, VideoOutOutputStatus* status); +int APS5_VABI sceVideoOutGetResolutionStatus(int handle, VideoOutResolutionStatus* status); int APS5_VABI sceVideoOutAllowOutputResolutionWqhdDetection(int handle); } @@ -46,6 +47,25 @@ int main() { Require(RejectsHandle(-1)); Require(RejectsHandle(NEVER_OPENED_HANDLE)); + VideoOutResolutionStatus resolution{}; + Require(sceVideoOutGetResolutionStatus(handle, &resolution) == 0); + Require(resolution.fullWidth == 1920 && resolution.fullHeight == 1080); + Require(resolution.paneWidth == 1920 && resolution.paneHeight == 1080); + bool rejected = false; + try { + sceVideoOutGetResolutionStatus(NEVER_OPENED_HANDLE, &resolution); + } catch (const std::runtime_error&) { + rejected = true; + } + Require(rejected); + rejected = false; + try { + sceVideoOutGetResolutionStatus(handle, nullptr); + } catch (const std::runtime_error&) { + rejected = true; + } + Require(rejected); + Require(sceVideoOutClose(handle) == 0); Require(RejectsHandle(handle)); LibcRunShutdown_nid_postfix();