feat(libSceHmd): add a disconnected-headset HLE stub

This commit is contained in:
julienlgy 2026-10-01 14:18:10 +02:00
parent bc51b337ad
commit 0c4dace39e
4 changed files with 223 additions and 0 deletions

View File

@ -0,0 +1,71 @@
#include "Hmd.hpp"
#include <atomic>
namespace {
std::atomic<bool> initialized{false};
std::int32_t Initialize(const Hmd::InitializeParam* param) {
if (initialized.load()) return Hmd::AlreadyInitialized;
if (!param) return Hmd::ParameterNull;
if (param->reserved0) return Hmd::UnsupportedFeature;
return initialized.exchange(true) ? Hmd::AlreadyInitialized : 0;
}
}
extern "C" {
std::int32_t APS5_VABI sceHmdInitialize(const Hmd::InitializeParam* param) {
return Initialize(param);
}
std::int32_t APS5_VABI sceHmdInitialize315(const Hmd::InitializeParam* param) {
return Initialize(param);
}
std::int32_t APS5_VABI sceHmdTerminate() {
return initialized.exchange(false) ? 0 : Hmd::NotInitialized;
}
std::int32_t APS5_VABI sceHmdOpen(std::int32_t userId, std::int32_t type, std::int32_t index,
Hmd::OpenParam* param) {
if (!initialized.load()) return Hmd::NotInitialized;
if (type != 0 || index != 0 || param || userId == -1 || userId == 0xff)
return Hmd::ParameterInvalid;
return Hmd::DeviceDisconnected;
}
std::int32_t APS5_VABI sceHmdClose(std::int32_t handle) {
(void)handle;
return initialized.load() ? Hmd::InvalidHandle : Hmd::NotInitialized;
}
std::int32_t APS5_VABI sceHmdGetDeviceInformation(Hmd::DeviceInformation* info) {
if (!info) return Hmd::ParameterNull;
if (!initialized.load()) return Hmd::NotInitialized;
*info = {};
info->status = Hmd::DeviceStatus::NotDetected;
info->userId = -1;
return 0;
}
std::int32_t APS5_VABI sceHmdGetDeviceInformationByHandle(std::int32_t handle,
Hmd::DeviceInformation* info) {
(void)handle;
(void)info;
return initialized.load() ? Hmd::InvalidHandle : Hmd::NotInitialized;
}
std::int32_t APS5_VABI sceHmdInternalGetDeviceStatus(Hmd::DeviceStatus* status) {
if (!status) return Hmd::ParameterNull;
*status = Hmd::DeviceStatus::NotDetected;
return 0;
}
std::int32_t APS5_VABI sceHmdInternalMmapIsConnect() {
return 0;
}
}

View File

@ -0,0 +1,68 @@
#ifndef CORE_LIBS_PRX_LIBSCEHMD_HMD_HPP
#define CORE_LIBS_PRX_LIBSCEHMD_HMD_HPP
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstddef>
#include <cstdint>
namespace Hmd {
inline constexpr std::int32_t AlreadyInitialized = static_cast<std::int32_t>(0x81110001);
inline constexpr std::int32_t NotInitialized = static_cast<std::int32_t>(0x81110002);
inline constexpr std::int32_t InvalidHandle = static_cast<std::int32_t>(0x81110003);
inline constexpr std::int32_t DeviceDisconnected = static_cast<std::int32_t>(0x81110004);
inline constexpr std::int32_t ParameterNull = static_cast<std::int32_t>(0x81110008);
inline constexpr std::int32_t ParameterInvalid = static_cast<std::int32_t>(0x81110009);
inline constexpr std::int32_t UnsupportedFeature = static_cast<std::int32_t>(0x81110016);
enum class DeviceStatus : std::uint32_t {
Ready = 0,
NotReady = 1,
NotDetected = 2,
HmuDisconnected = 3,
};
struct InitializeParam {
void* reserved0;
std::uint8_t reserved[8];
};
struct OpenParam;
struct DeviceInformation {
DeviceStatus status;
std::int32_t userId;
std::uint8_t reserved0[4];
std::uint32_t panelWidth;
std::uint32_t panelHeight;
std::uint16_t latency90Hz;
std::uint16_t latency120Hz;
std::uint8_t hmuMount;
std::uint8_t reserved1[7];
};
static_assert(sizeof(InitializeParam) == 16);
static_assert(sizeof(DeviceInformation) == 32);
static_assert(offsetof(DeviceInformation, userId) == 4);
static_assert(offsetof(DeviceInformation, panelWidth) == 12);
static_assert(offsetof(DeviceInformation, latency90Hz) == 20);
static_assert(offsetof(DeviceInformation, hmuMount) == 24);
}
extern "C" {
std::int32_t APS5_VABI sceHmdInitialize(const Hmd::InitializeParam* param);
std::int32_t APS5_VABI sceHmdInitialize315(const Hmd::InitializeParam* param);
std::int32_t APS5_VABI sceHmdTerminate();
std::int32_t APS5_VABI sceHmdOpen(std::int32_t userId, std::int32_t type, std::int32_t index,
Hmd::OpenParam* param);
std::int32_t APS5_VABI sceHmdClose(std::int32_t handle);
std::int32_t APS5_VABI sceHmdGetDeviceInformation(Hmd::DeviceInformation* info);
std::int32_t APS5_VABI sceHmdGetDeviceInformationByHandle(std::int32_t handle,
Hmd::DeviceInformation* info);
std::int32_t APS5_VABI sceHmdInternalGetDeviceStatus(Hmd::DeviceStatus* status);
std::int32_t APS5_VABI sceHmdInternalMmapIsConnect();
}
#endif

View File

