From a3876adc8484ee0a0335e90218ea24ba1418c2a0 Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Tue, 3 Nov 2026 11:44:33 +1300 Subject: [PATCH] fix(libSceHmd2): gate EnableMirroring, GetPredictedDisplayTime and SetRenderConfig on the reprojection session Since Initialize creates a reprojection session, these three still returned 0x8a72000b inside one. Gate them on reprojectionState like the neighbouring exports. Inside a session GetPredictedDisplayTime with a null output and SetRenderConfig with a bit outside bits 0 and 32 or a nonzero second or third argument return the codes measured in a PPSA01317 session; every other form keeps its throwing body. The test tells inside from outside a session and the debt entry says so. --- core/libs/prx/libSceHmd2/Export.cpp | 23 +++++++++++++++++++--- core/libs/tests/GuestHmd2.cpp | 30 +++++++++++++++++++++++++++++ docs/dev/debt/libSceHmd2.md | 2 +- 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceHmd2/Export.cpp b/core/libs/prx/libSceHmd2/Export.cpp index b8a719f448d7..78fe4c0b2c72 100644 --- a/core/libs/prx/libSceHmd2/Export.cpp +++ b/core/libs/prx/libSceHmd2/Export.cpp @@ -237,7 +237,12 @@ int APS5_VABI sceHmd2ReprojectionDisableVrMode() { } int APS5_VABI sceHmd2ReprojectionEnableMirroring(const Hmd2::MirroringParameter* parameter) { - return Hmd2::ReprojectionNotInitialized; + { + std::lock_guard lock(reprojectionMutex); + if (reprojectionState == nullptr) return Hmd2::ReprojectionNotInitialized; + } + NotImplemented_nid_no_patch(__func__); + return 0; } int APS5_VABI sceHmd2ReprojectionEnableVrMode(std::uint64_t mode) { @@ -258,7 +263,13 @@ Hmd2::SizeAlign APS5_VABI sceHmd2ReprojectionGetMirroringWorkMemorySizeAlign() { } int APS5_VABI sceHmd2ReprojectionGetPredictedDisplayTime(std::uint64_t frameId, std::uint64_t* time) { - return Hmd2::ReprojectionNotInitialized; + { + std::lock_guard lock(reprojectionMutex); + if (reprojectionState == nullptr) return Hmd2::ReprojectionNotInitialized; + } + if (time == nullptr) return Hmd2::ParameterNull; + NotImplemented_nid_no_patch(__func__); + return 0; } int APS5_VABI sceHmd2ReprojectionInitialize(const Hmd2::ReprojectionInitializeParameter* parameter, const void* reserved) { @@ -318,7 +329,13 @@ int APS5_VABI sceHmd2ReprojectionSetParamWithBuffer(std::uint32_t bufferSetId, s } int APS5_VABI sceHmd2ReprojectionSetRenderConfig(std::uint64_t flags, std::uint64_t reserved, std::uint64_t reserved2) { - return Hmd2::ReprojectionNotInitialized; + { + std::lock_guard lock(reprojectionMutex); + if (reprojectionState == nullptr) return Hmd2::ReprojectionNotInitialized; + } + if ((flags & ~(1ull | (1ull << 32))) != 0 || reserved != 0 || reserved2 != 0) return Hmd2::ParameterInvalid; + NotImplemented_nid_no_patch(__func__); + return 0; } int APS5_VABI sceHmd2ReprojectionSetTiming(std::int32_t timing) { diff --git a/core/libs/tests/GuestHmd2.cpp b/core/libs/tests/GuestHmd2.cpp index e6eee4ba7120..7ad8a6a0de1c 100644 --- a/core/libs/tests/GuestHmd2.cpp +++ b/core/libs/tests/GuestHmd2.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace Hmd2 { @@ -275,6 +276,33 @@ void SizeQueriesReturnTheConsoleSizes() { Require(work.size == 0x4000 && work.align == 0x100, "mirroring work memory size"); } +template +bool AnswersNoSession(Call call) { + try { + return call() == Hmd2::ReprojectionNotInitialized; + } catch (const std::exception&) { + return false; + } +} + +void TestGatedCallsInsideReprojectionSession() { + std::array memory; + memory.fill(0xaa); + const Hmd2::MirroringParameter mirroring{memory.data(), memory.data() + 32}; + std::uint64_t time = 0xaaaaaaaaaaaaaaaa; + Require(!AnswersNoSession([&] { return sceHmd2ReprojectionEnableMirroring(&mirroring); }), "mirroring inside a reprojection session answered as without one"); + Require(!AnswersNoSession([&] { return sceHmd2ReprojectionGetPredictedDisplayTime(1, &time); }), "predicted display time inside a reprojection session answered as without one"); + Require(!AnswersNoSession([&] { return sceHmd2ReprojectionSetRenderConfig(1, 0, 0); }), "render config inside a reprojection session answered as without one"); + Require(!AnswersNoSession([&] { return sceHmd2ReprojectionSetRenderConfig(1ull << 32, 0, 0); }), "render config with the second accepted bit inside a reprojection session answered as without one"); + for (const std::uint64_t frameId : {0ull, 1ull, 0x123456789ull}) { + Require(sceHmd2ReprojectionGetPredictedDisplayTime(frameId, nullptr) == Hmd2::ParameterNull, "predicted display time into a null output inside a reprojection session"); + } + Require(sceHmd2ReprojectionSetRenderConfig(0, 1, 1) == Hmd2::ParameterInvalid, "render config with nonzero reserved arguments inside a reprojection session"); + Require(sceHmd2ReprojectionSetRenderConfig(2, 0, 0) == Hmd2::ParameterInvalid, "render config with a bit outside the accepted ones inside a reprojection session"); + Require(sceHmd2ReprojectionSetRenderConfig(1, 1, 0) == Hmd2::ParameterInvalid && sceHmd2ReprojectionSetRenderConfig(1, 0, 1) == Hmd2::ParameterInvalid, "render config with one nonzero reserved argument inside a reprojection session"); + Require(sceHmd2ReprojectionSetRenderConfig(~0ull, 0, 0) == Hmd2::ParameterInvalid && sceHmd2ReprojectionSetRenderConfig(3, 0, 0) == Hmd2::ParameterInvalid, "render config with several bits inside a reprojection session"); +} + void TestReprojection() { Require(sceHmd2ReprojectionSetAllowPositionalReprojection(2) == 0, "the positional setting without a session did not succeed"); @@ -356,7 +384,9 @@ void TestReprojection() { const Hmd2::SizeAlign cleared = sceHmd2ReprojectionQueryBufferSizeAlign(); Require(cleared.size == Hmd2::ReprojectionBufferSize && cleared.align == Hmd2::ReprojectionBufferAlign, "clearing the positional setting did not restore the work-size answer"); + TestGatedCallsInsideReprojectionSession(); Require(sceHmd2ReprojectionTerminate() == 0, "terminate did not succeed"); + TestCallsWithoutReprojectionSession(); Require(sceHmd2ReprojectionSetTiming(3000) == Hmd2::ReprojectionNotInitialized, "setting the timing after terminate"); Require(sceHmd2ReprojectionEnableVrMode(Hmd2::OutputMode89_91Hz) == Hmd2::ReprojectionNotInitialized, diff --git a/docs/dev/debt/libSceHmd2.md b/docs/dev/debt/libSceHmd2.md index fb15e068397b..48662531da6b 100644 --- a/docs/dev/debt/libSceHmd2.md +++ b/docs/dev/debt/libSceHmd2.md @@ -62,7 +62,7 @@ - [Hmd2BeforeInitializeParameterNull](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x8a720008` to these 16 exports both with every argument null and with every argument pointing at a zeroed 16 KiB buffer, writing nothing: `MNtiJTDOPAU`, `RRy9WmqZfgU` (sceHmd2InternalDecryptData), `uobR4m9lgwc` (sceHmd2InternalGetMirroringMode), `-XPk5KWBYdo` (sceHmd2InternalRelocalizerSetResultData), `J44idh9izZM`, `Ps0wAvYaa1E`, `kLnMR8oQ1MY`, `kMz4rbBxvjo`, `DIU1sFRRaiE` (sceHmd2GazeGetCalibrationData), `ZQvU4vDzZGQ` (sceHmd2GazeSetUserCalibrationData), `TJpyGGP9YEY`, `Dih+Odk+RC0`, `oaqdSJw+HVY`, `m35gtHuanXU`, `CTM7QPLJF9Q` (sceHmd2InternalGetFlipInfo), `+rExHeC9lP0` (sceHmd2GazeObjectSelectionInitialize). Measured from a payload process on firmware 13.60, so a game process before initialization is assumed to answer the same; the arguments and the behaviour after initialization are not measured, and the handler throws once `sceHmd2Initialize` has succeeded - [Hmd2BeforeInitializeParameterInvalid](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x8a720009` to these 9 exports both with every argument null and with every argument pointing at a zeroed 16 KiB buffer, writing nothing: `bBh5bCp9coo` (sceHmd2InternalSetPositionResetIcon), `G3PNDE2VjfY` (sceHmd2InternalSetPowerOff), `nScCT0OPU0o`, `6Q0Ng7OTN9o`, `-fJKh4SnFR0` (sceHmd2InternalSetBudgetMode), `jUA2s-l0npY` (sceHmd2InternalSetForceIcon), `+9XhyAIJVj4`, `eBGK-o6E4RQ` (sceHmd2InternalBindDeviceWithUserId), `MTuFeL9184E`. Measured from a payload process on firmware 13.60, so a game process before initialization is assumed to answer the same; the arguments and the behaviour after initialization are not measured, and the handler throws once `sceHmd2Initialize` has succeeded - [Hmd2BeforeInitializeReprojectionNotInitialized](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x8a72000b` to these 10 exports both with every argument null and with every argument pointing at a zeroed 16 KiB buffer, writing nothing: `d3SEIYTP6wc`, `EVECbTvBezo`, `ciusPdJKjSU`, `DIwqTkcZiJo` (sceHmd2ReprojectionSetUserEventStart), `64ERZWVFBTE` (sceHmd2ReprojectionSetUserEventEnd), `z0+0gXUJWs8` (sceHmd2ReprojectionClearUserEventStart), `rjAd4nCY4DM` (sceHmd2ReprojectionClearUserEventEnd), `Acyg4vTdGm0` (sceHmd2ReprojectionGetCurrentOutput), `aGOWaEeg04I`, `9up+EPhw1No`. Measured from a payload process on firmware 13.60, so a game process before initialization is assumed to answer the same; the arguments and the behaviour after initialization are not measured, and the handler throws once `sceHmd2Initialize` has succeeded -- [sceHmd2ReprojectionEnableMirroring, sceHmd2ReprojectionGetPredictedDisplayTime and sceHmd2ReprojectionSetRenderConfig](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - the answers inside a reprojection session are not implemented (for the last two [measured](https://github.com/boykopovar/AnyPS5/pull/7658#issuecomment-6408246816) in a PPSA01317 session on firmware 13.40), so a session has to gate these calls once `sceHmd2ReprojectionInitialize` can create one. Without a session each returns 0x8a72000b for every argument and writes nothing: the firmware 13.40 module tests the session before its arguments, and a retail PS5 answered so from a payload before `sceHmd2Initialize` with null arguments and with zeroed buffers ([firmware 13.60](https://github.com/boykopovar/AnyPS5/pull/7798#issuecomment-6262489180)) and in a PPSA23479 game process whose arguments were not recorded ([firmware 13.40](https://github.com/boykopovar/AnyPS5/pull/8645#issuecomment-6307024384)) +- [sceHmd2ReprojectionEnableMirroring, sceHmd2ReprojectionGetPredictedDisplayTime and sceHmd2ReprojectionSetRenderConfig](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - inside a reprojection session they throw, except the answers [measured](https://github.com/boykopovar/AnyPS5/pull/7658#issuecomment-6408246816) in a PPSA01317 session on firmware 13.40: GetPredictedDisplayTime with a null output 0x8a720008, SetRenderConfig with a bit outside bits 0 and 32 or a nonzero second or third argument 0x8a720009. The 0x8a720019 GetPredictedDisplayTime answered there without a headset is not returned, because the answer with one is unknown. Without a session each returns 0x8a72000b for every argument and writes nothing, as a retail PS5 answered from a payload before `sceHmd2Initialize` ([firmware 13.60](https://github.com/boykopovar/AnyPS5/pull/7798#issuecomment-6262489180)) - [Hmd2BeforeInitializeError002C](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x8a72002c` to these 1 exports both with every argument null and with every argument pointing at a zeroed 16 KiB buffer, writing nothing: `V1kLQmP-17U` (sceHmd2GazeObjectSelectionTerminate). Measured from a payload process on firmware 13.60, so a game process before initialization is assumed to answer the same; the arguments and the behaviour after initialization are not measured, and the handler throws once `sceHmd2Initialize` has succeeded - [Hmd2BeforeInitializeError1002](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x8a721002` to these 5 exports both with every argument null and with every argument pointing at a zeroed 16 KiB buffer, writing nothing: `2LVD1EC5SYE`, `Bf87AL6zWxU`, `XKt7w1MODFY`, `Y5s22M-qUDA`, `JJKY-BtQJLg`. Measured from a payload process on firmware 13.60, so a game process before initialization is assumed to answer the same; the arguments and the behaviour after initialization are not measured, and the handler throws once `sceHmd2Initialize` has succeeded - [Hmd2BeforeInitializeError1003](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x8a721003` to these 1 exports both with every argument null and with every argument pointing at a zeroed 16 KiB buffer, writing nothing: `9kj9vw8UShQ`. Measured from a payload process on firmware 13.60, so a game process before initialization is assumed to answer the same; the arguments and the behaviour after initialization are not measured, and the handler throws once `sceHmd2Initialize` has succeeded