feat(libSceCompanionHttpd): keep the server state with no companion device

Initialize, register a request callback, start, stop and terminate keep the
library's state with its errors; no companion device connects, so
sceCompanionHttpdGetEvent always returns NO_EVENT and the callback is never
called. The response builders are declared and throw.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Savvanis SpyrosandClaude Opus 5.5 authored and blawesom committed 2026-10-20 09:39:58 +02:00
1 parent 3fd1cc393b
commit 346cafdb84
4 files changed
+277 -50

No files matched your search

@@ -0,0 +1,21 @@
add_library(libSceCompanionHttpd SHARED EXCLUDE_FROM_ALL Export.cpp)
target_include_directories(libSceCompanionHttpd PRIVATE ${LIBS_INCLUDE_DIR})
configure_windows_unwind(libSceCompanionHttpd)
if(MINGW)
foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static)
string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}")
endforeach()
endif()
if(BUILD_TESTING)
add_test_executable(guest_companion_httpd_tests ${LIBS_INCLUDE_DIR}/tests/GuestCompanionHttpd.cpp)
target_include_directories(guest_companion_httpd_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_companion_httpd_tests PRIVATE libSceCompanionHttpd libc)
configure_windows_unwind(guest_companion_httpd_tests)
add_test(NAME guest_companion_httpd COMMAND guest_companion_httpd_tests)
if(WIN32)
set_tests_properties(guest_companion_httpd PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceCompanionHttpd>")
endif()
endif()
+120 -49
View File
@@ -1,91 +1,162 @@
#include <cstdint>
#include <cstddef>
#include <cstring>
#include <mutex>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
namespace {
constexpr int CompanionHttpdErrorInvalidParam = static_cast<int>(0x80E40004u);
constexpr int CompanionHttpdErrorNotInitialized = static_cast<int>(0x80E40006u);
constexpr int CompanionHttpdErrorAlreadyInitialized = static_cast<int>(0x80E40007u);
constexpr int CompanionHttpdErrorNoEvent = static_cast<int>(0x80E40008u);
constexpr int CompanionHttpdErrorAlreadyStarted = static_cast<int>(0x80E4000Au);
constexpr int CompanionHttpdErrorNotStarted = static_cast<int>(0x80E4000Bu);
constexpr int CompanionHttpdErrorAlreadyRegistered = static_cast<int>(0x80E4000Cu);
struct CompanionHttpdOptParam {
std::uint64_t reserved;
void* workMemory;
std::uint64_t workMemorySize;
std::uint8_t settings[0x40];
const void* temporaryMountPoint;
};
static_assert(sizeof(CompanionHttpdOptParam) == 0x60);
using CompanionHttpdRequestCallback = int (*)(int, int, const void*, void*, void*);
struct CompanionHttpdState {
std::mutex mutex;
bool initialized = false;
bool started = false;
CompanionHttpdRequestCallback callback = nullptr;
void* callbackArgument = nullptr;
};
CompanionHttpdState& State() {
static CompanionHttpdState state;
return state;
}
}
extern "C" {
int APS5_VABI sceCompanionHttpdAddHeader() {
int APS5_VABI sceCompanionHttpdOptParamInitialize(CompanionHttpdOptParam* param) {
if (param == nullptr) return CompanionHttpdErrorInvalidParam;
std::memset(param, 0, sizeof(*param));
return 0;
}
int APS5_VABI sceCompanionHttpdInitialize(const CompanionHttpdOptParam* param) {
if (param == nullptr) return CompanionHttpdErrorInvalidParam;
auto& state = State();
std::lock_guard lock(state.mutex);
if (state.initialized) return CompanionHttpdErrorAlreadyInitialized;
state.initialized = true;
return 0;
}
int APS5_VABI sceCompanionHttpdTerminate(void) {
auto& state = State();
std::lock_guard lock(state.mutex);
if (!state.initialized) return CompanionHttpdErrorNotInitialized;
if (state.started) NotImplemented_nid_no_patch(__func__);
state.initialized = false;
state.callback = nullptr;
state.callbackArgument = nullptr;
return 0;
}
int APS5_VABI sceCompanionHttpdRegisterRequestCallback(CompanionHttpdRequestCallback function, void* argument) {
if (function == nullptr) return CompanionHttpdErrorInvalidParam;
auto& state = State();
std::lock_guard lock(state.mutex);
if (!state.initialized) return CompanionHttpdErrorNotInitialized;
if (state.callback != nullptr) return CompanionHttpdErrorAlreadyRegistered;
state.callback = function;
state.callbackArgument = argument;
return 0;
}
int APS5_VABI sceCompanionHttpdUnregisterRequestCallback(void) {
auto& state = State();
std::lock_guard lock(state.mutex);
if (!state.initialized) return CompanionHttpdErrorNotInitialized;
if (state.callback == nullptr) NotImplemented_nid_no_patch(__func__);
state.callback = nullptr;
state.callbackArgument = nullptr;
return 0;
}
int APS5_VABI sceCompanionHttpdStart(void) {
auto& state = State();
std::lock_guard lock(state.mutex);
if (!state.initialized) return CompanionHttpdErrorNotInitialized;
if (state.started) return CompanionHttpdErrorAlreadyStarted;
state.started = true;
return 0;
}
int APS5_VABI sceCompanionHttpdStop(void) {
auto& state = State();
std::lock_guard lock(state.mutex);
if (!state.initialized) return CompanionHttpdErrorNotInitialized;
if (!state.started) return CompanionHttpdErrorNotStarted;
state.started = false;
return 0;
}
int APS5_VABI sceCompanionHttpdGetEvent(void* event) {
if (event == nullptr) return CompanionHttpdErrorInvalidParam;
auto& state = State();
std::lock_guard lock(state.mutex);
if (!state.initialized) return CompanionHttpdErrorNotInitialized;
return CompanionHttpdErrorNoEvent;
}
int APS5_VABI sceCompanionHttpdAddHeader(const char* key, const char* value, void* response) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceCompanionHttpdGet2ndScreenStatus() {
int APS5_VABI sceCompanionHttpdSetStatus(int status, void* response) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceCompanionHttpdGetEvent() {
int APS5_VABI sceCompanionHttpdSetBody(const char* body, std::uint64_t size, void* response) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceCompanionHttpdGetUserId() {
int APS5_VABI sceCompanionHttpdGet2ndScreenStatus(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceCompanionHttpdInitialize() {
int APS5_VABI sceCompanionHttpdGetUserId(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceCompanionHttpdInitialize2() {
int APS5_VABI sceCompanionHttpdInitialize2(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceCompanionHttpdOptParamInitialize() {
int APS5_VABI sceCompanionHttpdRegisterRequestBodyReceptionCallback(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceCompanionHttpdRegisterRequestBodyReceptionCallback() {
int APS5_VABI sceCompanionHttpdRegisterRequestCallback2(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceCompanionHttpdRegisterRequestCallback() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceCompanionHttpdRegisterRequestCallback2() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceCompanionHttpdSetBody() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceCompanionHttpdSetStatus() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceCompanionHttpdStart() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceCompanionHttpdStop() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceCompanionHttpdTerminate() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceCompanionHttpdUnregisterRequestBodyReceptionCallback() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceCompanionHttpdUnregisterRequestCallback() {
int APS5_VABI sceCompanionHttpdUnregisterRequestBodyReceptionCallback(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
+129
View File
@@ -0,0 +1,129 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
#include <string>
extern "C" {
int APS5_VABI sceCompanionHttpdOptParamInitialize(void* param);
int APS5_VABI sceCompanionHttpdInitialize(const void* param);
int APS5_VABI sceCompanionHttpdTerminate(void);
int APS5_VABI sceCompanionHttpdRegisterRequestCallback(void* function, void* argument);
int APS5_VABI sceCompanionHttpdUnregisterRequestCallback(void);
int APS5_VABI sceCompanionHttpdStart(void);
int APS5_VABI sceCompanionHttpdStop(void);
int APS5_VABI sceCompanionHttpdGetEvent(void* event);
int APS5_VABI sceCompanionHttpdAddHeader(const char* key, const char* value, void* response);
int APS5_VABI sceCompanionHttpdSetStatus(int status, void* response);
int APS5_VABI sceCompanionHttpdSetBody(const char* body, std::uint64_t size, void* response);
int APS5_VABI sceCompanionHttpdGet2ndScreenStatus(void);
int APS5_VABI sceCompanionHttpdGetUserId(void);
int APS5_VABI sceCompanionHttpdInitialize2(void);
int APS5_VABI sceCompanionHttpdRegisterRequestBodyReceptionCallback(void);
int APS5_VABI sceCompanionHttpdRegisterRequestCallback2(void);
int APS5_VABI sceCompanionHttpdUnregisterRequestBodyReceptionCallback(void);
}
namespace {
constexpr int InvalidParam = static_cast<int>(0x80E40004u);
constexpr int NotInitialized = static_cast<int>(0x80E40006u);
constexpr int AlreadyInitialized = static_cast<int>(0x80E40007u);
constexpr int NoEvent = static_cast<int>(0x80E40008u);
constexpr int AlreadyStarted = static_cast<int>(0x80E4000Au);
constexpr int NotStarted = static_cast<int>(0x80E4000Bu);
constexpr int AlreadyRegistered = static_cast<int>(0x80E4000Cu);
void Require(bool condition, const char* message) {
if (!condition) {
std::fprintf(stderr, "CompanionHttpd: %s\n", message);
std::abort();
}
}
template <typename TCall>
void RequireNotImplemented(TCall call, const std::string& name) {
try {
call();
} catch (const std::runtime_error& error) {
Require(std::string(error.what()) == name + " not implemented", "a companion function threw another error");
return;
}
Require(false, "a companion function that should throw returned");
}
int APS5_VABI RequestCallback(int, int, const void*, void*, void*) {
std::fprintf(stderr, "CompanionHttpd: the request callback was called\n");
std::abort();
}
void CheckOptionBlock() {
unsigned char block[0x70];
std::memset(block, 0x5a, sizeof(block));
Require(sceCompanionHttpdOptParamInitialize(nullptr) == InvalidParam, "a null option block must be rejected");
Require(sceCompanionHttpdOptParamInitialize(block) == 0, "sceCompanionHttpdOptParamInitialize failed");
for (std::size_t index = 0; index < sizeof(block); ++index) {
Require(block[index] == (index < 0x60 ? 0u : 0x5au), "the option block must be zeroed over its 0x60 bytes and nothing past them");
}
}
void CheckStates() {
unsigned char param[0x60]{};
unsigned char event[0x104];
std::memset(event, 0x5a, sizeof(event));
unsigned char untouched[0x104];
std::memset(untouched, 0x5a, sizeof(untouched));
auto* callback = reinterpret_cast<void*>(&RequestCallback);
Require(sceCompanionHttpdGetEvent(event) == NotInitialized, "an event before initialization");
Require(sceCompanionHttpdStart() == NotInitialized, "a start before initialization");
Require(sceCompanionHttpdStop() == NotInitialized, "a stop before initialization");
Require(sceCompanionHttpdTerminate() == NotInitialized, "a termination before initialization");
Require(sceCompanionHttpdRegisterRequestCallback(callback, nullptr) == NotInitialized, "a callback before initialization");
Require(sceCompanionHttpdUnregisterRequestCallback() == NotInitialized, "an unregistration before initialization");
Require(sceCompanionHttpdInitialize(nullptr) == InvalidParam, "a null parameter must be rejected");
Require(sceCompanionHttpdInitialize(param) == 0, "sceCompanionHttpdInitialize failed");
Require(sceCompanionHttpdInitialize(param) == AlreadyInitialized, "a second initialization");
Require(sceCompanionHttpdRegisterRequestCallback(nullptr, nullptr) == InvalidParam, "a null callback must be rejected");
Require(sceCompanionHttpdRegisterRequestCallback(callback, param) == 0, "registering the request callback failed");
Require(sceCompanionHttpdRegisterRequestCallback(callback, param) == AlreadyRegistered, "a second callback");
Require(sceCompanionHttpdGetEvent(nullptr) == InvalidParam, "a null event must be rejected");
Require(sceCompanionHttpdGetEvent(event) == NoEvent, "no companion device connects before the start");
Require(sceCompanionHttpdStop() == NotStarted, "a stop before the start");
Require(sceCompanionHttpdStart() == 0, "sceCompanionHttpdStart failed");
Require(sceCompanionHttpdStart() == AlreadyStarted, "a second start");
Require(sceCompanionHttpdGetEvent(event) == NoEvent, "no companion device connects after the start");
Require(std::memcmp(event, untouched, sizeof(event)) == 0, "sceCompanionHttpdGetEvent wrote the event");
RequireNotImplemented([] { sceCompanionHttpdTerminate(); }, "sceCompanionHttpdTerminate");
Require(sceCompanionHttpdStop() == 0, "sceCompanionHttpdStop failed");
Require(sceCompanionHttpdUnregisterRequestCallback() == 0, "unregistering the request callback failed");
RequireNotImplemented([] { sceCompanionHttpdUnregisterRequestCallback(); }, "sceCompanionHttpdUnregisterRequestCallback");
Require(sceCompanionHttpdTerminate() == 0, "sceCompanionHttpdTerminate failed");
Require(sceCompanionHttpdTerminate() == NotInitialized, "a second termination");
Require(sceCompanionHttpdInitialize(param) == 0, "initializing again after the termination failed");
Require(sceCompanionHttpdRegisterRequestCallback(callback, nullptr) == 0, "the termination kept the callback");
Require(sceCompanionHttpdTerminate() == 0, "the last termination failed");
}
void CheckResponseBuilders() {
unsigned char response[0x20]{};
RequireNotImplemented([&] { sceCompanionHttpdAddHeader("key", "value", response); }, "sceCompanionHttpdAddHeader");
RequireNotImplemented([&] { sceCompanionHttpdSetStatus(200, response); }, "sceCompanionHttpdSetStatus");
RequireNotImplemented([&] { sceCompanionHttpdSetBody("body", 4, response); }, "sceCompanionHttpdSetBody");
RequireNotImplemented([] { sceCompanionHttpdGet2ndScreenStatus(); }, "sceCompanionHttpdGet2ndScreenStatus");
RequireNotImplemented([] { sceCompanionHttpdGetUserId(); }, "sceCompanionHttpdGetUserId");
RequireNotImplemented([] { sceCompanionHttpdInitialize2(); }, "sceCompanionHttpdInitialize2");
RequireNotImplemented([] { sceCompanionHttpdRegisterRequestBodyReceptionCallback(); }, "sceCompanionHttpdRegisterRequestBodyReceptionCallback");
RequireNotImplemented([] { sceCompanionHttpdRegisterRequestCallback2(); }, "sceCompanionHttpdRegisterRequestCallback2");
RequireNotImplemented([] { sceCompanionHttpdUnregisterRequestBodyReceptionCallback(); }, "sceCompanionHttpdUnregisterRequestBodyReceptionCallback");
}
}
int main() {
CheckOptionBlock();
CheckStates();
CheckResponseBuilders();
return 0;
}
+7 -1
View File
@@ -2,4 +2,10 @@
### Unknown function info
- [sceCompanionHttpdAddHeader, sceCompanionHttpdGet2ndScreenStatus, sceCompanionHttpdGetEvent, sceCompanionHttpdGetUserId, sceCompanionHttpdInitialize, sceCompanionHttpdInitialize2, sceCompanionHttpdOptParamInitialize, sceCompanionHttpdRegisterRequestBodyReceptionCallback, sceCompanionHttpdRegisterRequestCallback, sceCompanionHttpdRegisterRequestCallback2, sceCompanionHttpdSetBody, sceCompanionHttpdSetStatus, sceCompanionHttpdStart, sceCompanionHttpdStop, sceCompanionHttpdTerminate, sceCompanionHttpdUnregisterRequestBodyReceptionCallback and sceCompanionHttpdUnregisterRequestCallback](../../../core/libs/prx/libSceCompanionHttpd/Export.cpp) (libSceCompanionHttpd) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCompanionHttpd.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified
- [sceCompanionHttpdOptParamInitialize, sceCompanionHttpdInitialize](../../../core/libs/prx/libSceCompanionHttpd/Export.cpp) (libSceCompanionHttpd) - the option block is taken as 0x60 bytes: titles fill a work memory pointer at +0x08, its size at +0x10 and a temporary data mount point at +0x58 after `sceCompanionHttpdOptParamInitialize`. Its other fields and the defaults the console writes are unknown, so `sceCompanionHttpdOptParamInitialize` zeroes the 0x60 bytes and `sceCompanionHttpdInitialize` reads none of them; both reject a null block with `INVALID_PARAM`
- [sceCompanionHttpdAddHeader, sceCompanionHttpdSetStatus, sceCompanionHttpdSetBody](../../../core/libs/prx/libSceCompanionHttpd/Export.cpp) (libSceCompanionHttpd) - imported by PS4 titles and added without an implementation. They fill the response of a request callback, which is never called here, so every call throws
- [sceCompanionHttpdGet2ndScreenStatus, sceCompanionHttpdGetUserId, sceCompanionHttpdInitialize2, sceCompanionHttpdRegisterRequestBodyReceptionCallback, sceCompanionHttpdRegisterRequestCallback2, sceCompanionHttpdUnregisterRequestBodyReceptionCallback](../../../core/libs/prx/libSceCompanionHttpd/Export.cpp) (libSceCompanionHttpd) - the rest of the library's exports, declared without parameters and without an implementation; their signatures are not verified, so every call throws
### Functional
- [Companion server state](../../../core/libs/prx/libSceCompanionHttpd/Export.cpp) (libSceCompanionHttpd) - no HTTP server listens, so no companion device ever connects: `sceCompanionHttpdGetEvent` returns `NO_EVENT` without writing the event and the request callback is never called. Initialization, callback registration and start and stop are tracked with the errors of shadPS4's [companion_error.h](https://github.com/shadps4-emu/shadPS4/blob/eb9f66c349ae7c8fef780f4f55e98b8065947e14/src/core/libraries/companion/companion_error.h) (`ALREADY_REGISTERED` is 0x80E4000C, between `NOT_STARTED` and `NOT_CONNECTED`; the header has 0x80E4000, one digit short). Unregistering with no callback and terminating a started server throw, since the console's answers are unknown. Not measured on a console. Closes when: a companion server is hosted