From 776ecd3b3581f843a145de5307bbeee033d4518a Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:25:33 +0200 Subject: [PATCH] feat(psml): implement the MFSR 1100 calls for hardware without PSSR scePsmlMfsrGetContextBufferRequirement1100, scePsmlMfsrCreateContext1100 and scePsmlMfsrGetDispatchMfsrPacket1100 were not exported, so a title importing them failed to load. In Sony's libScePsml all three return 0x8A810001 (not initialized) without writing their outputs until scePsmlMfsrInit has succeeded. AnyPS5's scePsmlMfsrInit throws, so that is the only state a title can reach, and the three calls now return that error. The existing stubs for scePsmlMfsrInit, scePsmlMfsrReleaseContext and scePsmlMfsrGetDispatchMfsrPacketSizeInDwords get their real names and still throw. libScePsml_debug gets a CMakeLists.txt for the new guest_psml test. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libScePsml_debug/CMakeLists.txt | 20 +++++++ core/libs/prx/libScePsml_debug/Export.cpp | 34 +++++++++--- core/libs/tests/GuestPsml.cpp | 53 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 core/libs/prx/libScePsml_debug/CMakeLists.txt create mode 100644 core/libs/tests/GuestPsml.cpp diff --git a/core/libs/prx/libScePsml_debug/CMakeLists.txt b/core/libs/prx/libScePsml_debug/CMakeLists.txt new file mode 100644 index 000000000..ee07e525e --- /dev/null +++ b/core/libs/prx/libScePsml_debug/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(libScePsml_debug SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libScePsml_debug PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libScePsml_debug) + +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_psml_tests ${LIBS_INCLUDE_DIR}/tests/GuestPsml.cpp) + target_include_directories(guest_psml_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_psml_tests PRIVATE libScePsml_debug) + add_test(NAME guest_psml COMMAND guest_psml_tests) + if(WIN32) + set_tests_properties(guest_psml PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libScePsml_debug/Export.cpp b/core/libs/prx/libScePsml_debug/Export.cpp index 67d7cf76b..2e8ee6e64 100644 --- a/core/libs/prx/libScePsml_debug/Export.cpp +++ b/core/libs/prx/libScePsml_debug/Export.cpp @@ -3,22 +3,45 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr std::int32_t SCE_PSML_ERROR_NOT_INITIALIZED = static_cast(0x8A810001); + +} + extern "C" { +std::int32_t APS5_VABI scePsmlMfsrGetContextBufferRequirement1100(void* requirement, const void* param) { + (void)requirement; + (void)param; + return SCE_PSML_ERROR_NOT_INITIALIZED; +} + +std::int32_t APS5_VABI scePsmlMfsrCreateContext1100(void** context, const void* param) { + (void)context; + (void)param; + return SCE_PSML_ERROR_NOT_INITIALIZED; +} + +std::int32_t APS5_VABI scePsmlMfsrGetDispatchMfsrPacket1100(void* context, void* commandBuffer, const void* param) { + (void)context; + (void)commandBuffer; + (void)param; + return SCE_PSML_ERROR_NOT_INITIALIZED; +} + APS5_EXPORT("+2KpvixvL6E", scePsmlUnknown__P2KpvixvL6E); int APS5_VABI scePsmlUnknown__P2KpvixvL6E() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("3WVD91e12ZQ", scePsmlUnknown_3WVD91e12ZQ); -int APS5_VABI scePsmlUnknown_3WVD91e12ZQ() { +int APS5_VABI scePsmlMfsrInit() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("AHalTX9wFZY", scePsmlUnknown_AHalTX9wFZY); -int APS5_VABI scePsmlUnknown_AHalTX9wFZY() { +int APS5_VABI scePsmlMfsrGetDispatchMfsrPacketSizeInDwords() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -47,8 +70,7 @@ int APS5_VABI scePsmlUnknown_GJY0MvuTcs8() { return 0; } -APS5_EXPORT("JaLBe0P3jSU", scePsmlUnknown_JaLBe0P3jSU); -int APS5_VABI scePsmlUnknown_JaLBe0P3jSU() { +int APS5_VABI scePsmlMfsrReleaseContext() { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/tests/GuestPsml.cpp b/core/libs/tests/GuestPsml.cpp new file mode 100644 index 000000000..d0aac1d17 --- /dev/null +++ b/core/libs/tests/GuestPsml.cpp @@ -0,0 +1,53 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +extern "C" { +std::int32_t APS5_VABI scePsmlMfsrGetContextBufferRequirement1100(void* requirement, const void* param); +std::int32_t APS5_VABI scePsmlMfsrCreateContext1100(void** context, const void* param); +std::int32_t APS5_VABI scePsmlMfsrGetDispatchMfsrPacket1100(void* context, void* commandBuffer, const void* param); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr std::int32_t kErrNotInitialized = static_cast(0x8A810001); + +struct CommandBuffer { + std::uint32_t* cursor; + std::uint32_t sizeInDwords; +}; + +} + +int main() { + std::array param{}; + + std::array requirement; + requirement.fill(0xa5); + const auto untouchedRequirement = requirement; + Require(scePsmlMfsrGetContextBufferRequirement1100(requirement.data(), param.data()) == kErrNotInitialized); + Require(requirement == untouchedRequirement); + Require(scePsmlMfsrGetContextBufferRequirement1100(nullptr, nullptr) == kErrNotInitialized); + + std::uint64_t contextStorage = 0; + void* const sentinel = &contextStorage; + void* context = sentinel; + Require(scePsmlMfsrCreateContext1100(&context, param.data()) == kErrNotInitialized); + Require(context == sentinel); + Require(scePsmlMfsrCreateContext1100(nullptr, nullptr) == kErrNotInitialized); + + std::array dwords; + dwords.fill(0xa5a5a5a5u); + const auto untouchedDwords = dwords; + CommandBuffer commandBuffer{dwords.data(), static_cast(dwords.size())}; + Require(scePsmlMfsrGetDispatchMfsrPacket1100(&contextStorage, &commandBuffer, param.data()) == kErrNotInitialized); + Require(commandBuffer.cursor == dwords.data()); + Require(commandBuffer.sizeInDwords == dwords.size()); + Require(dwords == untouchedDwords); + Require(scePsmlMfsrGetDispatchMfsrPacket1100(nullptr, &commandBuffer, param.data()) == kErrNotInitialized); + Require(commandBuffer.cursor == dwords.data()); + Require(dwords == untouchedDwords); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..698d5c807 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -54,6 +54,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceAudioOutGetLastOutputTime](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - signature and behaviour taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [fpPS4](https://github.com/red-prig/fpPS4) reimplementations of the PS4 library, not confirmed on a PS5 title: the time is the process time at which `sceAudioOutOutput` or `sceAudioOutOutputs` accepted the port's last block, not the time the block reached the device - [AOWqIYsgVHs](../../core/libs/prx/libSceContentExport/Export.cpp) (libSceContentExport) - unknown name, signature - [GQTObcITIXI](../../core/libs/prx/libSceShare/Export.cpp) (libSceShare) - unknown name, signature +- [scePsmlMfsrGetContextBufferRequirement1100, scePsmlMfsrCreateContext1100, scePsmlMfsrGetDispatchMfsrPacket1100](../../core/libs/prx/libScePsml_debug/Export.cpp) (libScePsml_debug) - argument layouts unknown; only 0x8A810001 (not initialized) is returned - [BnMAMrsfVWo](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libc) - unknown name, signature - [scePngEncEncode](../../core/libs/prx/libScePngEnc/Export.cpp) (libScePngEnc) - whether rows may stay unfiltered when `filter_type` names a subset of the filters is unknown; as in shadPS4, they may only for 0 and for the all-filters mask - AudioIn, NpSessionSignaling and PlayerInvitationDialog exports added without an implementation have assumed signatures