diff --git a/core/libs/prx/libSceVideoOut/Unimplemented.cpp b/core/libs/prx/libSceVideoOut/Unimplemented.cpp index 0c946149c..ef58c55ad 100644 --- a/core/libs/prx/libSceVideoOut/Unimplemented.cpp +++ b/core/libs/prx/libSceVideoOut/Unimplemented.cpp @@ -17,11 +17,6 @@ int APS5_VABI sceVideoOutClearFlipMaster() { return 0; } -int APS5_VABI sceVideoOutGetOutputStatusWithVrr(int handle, void* status) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceVideoOutAdjustColor_(int handle, const VideoOutColorSettings* settings, uint32_t settings_size) { if (settings_size < sizeof(VideoOutColorSettings)) throw std::runtime_error("sceVideoOutAdjustColor_: VIDEO_OUT_ERROR_INVALID_VALUE"); return sceVideoOutAdjustColor(handle, settings); diff --git a/core/libs/prx/libSceVideoOut/src/Output.cpp b/core/libs/prx/libSceVideoOut/src/Output.cpp index 2a333efcc..35c322891 100644 --- a/core/libs/prx/libSceVideoOut/src/Output.cpp +++ b/core/libs/prx/libSceVideoOut/src/Output.cpp @@ -16,6 +16,14 @@ static constexpr std::uint32_t VIDEO_OUT_OUTPUT_OPTIONS_FIRST_WORD = 0x00FF0000; static constexpr std::size_t VIDEO_OUT_OUTPUT_OPTIONS_FREE_WORD = 3; +static VideoOutOutputStatus outputStatus(const auto& cfg) { + VideoOutOutputStatus status{}; + status.resolution = (cfg.width >= 3840 || cfg.height >= 2160) ? 2u : 1u; + status.dynamicRange = 1; + status.refreshRate = (cfg.outputMode == VIDEO_OUT_OUTPUT_MODE_119_88HZ) ? VIDEO_OUT_REFRESH_RATE_119_88HZ : VIDEO_OUT_REFRESH_RATE_59_94HZ; + return status; +} + int ValidateVideoOutOutputMode_nid_no_patch(uint64_t mode, const VideoOutOutputOptions* options, int displayRefreshRate) { switch (mode) { case VIDEO_OUT_OUTPUT_MODE_DEFAULT: @@ -159,13 +167,22 @@ int APS5_VABI sceVideoOutGetOutputStatus(int handle, VideoOutOutputStatus* statu } std::unique_lock lock(cfg->mutex); cfg->Check(); - status->resolution = (cfg->width >= 3840 || cfg->height >= 2160) ? 2u : 1u; - status->dynamicRange = 1; - status->refreshRate = (cfg->outputMode == VIDEO_OUT_OUTPUT_MODE_119_88HZ) ? VIDEO_OUT_REFRESH_RATE_119_88HZ : VIDEO_OUT_REFRESH_RATE_59_94HZ; - status->flags = 0; - status->reserved[0] = 0; - status->reserved[1] = 0; - status->reserved[2] = 0; + *status = outputStatus(*cfg); + return 0; +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); +} + +int APS5_VABI sceVideoOutGetOutputStatusWithVrr(int handle, void* status) try { + if (status == nullptr) return VIDEO_OUT_ERROR_INVALID_ADDRESS; + if (!VideoOutDriver::Get().HasConfig(handle) || !VideoOutDriver::Get().IsOpen(handle)) return VIDEO_OUT_ERROR_INVALID_HANDLE; + auto cfg = VideoOutDriver::Get().GetConfig(handle); + std::unique_lock lock(cfg->mutex); + cfg->Check(); + std::array bytes{}; + const auto output = outputStatus(*cfg); + std::memcpy(bytes.data(), &output, sizeof(output)); + std::memcpy(status, bytes.data(), bytes.size()); return 0; } catch (const ProcessShutdown&) { LibcAwaitExit_nid_postfix(); diff --git a/core/libs/tests/GuestVideoOutResolutionStatus.cpp b/core/libs/tests/GuestVideoOutResolutionStatus.cpp index 5294d7c29..e401ad302 100644 --- a/core/libs/tests/GuestVideoOutResolutionStatus.cpp +++ b/core/libs/tests/GuestVideoOutResolutionStatus.cpp @@ -1,6 +1,8 @@ #include "SceTypes.hpp" #include "prx/libc/include/Shutdown.hpp" #include "tests/VideoOutTestEnvironment.hpp" +#include +#include #include #include #include @@ -10,6 +12,7 @@ extern "C" { int APS5_VABI sceVideoOutOpen(int userId, int busType, int index, const void* param); int APS5_VABI sceVideoOutClose(int handle); int APS5_VABI sceVideoOutGetOutputStatus(int handle, VideoOutOutputStatus* status); +int APS5_VABI sceVideoOutGetOutputStatusWithVrr(int handle, void* status); int APS5_VABI sceVideoOutGetResolutionStatus(int handle, VideoOutResolutionStatus* status); } @@ -52,6 +55,18 @@ int main() { Require(sceVideoOutGetOutputStatus(handle, &output) == 0); Require(status.refreshRate == output.refreshRate); + std::array vrr; + vrr.fill(std::byte{0xff}); + Require(sceVideoOutGetOutputStatusWithVrr(handle, vrr.data()) == 0); + Require(std::memcmp(vrr.data(), &output, sizeof(output)) == 0); + for (std::size_t offset = sizeof(output); offset < 80; ++offset) Require(vrr[offset] == std::byte{0}); + for (std::size_t offset = 80; offset < vrr.size(); ++offset) Require(vrr[offset] == std::byte{0xff}); + vrr.fill(std::byte{0xff}); + Require(sceVideoOutGetOutputStatusWithVrr(handle, nullptr) == static_cast(0x80290002u)); + Require(sceVideoOutGetOutputStatusWithVrr(NEVER_OPENED_HANDLE, vrr.data()) == static_cast(0x8029000Bu)); + Require(sceVideoOutGetOutputStatusWithVrr(-1, vrr.data()) == static_cast(0x8029000Bu)); + for (const auto byte : vrr) Require(byte == std::byte{0xff}); + Require(Rejects(handle, nullptr)); Require(Rejects(0, &status)); Require(Rejects(-1, &status)); @@ -59,5 +74,6 @@ int main() { Require(sceVideoOutClose(handle) == 0); Require(Rejects(handle, &status)); + Require(sceVideoOutGetOutputStatusWithVrr(handle, vrr.data()) == static_cast(0x8029000Bu)); LibcRunShutdown_nid_postfix(); } diff --git a/docs/dev/debt/libSceVideoOut.md b/docs/dev/debt/libSceVideoOut.md index 0d67a74de..ce195aa30 100644 --- a/docs/dev/debt/libSceVideoOut.md +++ b/docs/dev/debt/libSceVideoOut.md @@ -9,7 +9,7 @@ ### Unknown function info - [sceVideoOutDeleteVrrActiveStatusEvent](../../../core/libs/prx/libSceVideoOut/src/Event.cpp) (libSceVideoOut, NID `-q-vbO859Tw`) - the name hashes to the NID PPSA02530 imports and is listed in [sce_symbols](https://github.com/zecoxao/sce_symbols) `aerolib.csv` at `fe2cc2948e`. The signature `(eq, handle)` and the behaviour, removing the handle's VRR status registration from the queue as the other `Delete*Event` calls do, follow sceVideoOutAddVrrActiveStatusEvent; no title has been observed calling it -- [sceVideoOutGetOutputStatusWithVrr](../../../core/libs/prx/libSceVideoOut/Unimplemented.cpp) (libSceVideoOut, NID `gWT7X8H0bYs`) - the name hashes to the imported NID. PPSA26344 version 02.002.000 passes a VideoOut handle and an output pointer at three call sites. It compares a 32-bit value at byte 4 with 2, tests bit 0x10 at byte 16, and, when set, compares low 16-bit values at bytes 48, 50 and 52 with 48, 60 and 120. The full output layout, size, field meanings and console errors are unknown. Every call throws without reading or writing caller memory. +- [sceVideoOutGetOutputStatusWithVrr](../../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut, NID `gWT7X8H0bYs`) - `(int handle, void* status)`, the 80-byte layout and the return codes follow the clean-room description in #2746. Bytes 0-47 are `sceVideoOutGetOutputStatus`'s; bytes 48-53, which the console copies from an internal query whose fields are not established, are written as zero, as are the rest, so the 16-bit value at byte 52 never adjusts `refreshRate`. A null `status` returns `0x80290002` and an unknown handle `0x8029000B`. The console's earlier global-flag check (`0x8029001C`) and its object-field check (`0x8029001B`) test states that are not modelled and are never reported. - [sceVideoOutRegisterBuffers2](../../../core/libs/prx/libSceVideoOut/src/Buffer.cpp) (libSceVideoOut) - buffer option 8 (STRICT_COLORIMETRY) is presented as option 0, with the stored values unchanged; what the console changes for it is unknown. Only options 0 and 8 are accepted - [sceVideoOutAddBufferAttributeOption](../../../core/libs/prx/libSceVideoOut/src/Buffer.cpp) (libSceVideoOut, NID `MBlv8YWoyOk`) - the signature `(VideoOutBufferAttribute2*, uint64_t option)` comes from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/60977b29103452b9681ae10c7a9011d44965bf16/src/SharpProspero/Interop/VideoOut/VideoOut.cs#L174-L176) and the call site in PPSA25380, which passes 0x20 and ignores the result. The option is ORed into `attribute->option`; whether the console validates the bits, keeps them elsewhere or fails on an unknown option is unknown. A null attribute throws. Option 0x20 makes `sceVideoOutRegisterBuffers2` throw, as every option other than 0 and 8; what it selects for an overlay buffer is unknown - [sceVideoOutRegisterBuffers2](../../../core/libs/prx/libSceVideoOut/src/Buffer.cpp) (libSceVideoOut) - DCC buffers (category 1): `dcc_control` is taken as the CB_DCC_CONTROL block layout, only bits 0x10026c are accepted; `dcc_cb_register_clear_color` as the register-clear texel, only 32-bit values are presented