diff --git a/core/libs/prx/libSceHmd/Export.cpp b/core/libs/prx/libSceHmd/Export.cpp new file mode 100644 index 000000000..36c2ca3c0 --- /dev/null +++ b/core/libs/prx/libSceHmd/Export.cpp @@ -0,0 +1,71 @@ +#include "Hmd.hpp" + +#include + +namespace { + +std::atomic 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; +} + +} diff --git a/core/libs/prx/libSceHmd/Hmd.hpp b/core/libs/prx/libSceHmd/Hmd.hpp new file mode 100644 index 000000000..cfbb52bff --- /dev/null +++ b/core/libs/prx/libSceHmd/Hmd.hpp @@ -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 +#include + +namespace Hmd { + +inline constexpr std::int32_t AlreadyInitialized = static_cast(0x81110001); +inline constexpr std::int32_t NotInitialized = static_cast(0x81110002); +inline constexpr std::int32_t InvalidHandle = static_cast(0x81110003); +inline constexpr std::int32_t DeviceDisconnected = static_cast(0x81110004); +inline constexpr std::int32_t ParameterNull = static_cast(0x81110008); +inline constexpr std::int32_t ParameterInvalid = static_cast(0x81110009); +inline constexpr std::int32_t UnsupportedFeature = static_cast(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 diff --git a/core/libs/tests/GuestHmd.cpp b/core/libs/tests/GuestHmd.cpp new file mode 100644 index 000000000..6b97bea9c --- /dev/null +++ b/core/libs/tests/GuestHmd.cpp @@ -0,0 +1,83 @@ +#include "prx/libSceHmd/Hmd.hpp" + +#include +#include +#include +#include + +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 = ¶m; + Require(sceHmdInitialize(¶m) == Hmd::UnsupportedFeature, "distortion unsupported"); + Require(sceHmdInitialize315(¶m) == Hmd::UnsupportedFeature, "distortion315 unsupported"); + param.reserved0 = nullptr; + + for (const auto initialize : {sceHmdInitialize, sceHmdInitialize315}) { + Require(initialize(¶m) == 0, "initialize failed"); + Require(sceHmdInitialize(¶m) == Hmd::AlreadyInitialized, "duplicate initialize"); + Require(sceHmdInitialize315(¶m) == 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(¶m)) == + 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(); + } +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 4f89d4097..755787bbb 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -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