@ -0,0 +1,83 @@
#include "prx/libSceHmd/Hmd.hpp"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <initializer_list>
namespace {
void Require(bool condition, const char* message) {
if (!condition) {
std::fprintf(stderr, "HMD: %s\n", message);
std::abort();
}
}
void CheckDisconnected() {
Hmd::DeviceStatus status = Hmd::DeviceStatus::Ready;
Require(sceHmdInternalGetDeviceStatus(&status) == 0, "status query failed");
Require(status == Hmd::DeviceStatus::NotDetected, "headset reported connected");
Require(sceHmdInternalGetDeviceStatus(nullptr) == Hmd::ParameterNull, "null status");
Require(sceHmdInternalMmapIsConnect() == 0, "connection reported present");
}
}
int main() {
Hmd::InitializeParam param{};
Hmd::DeviceInformation info;
std::memset(&info, 0xa5, sizeof(info));
const auto untouched = info;
CheckDisconnected();
Require(sceHmdTerminate() == Hmd::NotInitialized, "terminate before initialize");
Require(sceHmdOpen(1, 0, 0, nullptr) == Hmd::NotInitialized, "open before initialize");
Require(sceHmdClose(0) == Hmd::NotInitialized, "close before initialize");
Require(sceHmdGetDeviceInformation(nullptr) == Hmd::ParameterNull, "null information");
Require(sceHmdGetDeviceInformation(&info) == Hmd::NotInitialized, "query before initialize");
Require(std::memcmp(&info, &untouched, sizeof(info)) == 0, "failed query modified output");
Require(sceHmdGetDeviceInformationByHandle(0, &info) == Hmd::NotInitialized,
"handle query before initialize");
Require(sceHmdInitialize(nullptr) == Hmd::ParameterNull, "null initialize");
Require(sceHmdInitialize315(nullptr) == Hmd::ParameterNull, "null initialize315");
param.reserved0 = &param;
Require(sceHmdInitialize(&param) == Hmd::UnsupportedFeature, "distortion unsupported");
Require(sceHmdInitialize315(&param) == Hmd::UnsupportedFeature, "distortion315 unsupported");
param.reserved0 = nullptr;
for (const auto initialize : {sceHmdInitialize, sceHmdInitialize315}) {
Require(initialize(&param) == 0, "initialize failed");
Require(sceHmdInitialize(&param) == Hmd::AlreadyInitialized, "duplicate initialize");
Require(sceHmdInitialize315(&param) == Hmd::AlreadyInitialized, "shared initialization");
Require(sceHmdInitialize(nullptr) == Hmd::AlreadyInitialized, "duplicate precedence");
CheckDisconnected();
Require(sceHmdGetDeviceInformation(&info) == 0, "device query failed");
Hmd::DeviceInformation expected{};
expected.status = Hmd::DeviceStatus::NotDetected;
expected.userId = -1;
Require(std::memcmp(&info, &expected, sizeof(info)) == 0, "incorrect absent-device output");
Require(sceHmdGetDeviceInformation(nullptr) == Hmd::ParameterNull, "null device query");
Require(sceHmdOpen(-1, 0, 0, nullptr) == Hmd::ParameterInvalid, "invalid user");
Require(sceHmdOpen(0xff, 0, 0, nullptr) == Hmd::ParameterInvalid, "system user");
Require(sceHmdOpen(1, 1, 0, nullptr) == Hmd::ParameterInvalid, "invalid type");
Require(sceHmdOpen(1, 0, 1, nullptr) == Hmd::ParameterInvalid, "invalid index");
Require(sceHmdOpen(1, 0, 0, reinterpret_cast<Hmd::OpenParam*>(&param)) ==
Hmd::ParameterInvalid, "non-null reserved open parameter");
Require(sceHmdOpen(1, 0, 0, nullptr) == Hmd::DeviceDisconnected, "open faked a handle");
Require(sceHmdOpen(1, 0, 0, nullptr) == Hmd::DeviceDisconnected, "failed open created state");
for (const auto handle : {0, -1, 1, 0x0f000000}) {
Require(sceHmdClose(handle) == Hmd::InvalidHandle, "close accepted invalid handle");
Require(sceHmdGetDeviceInformationByHandle(handle, &info) == Hmd::InvalidHandle,
"query accepted invalid handle");
Require(std::memcmp(&info, &expected, sizeof(info)) == 0, "invalid handle modified output");
}
Require(sceHmdGetDeviceInformationByHandle(0, nullptr) == Hmd::InvalidHandle,
"null query accepted invalid handle");
Require(sceHmdTerminate() == 0, "terminate failed");
Require(sceHmdTerminate() == Hmd::NotInitialized, "duplicate terminate");
Require(sceHmdGetDeviceInformation(&info) == Hmd::NotInitialized, "query after terminate");
CheckDisconnected();
}
}

View File

@ -13,6 +13,7 @@
Throughout the project, every function at every stage either **does exactly what it's supposed to or throws an exception**. Everywhere... except:
- [libSceSaveDataDialog.native](../../core/libs/prx/libSceSaveDataDialog.native/Export.cpp)
- [libSceCommonDialog](../../core/libs/prx/libSceCommonDialog/Export.cpp)
- [libSceHmd](../../core/libs/prx/libSceHmd/Export.cpp) implements only the disconnected-headset path: initialization succeeds, device queries report `NotDetected`, and opening a device returns `DeviceDisconnected`. Headset support, tracking, rendering and additional HMD exports are not implemented; SDK-level ABI compatibility and in-game behaviour remain unverified.
- The shader recompiler [skips baryctric coordinates](../../core/shader/recompiler/Recompiler.cpp) (is not even passed to SpirvTargetOptions at row 212).
### Unknown function info