feat(libSceHttp): implement sceHttpAbortWaitRequest and the unlimited sceHttpWaitRequest
Deathloop (PPSA01670) imports sceHttpAbortWaitRequest; Planet Coaster (PPSA01736) and Palworld import sceHttpWaitRequest. Epoll handles are kept in a registry with an abort flag, a destroyed flag and a condition variable. A wait with a negative timeout blocks until sceHttpAbortWaitRequest, sceHttpDestroyEpoll or the sceHttpTerm of the last context wakes it; a bounded wait still returns 0 events. An abort stays set and makes every later wait return SCE_HTTP_ERROR_ABORTED (0x80431080); a wait woken by destroy or term returns 0 events. sceHttpDestroyEpoll no longer frees a handle a waiter is blocked on. sceHttpInit registers its context, and the epoll calls and sceHttpTerm return BEFORE_INIT without a live context and INVALID_ID for an unknown context or handle. Behaviour and error codes: shadPS4 src/core/libraries/network/http.cpp and http_error.h at a8a70da595da2a96e0410309472ba4a7eb558144. Signature (void* eh): Orbital tools/ida/db_types.json at b321fcdbd700cc02da1cb1ad25412781d5ec73c6 and SharpProspero src/SharpProspero/Interop/Net/Http.cs at 60977b29103452b9681ae10c7a9011d44965bf16. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit 766997df3f8329fd6e8e7aac2b5d83daf88c8292)
This commit is contained in:
parent
a11c8f7f70
commit
4c7d83a2bf
|
|
@ -1253,7 +1253,7 @@ union NetCtlInfo { std::uint8_t opaque[256]; };
|
|||
|
||||
using NetCtlCallback = void (*)(int, void*);
|
||||
|
||||
struct HttpEpoll {};
|
||||
struct HttpEpoll;
|
||||
using HttpEpollHandle = HttpEpoll*;
|
||||
using HttpsCallback = int (*)(int, unsigned int, void* const*, int, void*);
|
||||
using HttpRedirectCallback = int (*)(int, std::int32_t, std::int32_t*, const char*, void*);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ configure_windows_unwind(libSceHttp)
|
|||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_http_tests ${LIBS_INCLUDE_DIR}/tests/GuestHttp.cpp)
|
||||
target_include_directories(guest_http_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_http_tests PRIVATE libSceHttp)
|
||||
target_link_libraries(guest_http_tests PRIVATE libSceHttp libc)
|
||||
configure_windows_unwind(guest_http_tests)
|
||||
add_test(NAME guest_http COMMAND guest_http_tests)
|
||||
add_test_executable(guest_http_response_header_tests ${LIBS_INCLUDE_DIR}/tests/GuestHttpResponseHeader.cpp)
|
||||
target_include_directories(guest_http_response_header_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
|
|
|
|||
|
|
@ -5,12 +5,16 @@
|
|||
#include "prx/libSceHttp/src/HttpErrors.hpp"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
// No network is emulated: contexts, templates and requests can be created, but any request
|
||||
// that would touch the network fails with the library's network error.
|
||||
|
|
@ -23,6 +27,28 @@ struct RequestStatusRegistration {
|
|||
static std::mutex g_statusMutex;
|
||||
static std::map<int, RequestStatusRegistration> g_statusCallbacks;
|
||||
|
||||
struct HttpEpoll {
|
||||
std::mutex mutex;
|
||||
std::condition_variable wake;
|
||||
bool aborted = false;
|
||||
bool destroyed = false;
|
||||
};
|
||||
|
||||
static std::mutex g_stateMutex;
|
||||
static std::unordered_set<int> g_contexts;
|
||||
static std::unordered_map<HttpEpollHandle, std::shared_ptr<HttpEpoll>> g_epolls;
|
||||
|
||||
static std::shared_ptr<HttpEpoll> FindEpoll(HttpEpollHandle eh) {
|
||||
const auto it = g_epolls.find(eh);
|
||||
return it == g_epolls.end() ? nullptr : it->second;
|
||||
}
|
||||
|
||||
static void MarkDestroyed(HttpEpoll& epoll) {
|
||||
std::lock_guard lock(epoll.mutex);
|
||||
epoll.destroyed = true;
|
||||
epoll.wake.notify_all();
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI sceHttpAbortRequest(int request_id) {
|
||||
|
|
@ -55,9 +81,13 @@ int APS5_VABI sceHttpCreateConnectionWithURL(int tmpl_id, const char* url, int e
|
|||
}
|
||||
|
||||
int APS5_VABI sceHttpCreateEpoll(int http_ctx_id, HttpEpollHandle* eh) {
|
||||
(void)http_ctx_id;
|
||||
std::lock_guard lock(g_stateMutex);
|
||||
if (g_contexts.empty()) return ERROR_BEFORE_INIT;
|
||||
if (!g_contexts.contains(http_ctx_id)) return ERROR_INVALID_ID;
|
||||
if (!eh) return ERROR_INVALID_VALUE;
|
||||
*eh = new HttpEpoll{};
|
||||
auto epoll = std::make_shared<HttpEpoll>();
|
||||
*eh = epoll.get();
|
||||
g_epolls.emplace(epoll.get(), std::move(epoll));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -102,8 +132,17 @@ int APS5_VABI sceHttpDeleteTemplate(int tmpl_id) {
|
|||
}
|
||||
|
||||
int APS5_VABI sceHttpDestroyEpoll(int http_ctx_id, HttpEpollHandle eh) {
|
||||
(void)http_ctx_id;
|
||||
delete eh;
|
||||
std::shared_ptr<HttpEpoll> epoll;
|
||||
{
|
||||
std::lock_guard lock(g_stateMutex);
|
||||
if (g_contexts.empty()) return ERROR_BEFORE_INIT;
|
||||
if (!g_contexts.contains(http_ctx_id)) return ERROR_INVALID_ID;
|
||||
if (!eh) return ERROR_INVALID_VALUE;
|
||||
epoll = FindEpoll(eh);
|
||||
if (!epoll) return ERROR_INVALID_ID;
|
||||
g_epolls.erase(eh);
|
||||
}
|
||||
MarkDestroyed(*epoll);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -131,7 +170,10 @@ int APS5_VABI sceHttpInit_nid_postfix(int memid, int ssl_ctx_id, uint64_t pool_s
|
|||
(void)memid;
|
||||
(void)ssl_ctx_id;
|
||||
(void)pool_size;
|
||||
return g_nextHandle.fetch_add(1, std::memory_order_relaxed);
|
||||
const int id = g_nextHandle.fetch_add(1, std::memory_order_relaxed);
|
||||
std::lock_guard lock(g_stateMutex);
|
||||
g_contexts.insert(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHttpsDisableOption(int id, uint32_t ssl_flags) {
|
||||
|
|
@ -248,7 +290,16 @@ int APS5_VABI sceHttpsSetSslCallback(int id, HttpsCallback cbfunc, void* user_ar
|
|||
}
|
||||
|
||||
int APS5_VABI sceHttpTerm_nid_postfix(int http_ctx_id) {
|
||||
(void)http_ctx_id;
|
||||
std::vector<std::shared_ptr<HttpEpoll>> epolls;
|
||||
{
|
||||
std::lock_guard lock(g_stateMutex);
|
||||
if (g_contexts.empty()) return ERROR_BEFORE_INIT;
|
||||
if (g_contexts.erase(http_ctx_id) == 0) return ERROR_INVALID_ID;
|
||||
if (!g_contexts.empty()) return 0;
|
||||
for (auto& [handle, epoll] : g_epolls) epolls.push_back(std::move(epoll));
|
||||
g_epolls.clear();
|
||||
}
|
||||
for (const auto& epoll : epolls) MarkDestroyed(*epoll);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -258,10 +309,19 @@ int APS5_VABI sceHttpUnsetEpoll(int id) {
|
|||
}
|
||||
|
||||
int APS5_VABI sceHttpWaitRequest(HttpEpollHandle eh, HttpNBEvent* nbev, int maxevents, int timeout) {
|
||||
if (!eh || !nbev || maxevents <= 0) return ERROR_INVALID_VALUE;
|
||||
if (timeout < 0) NotImplemented_nid_no_patch(__func__);
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(timeout));
|
||||
return 0;
|
||||
std::shared_ptr<HttpEpoll> epoll;
|
||||
{
|
||||
std::lock_guard lock(g_stateMutex);
|
||||
if (g_contexts.empty()) return ERROR_BEFORE_INIT;
|
||||
if (!eh || !nbev || maxevents <= 0) return ERROR_INVALID_VALUE;
|
||||
epoll = FindEpoll(eh);
|
||||
if (!epoll) return ERROR_INVALID_ID;
|
||||
}
|
||||
std::unique_lock lock(epoll->mutex);
|
||||
const auto woken = [&] { return epoll->aborted || epoll->destroyed; };
|
||||
if (timeout < 0) epoll->wake.wait(lock, woken);
|
||||
else epoll->wake.wait_for(lock, std::chrono::microseconds(timeout), woken);
|
||||
return epoll->aborted ? ERROR_ABORTED : 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHttpCreateRequestWithURL(int conn_id, int method, const char* url, uint64_t content_length) {
|
||||
|
|
@ -434,8 +494,18 @@ int APS5_VABI sceHttpSetRedirectCallback(int id, HttpRedirectCallback cbfunc, vo
|
|||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHttpAbortWaitRequest(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
int APS5_VABI sceHttpAbortWaitRequest(HttpEpollHandle eh) {
|
||||
std::shared_ptr<HttpEpoll> epoll;
|
||||
{
|
||||
std::lock_guard lock(g_stateMutex);
|
||||
if (g_contexts.empty()) return ERROR_BEFORE_INIT;
|
||||
if (!eh) return ERROR_INVALID_VALUE;
|
||||
epoll = FindEpoll(eh);
|
||||
if (!epoll) return ERROR_INVALID_ID;
|
||||
}
|
||||
std::lock_guard lock(epoll->mutex);
|
||||
epoll->aborted = true;
|
||||
epoll->wake.notify_all();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBSCEHTTP_SRC_HTTPERRORS_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEHTTP_SRC_HTTPERRORS_HPP
|
||||
|
||||
inline constexpr int ERROR_BEFORE_INIT = static_cast<int>(0x80431001);
|
||||
inline constexpr int ERROR_OUT_OF_MEMORY = static_cast<int>(0x80431022);
|
||||
inline constexpr int ERROR_NETWORK = static_cast<int>(0x80431063);
|
||||
inline constexpr int ERROR_ABORTED = static_cast<int>(0x80431080);
|
||||
inline constexpr int ERROR_INVALID_ID = static_cast<int>(0x80431100);
|
||||
inline constexpr int ERROR_INVALID_VALUE = static_cast<int>(0x804311FE);
|
||||
inline constexpr int ERROR_INVALID_URL = static_cast<int>(0x80433060);
|
||||
inline constexpr int ERROR_PARSE_HTTP_INVALID_RESPONSE = static_cast<int>(0x80432060);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI sceHttpUriParse(SceHttpUriElement*, const char*, void*, std::size_t*, std::size_t);
|
||||
|
|
@ -18,6 +21,9 @@ int APS5_VABI sceHttpUriSweepPath(char*, const char*, std::size_t);
|
|||
int APS5_VABI sceHttpCreateEpoll(int, HttpEpollHandle*);
|
||||
int APS5_VABI sceHttpDestroyEpoll(int, HttpEpollHandle);
|
||||
int APS5_VABI sceHttpWaitRequest(HttpEpollHandle, HttpNBEvent*, int, int);
|
||||
int APS5_VABI sceHttpAbortWaitRequest(HttpEpollHandle);
|
||||
int APS5_VABI sceHttpInit_nid_postfix(int, int, std::uint64_t);
|
||||
int APS5_VABI sceHttpTerm_nid_postfix(int);
|
||||
int APS5_VABI sceHttpReadData(int, void*, std::size_t);
|
||||
int APS5_VABI sceHttpCreateRequest2(int, const char*, const char*, std::uint64_t);
|
||||
int APS5_VABI sceHttpsEnableOption(int, std::uint32_t);
|
||||
|
|
@ -199,16 +205,65 @@ int main() {
|
|||
Require(sceHttpUriMerge(merged, base, "http://bad host/", &required, sizeof(merged), 0) == invalidUrl);
|
||||
Require(required == 123);
|
||||
|
||||
constexpr int beforeInit = static_cast<int>(0x80431001);
|
||||
constexpr int aborted = static_cast<int>(0x80431080);
|
||||
constexpr int invalidId = static_cast<int>(0x80431100);
|
||||
HttpEpollHandle epoll = nullptr;
|
||||
Require(sceHttpCreateEpoll(1, nullptr) == invalidValue);
|
||||
Require(sceHttpCreateEpoll(1, &epoll) == 0 && epoll != nullptr);
|
||||
HttpNBEvent events[2]{};
|
||||
Require(sceHttpCreateEpoll(1, &epoll) == beforeInit);
|
||||
Require(sceHttpAbortWaitRequest(nullptr) == beforeInit);
|
||||
Require(sceHttpTerm_nid_postfix(1) == beforeInit);
|
||||
const int context = sceHttpInit_nid_postfix(0, 0, 0x10000);
|
||||
Require(context > 0);
|
||||
Require(sceHttpCreateEpoll(-1, &epoll) == invalidId);
|
||||
Require(sceHttpCreateEpoll(context, nullptr) == invalidValue);
|
||||
Require(sceHttpCreateEpoll(context, &epoll) == 0 && epoll != nullptr);
|
||||
Require(sceHttpWaitRequest(epoll, events, 2, 0) == 0);
|
||||
Require(sceHttpWaitRequest(epoll, events, 2, 1000) == 0);
|
||||
Require(sceHttpWaitRequest(epoll, nullptr, 2, 0) == invalidValue);
|
||||
Require(sceHttpWaitRequest(epoll, events, 0, 0) == invalidValue);
|
||||
Require(sceHttpWaitRequest(nullptr, events, 2, 0) == invalidValue);
|
||||
Require(sceHttpDestroyEpoll(1, epoll) == 0);
|
||||
const auto unknownEpoll = reinterpret_cast<HttpEpollHandle>(&events);
|
||||
Require(sceHttpAbortWaitRequest(nullptr) == invalidValue);
|
||||
Require(sceHttpAbortWaitRequest(unknownEpoll) == invalidId);
|
||||
Require(sceHttpWaitRequest(unknownEpoll, events, 2, -1) == invalidId);
|
||||
Require(sceHttpDestroyEpoll(context, nullptr) == invalidValue);
|
||||
Require(sceHttpDestroyEpoll(context, unknownEpoll) == invalidId);
|
||||
Require(sceHttpDestroyEpoll(-1, epoll) == invalidId);
|
||||
|
||||
auto waitWokenBy = [&](auto wake) {
|
||||
std::atomic<bool> started{false};
|
||||
std::atomic<int> result{1};
|
||||
std::thread waiter([&] {
|
||||
started = true;
|
||||
result = sceHttpWaitRequest(epoll, events, 2, -1);
|
||||
});
|
||||
while (!started) std::this_thread::yield();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
Require(result == 1);
|
||||
wake();
|
||||
waiter.join();
|
||||
return result.load();
|
||||
};
|
||||
Require(waitWokenBy([&] { Require(sceHttpAbortWaitRequest(epoll) == 0); }) == aborted);
|
||||
Require(sceHttpWaitRequest(epoll, events, 2, 0) == aborted);
|
||||
Require(sceHttpWaitRequest(epoll, events, 2, -1) == aborted);
|
||||
Require(sceHttpDestroyEpoll(context, epoll) == 0);
|
||||
Require(sceHttpAbortWaitRequest(epoll) == invalidId);
|
||||
Require(sceHttpDestroyEpoll(context, epoll) == invalidId);
|
||||
|
||||
Require(sceHttpCreateEpoll(context, &epoll) == 0);
|
||||
Require(waitWokenBy([&] { Require(sceHttpDestroyEpoll(context, epoll) == 0); }) == 0);
|
||||
|
||||
Require(sceHttpCreateEpoll(context, &epoll) == 0);
|
||||
const int otherContext = sceHttpInit_nid_postfix(0, 0, 0x10000);
|
||||
Require(otherContext > 0 && otherContext != context);
|
||||
Require(sceHttpTerm_nid_postfix(otherContext) == 0);
|
||||
Require(sceHttpTerm_nid_postfix(otherContext) == invalidId);
|
||||
Require(sceHttpWaitRequest(epoll, events, 2, 0) == 0);
|
||||
Require(waitWokenBy([&] { Require(sceHttpTerm_nid_postfix(context) == 0); }) == 0);
|
||||
Require(sceHttpWaitRequest(epoll, events, 2, 0) == beforeInit);
|
||||
Require(sceHttpTerm_nid_postfix(context) == beforeInit);
|
||||
|
||||
char data[16];
|
||||
Require(sceHttpReadData(1, data, sizeof(data)) == network);
|
||||
|
|
|
|||
|
|
@ -335,7 +335,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [sce::Json::InitParameter2](../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - layout inferred, not known: assumed to start with the allocator, its user data and the file buffer size, in that order, and the whole struct is assumed to fit in 40 bytes; `Initializer::initialize(InitParameter2)` ignores them as `initialize(InitParameter)` does
|
||||
- [sceAjmBatchJobSetResampleParameters, sceAjmBatchJobGetResampleInfo](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm) - the ratio is taken as decoded samples per output sample and num_samples as the decoded samples the resampler holds, both inferred from FMOD (Hades II); the console's interpolation is unknown, a 4-point Catmull-Rom is used. The flags argument is ignored, as in shadPS4 and Kyty
|
||||
- [scePlayerReviewDialogOpen, scePlayerReviewDialogGetResult](../../core/libs/prx/libSceCdlgPlayerReview/Export.cpp) (libSceCdlgPlayerReview) - the PS5-only dialog has no public reference: the parameter is not read and the result is assumed to start with the 32-bit common dialog result, as `sceWebBrowserDialogGetResult` assumes
|
||||
- [sceHttpWaitRequest](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the timeout is assumed to be in microseconds and a negative timeout to wait without limit; the invalid-value error for a null handle or event array or a non-positive event count is assumed, not measured. No request is ever pending, so the call waits for the timeout and returns 0 events, and a wait without limit throws
|
||||
- [sceHttpWaitRequest](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the timeout is assumed to be in microseconds and a negative timeout to wait without limit; the invalid-value error for a null handle or event array or a non-positive event count is assumed, not measured. No request is ever pending, so a wait returns 0 events when its timeout passes
|
||||
- [sceVideoOutAddPreVblankStartEvent](../../core/libs/prx/libSceVideoOut/src/Event.cpp) (libSceVideoOut) - how long before the vblank the console signals pre-vblank start is unknown. The event is triggered on each emulated vblank tick immediately before the vblank event, with its own count as the event data
|
||||
- [sched_get_priority_max, sched_get_priority_min](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - `SCHED_OTHER` (2) reports the same 256 to 767 range as `SCHED_FIFO` and `SCHED_RR`; the console's answer is unverified. FreeBSD accepts `SCHED_OTHER` with its own range, and shadPS4 rejects it with `EINVAL`. `pthread_setschedparam` ignores the policy, so a priority taken from this range applies
|
||||
|
||||
|
|
@ -364,6 +364,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [`APS5_THREAD_PRIORITY=rt`](../../core/libs/prx/libSceAgcDriver/Execution/src/ThreadPriority.cpp) (libSceAgcDriver) lowers the process's hard `RLIMIT_RTTIME` when it is above `APS5_THREAD_RTTIME_US` or rtkit's `RTTimeUSecMax`, because [rtkit](https://github.com/heftig/rtkit/blob/c295fa849f52b487be6433e69e08b46251950399/rtkit-daemon.c#L592-L637) (`verify_process_rttime`) reads the hard limit from `/proc/<pid>/limits` and refuses real-time scheduling when it is higher. An unprivileged process cannot raise the hard limit again, so it stays lowered for the process and its threads even when rtkit then refuses the request (burst limit, user or start-time checks) and the thread throws. rtkit's `MaxRealtimePriority` and `RTTimeUSecMax` are read first, so an unreachable rtkit or a priority above its maximum leaves the limit untouched. The lowered limit only matters for threads that later run under a real-time policy
|
||||
- On a device without VK_EXT_robustness2's `nullDescriptor`, such as MoltenVK, a null image descriptor in a typed heap is a [placeholder image](../../core/libs/prx/libSceAgcDriver/Graphics/src/PlaceholderImages.cpp) of that heap's type, 1×1 and cleared to zero, which every null descriptor of the heap shares. Reads give zeros, as `nullDescriptor` allows, but stores and atomics to it are kept, so a later read of a null storage image can return them, and a depth comparison compares against depth 0. A heap whose placeholder the device cannot create, for example 1D or 3D depth, 64-bit atomics without 64-bit image atomics, or multisampled storage without `shaderStorageImageMultisample`, throws when a null descriptor reaches it.
|
||||
- [libSceHttp2 asynchronous requests](../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) never reach the network: `sceHttp2SendRequestAsync` accepts the request, queues a completion whose result is the library's network error and triggers the user event named by the kqueue option (equeue at offset 0, event id at 8, user data at 16, layout inferred from PPSA17221); `sceHttp2WaitAsync` returns that completion. The completion's `event_type` value is unknown and left 0. PPSA17221 completes its request object only after `sceHttp2SendRequestAsync` returns, and its handler for a synchronous failure calls `shared_from_this` before that, so a send must not fail synchronously. A kqueue option or user event that is missing, an extra option, and a wait with no completed operation throw
|
||||
- [sceHttpAbortWaitRequest, sceHttpWaitRequest](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the abort takes only the epoll handle, as in [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json) and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Net/Http.cs); behaviour and error codes follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.cpp) (PS4), not confirmed on a PS5 title or hardware. An abort stays set: every later wait on the handle, a poll included, returns `SCE_HTTP_ERROR_ABORTED` (0x80431080) until the handle is destroyed. A wait woken by `sceHttpDestroyEpoll`, or by the `sceHttpTerm` that ends the last context, returns 0 events. The epoll calls return `BEFORE_INIT` (0x80431001) while no `sceHttpInit` context is alive and `INVALID_ID` (0x80431100) for an unknown context or handle, and `sceHttpTerm` returns the same errors instead of succeeding for any id. Handles are pointers, so a stale handle whose address a newer epoll reuses is taken as that epoll
|
||||
- [system](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) runs no command. It follows FreeBSD 11's `lib/libc/stdlib/system.c` for a sandbox without `/bin/sh`: a null command returns 1 unconditionally, as that version does, and any command returns the wait status of a child that could not exec the shell and called `_exit(127)` (`0x7f00`). Whether the console's application sandbox has no shell, and whether `vfork` succeeds there (FreeBSD returns -1 when it fails), is unverified
|
||||
- [sceKernelWriteThrottlingStatus](../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) reports no write throttling: the host SSD has no write budget, so the first qword of the 32-byte status is set to `UINT32_MAX` and the rest is zeroed. PPSA17221 reads only that qword: it multiplies it by 64 KiB and compares the product with byte thresholds up to 0x4AF00000 to pick one of five tiers. When the call fails the title assumes 0x4B000001 bytes, which selects the same top tier as `UINT32_MAX`. What the value measures on hardware (a remaining budget or a rate) and the meaning of the other three qwords are unknown
|
||||
- [/dev/random and /dev/urandom](../../core/libs/prx/libkernel/File/src/RandomDevice.cpp) (libkernel) opened for reading return a descriptor backed by the host null device whose reads are filled from `std::random_device`; both paths behave the same, never block, and positioned reads ignore the offset (FreeBSD `random(4)`). Only the exact paths are recognised, a `dup`ed descriptor reads as an empty file, `fstat`, `lseek`, `mmap` and `select` see the null device, and opening them for writing throws
|
||||
|
|
|
|||
Loading…
Reference in New Issue